alchemylab 1.4.10 → 1.4.12
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 +120 -108
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -29,16 +29,16 @@ Object.defineProperty(exports,"__esModule",{value:!0}),exports.BAD_REQUEST=expor
|
|
|
29
29
|
function r(e,t){return(0,h.join)(e,p,m,"pool-"+t)}function s(){const e=S.shift()
|
|
30
30
|
e&&setImmediate(e)}async function o(e,t){const o=Date.now()
|
|
31
31
|
!function(e){let t=!1
|
|
32
|
-
for(const r of
|
|
32
|
+
for(const r of b.values())r.busy&&r.busySince&&e-r.busySince>I&&(r.busy=!1,r.busySince=void 0,t=!0)
|
|
33
33
|
t&&s()}(o)
|
|
34
34
|
for(let s=0;t>s;s++){const t=r(e,s)
|
|
35
|
-
if(!(
|
|
35
|
+
if(!(b.get(t)||{busy:!1}).busy)return b.set(t,{busy:!0,busySince:o}),{path:t,index:s,storedFingerprint:await a(t)}}return null}function n(e){const t=b.get(e)
|
|
36
36
|
t&&t.busy&&(t.busy=!1,t.busySince=void 0,s())}function i(e,t){setTimeout((()=>n(e)),t).unref()}async function a(e){try{return(await d.promises.readFile((0,h.join)(e,f),"utf-8")).trim()||null}catch{return null}}async function c(e){const t=[]
|
|
37
37
|
return await async function r(s){const o=s?(0,h.join)(e,s):e
|
|
38
38
|
let n
|
|
39
39
|
try{n=await d.promises.readdir(o,{withFileTypes:!0})}catch{return}for(const e of n){const o=s?`${s}/${e.name}`:e.name
|
|
40
40
|
!s&&w.has(e.name)||!s&&v.has(e.name)||(e.isDirectory()?await r(o):(e.isFile()||e.isSymbolicLink())&&t.push(o))}}(""),t.sort(),t}async function l(e){try{const t=(await d.promises.readFile((0,h.join)(e,y),"utf-8")).split("\n").map((e=>e.trim())).filter(Boolean)
|
|
41
|
-
return new Set(t)}catch{return null}}Object.defineProperty(exports,"__esModule",{value:!0}),exports.getSlotPath=r,exports.acquireSlot=async function(e,t,r=
|
|
41
|
+
return new Set(t)}catch{return null}}Object.defineProperty(exports,"__esModule",{value:!0}),exports.getSlotPath=r,exports.acquireSlot=async function(e,t,r=E){return await o(e,t)||new Promise(((i,a)=>{let c=!1
|
|
42
42
|
const l=Date.now(),d=setTimeout((()=>{if(c)return
|
|
43
43
|
c=!0
|
|
44
44
|
const e=S.indexOf(u)
|
|
@@ -57,8 +57,8 @@ await d.promises.writeFile((0,h.join)(e,y),t.join("\n")+"\n","utf-8")}catch(e){c
|
|
|
57
57
|
if(!t)return{removed:0}
|
|
58
58
|
const r=await c(e)
|
|
59
59
|
let s=0
|
|
60
|
-
for(const o of r)if(!t.has(o))try{await d.promises.rm((0,h.join)(e,o),{force:!0}),s++}catch{}return{removed:s}},exports._resetForTests=function(){
|
|
61
|
-
const d=t(9896),u=t(6982),h=t(6928),g=t(8824),p=".alchemy_lab",m="test-worktrees",f=".fingerprint.sha256",y=".alchemy-warm-snapshot.txt",v=new Set([f,y]),w=new Set([".git"]),I=12e5,
|
|
60
|
+
for(const o of r)if(!t.has(o))try{await d.promises.rm((0,h.join)(e,o),{force:!0}),s++}catch{}return{removed:s}},exports._resetForTests=function(){b.clear(),S.length=0}
|
|
61
|
+
const d=t(9896),u=t(6982),h=t(6928),g=t(8824),p=".alchemy_lab",m="test-worktrees",f=".fingerprint.sha256",y=".alchemy-warm-snapshot.txt",v=new Set([f,y]),w=new Set([".git"]),I=12e5,E=6e5,b=new Map,S=[]},833(e,exports,t){"use strict"
|
|
62
62
|
var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
|
|
63
63
|
Object.defineProperty(exports,"__esModule",{value:!0}),exports.getOpenCodeUrlFromConfig=function(e){const t=o.default.join(e,".alchemy_lab","opencode.json")
|
|
64
64
|
try{if(!s.default.existsSync(t))return null
|
|
@@ -96,38 +96,50 @@ o.push(t),this.histograms.set(s,o)}recordValue(e,t,r={}){this.observeHistogram(e
|
|
|
96
96
|
const t=Date.now()
|
|
97
97
|
return(r={})=>{const s=Date.now()-t
|
|
98
98
|
this.observeHistogram(e,s,r)}}getMetrics(){return{counters:Object.fromEntries(this.counters),gauges:Object.fromEntries(this.gauges),histograms:Object.fromEntries(this.histograms)}}reset(){this.counters.clear(),this.gauges.clear(),this.histograms.clear()}}},1187(e,exports,t){"use strict"
|
|
99
|
-
function r(){const
|
|
99
|
+
function r(e,t,r){try{const s=a.default.readFileSync(e,"utf-8")
|
|
100
|
+
if(s.includes("ALCHEMYLAB_NO_CLAUDE_CODE"))return
|
|
101
|
+
if(!s.includes(h))return
|
|
102
|
+
a.default.writeFileSync(e,s.replace(h,'const source = inputs?.account ?? latestAccounts[0]?.source ?? accounts[0]?.source; if (typeof source === "undefined") { throw new Error("ALCHEMYLAB_NO_CLAUDE_CODE: No Claude Code credentials found on this machine. Install Claude Code first (npm i -g @anthropic-ai/claude-code), run `claude` once to authenticate, then restart alchemy."); }')),t.info("UnifiedLauncher: Patched opencode-claude-auth empty-accounts crash",{plugin:r})}catch{}}function s(){const e=i.default.homedir(),t=n.default.join(e,".alchemy_lab","opencode","config","opencode.json")
|
|
100
103
|
let r=null
|
|
101
|
-
try{const e=JSON.parse(
|
|
102
|
-
Array.isArray(e.plugin)&&(r=e.plugin.find((e=>"string"==typeof e&&/^opencode-claude-auth(@|$)/.test(e)))||null)}catch{r=null}const s=
|
|
104
|
+
try{const e=JSON.parse(a.default.readFileSync(t,"utf-8"))
|
|
105
|
+
Array.isArray(e.plugin)&&(r=e.plugin.find((e=>"string"==typeof e&&/^opencode-claude-auth(@|$)/.test(e)))||null)}catch{r=null}const s=n.default.join(e,".cache","opencode","packages"),o=r?n.default.join(s,r):null,c=!!o&&a.default.existsSync(o)
|
|
103
106
|
let l=null,d=!1
|
|
104
|
-
if(
|
|
105
|
-
if(d=
|
|
106
|
-
l="string"==typeof t.version?t.version:null}catch{l=null}}return{at:(new Date).toISOString(),pinnedSpec:r,expectedDir:
|
|
107
|
+
if(o){const e=n.default.join(o,"node_modules","opencode-claude-auth","package.json")
|
|
108
|
+
if(d=a.default.existsSync(e),d)try{const t=JSON.parse(a.default.readFileSync(e,"utf-8"))
|
|
109
|
+
l="string"==typeof t.version?t.version:null}catch{l=null}}return{at:(new Date).toISOString(),pinnedSpec:r,expectedDir:o,dirExists:c,installedPkgJsonExists:d,installedVersion:l}}var o=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
|
|
107
110
|
Object.defineProperty(exports,"__esModule",{value:!0}),exports.UnifiedLauncher=void 0
|
|
108
|
-
const o
|
|
109
|
-
exports.UnifiedLauncher=class{constructor(e,t,r){this.logger=e,this.installContext=t,this.configurer=r}async launch(e){const{projectPath:t,settings:
|
|
111
|
+
const n=o(t(6928)),i=o(t(857)),a=o(t(9896)),c=t(3962),l=t(5419),d=t(5317),u=t(8824),h="const source = inputs?.account ?? latestAccounts[0]?.source ?? accounts[0].source;"
|
|
112
|
+
exports.UnifiedLauncher=class{constructor(e,t,r){this.logger=e,this.installContext=t,this.configurer=r}async launch(e){const{projectPath:t,settings:r,multiAgent:o}=e,{installPath:n,nodePath:i}=this.installContext
|
|
110
113
|
this.logger.info("UnifiedLauncher: Starting launch",{projectPath:t,installPath:n})
|
|
111
|
-
try{const a=await this.configurer.configureOpenCode(n,t,i,
|
|
112
|
-
return a.success?(this.logger.info("UnifiedLauncher: Configuration complete",{configPath:a.configPath}),this.logger.info("[plugin-diag] pre-spawn",
|
|
113
|
-
if(!
|
|
114
|
-
const p
|
|
115
|
-
return new Promise((e=>{const t=
|
|
116
|
-
|
|
117
|
-
const
|
|
118
|
-
const t
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
114
|
+
try{const a=await this.configurer.configureOpenCode(n,t,i,r,o)
|
|
115
|
+
return a.success?(this.logger.info("UnifiedLauncher: Configuration complete",{configPath:a.configPath}),this.logger.info("[plugin-diag] pre-spawn",s()),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,u.getErrorMessage)(e)}),{success:!1,error:(0,u.getErrorMessage)(e)}}}async launchBackground(e,o){const{projectPath:h,envVars:g}=e,{resolveServeCommand:p,findOpencodeDir:m}=await Promise.resolve().then(t.bind(t,9068))
|
|
116
|
+
if(!m())try{(0,d.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:h})
|
|
117
|
+
const f=p()
|
|
118
|
+
return new Promise((e=>{const t=n.default.join(h,".alchemy_lab","opencode"),p=process.env.XDG_DATA_HOME||n.default.join(i.default.homedir(),".local","share")
|
|
119
|
+
try{a.default.mkdirSync(n.default.join(p,"opencode"),{recursive:!0})}catch(e){this.logger.warn("UnifiedLauncher: Could not pre-create opencode globalData dir",{error:(0,u.getErrorMessage)(e)})}try{!function(e){const t=function(){const e=n.default.join(i.default.homedir(),".alchemy_lab","opencode","config","opencode.json")
|
|
120
|
+
try{const t=JSON.parse(a.default.readFileSync(e,"utf-8"))
|
|
121
|
+
if(Array.isArray(t.plugin)){const e=t.plugin.find((e=>"string"==typeof e&&/^opencode-claude-auth@/.test(e)))
|
|
122
|
+
if("string"==typeof e){const t=e.indexOf("@",20)
|
|
123
|
+
if(-1!==t)return e.slice(t+1)}}}catch{}return"1.5.3"}(),s=n.default.join(i.default.homedir(),".cache","opencode","packages"),o=n.default.join(s,"opencode-claude-auth@"+t),c=n.default.join(o,"node_modules","opencode-claude-auth","dist","index.js")
|
|
124
|
+
if(!a.default.existsSync(c))try{a.default.mkdirSync(o,{recursive:!0}),a.default.writeFileSync(n.default.join(o,"package.json"),JSON.stringify({name:"alchemylab-claude-auth-wrapper",private:!0,version:"0.0.0"},null,2)),e.info("UnifiedLauncher: Pre-installing opencode-claude-auth plugin",{version:t,targetDir:o}),(0,d.execFileSync)("npm",["install","opencode-claude-auth@"+t,"--no-audit","--no-fund","--silent","--ignore-scripts"],{cwd:o,stdio:"pipe",timeout:12e4}),e.info("UnifiedLauncher: Pre-installed opencode-claude-auth plugin",{version:t})}catch(t){e.warn("UnifiedLauncher: claude-auth pre-install failed; opencode will install lazily and first OAuth click may crash",{error:(0,u.getErrorMessage)(t)})}if(a.default.existsSync(c)&&r(c,e,"opencode-claude-auth@"+t),a.default.existsSync(s)){let t
|
|
125
|
+
try{t=a.default.readdirSync(s)}catch{return}for(const o of t){if(!o.startsWith("opencode-claude-auth@"))continue
|
|
126
|
+
const t=n.default.join(s,o,"node_modules","opencode-claude-auth","dist","index.js")
|
|
127
|
+
t!==c&&r(t,e,o)}}}(this.logger)}catch(e){this.logger.warn("UnifiedLauncher: claude-auth ensure-patched failed",{error:(0,u.getErrorMessage)(e)})}const m=n.default.join(t,"log")
|
|
128
|
+
let y=null
|
|
129
|
+
const v=e=>{if(!y)return
|
|
130
|
+
const t=y
|
|
131
|
+
y=null,t.end(e)}
|
|
132
|
+
try{a.default.mkdirSync(m,{recursive:!0})
|
|
133
|
+
const e=n.default.join(m,"opencode-serve.log")
|
|
134
|
+
a.default.writeFileSync(e,`# opencode-serve.log \u2014 launched ${(new Date).toISOString()}\n`),y=a.default.createWriteStream(e,{flags:"a"})}catch(e){this.logger.warn("UnifiedLauncher: Could not open opencode log file",{error:(0,u.getErrorMessage)(e)})}const w=(0,d.spawn)(f.command,[...f.prependArgs,"--port","0","--data-dir",t],{stdio:["ignore","pipe","pipe"],cwd:h,detached:!0,env:{...process.env,...(0,l.getOpencodeSpawnEnv)(h,(0,c.isDevMode)()),...g}})
|
|
135
|
+
let I="",E=!1
|
|
136
|
+
const b=t=>{I+=t
|
|
137
|
+
const r=I.match(/listening on http:\/\/[^:]+:(\d+)/)
|
|
138
|
+
if(r&&!E){const t=parseInt(r[1],10)
|
|
139
|
+
E=!0,this.logger.info("UnifiedLauncher: Discovered opencode serve port",{pid:w.pid,port:t}),this.logger.info("[plugin-diag] port-ready",{pid:w.pid,port:t,...s()}),setTimeout((()=>{this.logger.info("[plugin-diag] port-ready+5s",{pid:w.pid,...s()})}),5e3).unref(),setTimeout((()=>{this.logger.info("[plugin-diag] port-ready+15s",{pid:w.pid,...s()})}),15e3).unref(),w.unref(),e({success:!0,pid:w.pid,port:t,configPath:o.configPath})}}
|
|
140
|
+
w.stdout?.on("data",(e=>{y&&y.write(e),b(e.toString())})),w.stderr?.on("data",(e=>{if(y){const t=e.toString().split("\n")
|
|
129
141
|
for(let e=0;e<t.length;e++){const r=t[e]
|
|
130
|
-
0===r.length&&e===t.length-1||
|
|
142
|
+
0===r.length&&e===t.length-1||y.write(`[stderr] ${r}\n`)}}b(e.toString())})),w.on("exit",((e,t)=>{v(`\n# opencode exited code=${e} signal=${t} at=${(new Date).toISOString()}\n`)})),setTimeout((()=>{E||(E=!0,this.logger.warn("UnifiedLauncher: Timeout waiting for opencode serve port",{outputBuffer:I.slice(0,500)}),w.unref(),e({success:!0,pid:w.pid,configPath:o.configPath,error:"Could not determine port from opencode serve output"}))}),1e4),w.on("error",(t=>{E||(E=!0,this.logger.error("UnifiedLauncher: opencode serve failed to start",{error:t.message}),v(`\n# child error: ${t.message}\n`),e({success:!1,error:"Failed to start opencode serve: "+t.message}))})),w.on("exit",(t=>{E||(E=!0,this.logger.error("UnifiedLauncher: opencode serve exited early",{code:t,output:I.slice(0,500)}),e({success:!1,error:"opencode serve exited with code "+t}))}))}))}}},1253(e,exports,t){"use strict"
|
|
131
143
|
var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
|
|
132
144
|
Object.defineProperty(exports,"__esModule",{value:!0}),exports.TokenManager=void 0
|
|
133
145
|
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"
|
|
@@ -203,8 +215,8 @@ const y=await f.json()
|
|
|
203
215
|
if(!y.id)throw Error("Session created in OpenCode but no ID returned")
|
|
204
216
|
const v=y.id
|
|
205
217
|
u.info("Pre-created OpenCode session",{sessionId:v,title:p,agent:t})
|
|
206
|
-
const w=this.registerSession({sessionId:v,parentSessionId:a,projectId:h,agent:t,title:p,specialization:c,specializationHeadline:l,callerAgentType:d,requestId:i}),I=this.resolveModel(t,n),
|
|
207
|
-
return{sessionId:v,model:
|
|
218
|
+
const w=this.registerSession({sessionId:v,parentSessionId:a,projectId:h,agent:t,title:p,specialization:c,specializationHeadline:l,callerAgentType:d,requestId:i}),I=this.resolveModel(t,n),E=i?(0,r.ensureRequestWorktree)(n,i,u).catch((e=>{u.warn("Pre-spawn worktree creation failed (non-fatal)",{requestId:i,error:(0,o.getErrorMessage)(e)})})):Promise.resolve(),[,b]=await Promise.all([w,I,E])
|
|
219
|
+
return{sessionId:v,model:b,serverUrl:g,initialBranch:i?"requests/"+i:void 0}}async registerSession(e){const{sessionId:t,parentSessionId:r,projectId:s,agent:n,title:a,specialization:c,specializationHeadline:l,callerAgentType:d,requestId:u}=e,{apiClient:h,logger:g}=this.deps
|
|
208
220
|
try{let e,p
|
|
209
221
|
if(r)try{const t=await h.post(`/api/projects/${s}/cli-sessions`,{externalSessionId:r,providerId:"opencode",status:"active",agentType:d||void 0}),o=t?.data||t
|
|
210
222
|
e=o?.id,p=o?.workflowId,g.info("Parent session upsert result",{parentExternalId:r,parentDbId:e??null,parentWorkflowId:p??null,responseHasSuccess:void 0!==t?.success,responseDataKeys:o?Object.keys(o):[]})}catch(e){g.warn("Failed to register parent session",{parentSessionId:r,error:(0,o.getErrorMessage)(e)})}const m={externalSessionId:t,providerId:"opencode",parentSessionId:e||null,title:a,status:"active",agentType:n,specialization:c,specializationHeadline:l,workflowId:p||void 0,persistent:i.PERSISTENT_ORCHESTRATOR_AGENTS.includes(n)||void 0}
|
|
@@ -248,8 +260,8 @@ if(!y)return void this.logger.info("Quest tick: no persistent alchemist session
|
|
|
248
260
|
const v=(i.currentIteration||0)+1
|
|
249
261
|
let w=!1
|
|
250
262
|
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),
|
|
252
|
-
try{await(0,s.executeSameSession)(I,y,b
|
|
263
|
+
const I=this.buildTickPrompt(e,v,h,p,f+1),E={logger:this.logger,projectId:this.projectId,projectPath:this.projectPath,apiClient:this.apiClient,cachedDefaultModel:this.getDefaultModel()??null},b={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}}}
|
|
264
|
+
try{await(0,s.executeSameSession)(I,y,E,b),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()
|
|
253
265
|
if(18e5>e-this.lastQuestsFetch)return this.cachedQuests
|
|
254
266
|
try{const t=await this.apiClient.get(`/api/projects/${this.projectId}/plans?type=Quest&limit=100`),r=t?.data||t||[]
|
|
255
267
|
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`)
|
|
@@ -305,7 +317,7 @@ Object.defineProperty(exports,"__esModule",{value:!0}),exports.partMeta=function
|
|
|
305
317
|
return{...e,_meta:{...s,alchemy:{...o,...r,kind:t}}}},exports.readAlchemyMeta=function(e){return e?._meta?.alchemy}},2366(e,exports,t){"use strict"
|
|
306
318
|
var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
|
|
307
319
|
Object.defineProperty(exports,"__esModule",{value:!0}),exports.AlchemyActionServer=void 0
|
|
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),
|
|
320
|
+
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),E=t(3962),b=t(2928),S=t(6563),C=t(1401),k=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
321
|
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
|
|
310
322
|
const r=(0,l.createMetricsCollector)(!1)
|
|
311
323
|
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)()
|
|
@@ -384,7 +396,7 @@ e&&(await fetch(e+"/config/reload",{method:"POST",signal:AbortSignal.timeout(5e3
|
|
|
384
396
|
if(!s)return void r.status(400).json({success:!1,error:"requestId is required"})
|
|
385
397
|
if(!/^req\d+$/.test(s))return void r.status(400).json({success:!1,error:"Invalid requestId format"})
|
|
386
398
|
this.logger.info("Generating journal for request",{requestId:s})
|
|
387
|
-
const o=`Generate a journal entry for request ${s}.\n\nSteps:\n1. Use the generate_request_journal tool with requestId: ${s}\n2. Write a concise narrative journal entry based on the returned data covering:\n - What was implemented and why\n - Key decisions and trade-offs made\n - Challenges encountered and solutions\n - Files modified and their purposes\n - How acceptance criteria were met\n3. Save as JOURNAL-${s}.md using document_operations (operation: create, type: Report)\n\nKeep it story-focused and informative.`,{resolveRunCommand:n}=await Promise.resolve().then(t.bind(t,9068)),i=n(),a=(0,D.spawn)(i.command,[...i.prependArgs,"run","--agent","journal",o],{detached:!0,stdio:"ignore",cwd:this.options.projectPath,env:{...process.env,...(0,I.getOpencodeSpawnEnv)(this.options.projectPath,(0,
|
|
399
|
+
const o=`Generate a journal entry for request ${s}.\n\nSteps:\n1. Use the generate_request_journal tool with requestId: ${s}\n2. Write a concise narrative journal entry based on the returned data covering:\n - What was implemented and why\n - Key decisions and trade-offs made\n - Challenges encountered and solutions\n - Files modified and their purposes\n - How acceptance criteria were met\n3. Save as JOURNAL-${s}.md using document_operations (operation: create, type: Report)\n\nKeep it story-focused and informative.`,{resolveRunCommand:n}=await Promise.resolve().then(t.bind(t,9068)),i=n(),a=(0,D.spawn)(i.command,[...i.prependArgs,"run","--agent","journal",o],{detached:!0,stdio:"ignore",cwd:this.options.projectPath,env:{...process.env,...(0,I.getOpencodeSpawnEnv)(this.options.projectPath,(0,E.isDevMode)())}})
|
|
388
400
|
a.pid&&this.trackChildPid(a.pid),a.unref(),r.json({success:!0,message:"Journal generation started for "+s})}catch(e){this.logger.error("Failed to generate journal",{error:(0,f.getErrorMessage)(e)}),r.status(500).json({success:!1,error:(0,f.getErrorMessage)(e)})}}async handleDirectReport(e,t){try{const r=this.projectId
|
|
389
401
|
if(!r)return void t.status(400).json({success:!1,error:"Project not registered"})
|
|
390
402
|
const{sessionId:s,requestId:o,summary:n,status:i}=e.body
|
|
@@ -438,7 +450,7 @@ if(r&&!a.has(r))return void t.status(400).json({success:!1,error:"agent must be
|
|
|
438
450
|
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)})
|
|
439
451
|
if(!c.success||!c.sessionId)return void t.status(500).json({success:!1,error:c.error||"spawn failed"})
|
|
440
452
|
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
|
|
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
|
|
453
|
+
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 k.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})
|
|
442
454
|
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
|
|
443
455
|
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)
|
|
444
456
|
if(!l.success)return void r.status(500).json({success:!1,error:l.error||l.message})
|
|
@@ -598,7 +610,7 @@ if(s.ok){const e=await s.json(),t=e?.data?.settings||e?.settings
|
|
|
598
610
|
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()
|
|
599
611
|
try{const e=d.default.join(this.options.projectPath,".alchemy_lab","opencode.json")
|
|
600
612
|
if(u.default.existsSync(e)){const t=u.default.readFileSync(e,"utf-8"),r=JSON.parse(t)
|
|
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
|
|
613
|
+
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 C.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 b.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()??[]
|
|
602
614
|
t.json({success:!0,data:r})}handleTestInjectStuckSession(e,t){if(!this.stuckSessionWatchdog)return void t.status(503).json({success:!1,error:"Watchdog not running"})
|
|
603
615
|
const{sessionId:r,title:s}=e.body||{},o=this.stuckSessionWatchdog.injectTestStuck(r,s)
|
|
604
616
|
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
|
|
@@ -898,10 +910,10 @@ if("busy"===o||"running"===o){i=!0
|
|
|
898
910
|
break}await s(500)}for(;Date.now()<n;){const o=await r(e,t)
|
|
899
911
|
if("error"===o)return{status:"error"}
|
|
900
912
|
if(("idle"===o||"completed"===o)&&i)return{status:"completed"}
|
|
901
|
-
"busy"!==o&&"running"!==o||(i=!0),await s(2e3)}return{status:"timeout"}}(y,w,g),
|
|
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)}(
|
|
903
|
-
return""}(
|
|
904
|
-
return{success:!0,message:"Prompt completed",data:{sessionId:w,status:I.status,messages:
|
|
913
|
+
"busy"!==o&&"running"!==o||(i=!0),await s(2e3)}return{status:"timeout"}}(y,w,g),E=Date.now()-v,b=await async function(e,t){try{const r=await fetch(`${e}/session/${t}/message?limit=200`,{signal:AbortSignal.timeout(1e4)})
|
|
914
|
+
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)}(b),C=function(e){for(let t=e.length-1;t>=0;t--)if("assistant"===e[t].role&&e[t].text)return e[t].text
|
|
915
|
+
return""}(b)
|
|
916
|
+
return{success:!0,message:"Prompt completed",data:{sessionId:w,status:I.status,messages:b,toolCallCount:S,elapsedMs:E,finalResponse:C}}}
|
|
905
917
|
const o=t(8148)},4769(e,exports,t){"use strict"
|
|
906
918
|
function r(e){return(0,o.getOpenCodeServerUrl)(e.projectPath,e.logger)}async function s(e,t,s,o){const n=r(s)
|
|
907
919
|
if(!n||!t)return!1
|
|
@@ -1035,38 +1047,38 @@ m.info("Starting run-tests action",{requestId:d,testProcessIds:u})
|
|
|
1035
1047
|
try{const l=await f.get(`/api/projects/${y}/requests/${d}`)
|
|
1036
1048
|
if(!l.success||!l.data)return{success:!1,message:`Request ${d} not found`,error:"Request not found"}
|
|
1037
1049
|
const g=l.data,I=g.branchName||"requests/"+d
|
|
1038
|
-
let
|
|
1039
|
-
if(u?.length)
|
|
1050
|
+
let E
|
|
1051
|
+
if(u?.length)E=(await Promise.all(u.map((e=>f.get(`/api/projects/${y}/test-processes/${e}`))))).filter((e=>e.success&&e.data)).map((e=>e.data))
|
|
1040
1052
|
else{const e=await f.get(`/api/projects/${y}/test-processes/active`)
|
|
1041
|
-
|
|
1042
|
-
const
|
|
1043
|
-
let
|
|
1044
|
-
try{
|
|
1045
|
-
try{if((0,s.existsSync)(
|
|
1046
|
-
await e.raw(["reset","--hard",I]),m.info("Reset pool slot to latest branch HEAD",{worktreePath:
|
|
1047
|
-
try{await T.raw(["worktree","remove","--force",
|
|
1048
|
-
try{if(!(await T.branch()).all.includes(I))return(0,c.releaseSlot)(
|
|
1049
|
-
await T.raw(["worktree","add","--detach",
|
|
1050
|
-
m.info("Pool slot worktree already exists, using as-is",{worktreePath:
|
|
1051
|
-
j||m.info("Pool slot fingerprint matches; skipping setup",{worktreePath:
|
|
1053
|
+
E=e.success&&e.data?Array.isArray(e.data)?e.data:[e.data]:[]}if(0===E.length)return{success:!1,message:"No test processes configured for this project. Set up testing first.",error:"No test processes"}
|
|
1054
|
+
const b=Math.max(1,Math.min(8,E[0]?.config?.poolSize??2)),S=E[0]?.config?.fingerprint||[]
|
|
1055
|
+
let C
|
|
1056
|
+
try{C=await(0,c.acquireSlot)(v,b)}catch(e){return{success:!1,message:"Could not acquire test worktree slot: "+(0,i.getErrorMessage)(e),error:(0,i.getErrorMessage)(e)}}const k=C.path,T=(0,n.simpleGit)(v)
|
|
1057
|
+
try{if((0,s.existsSync)(k))try{const e=(0,n.simpleGit)(k)
|
|
1058
|
+
await e.raw(["reset","--hard",I]),m.info("Reset pool slot to latest branch HEAD",{worktreePath:k,branchName:I,slotIndex:C.index})}catch(e){m.warn("Could not reset slot, recreating",{error:(0,i.getErrorMessage)(e)})
|
|
1059
|
+
try{await T.raw(["worktree","remove","--force",k])}catch{}}if(!(0,s.existsSync)(k)){(0,s.mkdirSync)((0,o.join)(v,".alchemy_lab","test-worktrees"),{recursive:!0})
|
|
1060
|
+
try{if(!(await T.branch()).all.includes(I))return(0,c.releaseSlot)(k),{success:!1,message:`Branch ${I} not found`,error:"Branch not found"}
|
|
1061
|
+
await T.raw(["worktree","add","--detach",k,I]),m.info("Created pool slot worktree (detached)",{worktreePath:k,branchName:I,slotIndex:C.index})}catch(e){if(!(e+"").includes("already exists"))return(0,c.releaseSlot)(k),{success:!1,message:"Failed to create pool slot worktree: "+(0,i.getErrorMessage)(e),error:(0,i.getErrorMessage)(e)}
|
|
1062
|
+
m.info("Pool slot worktree already exists, using as-is",{worktreePath:k})}}}catch(e){throw(0,c.releaseSlot)(k),e}const _=await(0,c.computeFingerprint)(k,S),j=0===S.length||_!==C.storedFingerprint,A=0===S.length?null:_===C.storedFingerprint
|
|
1063
|
+
j||m.info("Pool slot fingerprint matches; skipping setup",{worktreePath:k,slotIndex:C.index,fingerprintFiles:S})
|
|
1052
1064
|
const{execSync:P}=await Promise.resolve().then(t.t.bind(t,5317,23))
|
|
1053
|
-
if(j){for(const e of
|
|
1054
|
-
if(0!==t.length){m.info("Running setup commands for test process",{processName:e.name,commandCount:t.length,slotIndex:
|
|
1055
|
-
for(const r of t)try{m.info("Setup command",{command:r,worktreePath:
|
|
1056
|
-
e>0&&m.info("Pruned drift from warm slot",{worktreePath:
|
|
1057
|
-
try{const e=(0,n.simpleGit)(
|
|
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:
|
|
1065
|
+
if(j){for(const e of E){const t=e.config?.setup||[]
|
|
1066
|
+
if(0!==t.length){m.info("Running setup commands for test process",{processName:e.name,commandCount:t.length,slotIndex:C.index})
|
|
1067
|
+
for(const r of t)try{m.info("Setup command",{command:r,worktreePath:k}),P(r,{cwd:k,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)(k,_),await(0,c.writeWarmSnapshot)(k)}else{const{removed:e}=await(0,c.restoreToWarmSnapshot)(k)
|
|
1068
|
+
e>0&&m.info("Pruned drift from warm slot",{worktreePath:k,slotIndex:C.index,removed:e})}let M
|
|
1069
|
+
try{const e=(0,n.simpleGit)(k)
|
|
1070
|
+
M=(await e.revparse(["HEAD"])).trim()}catch(e){m.warn("Failed to capture worktree HEAD; commit_sha will be NULL on this run",{worktreePath:k,error:(0,i.getErrorMessage)(e)})}const O=[],R=[],D=[]
|
|
1059
1071
|
let x
|
|
1060
|
-
const $={worktreePath:
|
|
1061
|
-
for(const process of
|
|
1072
|
+
const $={worktreePath:k,commitSha:M,fingerprintHit:A}
|
|
1073
|
+
for(const process of E){let t
|
|
1062
1074
|
const s=e.testRunIds?.[O.length]
|
|
1063
1075
|
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)})
|
|
1064
1076
|
continue}const o=r(g,process,0,y,p,e.commandNames,e.skipCommandNames),n=p?"tester":"auto-tester"
|
|
1065
|
-
try{const e=await(0,a.spawnActionAgent)({agent:n,prompt:o,cwd:
|
|
1077
|
+
try{const e=await(0,a.spawnActionAgent)({agent:n,prompt:o,cwd:k,projectPath:v,projectId:y,title:`[${d}] Test: ${process.name}`,requestId:d,parentSessionId:h,allowParallel:!0,apiClient:f,logger:m,trackChildPid:w})
|
|
1066
1078
|
if(!e.success)throw Error(e.error||"Spawn failed")
|
|
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:
|
|
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)(
|
|
1069
|
-
for(const e of D);return(0,c.watchPidAndRelease)(x,
|
|
1079
|
+
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:k,pid:e.pid,sessionId:e.sessionId})}catch(e){m.error("Failed to spawn test-runner agent",{testRunId:t.id,error:(0,i.getErrorMessage)(e)})
|
|
1080
|
+
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)(k),{success:!1,message:"Failed to spawn any test agents",error:"All agent spawns failed"}
|
|
1081
|
+
for(const e of D);return(0,c.watchPidAndRelease)(x,k,12e5),{success:!0,message:`Started ${R.length} test run(s) for request ${d}`,data:{testRunIds:O,agentsSpawned:R.length,processCount:E.length,worktreePath:k,spawnedSessionIds:D}}}catch(e){const t=(0,i.getErrorMessage)(e)
|
|
1070
1082
|
return m.error("run-tests action failed",{requestId:d,error:t}),{success:!1,message:"Failed to run tests: "+t,error:t}}}
|
|
1071
1083
|
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"
|
|
1072
1084
|
Object.defineProperty(exports,"__esModule",{value:!0}),exports.TimerHookScheduler=void 0
|
|
@@ -1181,7 +1193,7 @@ Object.defineProperty(exports,"evaluateCondition",{enumerable:!0,get(){return i.
|
|
|
1181
1193
|
function t(e){if("string"!=typeof e)return e
|
|
1182
1194
|
try{return JSON.parse(e)}catch{return e}}Object.defineProperty(exports,"__esModule",{value:!0}),exports.parseToolEvents=function(e){const{toolName:o,sessionId:n,input:i,output:a,timing:c}=e
|
|
1183
1195
|
if("before"===c)return[{eventType:"tool_executed",timing:"before",variables:{tool:o,toolName:o,input:JSON.stringify(i??{}).substring(0,1e3),sessionId:n||""}}]
|
|
1184
|
-
const l=[],d=a||"",u={output:d,toolName:o,sessionId:n||""},h=o.includes("request_operations")||o.includes("complete_request"),g=o.includes("task_operations")||o.includes("complete_task"),p=o.includes("write_file"),m=o.includes("replace_in_files"),f=o.includes("quick_write")||o.includes("quick_replace")||o.includes("quick_manage"),y=o.includes("read_files"),v=o.includes("search"),w=o.includes("manage_files"),I=o.includes("document_operations"),
|
|
1196
|
+
const l=[],d=a||"",u={output:d,toolName:o,sessionId:n||""},h=o.includes("request_operations")||o.includes("complete_request"),g=o.includes("task_operations")||o.includes("complete_task"),p=o.includes("write_file"),m=o.includes("replace_in_files"),f=o.includes("quick_write")||o.includes("quick_replace")||o.includes("quick_manage"),y=o.includes("read_files"),v=o.includes("search"),w=o.includes("manage_files"),I=o.includes("document_operations"),E=o.includes("query_history")
|
|
1185
1197
|
if(h&&d){const e=i?.operation,t=function(e){for(const t of r){const r=e.match(t)
|
|
1186
1198
|
if(r)return r[1]}return null}(d)
|
|
1187
1199
|
t&&("merge"===e||"finalize"===e?l.push({eventType:"request_completed",timing:"after",variables:{...u,requestId:t}}):"complete"===e&&l.push({eventType:"request_ready_for_merge",timing:"after",variables:{...u,requestId:t}}))}else if(g&&d){const e=function(e){const t=e.match(s)
|
|
@@ -1192,7 +1204,7 @@ l.push({eventType:"replace_in_files",timing:"after",variables:{...u,paths:r.join
|
|
|
1192
1204
|
t&&(e.requestId=t[1]),l.push({eventType:"quick_edit",timing:"after",variables:e})}else if(y){const e=t(i?.paths)||[],r=(Array.isArray(e)?e:[]).map((e=>e.path||e.documentId)).filter(Boolean)
|
|
1193
1205
|
l.push({eventType:"read_files",timing:"after",variables:{...u,paths:r.join(", "),fileCount:r.length+""}})}else if(v)l.push({eventType:"search",timing:"after",variables:{...u,query:i?.needle||"",directory:i?.directory||"",fileExtension:i?.options?.fileExtension||"*"}})
|
|
1194
1206
|
else if(w){const e=t(i?.operations)||[],r=(Array.isArray(e)?e:[]).map((e=>e.operationType)).filter(Boolean)
|
|
1195
|
-
l.push({eventType:"manage_files",timing:"after",variables:{...u,operationTypes:[...new Set(r)].join(", ")}})}else I?l.push({eventType:"document_operations",timing:"after",variables:u}):
|
|
1207
|
+
l.push({eventType:"manage_files",timing:"after",variables:{...u,operationTypes:[...new Set(r)].join(", ")}})}else I?l.push({eventType:"document_operations",timing:"after",variables:u}):E&&l.push({eventType:"query_history",timing:"after",variables:u})
|
|
1196
1208
|
return l.push({eventType:"tool_executed",timing:"after",variables:{tool:o,toolName:o,output:d,sessionId:n||""}}),l}
|
|
1197
1209
|
const r=[/Request completed:\s*(req\d+)/i,/Request (req\d+) completed/i,/completed.*?(req\d{6})/i,/Request finalized:\s*(req\d+)/i,/Request (req\d+) finalized/i,/finalized.*?(req\d{6})/i,/Request (req\d+) merged/i,/merged.*?(req\d{6})/i],s=/Task\s+(tas\d+).*(?:completed|marked as completed)/i},7252(module){"use strict"
|
|
1198
1210
|
module.exports=require("express")},7388(e,exports,t){"use strict"
|
|
@@ -1397,8 +1409,8 @@ if(p&&(o.ALCHEMY_INITIAL_BRANCH=p),r(u,"Spawn env setup",{agent:t,initialBranch:
|
|
|
1397
1409
|
e>0?y.splice(e,0,"--model",g):y.push("--model",g)}r(u,"Spawning opencode",{args:y.map((e=>e===d?`[prompt:${e.length}chars]`:e)),model:g||"not specified",promptLength:d?.length,promptPreview:d?.slice(0,100),cwd:u,hasSessionId:!!h})
|
|
1398
1410
|
const v=(0,i.join)(u||process.cwd(),".alchemy_lab"),w=(0,i.join)(v,"spawn-stderr.log")
|
|
1399
1411
|
let I
|
|
1400
|
-
try{I=(0,n.openSync)(w,"a")}catch{}const
|
|
1401
|
-
return
|
|
1412
|
+
try{I=(0,n.openSync)(w,"a")}catch{}const E=(0,a.resolveRunCommand)(),b=(0,s.spawn)(E.command,[...E.prependArgs,...y],{detached:!0,stdio:["ignore","ignore",I??"ignore"],cwd:u||process.cwd(),env:o})
|
|
1413
|
+
return b.unref(),f&&f(b.pid),{success:!0,pid:b.pid}}catch(e){return{success:!1,error:(0,o.getErrorMessage)(e)}}}
|
|
1402
1414
|
const s=t(5317),o=t(8824),n=t(9896),i=t(6928),a=t(9068),c=t(5419),l=t(3962)},8148(e,exports,t){"use strict"
|
|
1403
1415
|
function r(e){return(0,n.getOpenCodeUrlFromConfig)(e)}Object.defineProperty(exports,"__esModule",{value:!0}),exports.getOpenCodeServerUrl=r,exports.spawnActionAgent=async function(e){const{agent:t,prompt:n,cwd:a,projectPath:c,title:l,requestId:d,parentSessionId:u,allowParallel:h,apiClient:g,logger:p,trackChildPid:m}=e
|
|
1404
1416
|
let f,y,v,w=null
|
|
@@ -1553,18 +1565,18 @@ let g=null,p=null,m=null,f=null,y=!1,v=!1
|
|
|
1553
1565
|
const w=c.join(s,".alchemy_lab","config.json")
|
|
1554
1566
|
if(!i){try{const e=await r.readFile(w,"utf-8"),t=JSON.parse(e)
|
|
1555
1567
|
t?.openCodeTools?(g=t.openCodeTools,this.logger.info("Found tool settings in project config",{projectConfigTools:g})):v=!0,t?.openCodeModel&&(p=t.openCodeModel)}catch(e){this.logger.debug("No project config found, will create with defaults"),v=!0}try{const e=await r.readFile(h,"utf-8"),t=JSON.parse(e)
|
|
1556
|
-
t?.mode?.alchemy?.tools&&(m=t.mode.alchemy.tools,this.logger.info("Found existing tool settings in opencode.json",{existingTools:m})),t?.model&&(f=t.model),t?.mode?.journal&&(y=!0)}catch(e){this.logger.info("No existing opencode.json found, will use defaults")}}const I=i?.openCodeTools||g||m||{glob:!1,grep:!1,ls:!1,list:!1,view:!1,read:!1,write:!1,edit:!1,patch:!1,diagnostics:!0,bash:!0===i?.enableBash,fetch:!1,sourcegraph:!0,agent:!1!==a,todoread:!1,todowrite:!1},
|
|
1557
|
-
i?.openCodeModel?
|
|
1568
|
+
t?.mode?.alchemy?.tools&&(m=t.mode.alchemy.tools,this.logger.info("Found existing tool settings in opencode.json",{existingTools:m})),t?.model&&(f=t.model),t?.mode?.journal&&(y=!0)}catch(e){this.logger.info("No existing opencode.json found, will use defaults")}}const I=i?.openCodeTools||g||m||{glob:!1,grep:!1,ls:!1,list:!1,view:!1,read:!1,write:!1,edit:!1,patch:!1,diagnostics:!0,bash:!0===i?.enableBash,fetch:!1,sourcegraph:!0,agent:!1!==a,todoread:!1,todowrite:!1},E={tools:I},b=i?.openCodeModel||p||f||"anthropic/claude-opus-4-5"
|
|
1569
|
+
i?.openCodeModel?E.model=i.openCodeModel:p?E.model=p:f&&(E.model=f)
|
|
1558
1570
|
const S=c.join(s,".opencode","agent")
|
|
1559
1571
|
await r.mkdir(S,{recursive:!0})
|
|
1560
|
-
const
|
|
1561
|
-
let
|
|
1562
|
-
if(i?.projectId)if(await this.seedAgentsFromRemote(i.projectId),_=await this.fetchProjectFromRemote(i.projectId),
|
|
1572
|
+
const C=b||"anthropic/claude-opus-4-5-20251101"
|
|
1573
|
+
let k,T=null,_=null
|
|
1574
|
+
if(i?.projectId)if(await this.seedAgentsFromRemote(i.projectId),_=await this.fetchProjectFromRemote(i.projectId),k=_?.metadata?.globalSystemPrompt,k&&this.logger.info("Using global system prompt from project metadata",{projectId:i.projectId,promptLength:k.length}),T=await this.fetchAgentsFromRemote(i.projectId),T&&T.length>0){this.logger.info("Using agents from remote database",{count:T.length})
|
|
1563
1575
|
for(const e of T){if(!e.enabled)continue
|
|
1564
|
-
const t=this.generateAgentMarkdown(e,d,k
|
|
1576
|
+
const t=this.generateAgentMarkdown(e,d,C,k),s=c.join(S,e.name+".md")
|
|
1565
1577
|
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")
|
|
1566
1578
|
else this.logger.warn("No projectId provided - agents must be configured via database")
|
|
1567
|
-
const j={$schema:"https://opencode.ai/config.json",model:
|
|
1579
|
+
const j={$schema:"https://opencode.ai/config.json",model:b,mcp:{[d]:{type:"local",command:[n,u,s],enabled:!0,timeout:6e5}}}
|
|
1568
1580
|
if(l&&process.env.REMOTE_SERVER_URL&&(j.mcp[d].environment={REMOTE_SERVER_URL:process.env.REMOTE_SERVER_URL}),T&&T.length>0){j.agent={}
|
|
1569
1581
|
for(const e of T){if(!e.enabled)continue
|
|
1570
1582
|
const t={}
|
|
@@ -1572,8 +1584,8 @@ for(const[r,s]of Object.entries(e.tools||{})){const e=s,n=r.replace(/-/g,"_")
|
|
|
1572
1584
|
t[o.OPENCODE_BUILTIN_TOOLS.has(r)?r:`${d}_${n}`]=e}const r=_?.metadata?.bashToolAgents||{}
|
|
1573
1585
|
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)
|
|
1574
1586
|
let s=""
|
|
1575
|
-
|
|
1576
|
-
const e=`---\ndescription: Fast codebase exploration and research subagent\nmode: subagent\n---\n\n${
|
|
1587
|
+
k&&(s+=k.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}}
|
|
1588
|
+
const e=`---\ndescription: Fast codebase exploration and research subagent\nmode: subagent\n---\n\n${k?k.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`
|
|
1577
1589
|
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))):[]
|
|
1578
1590
|
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})
|
|
1579
1591
|
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")
|
|
@@ -1688,59 +1700,59 @@ var i={};(()=>{"use strict"
|
|
|
1688
1700
|
function t(){const e=[(0,h.join)(__dirname,"..","package.json"),(0,h.join)(__dirname,"..","..","package.json")]
|
|
1689
1701
|
for(const t of e)try{const e=JSON.parse((0,d.readFileSync)(t,"utf-8"))
|
|
1690
1702
|
if(e.version)return e.version}catch{}return"unknown"}function r(){const e=t()
|
|
1691
|
-
console.log(`\nAlchemyLab CLI v${e}\n\nUSAGE:\n alchemy [command] [options]\n\nCOMMANDS:\n (none) Start action server + launch OpenCode (default)\n serve Start the action server only (for GUI-triggered actions)\n logout Log out and remove stored auth token\n\nOPTIONS:\n --port <n> Action server port (default: auto)\n --help, -h Show this help message\n --version, -v Show version information\n\nEXAMPLES:\n alchemy # Start server + OpenCode (default)\n alchemy serve # Start action server only\n alchemy logout # Log out\nFor more information, visit: https://alchemylabcode.com/\n`)}function s(e){const t=(0,h.join)(e,
|
|
1703
|
+
console.log(`\nAlchemyLab CLI v${e}\n\nUSAGE:\n alchemy [command] [options]\n\nCOMMANDS:\n (none) Start action server + launch OpenCode (default)\n serve Start the action server only (for GUI-triggered actions)\n logout Log out and remove stored auth token\n\nOPTIONS:\n --port <n> Action server port (default: auto)\n --help, -h Show this help message\n --version, -v Show version information\n\nEXAMPLES:\n alchemy # Start server + OpenCode (default)\n alchemy serve # Start action server only\n alchemy logout # Log out\nFor more information, visit: https://alchemylabcode.com/\n`)}function s(e){const t=(0,h.join)(e,b.ALCHEMY_DIR_NAME,"project.json")
|
|
1692
1704
|
return(0,d.existsSync)(t)}function o(e){const t=(0,p.platform)(),r="darwin"===t?"open":"win32"===t?"cmd":"xdg-open",s="win32"===t?["/c","start","",e]:[e];(0,g.spawn)(r,s,{detached:!0,stdio:"ignore"}).unref()}async function n(e,t=3e5){return new Promise((r=>{const s=setInterval((async()=>{await e.hasValidToken()&&(clearInterval(s),r(!0))}),2e3)
|
|
1693
1705
|
setTimeout((()=>{clearInterval(s),r(!1)}),t)}))}async function a(t){if(process.env.REMOTE_SERVER_URL)return process.env.REMOTE_SERVER_URL
|
|
1694
1706
|
try{const{createConfigManager:r}=await e.e(60).then(e.t.bind(e,9060,23)),s=r(t)
|
|
1695
1707
|
return(await s.getInstallationConfig()).remoteServerUrl||"https://api.alchemylabcode.com"}catch{return"https://api.alchemylabcode.com"}}async function c(e,t,r,s,o="cli-init"){try{const n=(0,h.basename)(r)||"Local Project",i=await fetch(e+"/api/projects",{method:"POST",headers:{"Content-Type":"application/json",Authorization:"Bearer "+t,"X-Client-Type":"alchemy-local-service","X-Service-Secret":process.env.SERVICE_SHARED_SECRET||"dev-service-secret-change-in-production"},body:JSON.stringify({name:n,description:"Project for "+n,status:"active",metadata:{basePath:r,createdVia:o}})})
|
|
1696
1708
|
if(i.ok){const e=await i.json()
|
|
1697
1709
|
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})
|
|
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),
|
|
1710
|
+
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),E=e(6800),b=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))
|
|
1699
1711
|
const i=process.argv.slice(2)
|
|
1700
1712
|
let g
|
|
1701
1713
|
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
1714
|
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,
|
|
1715
|
+
if(0===S.length)return void await async function(r={}){const i=process.cwd(),l=(0,h.join)(i,b.ALCHEMY_DIR_NAME)
|
|
1704
1716
|
await(0,u.mkdir)(l,{recursive:!0})
|
|
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}),
|
|
1706
|
-
await
|
|
1707
|
-
let T=await
|
|
1717
|
+
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}),C=await a(S),k=new m.TokenManager(S,i,{remoteServerUrl:C,allowInteractiveAuth:!1})
|
|
1718
|
+
await k.initialize()
|
|
1719
|
+
let T=await k.getToken()
|
|
1708
1720
|
if(!T?.token){console.log(""),console.log("\u{1f511} Not logged in. Opening browser to authenticate...")
|
|
1709
|
-
const e=await
|
|
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(
|
|
1721
|
+
const e=await k.startAuthenticationFlow()
|
|
1722
|
+
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(k)||(console.error("\u274c Authentication timed out. Run `alchemy` again to retry."),process.exit(1)),T=await k.getToken(),console.log(`\u2705 Welcome, ${T?.username||"user"}!`),console.log("")}let _=T?.token
|
|
1711
1723
|
await async function(e,t,r,o){if(s(e))return
|
|
1712
1724
|
console.log("\u{1f4c1} Initializing AlchemyLab project..."),console.log("")
|
|
1713
|
-
const n=(0,h.join)(e,
|
|
1725
|
+
const n=(0,h.join)(e,b.ALCHEMY_DIR_NAME)
|
|
1714
1726
|
let i
|
|
1715
1727
|
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")
|
|
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,
|
|
1717
|
-
const j=(0,h.join)(i,
|
|
1728
|
+
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,C,_,S)
|
|
1729
|
+
const j=(0,h.join)(i,b.ALCHEMY_DIR_NAME,"project.json")
|
|
1718
1730
|
let A
|
|
1719
1731
|
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(
|
|
1732
|
+
if(A=e.id||void 0,!1===e.registeredRemotely&&_){const t=await c(C,_,i,S,"cli-init-retry")
|
|
1721
1733
|
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
1734
|
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,
|
|
1724
|
-
try{const t=(0,v.getInstallContext)(),r=new f.MCPClientConfigurer(S,
|
|
1725
|
-
if(
|
|
1726
|
-
try{const e=await fetch(
|
|
1735
|
+
const M=r.port||await(0,E.findAvailablePort)(3456),O=new y.AlchemyActionServer(S,{projectPath:i,remoteServerUrl:C,port:M,authToken:_,tokenManager:k})
|
|
1736
|
+
try{const t=(0,v.getInstallContext)(),r=new f.MCPClientConfigurer(S,C,_),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
|
|
1737
|
+
if(C.includes("api.alchemylabcode.com"))return"https://alchemylabcode.com"
|
|
1738
|
+
try{const e=await fetch(C+"/health")
|
|
1727
1739
|
if(e.ok){const t=await e.json()
|
|
1728
|
-
if(t?.data?.frontendUrl)return t.data.frontendUrl}}catch{}return
|
|
1740
|
+
if(t?.data?.frontendUrl)return t.data.frontendUrl}}catch{}return C.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
1741
|
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,
|
|
1742
|
+
const e=(0,h.join)(i,b.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)")
|
|
1731
1743
|
console.log(""),console.log(` \u2192 Alchemy UI: \x1b]8;;${a}\x07${a}\x1b]8;;\x07`),console.log("")
|
|
1732
1744
|
let l=!1,u=null
|
|
1733
1745
|
const g=async()=>{l||(l=!0,u&&clearInterval(u),console.log(""),console.log("Shutting down..."),await O.stop(),process.exit(0))}
|
|
1734
1746
|
process.on("SIGTERM",g),process.on("SIGINT",g)
|
|
1735
|
-
const{InteractiveShell:p}=await e.e(994).then(e.t.bind(e,5994,23)),m=new p({projectPath:i,logger:S,remoteServerUrl:
|
|
1747
|
+
const{InteractiveShell:p}=await e.e(994).then(e.t.bind(e,5994,23)),m=new p({projectPath:i,logger:S,remoteServerUrl:C,authToken:_,projectId:A})
|
|
1736
1748
|
let y=!1
|
|
1737
|
-
u=setInterval((async()=>{if(!y)try{if(await
|
|
1749
|
+
u=setInterval((async()=>{if(!y)try{if(await k.hasValidToken())return
|
|
1738
1750
|
y=!0,console.log(""),console.log("\u26a0\ufe0f Auth token expired. Opening browser to re-authenticate...")
|
|
1739
|
-
const e=await
|
|
1740
|
-
if(console.log("\u{1f310} "+e.authUrl),o(e.authUrl),console.log("\u23f1\ufe0f Waiting for re-authentication..."),await n(
|
|
1751
|
+
const e=await k.startAuthenticationFlow()
|
|
1752
|
+
if(console.log("\u{1f310} "+e.authUrl),o(e.authUrl),console.log("\u23f1\ufe0f Waiting for re-authentication..."),await n(k)){const e=await k.getToken()
|
|
1741
1753
|
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
|
|
1743
|
-
switch("--help"!==
|
|
1754
|
+
const C=S[0].toLowerCase()
|
|
1755
|
+
switch("--help"!==C&&"-h"!==C||(r(),process.exit(0)),"--version"!==C&&"-v"!==C||(console.log("alchemy v"+t()),process.exit(0)),C){case"serve":await async function(e={}){const r=process.cwd()
|
|
1744
1756
|
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))
|
|
1745
1757
|
const o=(0,l.createEnvLogger)({serviceName:"alchemy-server",console:!0,baseDir:r}),n=t()
|
|
1746
1758
|
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)
|
|
@@ -1748,7 +1760,7 @@ const i=await a(o),c=new m.TokenManager(o,r,{remoteServerUrl:i,allowInteractiveA
|
|
|
1748
1760
|
await c.initialize()
|
|
1749
1761
|
const d=await c.getToken(),u=d?.token
|
|
1750
1762
|
u||console.log("\u26a0\ufe0f No auth token. Some features may be limited. Run `alchemy` to authenticate.")
|
|
1751
|
-
const h=e.port||await(0,
|
|
1763
|
+
const h=e.port||await(0,E.findAvailablePort)(3456),g=new y.AlchemyActionServer(o,{projectPath:r,remoteServerUrl:i,port:h,authToken:u,tokenManager:c})
|
|
1752
1764
|
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("")
|
|
1753
1765
|
let e=!1
|
|
1754
1766
|
const t=async()=>{e||(e=!0,console.log(""),console.log("Shutting down action server..."),await g.stop(),process.exit(0))}
|
|
@@ -1757,4 +1769,4 @@ break
|
|
|
1757
1769
|
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})
|
|
1758
1770
|
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.")}()
|
|
1759
1771
|
break
|
|
1760
|
-
default:console.error("\u274c Unknown command: "+
|
|
1772
|
+
default:console.error("\u274c Unknown command: "+C),console.log(""),r(),process.exit(1)}})().catch((e=>{console.error("\u{1f4a5} Unexpected error:",e),process.exit(1)}))})(),module.exports=i})()
|