alchemylab 1.4.7 → 1.4.9
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 +107 -110
- package/dist/http-server.js +680 -632
- package/dist/index.js +303 -255
- package/dist/mcp-direct.js +2254 -2206
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -109,25 +109,25 @@ const o=s(t(6928)),n=s(t(857)),i=s(t(9896)),a=t(3962),c=t(5419),l=t(5317),d=t(88
|
|
|
109
109
|
exports.UnifiedLauncher=class{constructor(e,t,r){this.logger=e,this.installContext=t,this.configurer=r}async launch(e){const{projectPath:t,settings:s,multiAgent:o}=e,{installPath:n,nodePath:i}=this.installContext
|
|
110
110
|
this.logger.info("UnifiedLauncher: Starting launch",{projectPath:t,installPath:n})
|
|
111
111
|
try{const a=await this.configurer.configureOpenCode(n,t,i,s,o)
|
|
112
|
-
return a.success?(this.logger.info("UnifiedLauncher: Configuration complete",{configPath:a.configPath}),this.logger.info("[plugin-diag] pre-spawn",r()),this.launchBackground(e,a)):(this.logger.error("UnifiedLauncher: Configuration failed",{error:a.error}),{success:!1,error:a.error})}catch(e){return this.logger.error("UnifiedLauncher: Launch failed",{error:(0,d.getErrorMessage)(e)}),{success:!1,error:(0,d.getErrorMessage)(e)}}}async launchBackground(e,s){const{projectPath:n,envVars:u}=e
|
|
113
|
-
try{(0,l.execFileSync)("which",["opencode"],{stdio:"pipe"})}catch{return{success:!1,error:"
|
|
114
|
-
const
|
|
112
|
+
return a.success?(this.logger.info("UnifiedLauncher: Configuration complete",{configPath:a.configPath}),this.logger.info("[plugin-diag] pre-spawn",r()),this.launchBackground(e,a)):(this.logger.error("UnifiedLauncher: Configuration failed",{error:a.error}),{success:!1,error:a.error})}catch(e){return this.logger.error("UnifiedLauncher: Launch failed",{error:(0,d.getErrorMessage)(e)}),{success:!1,error:(0,d.getErrorMessage)(e)}}}async launchBackground(e,s){const{projectPath:n,envVars:u}=e,{resolveServeCommand:h,findOpencodeDir:g}=await Promise.resolve().then(t.bind(t,9068))
|
|
113
|
+
if(!g())try{(0,l.execFileSync)("which",["opencode"],{stdio:"pipe"})}catch{return{success:!1,error:"alchemylab-opencode package not installed and no system `opencode` on PATH."}}this.logger.info("UnifiedLauncher: Launching opencode serve in background",{command:"opencode serve",cwd:n})
|
|
114
|
+
const p=h()
|
|
115
115
|
return new Promise((e=>{const t=o.default.join(n,".alchemy_lab","opencode"),h=o.default.join(t,"log")
|
|
116
|
-
let
|
|
117
|
-
const m=e=>{if(!
|
|
118
|
-
const t=
|
|
119
|
-
|
|
116
|
+
let g=null
|
|
117
|
+
const m=e=>{if(!g)return
|
|
118
|
+
const t=g
|
|
119
|
+
g=null,t.end(e)}
|
|
120
120
|
try{i.default.mkdirSync(h,{recursive:!0})
|
|
121
121
|
const e=o.default.join(h,"opencode-serve.log")
|
|
122
|
-
i.default.writeFileSync(e,`# opencode-serve.log \u2014 launched ${(new Date).toISOString()}\n`),
|
|
122
|
+
i.default.writeFileSync(e,`# opencode-serve.log \u2014 launched ${(new Date).toISOString()}\n`),g=i.default.createWriteStream(e,{flags:"a"})}catch(e){this.logger.warn("UnifiedLauncher: Could not open opencode log file",{error:(0,d.getErrorMessage)(e)})}const f=(0,l.spawn)(p.command,[...p.prependArgs,"--port","0","--data-dir",t],{stdio:["ignore","pipe","pipe"],cwd:n,detached:!0,env:{...process.env,...(0,c.getOpencodeSpawnEnv)(n,(0,a.isDevMode)()),...u}})
|
|
123
123
|
let y="",v=!1
|
|
124
124
|
const w=t=>{y+=t
|
|
125
125
|
const o=y.match(/listening on http:\/\/[^:]+:(\d+)/)
|
|
126
126
|
if(o&&!v){const t=parseInt(o[1],10)
|
|
127
127
|
v=!0,this.logger.info("UnifiedLauncher: Discovered opencode serve port",{pid:f.pid,port:t}),this.logger.info("[plugin-diag] port-ready",{pid:f.pid,port:t,...r()}),setTimeout((()=>{this.logger.info("[plugin-diag] port-ready+5s",{pid:f.pid,...r()})}),5e3).unref(),setTimeout((()=>{this.logger.info("[plugin-diag] port-ready+15s",{pid:f.pid,...r()})}),15e3).unref(),f.unref(),e({success:!0,pid:f.pid,port:t,configPath:s.configPath})}}
|
|
128
|
-
f.stdout?.on("data",(e=>{
|
|
128
|
+
f.stdout?.on("data",(e=>{g&&g.write(e),w(e.toString())})),f.stderr?.on("data",(e=>{if(g){const t=e.toString().split("\n")
|
|
129
129
|
for(let e=0;e<t.length;e++){const r=t[e]
|
|
130
|
-
0===r.length&&e===t.length-1||
|
|
130
|
+
0===r.length&&e===t.length-1||g.write(`[stderr] ${r}\n`)}}w(e.toString())})),f.on("exit",((e,t)=>{m(`\n# opencode exited code=${e} signal=${t} at=${(new Date).toISOString()}\n`)})),setTimeout((()=>{v||(v=!0,this.logger.warn("UnifiedLauncher: Timeout waiting for opencode serve port",{outputBuffer:y.slice(0,500)}),f.unref(),e({success:!0,pid:f.pid,configPath:s.configPath,error:"Could not determine port from opencode serve output"}))}),1e4),f.on("error",(t=>{v||(v=!0,this.logger.error("UnifiedLauncher: opencode serve failed to start",{error:t.message}),m(`\n# child error: ${t.message}\n`),e({success:!1,error:"Failed to start opencode serve: "+t.message}))})),f.on("exit",(t=>{v||(v=!0,this.logger.error("UnifiedLauncher: opencode serve exited early",{code:t,output:y.slice(0,500)}),e({success:!1,error:"opencode serve exited with code "+t}))}))}))}}},1253(e,exports,t){"use strict"
|
|
131
131
|
var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
|
|
132
132
|
Object.defineProperty(exports,"__esModule",{value:!0}),exports.TokenManager=void 0
|
|
133
133
|
const s=r(t(6928)),o=r(t(1943)),n=r(t(6982)),i=r(t(857)),a=t(9067),c=t(8824),l="alchemy-lab"
|
|
@@ -227,21 +227,29 @@ for(const t of e){if(this.inFlight.has(t.id))continue
|
|
|
227
227
|
const e=t.metadata||{}
|
|
228
228
|
if((e.status||o.QUEST_STATUS_OPEN)!==o.QUEST_STATUS_OPEN)continue
|
|
229
229
|
const r=e.tickIntervalSeconds||o.QUEST_DEFAULTS.tickIntervalSeconds,s=e.lastTickAt?Date.parse(e.lastTickAt):NaN,n=Number.isFinite(s)?s:0,i=Date.now()
|
|
230
|
-
n>0&&r>(i-n)/1e3||(this.inFlight.add(t.id),this.fireTick(t).finally((()=>this.inFlight.delete(t.id))))}}}async
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
try{const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
230
|
+
n>0&&r>(i-n)/1e3||(this.inFlight.add(t.id),this.fireTick(t).finally((()=>this.inFlight.delete(t.id))))}}}async nudge(e){if(!this.projectId)return
|
|
231
|
+
if(this.inFlight.has(e))return void this.logger.info("Quest nudge: already in flight, skipping",{questId:e})
|
|
232
|
+
let t
|
|
233
|
+
try{const r=await this.apiClient.get(`/api/projects/${this.projectId}/documents/${encodeURIComponent(e)}`),s=r?.data||r
|
|
234
|
+
if(!s||"Quest"!==s.type)return void this.logger.warn("Quest nudge: doc not found or wrong type",{questId:e,type:s?.type})
|
|
235
|
+
t=s}catch(t){return void this.logger.warn("Quest nudge: fetch failed",{questId:e,error:t+""})}const r=t.metadata||{}
|
|
236
|
+
if((r.status||o.QUEST_STATUS_OPEN)===o.QUEST_STATUS_OPEN){this.inFlight.add(t.id)
|
|
237
|
+
try{await this.fireTick(t,{idleOnly:!0})}finally{this.inFlight.delete(t.id)}}else this.logger.info("Quest nudge: not open, skipping",{questId:e,status:r.status})}async fireTick(e,t){const n=!0===t?.idleOnly,i=e.metadata||{},a=i.maxInFlight??o.QUEST_DEFAULTS.maxInFlight,c=i.maxPerDay??o.QUEST_DEFAULTS.maxPerDay,l=e.localId||e.id
|
|
238
|
+
let d
|
|
239
|
+
try{d=await this.fetchQuestRequests(e.id)}catch(e){return void this.logger.warn("Quest tick: fetch requests failed; skipping",{questId:l,error:e+""})}const u=new Set
|
|
240
|
+
try{const e=await this.apiClient.get(`/api/projects/${this.projectId}/tasks?status=in_progress,pending&limit=1000`),t=e?.data||e||[],r=new Set(d.map((e=>e.id)))
|
|
241
|
+
for(const e of t)e.requestId&&r.has(e.requestId)&&u.add(e.requestId)}catch(e){this.logger.warn("Quest tick: task fetch failed; counting by status only",{questId:l,error:e+""})}const{openCount:h,statusCount:g,taskOnlyCount:p}=r(d,u)
|
|
242
|
+
if(h>=a)return void this.logger.info("Quest tick: maxInFlight reached, skipping",{questId:l,openCount:h,maxInFlight:a,statusCount:g,taskOnlyCount:p})
|
|
243
|
+
if(n&&h>0)return void this.logger.info("Quest nudge: not idle, skipping",{questId:l,openCount:h})
|
|
244
|
+
const m=(new Date).toISOString().slice(0,10),f=i.ticksTodayDate===m?i.ticksTodayCount??0:0
|
|
245
|
+
if(f>=c)return void this.logger.info("Quest tick: maxPerDay reached, skipping",{questId:l,ticksToday:f,maxPerDay:c})
|
|
246
|
+
const y=await this.findAlchemistSessionId()
|
|
247
|
+
if(!y)return void this.logger.info("Quest tick: no persistent alchemist session in project, skipping",{questId:l,projectId:this.projectId})
|
|
248
|
+
const v=(i.currentIteration||0)+1
|
|
249
|
+
let w=!1
|
|
250
|
+
try{w=await this.bumpQuestMetadata(e,v)}catch(e){return void this.logger.warn("Quest tick: metadata PATCH failed; skipping prompt",{questId:l,error:e+""})}if(!w)return void this.logger.info("Quest tick: status changed during tick; skipping prompt",{questId:l})
|
|
251
|
+
const I=this.buildTickPrompt(e,v,h,p,f+1),b={logger:this.logger,projectId:this.projectId,projectPath:this.projectPath,apiClient:this.apiClient,cachedDefaultModel:this.getDefaultModel()??null},E={alchemy:{kind:"quest-tick",payload:{questId:l,iteration:v,criteria:i.criteria||[],openCount:h,taskOnlyCount:p,maxInFlight:a,ticksToday:f+1,maxPerDay:c,minIterations:i.minIterations??(0,o.defaultMinIterations)(i.completionMode),completionMode:i.completionMode||o.QUEST_COMPLETION_MODE_DEFAULT,consecutiveDoneAudits:i.consecutiveDoneAudits??0}}}
|
|
252
|
+
try{await(0,s.executeSameSession)(I,y,b,E),this.logger.info("Quest tick fired",{questId:l,iteration:v,alchemistSessionId:y})}catch(e){this.logger.warn("Quest tick: prompt send failed",{questId:l,error:e+""})}}invalidateCache(){this.lastQuestsFetch=0}invalidateAlchemistCache(){this.cachedAlchemistSessionId=null,this.cachedAlchemistAt=0}async fetchOpenQuests(){const e=Date.now()
|
|
245
253
|
if(18e5>e-this.lastQuestsFetch)return this.cachedQuests
|
|
246
254
|
try{const t=await this.apiClient.get(`/api/projects/${this.projectId}/plans?type=Quest&limit=100`),r=t?.data||t||[]
|
|
247
255
|
return this.cachedQuests=r.filter((e=>e&&(e.metadata?.status||o.QUEST_STATUS_OPEN)===o.QUEST_STATUS_OPEN)),this.lastQuestsFetch=e,this.cachedQuests}catch(t){throw this.lastQuestsFetch=e,t}}async fetchQuestRequests(e){const t=await this.apiClient.get(`/api/projects/${this.projectId}/requests?questDocumentId=${encodeURIComponent(e)}&limit=1000`)
|
|
@@ -254,8 +262,8 @@ this.logger.info("Quest tick: project active alchemist not active, ignoring",{pr
|
|
|
254
262
|
return 0===r.length?(this.logger.info("Quest tick: no alchemist session found in project",{projectId:this.projectId}),e(null)):(this.logger.info("Quest tick: active-alchemist not set, using first alchemist as fallback",{projectId:this.projectId,alchemistCount:r.length}),e(r[0].externalSessionId))}catch(e){return this.logger.warn("Quest tick: alchemist fallback lookup failed",{error:e+""}),null}}async bumpQuestMetadata(e,t){const r=await this.apiClient.get(`/api/projects/${this.projectId}/documents/${encodeURIComponent(e.id)}`),s=r?.data||r,n=s?.metadata||{}
|
|
255
263
|
if((n.status||o.QUEST_STATUS_OPEN)!==o.QUEST_STATUS_OPEN)return!1
|
|
256
264
|
const i=(new Date).toISOString().slice(0,10),a=(n.ticksTodayDate===i?n.ticksTodayCount??0:0)+1,c={...n,lastTickAt:(new Date).toISOString(),currentIteration:t,ticksTodayDate:i,ticksTodayCount:a}
|
|
257
|
-
return await this.apiClient.patch(`/api/projects/${this.projectId}/documents/${encodeURIComponent(e.id)}`,{metadata:c}),!0}buildTickPrompt(e,t,r,s,n){const i=e.metadata||{},a=e.localId||e.id,c=i.minIterations??o.QUEST_DEFAULTS.
|
|
258
|
-
return[`[Quest tick: ${a}, iteration ${t}]`,"","Filename: "+(e.filename||"(unknown)"),"
|
|
265
|
+
return await this.apiClient.patch(`/api/projects/${this.projectId}/documents/${encodeURIComponent(e.id)}`,{metadata:c}),!0}buildTickPrompt(e,t,r,s,n){const i=e.metadata||{},a=e.localId||e.id,c=i.minIterations??(0,o.defaultMinIterations)(i.completionMode),l=i.maxInFlight??o.QUEST_DEFAULTS.maxInFlight,d=i.maxPerDay??o.QUEST_DEFAULTS.maxPerDay,u=i.completionMode||o.QUEST_COMPLETION_MODE_DEFAULT,h=i.consecutiveDoneAudits??0,g="auto-close"===u?["A) If every requirement is satisfied with concrete evidence:",` Call quest_audit_complete(questId="${a}", summary, evidence?). The server bumps your audit streak. When the streak reaches ${c}, the quest auto-closes (the user opted into auto-close mode). Do NOT spawn work in this branch, that resets the streak to 0.`]:["A) If every requirement is satisfied with concrete evidence:",` Call quest_audit_complete(questId="${a}", summary, evidence?). The server bumps your audit streak. When the streak reaches ${c}, status flips to pending_review and the user adjudicates (close or reopen). Do NOT spawn work in this branch, that resets the streak to 0.`]
|
|
266
|
+
return[`[Quest tick: ${a}, iteration ${t}]`,"","Filename: "+(e.filename||"(unknown)"),"","## Quest body (prose context written by the planner)","",e.content&&e.content.trim().length>0?e.content.trim():"(empty body \u2014 no Goal / Criteria / Notes have been written yet)","",...i.criteria&&Array.isArray(i.criteria)&&i.criteria.length>0?[`## Structured criteria checklist (${i.criteria.filter((e=>"verified"===e.status)).length}/${i.criteria.length} verified)`,"",...i.criteria.map((e=>`- [${"verified"===e.status?"x":" "}] ${e.id}: ${e.description}${e.evidence?" \u2014 evidence: "+e.evidence:""}`)),"","Per-item statuses are authoritative when this list is present. The audit step below should reason per-criterion rather than against prose alone.",""]:[],"completionMode: "+u,`Audit streak: ${h}/${c} consecutive criteria-met audits`,`Caps: maxInFlight=${l} (room for ${Math.max(0,l-r)} more this tick), maxPerDay=${d}.`,`Currently in flight under this quest: ${r}${s>0?` (incl. ${s} via in-progress/pending tasks under non-active requests)`:""}. Ticks today (incl. this): ${n}/${d}.`,"",...1===t?["## First tick \u2014 kickoff","","This is the first tick of this quest. No work has been spawned yet and no prior state to audit. Skip the usual audit-then-act flow; instead:","1. Read the quest body above carefully. Derive concrete requirements from the Goal / Criteria for done / Notes (and the structured checklist if one is set up).",`2. Plan the initial work. You have room for ${l} parallel request(s) this tick. Identify which requirements can be tackled now and how they split into independent specialist requests.`,"3. Spawn one or more specialists via specialist_ops delegate \u2014 one per independent piece, up to the room above. If the work is one cohesive task, spawn a single request and let it finish before the next tick fans out.","4. Every specialist_ops delegate call MUST include this exact line in the prompt you write for the specialist so the spawned request is tagged to the quest:",` "When you call create_request, pass questDocumentId="${a}"."`," Without that the request will not be tagged and the runner cannot track it.","","If the quest body is empty or unclear, do NOT spawn work blind \u2014 ask the user a brief clarifying question via session_message_operations send instead. The quest-planner should have shaped the body during planning; if it didn't, something went wrong upstream.","","Do NOT call quest_audit_complete on the first tick. From the next tick onward you'll get the full audit-then-act prompt as the quest's state evolves.",""]:["## Audit step (do this first, every tick)","","Before deciding what to do, audit the current state against the criteria above. Criteria can come from two places: the quest body (prose Goal / Criteria for done / Notes the planner wrote) and, optionally, the structured checklist (per-item statuses). When the structured checklist is present, treat per-item statuses as the source of truth and use the body as context. When only the body is present, derive concrete requirements from the prose.","- Derive concrete requirements from the body (and per-item checklist if present). Number them if useful.","- For each requirement, identify the authoritative evidence that would prove it: files, command output, test results, merged requests, runtime behavior.","- Inspect the current evidence in the worktree right now. Use read_files, search, query_history, request_diff, get_request_information as needed. Do not rely on memory of earlier ticks.","- For each requirement, judge: proved, contradicted, incomplete, too-weak-to-verify, or missing.","- Treat indirect, weak, or merely-consistent-with-completion evidence as not achieved.","- Match the verification scope to the requirement's scope; do not use a narrow check to support a broad claim.","","## Action this tick (pick exactly one based on what the audit shows)","",...g,"","B) If the audit shows remaining work:",` You have room for ${Math.max(0,l-r)} more request(s) this tick (maxInFlight=${l}, currently in flight=${r}). Use the full fan-out width when the remaining work splits cleanly.`," - If the remaining work breaks into INDEPENDENT pieces (different files, no merge conflicts between them, no ordering dependency), make multiple specialist_ops delegate calls THIS tick \u2014 one per piece, up to the room above. Each goes to its own specialist; do not pack multiple pieces into one specialist prompt."," - If the remaining work is one cohesive task, or the pieces depend on each other, spawn a single request and let it finish before the next tick fans out."," Every specialist_ops delegate call you make this tick MUST include this exact line in the prompt you write for the specialist so the spawned request is tagged to the quest:",` "When you call create_request, pass questDocumentId="${a}"."`," Without that the request will not be tagged and the runner cannot track it. Spawning any request resets the audit streak to 0 (criteria are not actually met yet).",""],"## Fidelity","","- Optimize this tick for movement toward the actual end state described in the criteria, not the safest-looking next step.","- Do not narrow the criteria to fit what is already done.","- An edit, request, or audit is aligned only if it makes the criteria more true. Useful-looking work that preserves a different end state is misaligned.","- Only the user can flip status to 'closed' (except via auto-close mode, which is an explicit user opt-in on this quest). You may flip to 'pending_review' via quest_audit_complete when audit-then-pending mode is configured."].join("\n")}}},1405(e,exports,t){"use strict"
|
|
259
267
|
Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleStartBuildingAction=async function(e,t){const{plannerSessionId:n,parentSessionId:i,requestIds:a,summary:c}=e,{logger:l,apiClient:d,projectId:u,projectPath:h}=t
|
|
260
268
|
if(!n)return{success:!1,message:"plannerSessionId is required",error:"missing_params"}
|
|
261
269
|
l.info("Starting start-building action",{plannerSessionId:n,requestIds:a,payloadParent:i})
|
|
@@ -297,8 +305,8 @@ Object.defineProperty(exports,"__esModule",{value:!0}),exports.partMeta=function
|
|
|
297
305
|
return{...e,_meta:{...s,alchemy:{...o,...r,kind:t}}}},exports.readAlchemyMeta=function(e){return e?._meta?.alchemy}},2366(e,exports,t){"use strict"
|
|
298
306
|
var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
|
|
299
307
|
Object.defineProperty(exports,"__esModule",{value:!0}),exports.AlchemyActionServer=void 0
|
|
300
|
-
const s=r(t(7252)),o=r(t(8577)),n=t(9896),i=t(5959),a=t(2640),c=t(4192),l=t(1152),d=r(t(6928)),u=r(t(9896)),h=r(t(857)),g=t(5607),p=t(8984),m=t(8524),f=t(8824),y=t(2096),v=t(7210),w=t(7227),I=t(5419),b=t(3962),E=t(2928),S=t(6563),
|
|
301
|
-
class
|
|
308
|
+
const s=r(t(7252)),o=r(t(8577)),n=t(9896),i=t(5959),a=t(2640),c=t(4192),l=t(1152),d=r(t(6928)),u=r(t(9896)),h=r(t(857)),g=t(5607),p=t(8984),m=t(8524),f=t(8824),y=t(2096),v=t(7210),w=t(7227),I=t(5419),b=t(3962),E=t(2928),S=t(6563),k=t(1401),C=t(1292),T=t(8649),_=t(4306),j=t(9068),A=t(3435),P=t(8463),M=t(489),O=t(3807),R=t(7478),D=t(5317),x=t(355),$=t(833)
|
|
309
|
+
class q{updateAuthToken(e){this.options.authToken=e,this.apiClient.authToken=e,this.configurer.authToken=e}constructor(e,t){this.directReportDedup=new Map,this.activeMcpCalls=new Map,this.pendingAgentResync=!1,this.settingsSyncCounter=0,this.versionWarnings=[],this.updateBlocked=!1,this._pendingUpdateBlock=!1,this.isPolling=!1,this.isProcessingAction=!1,this.openCodeUrlCache=null,this.openCodePort=null,this.proxyAbortControllers=new Map,this.trackedChildPids=new Set,this.sseEventBuffer=[],this.droppedEventCount=0,this.firstDropAt=0,this.hasRemoteEventConsumers=!1,this.cachedProjectConfig=null,this.logger=e,this.options=t,this.port=t.port||3456,this.startTime=new Date
|
|
302
310
|
const r=(0,l.createMetricsCollector)(!1)
|
|
303
311
|
this.apiClient=new c.BaseApiClient(t.remoteServerUrl,e,r,t.authToken,{timeout:35e3}),this.configurer=new p.MCPClientConfigurer(e,t.remoteServerUrl,t.authToken),this.cliBridge=new m.CLIBridgeWebSocket(e),(0,m.setCLIBridge)(this.cliBridge),this.hookExecutor=new v.HookExecutor(e,"",t.projectPath,this.apiClient,{cachedDefaultModel:this.cachedDefaultModel,trackChildPid:e=>this.trackedChildPids.add(e)}),this.app=this.createApp()}createApp(){const e=(0,s.default)()
|
|
304
312
|
return e.use(((e,t,r)=>{t.setHeader("Access-Control-Allow-Private-Network","true"),r()})),e.use((0,o.default)()),e.use(s.default.json({limit:"10mb"})),e.get("/health",this.handleHealth.bind(this)),e.get("/api/commands/running",(async(e,t)=>{try{const e=16384,r=await Promise.all((0,i.listEntries)().map((async t=>{let r,s="",o=0,i=t.finishedAt??t.startedAt
|
|
@@ -430,7 +438,7 @@ if(r&&!a.has(r))return void t.status(400).json({success:!1,error:"agent must be
|
|
|
430
438
|
const c=await(0,T.spawnPlannerWithDoc)({agent:r||"planner",planId:s,prompt:o,title:n||"Planner",parentSessionId:i,apiClient:this.apiClient,logger:this.logger,projectId:this.projectId,projectPath:this.options.projectPath,trackChildPid:e=>this.trackedChildPids.add(e)})
|
|
431
439
|
if(!c.success||!c.sessionId)return void t.status(500).json({success:!1,error:c.error||"spawn failed"})
|
|
432
440
|
t.json({success:!0,data:{sessionId:c.sessionId,planId:c.planId,pid:c.pid}})}catch(e){(0,A.respondWithError)(t,this.logger,"Spawn planner",e)}}async handleSpawnSession(e,t){try{if(!(0,A.validateRequest)(e,t,this.projectId,["agent","prompt"]))return
|
|
433
|
-
const{agent:r,prompt:s,requestId:o,parentSessionId:n,title:i,specialization:a,specializationHeadline:c,allowParallel:l,callerAgentType:d}=e.body,u=this.projectId,h=new
|
|
441
|
+
const{agent:r,prompt:s,requestId:o,parentSessionId:n,title:i,specialization:a,specializationHeadline:c,allowParallel:l,callerAgentType:d}=e.body,u=this.projectId,h=new C.SessionSpawnService({apiClient:this.apiClient,logger:this.logger,projectId:u,getDefaultModel:()=>this.cachedDefaultModel||void 0}),g=await h.prepareSession({agent:r,prompt:s,projectPath:this.options.projectPath,requestId:o,parentSessionId:n,title:i,specialization:a,specializationHeadline:c,allowParallel:l,callerAgentType:d})
|
|
434
442
|
t.json({success:!0,data:g})}catch(e){(0,A.respondWithError)(t,this.logger,"Spawn session",e)}}async handleRunTestsNow(e,r){try{if(!(0,A.validateRequest)(e,r,this.projectId,["requestId"]))return
|
|
435
443
|
const{requestId:s,parentSessionId:o,testProcessIds:n,commandNames:i}=e.body||{},{handleRunTestsAction:a}=await Promise.resolve().then(t.bind(t,6555)),c={projectId:this.projectId,projectPath:this.options.projectPath,logger:this.logger,apiClient:this.apiClient,trackChildPid:e=>this.trackedChildPids.add(e),cachedDefaultModel:this.cachedDefaultModel??null},l=await a({requestId:s,parentSessionId:o,testProcessIds:n,commandNames:i,mode:"auto"},c)
|
|
436
444
|
if(!l.success)return void r.status(500).json({success:!1,error:l.error||l.message})
|
|
@@ -444,7 +452,7 @@ t.json({success:!0,data:{sessionId:i.sessionId,worktreePath:i.worktreePath,branc
|
|
|
444
452
|
if(!r)return void t.status(400).json({success:!1,error:"Project not registered"})
|
|
445
453
|
const{agentName:s}=e.params,o=await this.apiClient.get(`/api/projects/${r}/agents`),n=(o?.data||o||[]).find((e=>e.name===s))
|
|
446
454
|
if(!n)return void t.status(404).json({success:!1,error:`Agent '${s}' not found`})
|
|
447
|
-
t.json({success:!0,data:n})}catch(e){this.logger.debug("Failed to get agent by name",{error:(0,f.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,f.getErrorMessage)(e)})}}readProjectConfig(){if(this.cachedProjectConfig&&Date.now()-this.cachedProjectConfig.timestamp<
|
|
455
|
+
t.json({success:!0,data:n})}catch(e){this.logger.debug("Failed to get agent by name",{error:(0,f.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,f.getErrorMessage)(e)})}}readProjectConfig(){if(this.cachedProjectConfig&&Date.now()-this.cachedProjectConfig.timestamp<q.PROJECT_CONFIG_TTL)return this.cachedProjectConfig.config
|
|
448
456
|
const e=d.default.join(this.options.projectPath,".alchemy_lab","project.json")
|
|
449
457
|
try{if(!u.default.existsSync(e))return this.cachedProjectConfig={config:null,timestamp:Date.now()},null
|
|
450
458
|
const t=u.default.readFileSync(e,"utf-8"),r=JSON.parse(t)
|
|
@@ -543,7 +551,9 @@ if(n.success&&n.data&&void 0!==n.data.hasRemoteEventConsumers&&(this.hasRemoteEv
|
|
|
543
551
|
if(void 0!==e&&this.updateDefaultModel(e),void 0!==t&&t!==this.cachedAutoJournal){this.logger.info("autoJournal setting changed",{oldValue:this.cachedAutoJournal,newValue:t})
|
|
544
552
|
const e=new y.ProjectConfigService(this.logger)
|
|
545
553
|
await e.saveProjectConfig(this.options.projectPath,t),this.cachedAutoJournal=t}}if(n.success&&Array.isArray(n.data?.invalidations)&&n.data.invalidations.length>0){const e=n.data.invalidations
|
|
546
|
-
this.logger.info("Received cache invalidations from remote",{invalidations:e}),e.includes("hooks")&&this.timerHookScheduler?.invalidateCache(),e.includes("quests")&&this.questTickScheduler?.invalidateCache(),e.includes("alchemist")&&this.questTickScheduler?.invalidateAlchemistCache()}if(n.success&&Array.isArray(n.data?.
|
|
554
|
+
this.logger.info("Received cache invalidations from remote",{invalidations:e}),e.includes("hooks")&&this.timerHookScheduler?.invalidateCache(),e.includes("quests")&&this.questTickScheduler?.invalidateCache(),e.includes("alchemist")&&this.questTickScheduler?.invalidateAlchemistCache()}if(n.success&&Array.isArray(n.data?.questNudges)&&n.data.questNudges.length>0){const e=n.data.questNudges
|
|
555
|
+
this.logger.info("Received quest nudges from remote",{questNudges:e})
|
|
556
|
+
for(const t of e)this.questTickScheduler?.nudge(t).catch((e=>{this.logger.warn("Quest nudge dispatch failed",{questId:t,error:e+""})}))}if(n.success&&Array.isArray(n.data?.versionWarnings)&&this.applyVersionWarnings(n.data.versionWarnings),n.success&&n.data?.actions?.length>0){const e=[],t=[]
|
|
547
557
|
for(const r of n.data.actions)"opencode-proxy"===r.type||"run-tests"===r.type?e.push(r):t.push(r)
|
|
548
558
|
for(const t of e)this.processAction(t).catch((e=>this.logger.warn("Proxy action failed",{actionId:t.id,error:(0,f.getErrorMessage)(e)})))
|
|
549
559
|
if(t.length>0&&!this.isProcessingAction){this.isProcessingAction=!0
|
|
@@ -588,7 +598,7 @@ if(s.ok){const e=await s.json(),t=e?.data?.settings||e?.settings
|
|
|
588
598
|
void 0!==t?.defaultModel&&(this.updateDefaultModel(t.defaultModel),this.logger.info("Fetched defaultModel on startup via quick poll",{defaultModel:this.cachedDefaultModel}))}}catch(e){this.logger.debug("Quick poll for startup settings failed",{error:(0,f.getErrorMessage)(e)})}this.startPolling(),this.projectId&&this.startSSEEventForwarder(),this.projectId&&this.startStuckSessionWatchdog(),this.projectId&&this.startTimerHookScheduler(),this.projectId&&this.startQuestTickScheduler(),this.startMcpStaleCleanup()}trackChildPid(e){this.trackedChildPids.add(e)}killTrackedProcesses(){for(const e of this.trackedChildPids)try{process.kill(-e,"SIGTERM"),this.logger.info("Killed tracked process group",{pid:e})}catch(t){if("ESRCH"!==t?.code){try{process.kill(e,"SIGTERM")}catch{}this.logger.debug("Could not kill process group, fell back to single kill",{pid:e,error:(0,f.getErrorMessage)(t)})}}this.trackedChildPids.clear()
|
|
589
599
|
try{const e=d.default.join(this.options.projectPath,".alchemy_lab","opencode.json")
|
|
590
600
|
if(u.default.existsSync(e)){const t=u.default.readFileSync(e,"utf-8"),r=JSON.parse(t)
|
|
591
|
-
if(r.pid)try{process.kill(-r.pid,"SIGTERM"),this.logger.info("Killed OpenCode process group from config",{pid:r.pid})}catch(e){if("ESRCH"!==e?.code){try{process.kill(r.pid,"SIGTERM")}catch{}this.logger.debug("Could not kill OpenCode group, fell back to single kill",{pid:r.pid,error:(0,f.getErrorMessage)(e)})}}}}catch{}}startTimerHookScheduler(){this.timerHookScheduler=new S.TimerHookScheduler({hookExecutor:this.hookExecutor,apiClient:this.apiClient,logger:this.logger,getOpenCodeUrl:()=>this.getOpenCodeUrl(),getDefaultModel:()=>this.cachedDefaultModel||void 0,projectId:this.projectId||"",projectPath:this.options.projectPath}),this.timerHookScheduler.start()}startQuestTickScheduler(){this.questTickScheduler=new
|
|
601
|
+
if(r.pid)try{process.kill(-r.pid,"SIGTERM"),this.logger.info("Killed OpenCode process group from config",{pid:r.pid})}catch(e){if("ESRCH"!==e?.code){try{process.kill(r.pid,"SIGTERM")}catch{}this.logger.debug("Could not kill OpenCode group, fell back to single kill",{pid:r.pid,error:(0,f.getErrorMessage)(e)})}}}}catch{}}startTimerHookScheduler(){this.timerHookScheduler=new S.TimerHookScheduler({hookExecutor:this.hookExecutor,apiClient:this.apiClient,logger:this.logger,getOpenCodeUrl:()=>this.getOpenCodeUrl(),getDefaultModel:()=>this.cachedDefaultModel||void 0,projectId:this.projectId||"",projectPath:this.options.projectPath}),this.timerHookScheduler.start()}startQuestTickScheduler(){this.questTickScheduler=new k.QuestTickScheduler({apiClient:this.apiClient,logger:this.logger,getOpenCodeUrl:()=>this.getOpenCodeUrl(),getDefaultModel:()=>this.cachedDefaultModel||void 0,projectId:this.projectId||"",projectPath:this.options.projectPath}),this.questTickScheduler.start()}startStuckSessionWatchdog(){this.stuckSessionWatchdog=new E.StuckSessionWatchdog({getOpenCodeUrl:()=>this.getOpenCodeUrl(),getDefaultModel:()=>this.cachedDefaultModel||void 0,logger:this.logger,onStuckDetected:e=>this.handleStuckSessionDetected(e),onStuckResolved:e=>this.handleStuckSessionResolved(e)}),this.stuckSessionWatchdog.start()}handleStuckSessionDetected(e){this.sseEventBuffer.push({type:"session.stuck",data:{type:"session.stuck",properties:{sessionId:e.sessionId,messageId:e.messageId,detectedAt:e.detectedAt,sessionTitle:e.sessionTitle,retryCount:e.retryCount}}}),this.logger.warn("Stuck session detected",{sessionId:e.sessionId,title:e.sessionTitle})}handleStuckSessionResolved(e){this.sseEventBuffer.push({type:"session.unstuck",data:{type:"session.unstuck",properties:{sessionId:e}}})}handleGetStuckSessions(e,t){const r=this.stuckSessionWatchdog?.getStuckSessions()??[]
|
|
592
602
|
t.json({success:!0,data:r})}handleTestInjectStuckSession(e,t){if(!this.stuckSessionWatchdog)return void t.status(503).json({success:!1,error:"Watchdog not running"})
|
|
593
603
|
const{sessionId:r,title:s}=e.body||{},o=this.stuckSessionWatchdog.injectTestStuck(r,s)
|
|
594
604
|
this.logger.info("Test-injected stuck session",{sessionId:o.sessionId}),t.json({success:!0,data:o})}async handleRetryStuckSession(e,t){const{sessionId:r}=e.params
|
|
@@ -602,7 +612,7 @@ for(const[e]of this.proxyAbortControllers)try{e.abort()}catch{}this.proxyAbortCo
|
|
|
602
612
|
try{const e=d.default.join(this.options.projectPath,".alchemy_lab","action-server.json")
|
|
603
613
|
u.default.existsSync(e)&&u.default.unlinkSync(e)}catch{}if(this.projectId)try{const{removeEntry:e}=await Promise.resolve().then(t.bind(t,2640))
|
|
604
614
|
e(this.projectId)}catch{}if(this.isPolling=!1,this.pollAbortController&&this.pollAbortController.abort(),this.pollingInterval&&clearInterval(this.pollingInterval),this.heartbeatInterval&&clearInterval(this.heartbeatInterval),this.serverId)try{await this.apiClient.post(`/api/servers/${this.serverId}/disconnect`)}catch{}this.cliBridge.close(),await new Promise((e=>{if(this.server){const t=setTimeout((()=>{this.logger.warn("Graceful shutdown timed out after 10s, forcing close"),e()}),1e4)
|
|
605
|
-
this.server.close((()=>{clearTimeout(t),e()}))}else e()})),this.logger.info("AlchemyActionServer stopped")}getPort(){return this.port}isRunning(){return!!this.server}}exports.AlchemyActionServer=
|
|
615
|
+
this.server.close((()=>{clearTimeout(t),e()}))}else e()})),this.logger.info("AlchemyActionServer stopped")}getPort(){return this.port}isRunning(){return!!this.server}}exports.AlchemyActionServer=q,q.PROJECT_CONFIG_TTL=3e4},2424(e,exports,t){"use strict"
|
|
606
616
|
Object.defineProperty(exports,"__esModule",{value:!0}),exports.ensureRequestWorktree=async function(e,t,i){const a="requests/"+t,c=(0,s.join)(e,n.ALCHEMY_DIR_NAME,"worktrees"),l=(0,s.join)(c,t)
|
|
607
617
|
if((0,r.existsSync)(l))return void i.info("Worktree already exists on disk, skipping pre-spawn creation",{requestId:t,worktreePath:l})
|
|
608
618
|
const d=(0,o.simpleGit)(e);(0,r.mkdirSync)(c,{recursive:!0})
|
|
@@ -707,7 +717,7 @@ break
|
|
|
707
717
|
case"parent_session":case"ancestor_chain":await(0,o.executeInjectPrompt)(e,e.promptTemplate,i,a,this.ctx)}else await(0,o.executeBackground)(e,t,i,a.requestId,this.ctx)
|
|
708
718
|
const c=(0,s.determineEventToEmit)(e,a)
|
|
709
719
|
c&&await this.processCustomEvent(c,a,r+1)}}async notifyHookTrigger(e){try{await this.ctx.apiClient.post(`/api/projects/${this.ctx.projectId}/hook-triggers`,{hookId:e.id,hookName:e.name,eventType:e.eventType,action:e.action,target:e.target,agent:e.agent})}catch{}}}},3416(e,exports){"use strict"
|
|
710
|
-
Object.defineProperty(exports,"__esModule",{value:!0}),exports.
|
|
720
|
+
Object.defineProperty(exports,"__esModule",{value:!0}),exports.QUEST_METADATA_KEYS=exports.QUEST_COMPLETION_MODE_DEFAULT=exports.QUEST_COMPLETION_MODES=exports.QUEST_STATUSES=exports.QUEST_STATUS_DRAFT=exports.QUEST_STATUS_CLOSED=exports.QUEST_STATUS_PENDING_REVIEW=exports.QUEST_STATUS_OPEN=exports.QUEST_DEFAULTS=void 0,exports.defaultMinIterations=function(e){return"auto-close"===e?3:1},exports.QUEST_DEFAULTS={tickIntervalSeconds:1800,maxInFlight:1,maxPerDay:24,minIterations:3,consecutiveDoneAudits:0},exports.QUEST_STATUS_OPEN="open",exports.QUEST_STATUS_PENDING_REVIEW="pending_review",exports.QUEST_STATUS_CLOSED="closed",exports.QUEST_STATUS_DRAFT="draft",exports.QUEST_STATUSES=[exports.QUEST_STATUS_DRAFT,exports.QUEST_STATUS_OPEN,exports.QUEST_STATUS_PENDING_REVIEW,exports.QUEST_STATUS_CLOSED],exports.QUEST_COMPLETION_MODES=["auto-close","audit-then-pending"],exports.QUEST_COMPLETION_MODE_DEFAULT="audit-then-pending",exports.QUEST_METADATA_KEYS=["criteria","tickIntervalSeconds","maxInFlight","maxPerDay","minIterations","status","completionMode"]},3435(e,exports,t){"use strict"
|
|
711
721
|
Object.defineProperty(exports,"__esModule",{value:!0}),exports.validateRequest=function(e,t,r,s=[]){if(!r)return t.status(400).json({success:!1,error:"No project configured"}),!1
|
|
712
722
|
const o=e.body??{},n=s.filter((e=>void 0===o[e]||null===o[e]||""===o[e]))
|
|
713
723
|
if(n.length>0){const e=1===n.length?"is":"are"
|
|
@@ -889,9 +899,9 @@ break}await s(500)}for(;Date.now()<n;){const o=await r(e,t)
|
|
|
889
899
|
if("error"===o)return{status:"error"}
|
|
890
900
|
if(("idle"===o||"completed"===o)&&i)return{status:"completed"}
|
|
891
901
|
"busy"!==o&&"running"!==o||(i=!0),await s(2e3)}return{status:"timeout"}}(y,w,g),b=Date.now()-v,E=await async function(e,t){try{const r=await fetch(`${e}/session/${t}/message?limit=200`,{signal:AbortSignal.timeout(1e4)})
|
|
892
|
-
return r.ok?(await r.json()).map((e=>({role:e.info?.role||"unknown",text:(e.parts||[]).filter((e=>"text"===e.type)).map((e=>e.text)).join("\n"),toolCalls:(e.parts||[]).filter((e=>"tool_use"===e.type)).map((e=>({name:e.name,id:e.id}))),timestamp:e.info?.time?.created}))):[]}catch{return[]}}(y,w),S=function(e){return e.reduce(((e,t)=>e+(t.toolCalls?.length||0)),0)}(E),
|
|
902
|
+
return r.ok?(await r.json()).map((e=>({role:e.info?.role||"unknown",text:(e.parts||[]).filter((e=>"text"===e.type)).map((e=>e.text)).join("\n"),toolCalls:(e.parts||[]).filter((e=>"tool_use"===e.type)).map((e=>({name:e.name,id:e.id}))),timestamp:e.info?.time?.created}))):[]}catch{return[]}}(y,w),S=function(e){return e.reduce(((e,t)=>e+(t.toolCalls?.length||0)),0)}(E),k=function(e){for(let t=e.length-1;t>=0;t--)if("assistant"===e[t].role&&e[t].text)return e[t].text
|
|
893
903
|
return""}(E)
|
|
894
|
-
return{success:!0,message:"Prompt completed",data:{sessionId:w,status:I.status,messages:E,toolCallCount:S,elapsedMs:b,finalResponse:
|
|
904
|
+
return{success:!0,message:"Prompt completed",data:{sessionId:w,status:I.status,messages:E,toolCallCount:S,elapsedMs:b,finalResponse:k}}}
|
|
895
905
|
const o=t(8148)},4769(e,exports,t){"use strict"
|
|
896
906
|
function r(e){return(0,o.getOpenCodeServerUrl)(e.projectPath,e.logger)}async function s(e,t,s,o){const n=r(s)
|
|
897
907
|
if(!n||!t)return!1
|
|
@@ -1030,33 +1040,33 @@ if(u?.length)b=(await Promise.all(u.map((e=>f.get(`/api/projects/${y}/test-proce
|
|
|
1030
1040
|
else{const e=await f.get(`/api/projects/${y}/test-processes/active`)
|
|
1031
1041
|
b=e.success&&e.data?Array.isArray(e.data)?e.data:[e.data]:[]}if(0===b.length)return{success:!1,message:"No test processes configured for this project. Set up testing first.",error:"No test processes"}
|
|
1032
1042
|
const E=Math.max(1,Math.min(8,b[0]?.config?.poolSize??2)),S=b[0]?.config?.fingerprint||[]
|
|
1033
|
-
let
|
|
1034
|
-
try{
|
|
1035
|
-
try{if((0,s.existsSync)(
|
|
1036
|
-
await e.raw(["reset","--hard",I]),m.info("Reset pool slot to latest branch HEAD",{worktreePath:
|
|
1037
|
-
try{await T.raw(["worktree","remove","--force",
|
|
1038
|
-
try{if(!(await T.branch()).all.includes(I))return(0,c.releaseSlot)(
|
|
1039
|
-
await T.raw(["worktree","add","--detach",
|
|
1040
|
-
m.info("Pool slot worktree already exists, using as-is",{worktreePath:
|
|
1041
|
-
j||m.info("Pool slot fingerprint matches; skipping setup",{worktreePath:
|
|
1043
|
+
let k
|
|
1044
|
+
try{k=await(0,c.acquireSlot)(v,E)}catch(e){return{success:!1,message:"Could not acquire test worktree slot: "+(0,i.getErrorMessage)(e),error:(0,i.getErrorMessage)(e)}}const C=k.path,T=(0,n.simpleGit)(v)
|
|
1045
|
+
try{if((0,s.existsSync)(C))try{const e=(0,n.simpleGit)(C)
|
|
1046
|
+
await e.raw(["reset","--hard",I]),m.info("Reset pool slot to latest branch HEAD",{worktreePath:C,branchName:I,slotIndex:k.index})}catch(e){m.warn("Could not reset slot, recreating",{error:(0,i.getErrorMessage)(e)})
|
|
1047
|
+
try{await T.raw(["worktree","remove","--force",C])}catch{}}if(!(0,s.existsSync)(C)){(0,s.mkdirSync)((0,o.join)(v,".alchemy_lab","test-worktrees"),{recursive:!0})
|
|
1048
|
+
try{if(!(await T.branch()).all.includes(I))return(0,c.releaseSlot)(C),{success:!1,message:`Branch ${I} not found`,error:"Branch not found"}
|
|
1049
|
+
await T.raw(["worktree","add","--detach",C,I]),m.info("Created pool slot worktree (detached)",{worktreePath:C,branchName:I,slotIndex:k.index})}catch(e){if(!(e+"").includes("already exists"))return(0,c.releaseSlot)(C),{success:!1,message:"Failed to create pool slot worktree: "+(0,i.getErrorMessage)(e),error:(0,i.getErrorMessage)(e)}
|
|
1050
|
+
m.info("Pool slot worktree already exists, using as-is",{worktreePath:C})}}}catch(e){throw(0,c.releaseSlot)(C),e}const _=await(0,c.computeFingerprint)(C,S),j=0===S.length||_!==k.storedFingerprint,A=0===S.length?null:_===k.storedFingerprint
|
|
1051
|
+
j||m.info("Pool slot fingerprint matches; skipping setup",{worktreePath:C,slotIndex:k.index,fingerprintFiles:S})
|
|
1042
1052
|
const{execSync:P}=await Promise.resolve().then(t.t.bind(t,5317,23))
|
|
1043
1053
|
if(j){for(const e of b){const t=e.config?.setup||[]
|
|
1044
|
-
if(0!==t.length){m.info("Running setup commands for test process",{processName:e.name,commandCount:t.length,slotIndex:
|
|
1045
|
-
for(const r of t)try{m.info("Setup command",{command:r,worktreePath:
|
|
1046
|
-
e>0&&m.info("Pruned drift from warm slot",{worktreePath:
|
|
1047
|
-
try{const e=(0,n.simpleGit)(
|
|
1048
|
-
M=(await e.revparse(["HEAD"])).trim()}catch(e){m.warn("Failed to capture worktree HEAD; commit_sha will be NULL on this run",{worktreePath:
|
|
1054
|
+
if(0!==t.length){m.info("Running setup commands for test process",{processName:e.name,commandCount:t.length,slotIndex:k.index})
|
|
1055
|
+
for(const r of t)try{m.info("Setup command",{command:r,worktreePath:C}),P(r,{cwd:C,timeout:12e4,encoding:"utf-8",stdio:["pipe","pipe","pipe"],shell:"/bin/sh",env:{...process.env,FORCE_COLOR:"0"}})}catch(t){m.warn("Setup command failed",{command:r,processName:e.name,exitCode:t.status,stderr:(t.stderr||"").slice(-500)})}}}S.length>0&&await(0,c.writeSlotFingerprint)(C,_),await(0,c.writeWarmSnapshot)(C)}else{const{removed:e}=await(0,c.restoreToWarmSnapshot)(C)
|
|
1056
|
+
e>0&&m.info("Pruned drift from warm slot",{worktreePath:C,slotIndex:k.index,removed:e})}let M
|
|
1057
|
+
try{const e=(0,n.simpleGit)(C)
|
|
1058
|
+
M=(await e.revparse(["HEAD"])).trim()}catch(e){m.warn("Failed to capture worktree HEAD; commit_sha will be NULL on this run",{worktreePath:C,error:(0,i.getErrorMessage)(e)})}const O=[],R=[],D=[]
|
|
1049
1059
|
let x
|
|
1050
|
-
const $={worktreePath:
|
|
1060
|
+
const $={worktreePath:C,commitSha:M,fingerprintHit:A}
|
|
1051
1061
|
for(const process of b){let t
|
|
1052
1062
|
const s=e.testRunIds?.[O.length]
|
|
1053
1063
|
try{s?(await f.patch(`/api/projects/${y}/test-runs/${s}`,$),t=(await f.get(`/api/projects/${y}/test-runs/${s}`)).data,O.push(t.id)):(t=(await f.post(`/api/projects/${y}/test-runs`,{requestId:d,testProcessId:process.id,parentSessionId:h,...$})).data,O.push(t.id))}catch(e){m.error("Failed to get/create test run record",{requestId:d,processId:process.id,preCreatedId:s,error:(0,i.getErrorMessage)(e)})
|
|
1054
1064
|
continue}const o=r(g,process,0,y,p,e.commandNames,e.skipCommandNames),n=p?"tester":"auto-tester"
|
|
1055
|
-
try{const e=await(0,a.spawnActionAgent)({agent:n,prompt:o,cwd:
|
|
1065
|
+
try{const e=await(0,a.spawnActionAgent)({agent:n,prompt:o,cwd:C,projectPath:v,projectId:y,title:`[${d}] Test: ${process.name}`,requestId:d,parentSessionId:h,allowParallel:!0,apiClient:f,logger:m,trackChildPid:w})
|
|
1056
1066
|
if(!e.success)throw Error(e.error||"Spawn failed")
|
|
1057
|
-
R.push(t.id),e.sessionId&&D.push(e.sessionId),e.pid&&(x=e.pid),e.sessionId&&(f.patch(`/api/projects/${y}/test-runs/${t.id}`,{testerSessionId:e.sessionId}).catch((e=>m.warn("Failed to set testerSessionId on test run",{error:(0,i.getErrorMessage)(e)}))),f.post(`/api/projects/${y}/session-test-process-associations`,{opencodeSessionId:e.sessionId,testProcessId:process.id}).catch((t=>m.warn("Failed to create session\u2194test-process association",{sessionId:e.sessionId,testProcessId:process.id,error:(0,i.getErrorMessage)(t)})))),m.info("Spawned test-runner agent",{testRunId:t.id,processName:process.name,worktreePath:
|
|
1058
|
-
try{await f.patch(`/api/projects/${y}/test-runs/${t.id}`,{status:"error",results:{steps:[],summary:"Failed to spawn test agent: "+(0,i.getErrorMessage)(e)}})}catch{}}}if(0===R.length)return(0,c.releaseSlot)(
|
|
1059
|
-
for(const e of D);return(0,c.watchPidAndRelease)(x,
|
|
1067
|
+
R.push(t.id),e.sessionId&&D.push(e.sessionId),e.pid&&(x=e.pid),e.sessionId&&(f.patch(`/api/projects/${y}/test-runs/${t.id}`,{testerSessionId:e.sessionId}).catch((e=>m.warn("Failed to set testerSessionId on test run",{error:(0,i.getErrorMessage)(e)}))),f.post(`/api/projects/${y}/session-test-process-associations`,{opencodeSessionId:e.sessionId,testProcessId:process.id}).catch((t=>m.warn("Failed to create session\u2194test-process association",{sessionId:e.sessionId,testProcessId:process.id,error:(0,i.getErrorMessage)(t)})))),m.info("Spawned test-runner agent",{testRunId:t.id,processName:process.name,worktreePath:C,pid:e.pid,sessionId:e.sessionId})}catch(e){m.error("Failed to spawn test-runner agent",{testRunId:t.id,error:(0,i.getErrorMessage)(e)})
|
|
1068
|
+
try{await f.patch(`/api/projects/${y}/test-runs/${t.id}`,{status:"error",results:{steps:[],summary:"Failed to spawn test agent: "+(0,i.getErrorMessage)(e)}})}catch{}}}if(0===R.length)return(0,c.releaseSlot)(C),{success:!1,message:"Failed to spawn any test agents",error:"All agent spawns failed"}
|
|
1069
|
+
for(const e of D);return(0,c.watchPidAndRelease)(x,C,12e5),{success:!0,message:`Started ${R.length} test run(s) for request ${d}`,data:{testRunIds:O,agentsSpawned:R.length,processCount:b.length,worktreePath:C,spawnedSessionIds:D}}}catch(e){const t=(0,i.getErrorMessage)(e)
|
|
1060
1070
|
return m.error("run-tests action failed",{requestId:d,error:t}),{success:!1,message:"Failed to run tests: "+t,error:t}}}
|
|
1061
1071
|
const s=t(9896),o=t(6928),n=t(3807),i=t(8824),a=t(8148),c=t(489)},6563(e,exports,t){"use strict"
|
|
1062
1072
|
Object.defineProperty(exports,"__esModule",{value:!0}),exports.TimerHookScheduler=void 0
|
|
@@ -1440,7 +1450,7 @@ e&&e.error&&"retryable"in e.error&&(this.details.retryable=!!e.error.retryable)}
|
|
|
1440
1450
|
Object.defineProperty(exports,"__esModule",{value:!0}),exports.spawnPlannerWithDoc=async function(e){const{agent:t,prompt:o,title:n,parentSessionId:i,requestId:a,apiClient:c,logger:l,projectId:d,projectPath:u,trackChildPid:h}=e
|
|
1441
1451
|
let g=e.planId
|
|
1442
1452
|
if(!g){const r=`PLAN-${(e.planName||n).replace(/\.md$/i,"")}.md`
|
|
1443
|
-
try{const e=await c.post(`/api/projects/${d}/documents`,{filename:r,content:" ",type:"Plan"})
|
|
1453
|
+
try{const e=await c.post(`/api/projects/${d}/documents`,{filename:r,content:" ",type:"Plan",metadata:{isStub:!0}})
|
|
1444
1454
|
g=e?.data?.id,l.info("spawnPlannerWithDoc: pre-created plan document",{planId:g,filename:r,agent:t})}catch(e){l.warn("spawnPlannerWithDoc: failed to pre-create plan document; planner will fall back to plan_write create",{error:(0,s.getErrorMessage)(e),agent:t})}}const p="quest-planner"===t?"quest":"plan",m=g?`[Session Context]\n- ${p}Id: ${g} \u2014 this is YOUR associated ${p} document. It already exists with a placeholder filename.\n- Use plan_write (update / append / replace) with planId "${g}" to fill it in.${"quest-planner"===t?" For quests, plan_write update also accepts criteria / tickIntervalSeconds / maxInFlight / maxPerDay / minIterations / status.":""}${"quest-planner"===t?"":'\n- On your FIRST plan_write call, include a "name" field with a short descriptive title so the document gets a meaningful filename.'}\n- Do NOT call plan_write "create" \u2014 always modify the document above.\n- Do NOT call plan_read "get-active" \u2014 your associated ${p} is given here, not via the active-${p} slot.\n\n${o}`:o,f=await(0,r.spawnActionAgent)({agent:t,prompt:m,cwd:u,projectPath:u,projectId:d,title:n,requestId:a,parentSessionId:i,apiClient:c,logger:l,trackChildPid:h})
|
|
1445
1455
|
if(!f.success||!f.sessionId)return{success:!1,error:f.error||"spawnActionAgent returned no sessionId"}
|
|
1446
1456
|
l.info("spawnPlannerWithDoc: spawn succeeded",{agent:t,sessionId:f.sessionId,pid:f.pid,planId:g})
|
|
@@ -1547,11 +1557,11 @@ t?.mode?.alchemy?.tools&&(m=t.mode.alchemy.tools,this.logger.info("Found existin
|
|
|
1547
1557
|
i?.openCodeModel?b.model=i.openCodeModel:p?b.model=p:f&&(b.model=f)
|
|
1548
1558
|
const S=c.join(s,".opencode","agent")
|
|
1549
1559
|
await r.mkdir(S,{recursive:!0})
|
|
1550
|
-
const
|
|
1551
|
-
let
|
|
1552
|
-
if(i?.projectId)if(await this.seedAgentsFromRemote(i.projectId),_=await this.fetchProjectFromRemote(i.projectId),
|
|
1560
|
+
const k=E||"anthropic/claude-opus-4-5-20251101"
|
|
1561
|
+
let C,T=null,_=null
|
|
1562
|
+
if(i?.projectId)if(await this.seedAgentsFromRemote(i.projectId),_=await this.fetchProjectFromRemote(i.projectId),C=_?.metadata?.globalSystemPrompt,C&&this.logger.info("Using global system prompt from project metadata",{projectId:i.projectId,promptLength:C.length}),T=await this.fetchAgentsFromRemote(i.projectId),T&&T.length>0){this.logger.info("Using agents from remote database",{count:T.length})
|
|
1553
1563
|
for(const e of T){if(!e.enabled)continue
|
|
1554
|
-
const t=this.generateAgentMarkdown(e,d,C
|
|
1564
|
+
const t=this.generateAgentMarkdown(e,d,k,C),s=c.join(S,e.name+".md")
|
|
1555
1565
|
await r.writeFile(s,t,"utf8"),this.logger.info(`Created ${e.name} agent file from remote config`,{agentPath:s,mode:e.mode})}}else this.logger.warn("No agents found - check remote server or enable agents via UI")
|
|
1556
1566
|
else this.logger.warn("No projectId provided - agents must be configured via database")
|
|
1557
1567
|
const j={$schema:"https://opencode.ai/config.json",model:E,mcp:{[d]:{type:"local",command:[n,u,s],enabled:!0,timeout:6e5}}}
|
|
@@ -1562,8 +1572,8 @@ for(const[r,s]of Object.entries(e.tools||{})){const e=s,n=r.replace(/-/g,"_")
|
|
|
1562
1572
|
t[o.OPENCODE_BUILTIN_TOOLS.has(r)?r:`${d}_${n}`]=e}const r=_?.metadata?.bashToolAgents||{}
|
|
1563
1573
|
e.name in r&&(t.bash=!!r[e.name]),t.skill=!1,("subagent"===e.mode||"tester"===e.name||"auto-tester"===e.name)&&void 0===t.task&&(t.task=!1)
|
|
1564
1574
|
let s=""
|
|
1565
|
-
|
|
1566
|
-
const e=`---\ndescription: Fast codebase exploration and research subagent\nmode: subagent\n---\n\n${
|
|
1575
|
+
C&&(s+=C.trim()+"\n\n"),e.prompt&&(s+=e.prompt),e.promptAddition&&(s+="\n\n"+e.promptAddition),j.agent[e.name]={tools:t,...s?{prompt:s}:{},...e.description?{description:e.description}:{},mode:"utility"===e.mode?"primary":e.mode||"primary",...e.color?{color:e.color}:{}}}j.agent.general={...j.agent.general,disable:!0},j.agent.explore={...j.agent.explore,tools:{grep:!1,glob:!1,list:!1,read:!1,bash:!1,[d+"_search"]:!0,[d+"_read_files"]:!0,[d+"_view_asset"]:!0,[d+"_query_history"]:!0,webfetch:!0,websearch:!0,codesearch:!0}}
|
|
1576
|
+
const e=`---\ndescription: Fast codebase exploration and research subagent\nmode: subagent\n---\n\n${C?C.trim()+"\n\n":""}You are a codebase exploration agent. Your job is to find specific information and return concise, targeted answers.\n\n## Response Guidelines\n\n- **Be concise.** Return only what was asked for. A typical answer is 100-500 words.\n- **Lead with the answer.** State findings first, then supporting details.\n- **Reference specific files and lines.** Use \`file_path:line_number\` format.\n- **Don't exhaustively document everything you find.** Focus on what's relevant to the question.\n- **Use structured output** when listing multiple items (bullet points, not prose).\n\n## Strategy\n\n1. Start with \`search\` to find relevant files by name or content patterns\n2. Use \`read_files\` to read specific sections (use line ranges, don't read entire large files)\n3. Use \`query_history\` for understanding why code changed or tracing decisions\n\n## Anti-patterns to Avoid\n\n- Don't produce multi-thousand-word reports for simple questions\n- Don't read every file in a directory when a targeted search would suffice\n- Don't explain how tools work \u2014 just use them and report findings\n`
|
|
1567
1577
|
await r.writeFile(c.join(S,"explore.md"),e,"utf8"),this.logger.info("Created explore agent override with research prompt"),this.logger.info("Added agent tools to opencode.json",{agentCount:Object.keys(j.agent).length})}const A=e=>"string"==typeof e&&e.includes(".opencode/plugin/"),P=(await this.fetchPluginManifest())["opencode-claude-auth"],M=j.plugin,O=Array.isArray(M)?M.filter((e=>!A(e))):[]
|
|
1568
1578
|
P&&this.applyAuthPluginPin(O,P),O.length>0?j.plugin=O:delete j.plugin,await r.writeFile(h,JSON.stringify(j,null,2),"utf8"),this.logger.info("OpenCode configured successfully",{configPath:h})
|
|
1569
1579
|
try{const e=await Promise.resolve().then(t.t.bind(t,857,23)),s=c.join(e.homedir(),".alchemy_lab","opencode","config"),o=c.join(s,"opencode.json")
|
|
@@ -1687,63 +1697,50 @@ if(i.ok){const e=await i.json()
|
|
|
1687
1697
|
return e.data?.id||e.id}s.info(`Project registration failed (HTTP ${i.status})`)}catch(e){s.info("Project registration failed",{error:(0,I.getErrorMessage)(e)})}}Object.defineProperty(i,"__esModule",{value:!0})
|
|
1688
1698
|
const l=e(3444),d=e(9896),u=e(1943),h=e(6928),g=e(5317),p=e(857),m=e(1253),f=e(8984),y=e(2366),v=e(3962),w=e(1187),I=e(8824),b=e(6800),E=e(4139);(async function(){23===parseInt(process.version.replace("v","").split(".")[0],10)&&(console.error(`\n\u274c Node.js ${process.version} is not supported.`),console.error(" AlchemyLab requires Node.js 22 or 24+ (Node 23 has a broken sqlite API)."),console.error("\n Switch with: nvm use 22 or nvm use 24\n"),process.exit(1))
|
|
1689
1699
|
const i=process.argv.slice(2)
|
|
1690
|
-
let
|
|
1691
|
-
const
|
|
1692
|
-
const
|
|
1693
|
-
if(0===
|
|
1700
|
+
let g
|
|
1701
|
+
const p=i.indexOf("--port");-1!==p&&i[p+1]&&(g=parseInt(i[p+1],10),isNaN(g)&&(console.error("\u274c Invalid port number"),process.exit(1)))
|
|
1702
|
+
const S=i.filter(((e,t)=>"--port"!==e&&(-1===p||t!==p+1)))
|
|
1703
|
+
if(0===S.length)return void await async function(r={}){const i=process.cwd(),l=(0,h.join)(i,E.ALCHEMY_DIR_NAME)
|
|
1694
1704
|
await(0,u.mkdir)(l,{recursive:!0})
|
|
1695
|
-
const
|
|
1696
|
-
await
|
|
1697
|
-
let
|
|
1698
|
-
if(!
|
|
1699
|
-
const e=await
|
|
1700
|
-
console.log("\u{1f310} "+e.authUrl),o(e.authUrl),console.log(""),console.log("\u{1f446} Browser window opened. Complete login there."),console.log("\u23f1\ufe0f Waiting for authentication..."),await n(
|
|
1705
|
+
const g=(0,h.join)(l,"server.log"),{createLogger:p}=await Promise.resolve().then(e.t.bind(e,2782,23)),S=p({serviceName:"alchemy-server",console:!1,logToFile:!0,logFilePath:g,baseDir:i}),k=await a(S),C=new m.TokenManager(S,i,{remoteServerUrl:k,allowInteractiveAuth:!1})
|
|
1706
|
+
await C.initialize()
|
|
1707
|
+
let T=await C.getToken()
|
|
1708
|
+
if(!T?.token){console.log(""),console.log("\u{1f511} Not logged in. Opening browser to authenticate...")
|
|
1709
|
+
const e=await C.startAuthenticationFlow()
|
|
1710
|
+
console.log("\u{1f310} "+e.authUrl),o(e.authUrl),console.log(""),console.log("\u{1f446} Browser window opened. Complete login there."),console.log("\u23f1\ufe0f Waiting for authentication..."),await n(C)||(console.error("\u274c Authentication timed out. Run `alchemy` again to retry."),process.exit(1)),T=await C.getToken(),console.log(`\u2705 Welcome, ${T?.username||"user"}!`),console.log("")}let _=T?.token
|
|
1701
1711
|
await async function(e,t,r,o){if(s(e))return
|
|
1702
1712
|
console.log("\u{1f4c1} Initializing AlchemyLab project..."),console.log("")
|
|
1703
1713
|
const n=(0,h.join)(e,E.ALCHEMY_DIR_NAME)
|
|
1704
1714
|
let i
|
|
1705
1715
|
await(0,u.mkdir)(n,{recursive:!0}),r?(console.log(`\u{1f4cb} Registering project "${(0,h.basename)(e)||"Local Project"}"...`),i=await c(t,r,e,o),console.log(i?"\u2705 Project registered: "+i:"\u26a0\ufe0f Project registration failed (will retry next run)")):console.log("\u26a0\ufe0f No auth token \u2014 skipping project registration")
|
|
1706
|
-
const a=(0,h.join)(n,"project.json");(0,d.writeFileSync)(a,JSON.stringify({id:i||null,name:(0,h.basename)(e)||"Local Project",status:"active",registeredRemotely:!!i,createdAt:(new Date).toISOString(),updatedAt:(new Date).toISOString(),metadata:{basePath:e,alchemyPath:n}},null,2),"utf-8"),console.log("")}(i,
|
|
1707
|
-
const
|
|
1708
|
-
let
|
|
1709
|
-
try{const e=JSON.parse((0,d.readFileSync)(
|
|
1710
|
-
if(
|
|
1711
|
-
t&&(e.id=t,e.registeredRemotely=!0,e.updatedAt=(new Date).toISOString(),(0,d.writeFileSync)(
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
t.
|
|
1715
|
-
|
|
1716
|
-
try{
|
|
1717
|
-
try{return(0,g.execFileSync)("which",["opencode"],{stdio:"pipe"}),void console.log("\u2705 OpenCode installed successfully via go install")}catch{console.log("\u26a0\ufe0f go install completed but opencode not found in PATH"),console.log(" Ensure $(go env GOPATH)/bin is in your PATH")}}catch{}try{const{platform:t,arch:s}=await Promise.resolve().then(e.t.bind(e,857,23)),o=t(),n=s(),i="darwin"===o?"darwin":"linux"===o?"linux":null,a="arm64"===n?"arm64":"x64"===n?"amd64":null
|
|
1718
|
-
if(i&&a){const e=`opencode_${i}_${a}`,t="latest"===r?"https://github.com/sst/opencode/releases/latest/download/"+e:`https://github.com/sst/opencode/releases/download/v${r}/${e}`
|
|
1719
|
-
console.log(" Downloading from: "+t)
|
|
1720
|
-
const s=(0,h.join)((0,p.homedir)(),".local","bin")
|
|
1721
|
-
await(0,u.mkdir)(s,{recursive:!0})
|
|
1722
|
-
const o=(0,h.join)(s,"opencode")
|
|
1723
|
-
return(0,g.execFileSync)("curl",["-fsSL","-o",o,t],{stdio:"inherit",timeout:6e4}),(0,g.execFileSync)("chmod",["+x",o],{stdio:"pipe"}),(process.env.PATH||"").split(":").includes(s)||(console.log(` \u26a0\ufe0f Add ${s} to your PATH:`),console.log(` export PATH="${s}:$PATH"`),console.log(""),process.env.PATH=`${s}:${process.env.PATH}`),void console.log("\u2705 OpenCode installed successfully")}}catch(e){console.log("\u26a0\ufe0f Binary download failed: "+(0,I.getErrorMessage)(e))}console.log(""),console.log("\u26a0\ufe0f Could not auto-install OpenCode."),console.log(""),console.log(" Install manually:"),console.log(" go install github.com/sst/opencode@latest"),console.log(" Or download from:"),console.log(" https://github.com/sst/opencode/releases"),console.log("")}(i)
|
|
1724
|
-
const O=t()
|
|
1725
|
-
console.log(["\n .oo 8 8 o 8 \n .P 8 8 8 8 8 \n .P 8 8 .oPYo. 8oPYo. .oPYo. ooYoYo. o o 8 .oPYo. 8oPYo. \n oPooo8 8 8 ' 8 8 8oooo8 8' 8 8 8 8 8 .oooo8 8 8 \n .P 8 8 8 . 8 8 8. 8 8 8 8 8 8 8 8 8 8 \n.P 8 8 `YooP' 8 8 `Yooo' 8 8 8 `YooP8 8oooo `YooP8 `YooP' \n..:::::....:.....:..:::..:.....:..:..:..:....8 ......:.....::.....:","::::::::::::::::::::::::::::::::::::::::::ooP'.::::::::::::::::::: v"+O,"::::::::::::::::::::::::::::::::::::::::::...::::::::::::::::::::::\n"].join("\n")),console.log("\u{1f4c1} Project: "+i)
|
|
1726
|
-
const R=r.port||await(0,b.findAvailablePort)(3456),D=new y.AlchemyActionServer(k,{projectPath:i,remoteServerUrl:T,port:R,authToken:A,tokenManager:_})
|
|
1727
|
-
try{const t=(0,v.getInstallContext)(),r=new f.MCPClientConfigurer(k,T,A),s=new w.UnifiedLauncher(k,t,r),[,a,c]=await Promise.all([D.start(),(async()=>{if(process.env.FRONTEND_URL)return process.env.FRONTEND_URL
|
|
1728
|
-
if(T.includes("api.alchemylabcode.com"))return"https://alchemylabcode.com"
|
|
1729
|
-
try{const e=await fetch(T+"/health")
|
|
1716
|
+
const a=(0,h.join)(n,"project.json");(0,d.writeFileSync)(a,JSON.stringify({id:i||null,name:(0,h.basename)(e)||"Local Project",status:"active",registeredRemotely:!!i,createdAt:(new Date).toISOString(),updatedAt:(new Date).toISOString(),metadata:{basePath:e,alchemyPath:n}},null,2),"utf-8"),console.log("")}(i,k,_,S)
|
|
1717
|
+
const j=(0,h.join)(i,E.ALCHEMY_DIR_NAME,"project.json")
|
|
1718
|
+
let A
|
|
1719
|
+
try{const e=JSON.parse((0,d.readFileSync)(j,"utf-8"))
|
|
1720
|
+
if(A=e.id||void 0,!1===e.registeredRemotely&&_){const t=await c(k,_,i,S,"cli-init-retry")
|
|
1721
|
+
t&&(e.id=t,e.registeredRemotely=!0,e.updatedAt=(new Date).toISOString(),(0,d.writeFileSync)(j,JSON.stringify(e,null,2),"utf-8"),A=t,console.log("\u2705 Project registered: "+t))}}catch{S.warn("Could not read project.json")}const P=t()
|
|
1722
|
+
console.log(["\n .oo 8 8 o 8 \n .P 8 8 8 8 8 \n .P 8 8 .oPYo. 8oPYo. .oPYo. ooYoYo. o o 8 .oPYo. 8oPYo. \n oPooo8 8 8 ' 8 8 8oooo8 8' 8 8 8 8 8 .oooo8 8 8 \n .P 8 8 8 . 8 8 8. 8 8 8 8 8 8 8 8 8 8 \n.P 8 8 `YooP' 8 8 `Yooo' 8 8 8 `YooP8 8oooo `YooP8 `YooP' \n..:::::....:.....:..:::..:.....:..:..:..:....8 ......:.....::.....:","::::::::::::::::::::::::::::::::::::::::::ooP'.::::::::::::::::::: v"+P,"::::::::::::::::::::::::::::::::::::::::::...::::::::::::::::::::::\n"].join("\n")),console.log("\u{1f4c1} Project: "+i)
|
|
1723
|
+
const M=r.port||await(0,b.findAvailablePort)(3456),O=new y.AlchemyActionServer(S,{projectPath:i,remoteServerUrl:k,port:M,authToken:_,tokenManager:C})
|
|
1724
|
+
try{const t=(0,v.getInstallContext)(),r=new f.MCPClientConfigurer(S,k,_),s=new w.UnifiedLauncher(S,t,r),[,a,c]=await Promise.all([O.start(),(async()=>{if(process.env.FRONTEND_URL)return process.env.FRONTEND_URL
|
|
1725
|
+
if(k.includes("api.alchemylabcode.com"))return"https://alchemylabcode.com"
|
|
1726
|
+
try{const e=await fetch(k+"/health")
|
|
1730
1727
|
if(e.ok){const t=await e.json()
|
|
1731
|
-
if(t?.data?.frontendUrl)return t.data.frontendUrl}}catch{}return
|
|
1732
|
-
if(console.log(""),console.log(" Action server .... port "+
|
|
1733
|
-
const e=(0,h.join)(i,E.ALCHEMY_DIR_NAME,"opencode.json");(0,d.writeFileSync)(e,JSON.stringify({port:c.port,pid:"pid"in c?c.pid:void 0,startedAt:(new Date).toISOString()},null,2)),
|
|
1728
|
+
if(t?.data?.frontendUrl)return t.data.frontendUrl}}catch{}return k.replace(/:\d+/,":3000")})(),s.launch({projectPath:i,settings:A?{projectId:A}:void 0}).catch((e=>(S.warn("Failed to launch OpenCode serve",{error:(0,I.getErrorMessage)(e)}),{success:!1,error:(0,I.getErrorMessage)(e)})))])
|
|
1729
|
+
if(console.log(""),console.log(" Action server .... port "+O.getPort()),c.success&&"port"in c&&c.port){"pid"in c&&c.pid&&O.trackChildPid(c.pid),O.setOpenCodePort(c.port),console.log(" OpenCode serve ... port "+c.port)
|
|
1730
|
+
const e=(0,h.join)(i,E.ALCHEMY_DIR_NAME,"opencode.json");(0,d.writeFileSync)(e,JSON.stringify({port:c.port,pid:"pid"in c?c.pid:void 0,startedAt:(new Date).toISOString()},null,2)),S.info("Wrote OpenCode config",{path:e,port:c.port})}else c.success?console.log(" OpenCode serve ... started"):console.log(" OpenCode serve ... failed (run manually: opencode serve)")
|
|
1734
1731
|
console.log(""),console.log(` \u2192 Alchemy UI: \x1b]8;;${a}\x07${a}\x1b]8;;\x07`),console.log("")
|
|
1735
1732
|
let l=!1,u=null
|
|
1736
|
-
const g=async()=>{l||(l=!0,u&&clearInterval(u),console.log(""),console.log("Shutting down..."),await
|
|
1733
|
+
const g=async()=>{l||(l=!0,u&&clearInterval(u),console.log(""),console.log("Shutting down..."),await O.stop(),process.exit(0))}
|
|
1737
1734
|
process.on("SIGTERM",g),process.on("SIGINT",g)
|
|
1738
|
-
const{InteractiveShell:p}=await e.e(994).then(e.t.bind(e,5994,23)),m=new p({projectPath:i,logger:
|
|
1735
|
+
const{InteractiveShell:p}=await e.e(994).then(e.t.bind(e,5994,23)),m=new p({projectPath:i,logger:S,remoteServerUrl:k,authToken:_,projectId:A})
|
|
1739
1736
|
let y=!1
|
|
1740
|
-
u=setInterval((async()=>{if(!y)try{if(await
|
|
1737
|
+
u=setInterval((async()=>{if(!y)try{if(await C.hasValidToken())return
|
|
1741
1738
|
y=!0,console.log(""),console.log("\u26a0\ufe0f Auth token expired. Opening browser to re-authenticate...")
|
|
1742
|
-
const e=await
|
|
1743
|
-
if(console.log("\u{1f310} "+e.authUrl),o(e.authUrl),console.log("\u23f1\ufe0f Waiting for re-authentication..."),await n(
|
|
1744
|
-
e?.token&&(
|
|
1745
|
-
const
|
|
1746
|
-
switch("--help"!==
|
|
1739
|
+
const e=await C.startAuthenticationFlow()
|
|
1740
|
+
if(console.log("\u{1f310} "+e.authUrl),o(e.authUrl),console.log("\u23f1\ufe0f Waiting for re-authentication..."),await n(C)){const e=await C.getToken()
|
|
1741
|
+
e?.token&&(_=e.token,O.updateAuthToken(_)),console.log("\u2705 Re-authenticated successfully.")}else console.log("\u274c Re-authentication timed out. Some features may not work until you restart.")}catch(e){S.warn("Token watchdog check failed",{error:(0,I.getErrorMessage)(e)})}finally{y=!1}}),3e5),await m.start(g)}catch(e){S.error("Failed to start server",{error:(0,I.getErrorMessage)(e)}),console.error("\u274c Failed to start: "+(0,I.getErrorMessage)(e)),process.exit(1)}}({port:g})
|
|
1742
|
+
const k=S[0].toLowerCase()
|
|
1743
|
+
switch("--help"!==k&&"-h"!==k||(r(),process.exit(0)),"--version"!==k&&"-v"!==k||(console.log("alchemy v"+t()),process.exit(0)),k){case"serve":await async function(e={}){const r=process.cwd()
|
|
1747
1744
|
s(r)||(console.log("\u{1f4c1} Current directory is not an AlchemyLab project."),console.log(""),console.log("\u{1f4a1} Run `alchemy` to initialize this project."),console.log(""),process.exit(1))
|
|
1748
1745
|
const o=(0,l.createEnvLogger)({serviceName:"alchemy-server",console:!0,baseDir:r}),n=t()
|
|
1749
1746
|
console.log(`\n\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557\n\u2551 AlchemyLab Server v${n.padEnd(10)} \u2551\n\u255a\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255d\n`),console.log("\u{1f4c1} Project: "+r)
|
|
@@ -1755,9 +1752,9 @@ const h=e.port||await(0,b.findAvailablePort)(3456),g=new y.AlchemyActionServer(o
|
|
|
1755
1752
|
try{await g.start(),console.log(""),console.log("\u2705 Action server running!"),console.log(` Health: http://127.0.0.1:${g.getPort()}/health`),console.log(""),console.log("Press Ctrl+C to stop the server."),console.log("")
|
|
1756
1753
|
let e=!1
|
|
1757
1754
|
const t=async()=>{e||(e=!0,console.log(""),console.log("Shutting down action server..."),await g.stop(),process.exit(0))}
|
|
1758
|
-
process.on("SIGTERM",t),process.on("SIGINT",t),await new Promise((()=>{}))}catch(e){o.error("Failed to start server",{error:(0,I.getErrorMessage)(e)}),console.error("\u274c Failed to start: "+(0,I.getErrorMessage)(e)),process.exit(1)}}({port:
|
|
1755
|
+
process.on("SIGTERM",t),process.on("SIGINT",t),await new Promise((()=>{}))}catch(e){o.error("Failed to start server",{error:(0,I.getErrorMessage)(e)}),console.error("\u274c Failed to start: "+(0,I.getErrorMessage)(e)),process.exit(1)}}({port:g})
|
|
1759
1756
|
break
|
|
1760
1757
|
case"logout":await async function(){const e=process.cwd(),t=(0,l.createEnvLogger)({serviceName:"alchemy-cli",console:!1,baseDir:e}),r=await a(t),s=new m.TokenManager(t,e,{remoteServerUrl:r,allowInteractiveAuth:!1})
|
|
1761
1758
|
await s.initialize(),await s.hasValidToken()?await s.removeToken()?console.log("Logged out successfully."):(console.error("Failed to remove auth token."),process.exit(1)):console.log("Not currently logged in.")}()
|
|
1762
1759
|
break
|
|
1763
|
-
default:console.error("\u274c Unknown command: "+
|
|
1760
|
+
default:console.error("\u274c Unknown command: "+k),console.log(""),r(),process.exit(1)}})().catch((e=>{console.error("\u{1f4a5} Unexpected error:",e),process.exit(1)}))})(),module.exports=i})()
|