alchemylab 1.4.11 → 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.
Files changed (2) hide show
  1. package/dist/cli.js +120 -109
  2. 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 E.values())r.busy&&r.busySince&&e-r.busySince>I&&(r.busy=!1,r.busySince=void 0,t=!0)
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(!(E.get(t)||{busy:!1}).busy)return E.set(t,{busy:!0,busySince:o}),{path:t,index:s,storedFingerprint:await a(t)}}return null}function n(e){const t=E.get(e)
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=b){return await o(e,t)||new Promise(((i,a)=>{let c=!1
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(){E.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,b=6e5,E=new Map,S=[]},833(e,exports,t){"use strict"
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,39 +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 e=n.default.homedir(),t=o.default.join(e,".alchemy_lab","opencode","config","opencode.json")
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(i.default.readFileSync(t,"utf-8"))
102
- Array.isArray(e.plugin)&&(r=e.plugin.find((e=>"string"==typeof e&&/^opencode-claude-auth(@|$)/.test(e)))||null)}catch{r=null}const s=o.default.join(e,".cache","opencode","packages"),a=r?o.default.join(s,r):null,c=!!a&&i.default.existsSync(a)
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(a){const e=o.default.join(a,"node_modules","opencode-claude-auth","package.json")
105
- if(d=i.default.existsSync(e),d)try{const t=JSON.parse(i.default.readFileSync(e,"utf-8"))
106
- l="string"==typeof t.version?t.version:null}catch{l=null}}return{at:(new Date).toISOString(),pinnedSpec:r,expectedDir:a,dirExists:c,installedPkgJsonExists:d,installedVersion:l}}var s=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
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=s(t(6928)),n=s(t(857)),i=s(t(9896)),a=t(3962),c=t(5419),l=t(5317),d=t(8824)
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
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,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:u,envVars:h}=e,{resolveServeCommand:g,findOpencodeDir:p}=await Promise.resolve().then(t.bind(t,9068))
113
- if(!p())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:u})
114
- const m=g()
115
- return new Promise((e=>{const t=o.default.join(u,".alchemy_lab","opencode"),g=process.env.XDG_DATA_HOME||o.default.join(n.default.homedir(),".local","share")
116
- try{i.default.mkdirSync(o.default.join(g,"opencode"),{recursive:!0})}catch(e){this.logger.warn("UnifiedLauncher: Could not pre-create opencode globalData dir",{error:(0,d.getErrorMessage)(e)})}const p=o.default.join(t,"log")
117
- let f=null
118
- const y=e=>{if(!f)return
119
- const t=f
120
- f=null,t.end(e)}
121
- try{i.default.mkdirSync(p,{recursive:!0})
122
- const e=o.default.join(p,"opencode-serve.log")
123
- i.default.writeFileSync(e,`# opencode-serve.log \u2014 launched ${(new Date).toISOString()}\n`),f=i.default.createWriteStream(e,{flags:"a"})}catch(e){this.logger.warn("UnifiedLauncher: Could not open opencode log file",{error:(0,d.getErrorMessage)(e)})}const v=(0,l.spawn)(m.command,[...m.prependArgs,"--port","0","--data-dir",t],{stdio:["ignore","pipe","pipe"],cwd:u,detached:!0,env:{...process.env,...(0,c.getOpencodeSpawnEnv)(u,(0,a.isDevMode)()),...h}})
124
- let w="",I=!1
125
- const b=t=>{w+=t
126
- const o=w.match(/listening on http:\/\/[^:]+:(\d+)/)
127
- if(o&&!I){const t=parseInt(o[1],10)
128
- I=!0,this.logger.info("UnifiedLauncher: Discovered opencode serve port",{pid:v.pid,port:t}),this.logger.info("[plugin-diag] port-ready",{pid:v.pid,port:t,...r()}),setTimeout((()=>{this.logger.info("[plugin-diag] port-ready+5s",{pid:v.pid,...r()})}),5e3).unref(),setTimeout((()=>{this.logger.info("[plugin-diag] port-ready+15s",{pid:v.pid,...r()})}),15e3).unref(),v.unref(),e({success:!0,pid:v.pid,port:t,configPath:s.configPath})}}
129
- v.stdout?.on("data",(e=>{f&&f.write(e),b(e.toString())})),v.stderr?.on("data",(e=>{if(f){const t=e.toString().split("\n")
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")
130
141
  for(let e=0;e<t.length;e++){const r=t[e]
131
- 0===r.length&&e===t.length-1||f.write(`[stderr] ${r}\n`)}}b(e.toString())})),v.on("exit",((e,t)=>{y(`\n# opencode exited code=${e} signal=${t} at=${(new Date).toISOString()}\n`)})),setTimeout((()=>{I||(I=!0,this.logger.warn("UnifiedLauncher: Timeout waiting for opencode serve port",{outputBuffer:w.slice(0,500)}),v.unref(),e({success:!0,pid:v.pid,configPath:s.configPath,error:"Could not determine port from opencode serve output"}))}),1e4),v.on("error",(t=>{I||(I=!0,this.logger.error("UnifiedLauncher: opencode serve failed to start",{error:t.message}),y(`\n# child error: ${t.message}\n`),e({success:!1,error:"Failed to start opencode serve: "+t.message}))})),v.on("exit",(t=>{I||(I=!0,this.logger.error("UnifiedLauncher: opencode serve exited early",{code:t,output:w.slice(0,500)}),e({success:!1,error:"opencode serve exited with code "+t}))}))}))}}},1253(e,exports,t){"use strict"
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"
132
143
  var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
133
144
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.TokenManager=void 0
134
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"
@@ -204,8 +215,8 @@ const y=await f.json()
204
215
  if(!y.id)throw Error("Session created in OpenCode but no ID returned")
205
216
  const v=y.id
206
217
  u.info("Pre-created OpenCode session",{sessionId:v,title:p,agent:t})
207
- 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),b=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(),[,E]=await Promise.all([w,I,b])
208
- return{sessionId:v,model:E,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
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
209
220
  try{let e,p
210
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
211
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}
@@ -249,8 +260,8 @@ if(!y)return void this.logger.info("Quest tick: no persistent alchemist session
249
260
  const v=(i.currentIteration||0)+1
250
261
  let w=!1
251
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})
252
- 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}}}
253
- 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()
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()
254
265
  if(18e5>e-this.lastQuestsFetch)return this.cachedQuests
255
266
  try{const t=await this.apiClient.get(`/api/projects/${this.projectId}/plans?type=Quest&limit=100`),r=t?.data||t||[]
256
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`)
@@ -306,7 +317,7 @@ Object.defineProperty(exports,"__esModule",{value:!0}),exports.partMeta=function
306
317
  return{...e,_meta:{...s,alchemy:{...o,...r,kind:t}}}},exports.readAlchemyMeta=function(e){return e?._meta?.alchemy}},2366(e,exports,t){"use strict"
307
318
  var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
308
319
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.AlchemyActionServer=void 0
309
- 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)
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)
310
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
311
322
  const r=(0,l.createMetricsCollector)(!1)
312
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)()
@@ -385,7 +396,7 @@ e&&(await fetch(e+"/config/reload",{method:"POST",signal:AbortSignal.timeout(5e3
385
396
  if(!s)return void r.status(400).json({success:!1,error:"requestId is required"})
386
397
  if(!/^req\d+$/.test(s))return void r.status(400).json({success:!1,error:"Invalid requestId format"})
387
398
  this.logger.info("Generating journal for request",{requestId:s})
388
- 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,b.isDevMode)())}})
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)())}})
389
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
390
401
  if(!r)return void t.status(400).json({success:!1,error:"Project not registered"})
391
402
  const{sessionId:s,requestId:o,summary:n,status:i}=e.body
@@ -439,7 +450,7 @@ if(r&&!a.has(r))return void t.status(400).json({success:!1,error:"agent must be
439
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)})
440
451
  if(!c.success||!c.sessionId)return void t.status(500).json({success:!1,error:c.error||"spawn failed"})
441
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
442
- 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})
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})
443
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
444
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)
445
456
  if(!l.success)return void r.status(500).json({success:!1,error:l.error||l.message})
@@ -599,7 +610,7 @@ if(s.ok){const e=await s.json(),t=e?.data?.settings||e?.settings
599
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()
600
611
  try{const e=d.default.join(this.options.projectPath,".alchemy_lab","opencode.json")
601
612
  if(u.default.existsSync(e)){const t=u.default.readFileSync(e,"utf-8"),r=JSON.parse(t)
602
- 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()??[]
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()??[]
603
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"})
604
615
  const{sessionId:r,title:s}=e.body||{},o=this.stuckSessionWatchdog.injectTestStuck(r,s)
605
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
@@ -899,10 +910,10 @@ if("busy"===o||"running"===o){i=!0
899
910
  break}await s(500)}for(;Date.now()<n;){const o=await r(e,t)
900
911
  if("error"===o)return{status:"error"}
901
912
  if(("idle"===o||"completed"===o)&&i)return{status:"completed"}
902
- "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)})
903
- 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
904
- return""}(E)
905
- return{success:!0,message:"Prompt completed",data:{sessionId:w,status:I.status,messages:E,toolCallCount:S,elapsedMs:b,finalResponse:k}}}
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}}}
906
917
  const o=t(8148)},4769(e,exports,t){"use strict"
907
918
  function r(e){return(0,o.getOpenCodeServerUrl)(e.projectPath,e.logger)}async function s(e,t,s,o){const n=r(s)
908
919
  if(!n||!t)return!1
@@ -1036,38 +1047,38 @@ m.info("Starting run-tests action",{requestId:d,testProcessIds:u})
1036
1047
  try{const l=await f.get(`/api/projects/${y}/requests/${d}`)
1037
1048
  if(!l.success||!l.data)return{success:!1,message:`Request ${d} not found`,error:"Request not found"}
1038
1049
  const g=l.data,I=g.branchName||"requests/"+d
1039
- let b
1040
- if(u?.length)b=(await Promise.all(u.map((e=>f.get(`/api/projects/${y}/test-processes/${e}`))))).filter((e=>e.success&&e.data)).map((e=>e.data))
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))
1041
1052
  else{const e=await f.get(`/api/projects/${y}/test-processes/active`)
1042
- 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"}
1043
- const E=Math.max(1,Math.min(8,b[0]?.config?.poolSize??2)),S=b[0]?.config?.fingerprint||[]
1044
- let k
1045
- 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)
1046
- try{if((0,s.existsSync)(C))try{const e=(0,n.simpleGit)(C)
1047
- 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)})
1048
- 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})
1049
- try{if(!(await T.branch()).all.includes(I))return(0,c.releaseSlot)(C),{success:!1,message:`Branch ${I} not found`,error:"Branch not found"}
1050
- 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)}
1051
- 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
1052
- j||m.info("Pool slot fingerprint matches; skipping setup",{worktreePath:C,slotIndex:k.index,fingerprintFiles:S})
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})
1053
1064
  const{execSync:P}=await Promise.resolve().then(t.t.bind(t,5317,23))
1054
- if(j){for(const e of b){const t=e.config?.setup||[]
1055
- if(0!==t.length){m.info("Running setup commands for test process",{processName:e.name,commandCount:t.length,slotIndex:k.index})
1056
- 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)
1057
- e>0&&m.info("Pruned drift from warm slot",{worktreePath:C,slotIndex:k.index,removed:e})}let M
1058
- try{const e=(0,n.simpleGit)(C)
1059
- 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=[]
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=[]
1060
1071
  let x
1061
- const $={worktreePath:C,commitSha:M,fingerprintHit:A}
1062
- for(const process of b){let t
1072
+ const $={worktreePath:k,commitSha:M,fingerprintHit:A}
1073
+ for(const process of E){let t
1063
1074
  const s=e.testRunIds?.[O.length]
1064
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)})
1065
1076
  continue}const o=r(g,process,0,y,p,e.commandNames,e.skipCommandNames),n=p?"tester":"auto-tester"
1066
- 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})
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})
1067
1078
  if(!e.success)throw Error(e.error||"Spawn failed")
1068
- 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)})
1069
- 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"}
1070
- 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)
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)
1071
1082
  return m.error("run-tests action failed",{requestId:d,error:t}),{success:!1,message:"Failed to run tests: "+t,error:t}}}
1072
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"
1073
1084
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.TimerHookScheduler=void 0
@@ -1182,7 +1193,7 @@ Object.defineProperty(exports,"evaluateCondition",{enumerable:!0,get(){return i.
1182
1193
  function t(e){if("string"!=typeof e)return e
1183
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
1184
1195
  if("before"===c)return[{eventType:"tool_executed",timing:"before",variables:{tool:o,toolName:o,input:JSON.stringify(i??{}).substring(0,1e3),sessionId:n||""}}]
1185
- 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"),b=o.includes("query_history")
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")
1186
1197
  if(h&&d){const e=i?.operation,t=function(e){for(const t of r){const r=e.match(t)
1187
1198
  if(r)return r[1]}return null}(d)
1188
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)
@@ -1193,7 +1204,7 @@ l.push({eventType:"replace_in_files",timing:"after",variables:{...u,paths:r.join
1193
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)
1194
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||"*"}})
1195
1206
  else if(w){const e=t(i?.operations)||[],r=(Array.isArray(e)?e:[]).map((e=>e.operationType)).filter(Boolean)
1196
- 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}):b&&l.push({eventType:"query_history",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})
1197
1208
  return l.push({eventType:"tool_executed",timing:"after",variables:{tool:o,toolName:o,output:d,sessionId:n||""}}),l}
1198
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"
1199
1210
  module.exports=require("express")},7388(e,exports,t){"use strict"
@@ -1398,8 +1409,8 @@ if(p&&(o.ALCHEMY_INITIAL_BRANCH=p),r(u,"Spawn env setup",{agent:t,initialBranch:
1398
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})
1399
1410
  const v=(0,i.join)(u||process.cwd(),".alchemy_lab"),w=(0,i.join)(v,"spawn-stderr.log")
1400
1411
  let I
1401
- try{I=(0,n.openSync)(w,"a")}catch{}const b=(0,a.resolveRunCommand)(),E=(0,s.spawn)(b.command,[...b.prependArgs,...y],{detached:!0,stdio:["ignore","ignore",I??"ignore"],cwd:u||process.cwd(),env:o})
1402
- return E.unref(),f&&f(E.pid),{success:!0,pid:E.pid}}catch(e){return{success:!1,error:(0,o.getErrorMessage)(e)}}}
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)}}}
1403
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"
1404
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
1405
1416
  let f,y,v,w=null
@@ -1554,18 +1565,18 @@ let g=null,p=null,m=null,f=null,y=!1,v=!1
1554
1565
  const w=c.join(s,".alchemy_lab","config.json")
1555
1566
  if(!i){try{const e=await r.readFile(w,"utf-8"),t=JSON.parse(e)
1556
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)
1557
- 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},b={tools:I},E=i?.openCodeModel||p||f||"anthropic/claude-opus-4-5"
1558
- i?.openCodeModel?b.model=i.openCodeModel:p?b.model=p:f&&(b.model=f)
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)
1559
1570
  const S=c.join(s,".opencode","agent")
1560
1571
  await r.mkdir(S,{recursive:!0})
1561
- const k=E||"anthropic/claude-opus-4-5-20251101"
1562
- let C,T=null,_=null
1563
- 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})
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})
1564
1575
  for(const e of T){if(!e.enabled)continue
1565
- const t=this.generateAgentMarkdown(e,d,k,C),s=c.join(S,e.name+".md")
1576
+ const t=this.generateAgentMarkdown(e,d,C,k),s=c.join(S,e.name+".md")
1566
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")
1567
1578
  else this.logger.warn("No projectId provided - agents must be configured via database")
1568
- const j={$schema:"https://opencode.ai/config.json",model:E,mcp:{[d]:{type:"local",command:[n,u,s],enabled:!0,timeout:6e5}}}
1579
+ const j={$schema:"https://opencode.ai/config.json",model:b,mcp:{[d]:{type:"local",command:[n,u,s],enabled:!0,timeout:6e5}}}
1569
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={}
1570
1581
  for(const e of T){if(!e.enabled)continue
1571
1582
  const t={}
@@ -1573,8 +1584,8 @@ for(const[r,s]of Object.entries(e.tools||{})){const e=s,n=r.replace(/-/g,"_")
1573
1584
  t[o.OPENCODE_BUILTIN_TOOLS.has(r)?r:`${d}_${n}`]=e}const r=_?.metadata?.bashToolAgents||{}
1574
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)
1575
1586
  let s=""
1576
- 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}}
1577
- 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`
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`
1578
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))):[]
1579
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})
1580
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")
@@ -1689,59 +1700,59 @@ var i={};(()=>{"use strict"
1689
1700
  function t(){const e=[(0,h.join)(__dirname,"..","package.json"),(0,h.join)(__dirname,"..","..","package.json")]
1690
1701
  for(const t of e)try{const e=JSON.parse((0,d.readFileSync)(t,"utf-8"))
1691
1702
  if(e.version)return e.version}catch{}return"unknown"}function r(){const e=t()
1692
- 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,E.ALCHEMY_DIR_NAME,"project.json")
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")
1693
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)
1694
1705
  setTimeout((()=>{clearInterval(s),r(!1)}),t)}))}async function a(t){if(process.env.REMOTE_SERVER_URL)return process.env.REMOTE_SERVER_URL
1695
1706
  try{const{createConfigManager:r}=await e.e(60).then(e.t.bind(e,9060,23)),s=r(t)
1696
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}})})
1697
1708
  if(i.ok){const e=await i.json()
1698
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})
1699
- 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))
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))
1700
1711
  const i=process.argv.slice(2)
1701
1712
  let g
1702
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)))
1703
1714
  const S=i.filter(((e,t)=>"--port"!==e&&(-1===p||t!==p+1)))
1704
- if(0===S.length)return void await async function(r={}){const i=process.cwd(),l=(0,h.join)(i,E.ALCHEMY_DIR_NAME)
1715
+ if(0===S.length)return void await async function(r={}){const i=process.cwd(),l=(0,h.join)(i,b.ALCHEMY_DIR_NAME)
1705
1716
  await(0,u.mkdir)(l,{recursive:!0})
1706
- 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})
1707
- await C.initialize()
1708
- let T=await C.getToken()
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()
1709
1720
  if(!T?.token){console.log(""),console.log("\u{1f511} Not logged in. Opening browser to authenticate...")
1710
- const e=await C.startAuthenticationFlow()
1711
- 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
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
1712
1723
  await async function(e,t,r,o){if(s(e))return
1713
1724
  console.log("\u{1f4c1} Initializing AlchemyLab project..."),console.log("")
1714
- const n=(0,h.join)(e,E.ALCHEMY_DIR_NAME)
1725
+ const n=(0,h.join)(e,b.ALCHEMY_DIR_NAME)
1715
1726
  let i
1716
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")
1717
- 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)
1718
- const j=(0,h.join)(i,E.ALCHEMY_DIR_NAME,"project.json")
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")
1719
1730
  let A
1720
1731
  try{const e=JSON.parse((0,d.readFileSync)(j,"utf-8"))
1721
- if(A=e.id||void 0,!1===e.registeredRemotely&&_){const t=await c(k,_,i,S,"cli-init-retry")
1732
+ if(A=e.id||void 0,!1===e.registeredRemotely&&_){const t=await c(C,_,i,S,"cli-init-retry")
1722
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()
1723
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)
1724
- const M=r.port||await(0,b.findAvailablePort)(3456),O=new y.AlchemyActionServer(S,{projectPath:i,remoteServerUrl:k,port:M,authToken:_,tokenManager:C})
1725
- 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
1726
- if(k.includes("api.alchemylabcode.com"))return"https://alchemylabcode.com"
1727
- try{const e=await fetch(k+"/health")
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")
1728
1739
  if(e.ok){const t=await e.json()
1729
- 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)})))])
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)})))])
1730
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)
1731
- 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)")
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)")
1732
1743
  console.log(""),console.log(` \u2192 Alchemy UI: \x1b]8;;${a}\x07${a}\x1b]8;;\x07`),console.log("")
1733
1744
  let l=!1,u=null
1734
1745
  const g=async()=>{l||(l=!0,u&&clearInterval(u),console.log(""),console.log("Shutting down..."),await O.stop(),process.exit(0))}
1735
1746
  process.on("SIGTERM",g),process.on("SIGINT",g)
1736
- 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})
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})
1737
1748
  let y=!1
1738
- u=setInterval((async()=>{if(!y)try{if(await C.hasValidToken())return
1749
+ u=setInterval((async()=>{if(!y)try{if(await k.hasValidToken())return
1739
1750
  y=!0,console.log(""),console.log("\u26a0\ufe0f Auth token expired. Opening browser to re-authenticate...")
1740
- const e=await C.startAuthenticationFlow()
1741
- 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()
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()
1742
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})
1743
- const k=S[0].toLowerCase()
1744
- 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()
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()
1745
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))
1746
1757
  const o=(0,l.createEnvLogger)({serviceName:"alchemy-server",console:!0,baseDir:r}),n=t()
1747
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)
@@ -1749,7 +1760,7 @@ const i=await a(o),c=new m.TokenManager(o,r,{remoteServerUrl:i,allowInteractiveA
1749
1760
  await c.initialize()
1750
1761
  const d=await c.getToken(),u=d?.token
1751
1762
  u||console.log("\u26a0\ufe0f No auth token. Some features may be limited. Run `alchemy` to authenticate.")
1752
- const h=e.port||await(0,b.findAvailablePort)(3456),g=new y.AlchemyActionServer(o,{projectPath:r,remoteServerUrl:i,port:h,authToken:u,tokenManager:c})
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})
1753
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("")
1754
1765
  let e=!1
1755
1766
  const t=async()=>{e||(e=!0,console.log(""),console.log("Shutting down action server..."),await g.stop(),process.exit(0))}
@@ -1758,4 +1769,4 @@ break
1758
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})
1759
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.")}()
1760
1771
  break
1761
- 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})()
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})()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alchemylab",
3
- "version": "1.4.11",
3
+ "version": "1.4.12",
4
4
  "private": false,
5
5
  "description": "AlchemyLab - agentic coding IDEm",
6
6
  "author": "Testfire Software",