alchemylab 1.4.11 → 1.4.13
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 +136 -115
- package/dist/http-server.js +7 -6
- package/dist/index.js +7 -6
- package/dist/mcp-direct.js +7 -6
- package/package.json +2 -2
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,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
|
|
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
|
|
115
|
-
return new Promise((e=>{const t=
|
|
116
|
-
try{
|
|
117
|
-
|
|
118
|
-
const
|
|
119
|
-
const t=
|
|
120
|
-
|
|
121
|
-
try{
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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||
|
|
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),
|
|
208
|
-
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
|
|
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),
|
|
253
|
-
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()
|
|
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),
|
|
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,
|
|
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
|
|
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
|
|
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),
|
|
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)}(
|
|
904
|
-
return""}(
|
|
905
|
-
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}}}
|
|
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
|
|
@@ -936,17 +947,17 @@ module.exports=require("winston")},5317(module){"use strict"
|
|
|
936
947
|
module.exports=require("child_process")},5419(e,exports,t){"use strict"
|
|
937
948
|
function r(e){return(0,o.join)((0,s.homedir)(),n.ALCHEMY_DIR_NAME,e?"opencode-dev":"opencode")}Object.defineProperty(exports,"__esModule",{value:!0}),exports.getGlobalConfigDir=r,exports.getOpencodeSpawnEnv=function(e,t){return{OPENCODE_CLIENT:"alchemy",OPENCODE_DATA_DIR:(0,o.join)(e,n.ALCHEMY_DIR_NAME,"opencode"),OPENCODE_CONFIG_DIR:r(t)}}
|
|
938
949
|
const s=t(857),o=t(6928),n=t(4139)},5607(e,exports,t){"use strict"
|
|
939
|
-
Object.defineProperty(exports,"__esModule",{value:!0}),exports.executeAction=async function(e,t,r){const s=
|
|
950
|
+
Object.defineProperty(exports,"__esModule",{value:!0}),exports.executeAction=async function(e,t,r){const s=y[e]
|
|
940
951
|
if(!s)return{success:!1,message:"Unknown action type: "+e,error:"No handler registered for action type: "+e}
|
|
941
|
-
try{return await s(t,r)}catch(t){const s=(0,
|
|
952
|
+
try{return await s(t,r)}catch(t){const s=(0,m.getErrorMessage)(t)
|
|
942
953
|
return r.logger.error("Action handler failed",{actionType:e,error:s}),{success:!1,message:"Action failed: "+s,error:s}}}
|
|
943
|
-
const r=t(6636),s=t(7453),o=t(5700),n=t(848),i=t(9735),a=t(7767),c=t(9266),l=t(6555),d=t(3614),u=t(4654),h=t(7078),g=t(
|
|
954
|
+
const r=t(6636),s=t(7453),o=t(5700),n=t(848),i=t(9735),a=t(7767),c=t(9266),l=t(6555),d=t(3614),u=t(4654),h=t(7078),g=t(7775),p=t(1405),m=t(8824),f=t(2272),y={revert_request:r.handleRevertRequestAction,"revert-request":r.handleRevertRequestAction,complete_request:s.handleCompleteRequestAction,"complete-request":s.handleCompleteRequestAction,"finalize-request":n.handleFinalizeRequestAction,"approve-request":i.handleApproveRequestAction,"approve-and-work-request":a.handleApproveAndWorkRequestAction,"restart-mcp":o.handleRestartMcpAction,"opencode-proxy":c.handleOpenCodeProxyAction,"run-tests":l.handleRunTestsAction,async"inject-prompt"(e,r){const{sessionId:s,message:o,kind:n,partPayload:i}=e
|
|
944
955
|
if(!s||!o)return{success:!1,message:"sessionId and message are required",error:"missing_params"}
|
|
945
956
|
const{getOpenCodeUrlFromConfig:a}=await Promise.resolve().then(t.t.bind(t,833,23)),c=a(r.projectPath)
|
|
946
957
|
if(!c)return{success:!1,message:"Cannot read OpenCode config",error:"no_opencode"}
|
|
947
|
-
try{const e=await fetch(`${c}/session/${s}/prompt_async`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({parts:[{type:"text",text:o,metadata:(0,
|
|
958
|
+
try{const e=await fetch(`${c}/session/${s}/prompt_async`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({parts:[{type:"text",text:o,metadata:(0,f.partMeta)(n??"inserted-prompt",i)}],...r.cachedDefaultModel?(()=>{const[e,...t]=r.cachedDefaultModel.split("/")
|
|
948
959
|
return{model:{providerID:e,modelID:t.join("/")}}})():{}}),signal:AbortSignal.timeout(1e4)})
|
|
949
|
-
return e.ok?{success:!0,message:"Prompt injected into session "+s}:{success:!1,message:"prompt_async failed: "+e.status,error:"prompt_failed"}}catch(e){return{success:!1,message:(0,
|
|
960
|
+
return e.ok?{success:!0,message:"Prompt injected into session "+s}:{success:!1,message:"prompt_async failed: "+e.status,error:"prompt_failed"}}catch(e){return{success:!1,message:(0,m.getErrorMessage)(e),error:"prompt_error"}}},"fork-filesystem":d.handleForkFilesystemAction,"prompt-and-wait":u.handlePromptAndWaitAction,"run-ab-prompt":u.handlePromptAndWaitAction,"spawn-auto-planner":h.handleSpawnAutoPlannerAction,"spawn-reviewer":g.handleSpawnReviewerAction,"start-building":p.handleStartBuildingAction}},5671(e,exports,t){"use strict"
|
|
950
961
|
function r(e){const[t,...r]=e.split("/")
|
|
951
962
|
return{provider:t||"anthropic",modelId:r.join("/")||e}}function s(e){const t={anthropic:!1,openai:!1}
|
|
952
963
|
try{const s=(0,a.join)(e,"opencode.json")
|
|
@@ -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
|
|
1040
|
-
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))
|
|
1041
1052
|
else{const e=await f.get(`/api/projects/${y}/test-processes/active`)
|
|
1042
|
-
|
|
1043
|
-
const
|
|
1044
|
-
let
|
|
1045
|
-
try{
|
|
1046
|
-
try{if((0,s.existsSync)(
|
|
1047
|
-
await e.raw(["reset","--hard",I]),m.info("Reset pool slot to latest branch HEAD",{worktreePath:
|
|
1048
|
-
try{await T.raw(["worktree","remove","--force",
|
|
1049
|
-
try{if(!(await T.branch()).all.includes(I))return(0,c.releaseSlot)(
|
|
1050
|
-
await T.raw(["worktree","add","--detach",
|
|
1051
|
-
m.info("Pool slot worktree already exists, using as-is",{worktreePath:
|
|
1052
|
-
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})
|
|
1053
1064
|
const{execSync:P}=await Promise.resolve().then(t.t.bind(t,5317,23))
|
|
1054
|
-
if(j){for(const e of
|
|
1055
|
-
if(0!==t.length){m.info("Running setup commands for test process",{processName:e.name,commandCount:t.length,slotIndex:
|
|
1056
|
-
for(const r of t)try{m.info("Setup command",{command:r,worktreePath:
|
|
1057
|
-
e>0&&m.info("Pruned drift from warm slot",{worktreePath:
|
|
1058
|
-
try{const e=(0,n.simpleGit)(
|
|
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:
|
|
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:
|
|
1062
|
-
for(const process of
|
|
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:
|
|
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:
|
|
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)(
|
|
1070
|
-
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)
|
|
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"),
|
|
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}):
|
|
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"
|
|
@@ -1383,7 +1394,17 @@ return{model:{providerID:e,modelID:r.join("/")}}})():{}})})
|
|
|
1383
1394
|
i.ok?n.info("Pushed notification to CLI session",{sessionId:f}):n.warn("Failed to push notification to CLI session",{status:i.status})}}catch(e){n.warn("Could not push notification to CLI session (may be offline)",{error:(0,r.getErrorMessage)(e)})}}catch(e){n.warn("Failed to notify target session, spawning work agent instead",{requestId:o,error:(0,r.getErrorMessage)(e)})}if(!p)try{const e=await(0,s.spawnActionAgent)({agent:"work",prompt:h,cwd:c,projectPath:c,projectId:a,title:`[${o}] ${d.title}`,requestId:o,parentSessionId:g,apiClient:i,logger:n,trackChildPid:l})
|
|
1384
1395
|
m=e.success,m?n.info("Spawned work agent for request",{requestId:o,sessionId:e.sessionId}):n.warn("Failed to spawn work agent",{requestId:o,error:e.error})}catch(e){n.warn("Failed to spawn work agent",{requestId:o,error:(0,r.getErrorMessage)(e)})}return n.info("Approve-and-work-request action completed",{requestId:o,notifiedSession:p,agentSpawned:m}),{success:!0,message:`Successfully approved request ${o}: ${d.title}${p?" \u2014 creating session notified":m?" \u2014 work agent spawned":""}`,data:{operationType:"approve-and-work",previousStatus:"pending_approval",newStatus:"open",notifiedSession:p,agentSpawned:m}}}catch(e){const t=(0,r.getErrorMessage)(e)
|
|
1385
1396
|
return n.error("approve-and-work-request action failed",{requestId:o,error:t}),{success:!1,message:"Failed to approve request: "+t,error:t}}}
|
|
1386
|
-
const r=t(8824),s=t(8148)},
|
|
1397
|
+
const r=t(8824),s=t(8148)},7775(e,exports,t){"use strict"
|
|
1398
|
+
Object.defineProperty(exports,"__esModule",{value:!0}),exports.REVIEWER_FOCUSES=void 0,exports.handleSpawnReviewerAction=async function(e,t){const{requestId:o,parentSessionId:n,focus:i}=e,{logger:a,apiClient:c,projectId:l,projectPath:d,trackChildPid:u}=t
|
|
1399
|
+
if(!o||!n||!i)return{success:!1,message:"spawn-reviewer requires requestId, parentSessionId, and focus",error:"Missing payload fields"}
|
|
1400
|
+
if("string"!=typeof(h=i)||!exports.REVIEWER_FOCUSES.includes(h))return{success:!1,message:"Invalid focus: "+i,error:"Invalid focus"}
|
|
1401
|
+
var h
|
|
1402
|
+
a.info("Starting spawn-reviewer action",{requestId:o,focus:i,parentSessionId:n})
|
|
1403
|
+
try{const e=function(e,t,r){return`[Session Context]\n- Request id: ${e}\n- Your focus: ${t}\n- Coder session to report to: ${r}\n\nYou are reviewing this request's diff with a single focus: **${t}**. Other dimensions are being reviewed in parallel by your peers \u2014 ignore them silently.\n\n1. Call \`request_diff\` to read the diff.\n2. Use \`read_files\` and \`search\` to gather context for any finding you intend to flag.\n3. Categorize each finding into must_fix / nice_to_have / out_of_scope per your agent instructions.\n4. Send ONE report via \`session_message_operations send\` with type "report", toSessionId "${r}", finish true, and the structured markdown summary.\n5. Exit. Do not send follow-ups.`}(o,i,n),t=`Reviewer (${i}) for ${o}`,s=await(0,r.spawnActionAgent)({agent:"reviewer",prompt:e,cwd:d,projectPath:d,projectId:l,title:t,requestId:o,parentSessionId:n,allowParallel:!0,apiClient:c,logger:a,trackChildPid:u})
|
|
1404
|
+
return s.success&&s.sessionId?{success:!0,message:`Reviewer spawned: ${i} for ${o}`,data:{sessionId:s.sessionId,pid:s.pid,focus:i}}:{success:!1,message:`Failed to spawn reviewer (${i}): ${s.error}`,error:s.error||"Spawn failed"}}catch(e){const t=(0,s.getErrorMessage)(e)
|
|
1405
|
+
return a.error("spawn-reviewer action failed",{requestId:o,focus:i,error:t}),{success:!1,message:`Failed to spawn reviewer (${i}): ${t}`,error:t}}}
|
|
1406
|
+
const r=t(8148),s=t(8824)
|
|
1407
|
+
exports.REVIEWER_FOCUSES=["REUSE","QUALITY","EFFICIENCY"]},7904(e,exports){"use strict"
|
|
1387
1408
|
Object.defineProperty(exports,"__esModule",{value:!0}),exports.REPORT_STATUS_EMOJI=void 0,exports.formatReportNotification=function(e){const{messageCode:t,status:r,summary:s,details:o,fromSessionId:n,senderAgent:i}=e,a=[`${exports.REPORT_STATUS_EMOJI[r]||"\u{1f4cb}"} **Report \u2014 ${r}**`]
|
|
1388
1409
|
return t&&a.push("**Report Code:** "+t),i&&a.push(n?`from: ${i} (${n})`:"from: "+i),a.push("",s||"Task completed"),o&&a.push("",o),t&&a.push("",`Use \`session_message_operations\` with \`reply\` operation and message code \`${t}\` to acknowledge.`),a.join("\n")},exports.REPORT_STATUS_EMOJI={success:"\u2705",partial:"\u26a0\ufe0f",failure:"\u274c",blocked:"\u{1f6ab}"}},7960(e,exports){"use strict"
|
|
1389
1410
|
function t(){const e=process.argv[1]||""
|
|
@@ -1398,8 +1419,8 @@ if(p&&(o.ALCHEMY_INITIAL_BRANCH=p),r(u,"Spawn env setup",{agent:t,initialBranch:
|
|
|
1398
1419
|
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
1420
|
const v=(0,i.join)(u||process.cwd(),".alchemy_lab"),w=(0,i.join)(v,"spawn-stderr.log")
|
|
1400
1421
|
let I
|
|
1401
|
-
try{I=(0,n.openSync)(w,"a")}catch{}const
|
|
1402
|
-
return
|
|
1422
|
+
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})
|
|
1423
|
+
return b.unref(),f&&f(b.pid),{success:!0,pid:b.pid}}catch(e){return{success:!1,error:(0,o.getErrorMessage)(e)}}}
|
|
1403
1424
|
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
1425
|
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
1426
|
let f,y,v,w=null
|
|
@@ -1554,18 +1575,18 @@ let g=null,p=null,m=null,f=null,y=!1,v=!1
|
|
|
1554
1575
|
const w=c.join(s,".alchemy_lab","config.json")
|
|
1555
1576
|
if(!i){try{const e=await r.readFile(w,"utf-8"),t=JSON.parse(e)
|
|
1556
1577
|
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},
|
|
1558
|
-
i?.openCodeModel?
|
|
1578
|
+
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"
|
|
1579
|
+
i?.openCodeModel?E.model=i.openCodeModel:p?E.model=p:f&&(E.model=f)
|
|
1559
1580
|
const S=c.join(s,".opencode","agent")
|
|
1560
1581
|
await r.mkdir(S,{recursive:!0})
|
|
1561
|
-
const
|
|
1562
|
-
let
|
|
1563
|
-
if(i?.projectId)if(await this.seedAgentsFromRemote(i.projectId),_=await this.fetchProjectFromRemote(i.projectId),
|
|
1582
|
+
const C=b||"anthropic/claude-opus-4-5-20251101"
|
|
1583
|
+
let k,T=null,_=null
|
|
1584
|
+
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
1585
|
for(const e of T){if(!e.enabled)continue
|
|
1565
|
-
const t=this.generateAgentMarkdown(e,d,k
|
|
1586
|
+
const t=this.generateAgentMarkdown(e,d,C,k),s=c.join(S,e.name+".md")
|
|
1566
1587
|
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
1588
|
else this.logger.warn("No projectId provided - agents must be configured via database")
|
|
1568
|
-
const j={$schema:"https://opencode.ai/config.json",model:
|
|
1589
|
+
const j={$schema:"https://opencode.ai/config.json",model:b,mcp:{[d]:{type:"local",command:[n,u,s],enabled:!0,timeout:6e5}}}
|
|
1569
1590
|
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
1591
|
for(const e of T){if(!e.enabled)continue
|
|
1571
1592
|
const t={}
|
|
@@ -1573,8 +1594,8 @@ for(const[r,s]of Object.entries(e.tools||{})){const e=s,n=r.replace(/-/g,"_")
|
|
|
1573
1594
|
t[o.OPENCODE_BUILTIN_TOOLS.has(r)?r:`${d}_${n}`]=e}const r=_?.metadata?.bashToolAgents||{}
|
|
1574
1595
|
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
1596
|
let s=""
|
|
1576
|
-
|
|
1577
|
-
const e=`---\ndescription: Fast codebase exploration and research subagent\nmode: subagent\n---\n\n${
|
|
1597
|
+
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}}
|
|
1598
|
+
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
1599
|
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
1600
|
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
1601
|
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 +1710,59 @@ var i={};(()=>{"use strict"
|
|
|
1689
1710
|
function t(){const e=[(0,h.join)(__dirname,"..","package.json"),(0,h.join)(__dirname,"..","..","package.json")]
|
|
1690
1711
|
for(const t of e)try{const e=JSON.parse((0,d.readFileSync)(t,"utf-8"))
|
|
1691
1712
|
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,
|
|
1713
|
+
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
1714
|
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
1715
|
setTimeout((()=>{clearInterval(s),r(!1)}),t)}))}async function a(t){if(process.env.REMOTE_SERVER_URL)return process.env.REMOTE_SERVER_URL
|
|
1695
1716
|
try{const{createConfigManager:r}=await e.e(60).then(e.t.bind(e,9060,23)),s=r(t)
|
|
1696
1717
|
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
1718
|
if(i.ok){const e=await i.json()
|
|
1698
1719
|
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),
|
|
1720
|
+
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
1721
|
const i=process.argv.slice(2)
|
|
1701
1722
|
let g
|
|
1702
1723
|
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
1724
|
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,
|
|
1725
|
+
if(0===S.length)return void await async function(r={}){const i=process.cwd(),l=(0,h.join)(i,b.ALCHEMY_DIR_NAME)
|
|
1705
1726
|
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}),
|
|
1707
|
-
await
|
|
1708
|
-
let T=await
|
|
1727
|
+
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})
|
|
1728
|
+
await k.initialize()
|
|
1729
|
+
let T=await k.getToken()
|
|
1709
1730
|
if(!T?.token){console.log(""),console.log("\u{1f511} Not logged in. Opening browser to authenticate...")
|
|
1710
|
-
const e=await
|
|
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(
|
|
1731
|
+
const e=await k.startAuthenticationFlow()
|
|
1732
|
+
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
1733
|
await async function(e,t,r,o){if(s(e))return
|
|
1713
1734
|
console.log("\u{1f4c1} Initializing AlchemyLab project..."),console.log("")
|
|
1714
|
-
const n=(0,h.join)(e,
|
|
1735
|
+
const n=(0,h.join)(e,b.ALCHEMY_DIR_NAME)
|
|
1715
1736
|
let i
|
|
1716
1737
|
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,
|
|
1718
|
-
const j=(0,h.join)(i,
|
|
1738
|
+
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)
|
|
1739
|
+
const j=(0,h.join)(i,b.ALCHEMY_DIR_NAME,"project.json")
|
|
1719
1740
|
let A
|
|
1720
1741
|
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(
|
|
1742
|
+
if(A=e.id||void 0,!1===e.registeredRemotely&&_){const t=await c(C,_,i,S,"cli-init-retry")
|
|
1722
1743
|
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
1744
|
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,
|
|
1725
|
-
try{const t=(0,v.getInstallContext)(),r=new f.MCPClientConfigurer(S,
|
|
1726
|
-
if(
|
|
1727
|
-
try{const e=await fetch(
|
|
1745
|
+
const M=r.port||await(0,E.findAvailablePort)(3456),O=new y.AlchemyActionServer(S,{projectPath:i,remoteServerUrl:C,port:M,authToken:_,tokenManager:k})
|
|
1746
|
+
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
|
|
1747
|
+
if(C.includes("api.alchemylabcode.com"))return"https://alchemylabcode.com"
|
|
1748
|
+
try{const e=await fetch(C+"/health")
|
|
1728
1749
|
if(e.ok){const t=await e.json()
|
|
1729
|
-
if(t?.data?.frontendUrl)return t.data.frontendUrl}}catch{}return
|
|
1750
|
+
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
1751
|
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,
|
|
1752
|
+
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
1753
|
console.log(""),console.log(` \u2192 Alchemy UI: \x1b]8;;${a}\x07${a}\x1b]8;;\x07`),console.log("")
|
|
1733
1754
|
let l=!1,u=null
|
|
1734
1755
|
const g=async()=>{l||(l=!0,u&&clearInterval(u),console.log(""),console.log("Shutting down..."),await O.stop(),process.exit(0))}
|
|
1735
1756
|
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:
|
|
1757
|
+
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
1758
|
let y=!1
|
|
1738
|
-
u=setInterval((async()=>{if(!y)try{if(await
|
|
1759
|
+
u=setInterval((async()=>{if(!y)try{if(await k.hasValidToken())return
|
|
1739
1760
|
y=!0,console.log(""),console.log("\u26a0\ufe0f Auth token expired. Opening browser to re-authenticate...")
|
|
1740
|
-
const e=await
|
|
1741
|
-
if(console.log("\u{1f310} "+e.authUrl),o(e.authUrl),console.log("\u23f1\ufe0f Waiting for re-authentication..."),await n(
|
|
1761
|
+
const e=await k.startAuthenticationFlow()
|
|
1762
|
+
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
1763
|
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
|
|
1744
|
-
switch("--help"!==
|
|
1764
|
+
const C=S[0].toLowerCase()
|
|
1765
|
+
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
1766
|
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
1767
|
const o=(0,l.createEnvLogger)({serviceName:"alchemy-server",console:!0,baseDir:r}),n=t()
|
|
1747
1768
|
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 +1770,7 @@ const i=await a(o),c=new m.TokenManager(o,r,{remoteServerUrl:i,allowInteractiveA
|
|
|
1749
1770
|
await c.initialize()
|
|
1750
1771
|
const d=await c.getToken(),u=d?.token
|
|
1751
1772
|
u||console.log("\u26a0\ufe0f No auth token. Some features may be limited. Run `alchemy` to authenticate.")
|
|
1752
|
-
const h=e.port||await(0,
|
|
1773
|
+
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
1774
|
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
1775
|
let e=!1
|
|
1755
1776
|
const t=async()=>{e||(e=!0,console.log(""),console.log("Shutting down action server..."),await g.stop(),process.exit(0))}
|
|
@@ -1758,4 +1779,4 @@ break
|
|
|
1758
1779
|
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
1780
|
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
1781
|
break
|
|
1761
|
-
default:console.error("\u274c Unknown command: "+
|
|
1782
|
+
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/dist/http-server.js
CHANGED
|
@@ -1385,11 +1385,12 @@ const o=process.config||{},s=o.commands||[]
|
|
|
1385
1385
|
let a=`## ${process.name}\n`
|
|
1386
1386
|
process.description&&(a+=process.description+"\n"),process.scope&&(a+=`**Scope:** \`${process.scope}\`\n`),a+=`**Status:** ${process.isActive?"Active":"Inactive"}\n\n`,o.setup?.length&&(a+=`### Setup\n${o.setup.map((e=>`\`\`\`\n${e}\n\`\`\``)).join("\n")}\n\n`),a+=`### Test Commands (${s.length})\n`
|
|
1387
1387
|
for(let e=0;e<s.length;e++){const t=s[e]
|
|
1388
|
-
a+=`\n**${e+1}. ${t.name}**${t.required?" \u2014 REQUIRED":" \u2014 optional"}\n`,a+=` Command: \`${t.command}\`\n`,t.timeout&&(a+=` Timeout: ${t.timeout}s\n`)}return Array.isArray(o.fingerprint)&&o.fingerprint.length>0&&(a+=`\n### Fingerprint Files\n${o.fingerprint.map((e=>`- \`${e}\``)).join("\n")}\n`),"number"==typeof o.poolSize&&(a+=`\n**Pool size:** ${o.poolSize}\n`),i({success:!0}),{content:[{type:"text",text:a}]}}catch(e){return i({success:!1,error:"api_error"}),{content:[{type:"text",text:"Failed to get test process: "+(0,r.getErrorMessage)(e)}],isError:!0}}},exports.handleTestingProcessCreate=async function(e,
|
|
1389
|
-
try{const
|
|
1390
|
-
void 0!==e.fingerprint&&(
|
|
1391
|
-
const
|
|
1392
|
-
|
|
1388
|
+
a+=`\n**${e+1}. ${t.name}**${t.required?" \u2014 REQUIRED":" \u2014 optional"}\n`,a+=` Command: \`${t.command}\`\n`,t.timeout&&(a+=` Timeout: ${t.timeout}s\n`)}return Array.isArray(o.fingerprint)&&o.fingerprint.length>0&&(a+=`\n### Fingerprint Files\n${o.fingerprint.map((e=>`- \`${e}\``)).join("\n")}\n`),"number"==typeof o.poolSize&&(a+=`\n**Pool size:** ${o.poolSize}\n`),i({success:!0}),{content:[{type:"text",text:a}]}}catch(e){return i({success:!1,error:"api_error"}),{content:[{type:"text",text:"Failed to get test process: "+(0,r.getErrorMessage)(e)}],isError:!0}}},exports.handleTestingProcessCreate=async function(e,n,o,s,i){const a=i.startTimer("tool_execution_time")
|
|
1389
|
+
try{const i={setup:e.setup||[],commands:e.commands.map((e=>({name:e.name,command:e.command,required:!1!==e.required,timeout:e.timeout||300})))}
|
|
1390
|
+
void 0!==e.fingerprint&&(i.fingerprint=e.fingerprint),void 0!==e.poolSize&&(i.poolSize=e.poolSize)
|
|
1391
|
+
const c=await o.proxyPost(`/api/projects/${n.projectId}/test-processes`,{name:e.name,description:e.description,scope:e.scope,config:i,createdBy:"test-architect"}),process=c?.data||c
|
|
1392
|
+
try{const{sessionIdentity:e}=await Promise.resolve().then(t.bind(t,6688)),r=e.getIdentity()
|
|
1393
|
+
r?.sessionId&&process?.id&&await o.proxyPost(`/api/projects/${n.projectId}/session-test-process-associations`,{opencodeSessionId:r.sessionId,testProcessId:process.id})}catch(e){s.warn("Failed to write session-test-process association",{error:(0,r.getErrorMessage)(e)})}return a({success:!0}),{content:[{type:"text",text:`\u2705 Created test process **${process.name}** (${process.id}) with ${i.commands.length} test commands.${e.scope?`\nScope: \`${e.scope}\``:""}\n\nThe process is now active and will run when requests are completed.`}]}}catch(e){return a({success:!1,error:"api_error"}),{content:[{type:"text",text:"Failed to create test process: "+(0,r.getErrorMessage)(e)}],isError:!0}}},exports.handleTestingProcessUpdate=async function(e,t,n,o,s){const i=s.startTimer("tool_execution_time")
|
|
1393
1394
|
try{const r={}
|
|
1394
1395
|
if(void 0!==e.name&&(r.name=e.name),void 0!==e.description&&(r.description=e.description),void 0!==e.scope&&(r.scope=e.scope),void 0!==e.isActive&&(r.isActive=e.isActive),void 0!==e.commands||void 0!==e.setup||void 0!==e.fingerprint||void 0!==e.poolSize){const o=await n.proxyGet(`/api/projects/${t.projectId}/test-processes/${e.processId}`),s=o?.data||o,i=s?.config||{},a={setup:e.setup??i.setup??[],commands:e.commands?e.commands.map((e=>({name:e.name,command:e.command,required:!1!==e.required,timeout:e.timeout||300}))):i.commands??[]}
|
|
1395
1396
|
void 0!==e.fingerprint?a.fingerprint=e.fingerprint:void 0!==i.fingerprint&&(a.fingerprint=i.fingerprint),void 0!==e.poolSize?a.poolSize=e.poolSize:void 0!==i.poolSize&&(a.poolSize=i.poolSize),void 0!==i.verifyAcceptanceCriteria&&(a.verifyAcceptanceCriteria=i.verifyAcceptanceCriteria),r.config=a}const o=await n.proxyPatch(`/api/projects/${t.projectId}/test-processes/${e.processId}`,r),process=o?.data||o
|
|
@@ -3337,7 +3338,7 @@ return m({success:!1,error:"status_transition_failed"}),{content:[{type:"text",t
|
|
|
3337
3338
|
try{C=await f.getRequest(n)}catch(e){const t=(0,s.getErrorMessage)(e)
|
|
3338
3339
|
return m({success:!1,error:"fetch_request_failed"}),{content:[{type:"text",text:"Failed to fetch request criteria: "+t}],isError:!0}}const _=C?.acceptanceCriteria||[]
|
|
3339
3340
|
if(m({success:!0}),w){const e=g.completionMode
|
|
3340
|
-
return{content:[{type:"text",text:`Request ${n} moved to ready_for_review. ${"ask-me"===e?"A notification has been created asking the user to choose how to handle testing (auto-test, interactive-test, or skip).":`Tests are running automatically in the background (${e} mode).
|
|
3341
|
+
return{content:[{type:"text",text:`Request ${n} moved to ready_for_review. ${"ask-me"===e?"A notification has been created asking the user to choose how to handle testing (auto-test, interactive-test, or skip). Stop here; you will receive a report when testing finishes.":`Tests are running automatically in the background (${e} mode). Stop here; a tester subagent will run the test commands and post a report back to you when done.`}\n\n## When the report arrives\n- **Passed** \u2014 call \`merge\` with verification evidence for each acceptance criterion to finalize the request.\n- **Failed** \u2014 fix the issues and call \`complete\` again.\n\nDo NOT call merge, finalize, or any other tool BEFORE the report arrives \u2014 stop here and wait.`}]}}if(I)return{content:[{type:"text",text:`Request ${n} submitted for human review.${g.testsSkipped&&l?`\n\n_Tests skipped: ${l}_`:""}\n\nThe project is set to manual-review \u2014 a person will approve and merge from the UI. Do NOT call merge yourself.`}]}
|
|
3341
3342
|
const E=k?`\n\n_Skipped re-running tests: ${k}._\n`:"",S=y?`\n\n_No test processes are configured for this project; ${g.completionMode} mode is being treated as auto-merge._\n`:""
|
|
3342
3343
|
return 0===_.length?{content:[{type:"text",text:`Request ${n} is ready. ${E}${S}No acceptance criteria defined \u2014 call merge directly.`}]}:{content:[{type:"text",text:`Request ${n} completion started (${v}).${E}${S}\n\n## Next Step: Verify & Merge\n\n${(0,i.formatVerifyInstructions)(_)}\n\nUse the merge operation with your verification evidence to merge to main.`}]}}catch(r){let s="An unexpected error occurred",i=""
|
|
3343
3344
|
r instanceof n.ZodError?(s="Validation error",i=r.errors.map((e=>`${e.path.join(".")}: ${e.message}`)).join(", ")):r instanceof Error?(s=r.message,i=r.stack||""):i=r+"",p.error("Complete request failed",{error:s,details:i})
|
package/dist/index.js
CHANGED
|
@@ -1387,11 +1387,12 @@ const o=process.config||{},s=o.commands||[]
|
|
|
1387
1387
|
let a=`## ${process.name}\n`
|
|
1388
1388
|
process.description&&(a+=process.description+"\n"),process.scope&&(a+=`**Scope:** \`${process.scope}\`\n`),a+=`**Status:** ${process.isActive?"Active":"Inactive"}\n\n`,o.setup?.length&&(a+=`### Setup\n${o.setup.map((e=>`\`\`\`\n${e}\n\`\`\``)).join("\n")}\n\n`),a+=`### Test Commands (${s.length})\n`
|
|
1389
1389
|
for(let e=0;e<s.length;e++){const t=s[e]
|
|
1390
|
-
a+=`\n**${e+1}. ${t.name}**${t.required?" \u2014 REQUIRED":" \u2014 optional"}\n`,a+=` Command: \`${t.command}\`\n`,t.timeout&&(a+=` Timeout: ${t.timeout}s\n`)}return Array.isArray(o.fingerprint)&&o.fingerprint.length>0&&(a+=`\n### Fingerprint Files\n${o.fingerprint.map((e=>`- \`${e}\``)).join("\n")}\n`),"number"==typeof o.poolSize&&(a+=`\n**Pool size:** ${o.poolSize}\n`),i({success:!0}),{content:[{type:"text",text:a}]}}catch(e){return i({success:!1,error:"api_error"}),{content:[{type:"text",text:"Failed to get test process: "+(0,r.getErrorMessage)(e)}],isError:!0}}},exports.handleTestingProcessCreate=async function(e,
|
|
1391
|
-
try{const
|
|
1392
|
-
void 0!==e.fingerprint&&(
|
|
1393
|
-
const
|
|
1394
|
-
|
|
1390
|
+
a+=`\n**${e+1}. ${t.name}**${t.required?" \u2014 REQUIRED":" \u2014 optional"}\n`,a+=` Command: \`${t.command}\`\n`,t.timeout&&(a+=` Timeout: ${t.timeout}s\n`)}return Array.isArray(o.fingerprint)&&o.fingerprint.length>0&&(a+=`\n### Fingerprint Files\n${o.fingerprint.map((e=>`- \`${e}\``)).join("\n")}\n`),"number"==typeof o.poolSize&&(a+=`\n**Pool size:** ${o.poolSize}\n`),i({success:!0}),{content:[{type:"text",text:a}]}}catch(e){return i({success:!1,error:"api_error"}),{content:[{type:"text",text:"Failed to get test process: "+(0,r.getErrorMessage)(e)}],isError:!0}}},exports.handleTestingProcessCreate=async function(e,n,o,s,i){const a=i.startTimer("tool_execution_time")
|
|
1391
|
+
try{const i={setup:e.setup||[],commands:e.commands.map((e=>({name:e.name,command:e.command,required:!1!==e.required,timeout:e.timeout||300})))}
|
|
1392
|
+
void 0!==e.fingerprint&&(i.fingerprint=e.fingerprint),void 0!==e.poolSize&&(i.poolSize=e.poolSize)
|
|
1393
|
+
const c=await o.proxyPost(`/api/projects/${n.projectId}/test-processes`,{name:e.name,description:e.description,scope:e.scope,config:i,createdBy:"test-architect"}),process=c?.data||c
|
|
1394
|
+
try{const{sessionIdentity:e}=await Promise.resolve().then(t.bind(t,6688)),r=e.getIdentity()
|
|
1395
|
+
r?.sessionId&&process?.id&&await o.proxyPost(`/api/projects/${n.projectId}/session-test-process-associations`,{opencodeSessionId:r.sessionId,testProcessId:process.id})}catch(e){s.warn("Failed to write session-test-process association",{error:(0,r.getErrorMessage)(e)})}return a({success:!0}),{content:[{type:"text",text:`\u2705 Created test process **${process.name}** (${process.id}) with ${i.commands.length} test commands.${e.scope?`\nScope: \`${e.scope}\``:""}\n\nThe process is now active and will run when requests are completed.`}]}}catch(e){return a({success:!1,error:"api_error"}),{content:[{type:"text",text:"Failed to create test process: "+(0,r.getErrorMessage)(e)}],isError:!0}}},exports.handleTestingProcessUpdate=async function(e,t,n,o,s){const i=s.startTimer("tool_execution_time")
|
|
1395
1396
|
try{const r={}
|
|
1396
1397
|
if(void 0!==e.name&&(r.name=e.name),void 0!==e.description&&(r.description=e.description),void 0!==e.scope&&(r.scope=e.scope),void 0!==e.isActive&&(r.isActive=e.isActive),void 0!==e.commands||void 0!==e.setup||void 0!==e.fingerprint||void 0!==e.poolSize){const o=await n.proxyGet(`/api/projects/${t.projectId}/test-processes/${e.processId}`),s=o?.data||o,i=s?.config||{},a={setup:e.setup??i.setup??[],commands:e.commands?e.commands.map((e=>({name:e.name,command:e.command,required:!1!==e.required,timeout:e.timeout||300}))):i.commands??[]}
|
|
1397
1398
|
void 0!==e.fingerprint?a.fingerprint=e.fingerprint:void 0!==i.fingerprint&&(a.fingerprint=i.fingerprint),void 0!==e.poolSize?a.poolSize=e.poolSize:void 0!==i.poolSize&&(a.poolSize=i.poolSize),void 0!==i.verifyAcceptanceCriteria&&(a.verifyAcceptanceCriteria=i.verifyAcceptanceCriteria),r.config=a}const o=await n.proxyPatch(`/api/projects/${t.projectId}/test-processes/${e.processId}`,r),process=o?.data||o
|
|
@@ -3346,7 +3347,7 @@ return g({success:!1,error:"status_transition_failed"}),{content:[{type:"text",t
|
|
|
3346
3347
|
try{C=await f.getRequest(n)}catch(e){const t=(0,s.getErrorMessage)(e)
|
|
3347
3348
|
return g({success:!1,error:"fetch_request_failed"}),{content:[{type:"text",text:"Failed to fetch request criteria: "+t}],isError:!0}}const _=C?.acceptanceCriteria||[]
|
|
3348
3349
|
if(g({success:!0}),w){const e=m.completionMode
|
|
3349
|
-
return{content:[{type:"text",text:`Request ${n} moved to ready_for_review. ${"ask-me"===e?"A notification has been created asking the user to choose how to handle testing (auto-test, interactive-test, or skip).":`Tests are running automatically in the background (${e} mode).
|
|
3350
|
+
return{content:[{type:"text",text:`Request ${n} moved to ready_for_review. ${"ask-me"===e?"A notification has been created asking the user to choose how to handle testing (auto-test, interactive-test, or skip). Stop here; you will receive a report when testing finishes.":`Tests are running automatically in the background (${e} mode). Stop here; a tester subagent will run the test commands and post a report back to you when done.`}\n\n## When the report arrives\n- **Passed** \u2014 call \`merge\` with verification evidence for each acceptance criterion to finalize the request.\n- **Failed** \u2014 fix the issues and call \`complete\` again.\n\nDo NOT call merge, finalize, or any other tool BEFORE the report arrives \u2014 stop here and wait.`}]}}if(b)return{content:[{type:"text",text:`Request ${n} submitted for human review.${m.testsSkipped&&l?`\n\n_Tests skipped: ${l}_`:""}\n\nThe project is set to manual-review \u2014 a person will approve and merge from the UI. Do NOT call merge yourself.`}]}
|
|
3350
3351
|
const E=k?`\n\n_Skipped re-running tests: ${k}._\n`:"",S=y?`\n\n_No test processes are configured for this project; ${m.completionMode} mode is being treated as auto-merge._\n`:""
|
|
3351
3352
|
return 0===_.length?{content:[{type:"text",text:`Request ${n} is ready. ${E}${S}No acceptance criteria defined \u2014 call merge directly.`}]}:{content:[{type:"text",text:`Request ${n} completion started (${v}).${E}${S}\n\n## Next Step: Verify & Merge\n\n${(0,i.formatVerifyInstructions)(_)}\n\nUse the merge operation with your verification evidence to merge to main.`}]}}catch(r){let s="An unexpected error occurred",i=""
|
|
3352
3353
|
r instanceof n.ZodError?(s="Validation error",i=r.errors.map((e=>`${e.path.join(".")}: ${e.message}`)).join(", ")):r instanceof Error?(s=r.message,i=r.stack||""):i=r+"",p.error("Complete request failed",{error:s,details:i})
|
package/dist/mcp-direct.js
CHANGED
|
@@ -1284,11 +1284,12 @@ const s=process.config||{},o=s.commands||[]
|
|
|
1284
1284
|
let a=`## ${process.name}\n`
|
|
1285
1285
|
process.description&&(a+=process.description+"\n"),process.scope&&(a+=`**Scope:** \`${process.scope}\`\n`),a+=`**Status:** ${process.isActive?"Active":"Inactive"}\n\n`,s.setup?.length&&(a+=`### Setup\n${s.setup.map((e=>`\`\`\`\n${e}\n\`\`\``)).join("\n")}\n\n`),a+=`### Test Commands (${o.length})\n`
|
|
1286
1286
|
for(let e=0;e<o.length;e++){const t=o[e]
|
|
1287
|
-
a+=`\n**${e+1}. ${t.name}**${t.required?" \u2014 REQUIRED":" \u2014 optional"}\n`,a+=` Command: \`${t.command}\`\n`,t.timeout&&(a+=` Timeout: ${t.timeout}s\n`)}return Array.isArray(s.fingerprint)&&s.fingerprint.length>0&&(a+=`\n### Fingerprint Files\n${s.fingerprint.map((e=>`- \`${e}\``)).join("\n")}\n`),"number"==typeof s.poolSize&&(a+=`\n**Pool size:** ${s.poolSize}\n`),i({success:!0}),{content:[{type:"text",text:a}]}}catch(e){return i({success:!1,error:"api_error"}),{content:[{type:"text",text:"Failed to get test process: "+(0,r.getErrorMessage)(e)}],isError:!0}}},exports.handleTestingProcessCreate=async function(e,
|
|
1288
|
-
try{const
|
|
1289
|
-
void 0!==e.fingerprint&&(
|
|
1290
|
-
const
|
|
1291
|
-
|
|
1287
|
+
a+=`\n**${e+1}. ${t.name}**${t.required?" \u2014 REQUIRED":" \u2014 optional"}\n`,a+=` Command: \`${t.command}\`\n`,t.timeout&&(a+=` Timeout: ${t.timeout}s\n`)}return Array.isArray(s.fingerprint)&&s.fingerprint.length>0&&(a+=`\n### Fingerprint Files\n${s.fingerprint.map((e=>`- \`${e}\``)).join("\n")}\n`),"number"==typeof s.poolSize&&(a+=`\n**Pool size:** ${s.poolSize}\n`),i({success:!0}),{content:[{type:"text",text:a}]}}catch(e){return i({success:!1,error:"api_error"}),{content:[{type:"text",text:"Failed to get test process: "+(0,r.getErrorMessage)(e)}],isError:!0}}},exports.handleTestingProcessCreate=async function(e,n,s,o,i){const a=i.startTimer("tool_execution_time")
|
|
1288
|
+
try{const i={setup:e.setup||[],commands:e.commands.map((e=>({name:e.name,command:e.command,required:!1!==e.required,timeout:e.timeout||300})))}
|
|
1289
|
+
void 0!==e.fingerprint&&(i.fingerprint=e.fingerprint),void 0!==e.poolSize&&(i.poolSize=e.poolSize)
|
|
1290
|
+
const c=await s.proxyPost(`/api/projects/${n.projectId}/test-processes`,{name:e.name,description:e.description,scope:e.scope,config:i,createdBy:"test-architect"}),process=c?.data||c
|
|
1291
|
+
try{const{sessionIdentity:e}=await Promise.resolve().then(t.bind(t,6688)),r=e.getIdentity()
|
|
1292
|
+
r?.sessionId&&process?.id&&await s.proxyPost(`/api/projects/${n.projectId}/session-test-process-associations`,{opencodeSessionId:r.sessionId,testProcessId:process.id})}catch(e){o.warn("Failed to write session-test-process association",{error:(0,r.getErrorMessage)(e)})}return a({success:!0}),{content:[{type:"text",text:`\u2705 Created test process **${process.name}** (${process.id}) with ${i.commands.length} test commands.${e.scope?`\nScope: \`${e.scope}\``:""}\n\nThe process is now active and will run when requests are completed.`}]}}catch(e){return a({success:!1,error:"api_error"}),{content:[{type:"text",text:"Failed to create test process: "+(0,r.getErrorMessage)(e)}],isError:!0}}},exports.handleTestingProcessUpdate=async function(e,t,n,s,o){const i=o.startTimer("tool_execution_time")
|
|
1292
1293
|
try{const r={}
|
|
1293
1294
|
if(void 0!==e.name&&(r.name=e.name),void 0!==e.description&&(r.description=e.description),void 0!==e.scope&&(r.scope=e.scope),void 0!==e.isActive&&(r.isActive=e.isActive),void 0!==e.commands||void 0!==e.setup||void 0!==e.fingerprint||void 0!==e.poolSize){const s=await n.proxyGet(`/api/projects/${t.projectId}/test-processes/${e.processId}`),o=s?.data||s,i=o?.config||{},a={setup:e.setup??i.setup??[],commands:e.commands?e.commands.map((e=>({name:e.name,command:e.command,required:!1!==e.required,timeout:e.timeout||300}))):i.commands??[]}
|
|
1294
1295
|
void 0!==e.fingerprint?a.fingerprint=e.fingerprint:void 0!==i.fingerprint&&(a.fingerprint=i.fingerprint),void 0!==e.poolSize?a.poolSize=e.poolSize:void 0!==i.poolSize&&(a.poolSize=i.poolSize),void 0!==i.verifyAcceptanceCriteria&&(a.verifyAcceptanceCriteria=i.verifyAcceptanceCriteria),r.config=a}const s=await n.proxyPatch(`/api/projects/${t.projectId}/test-processes/${e.processId}`,r),process=s?.data||s
|
|
@@ -3113,7 +3114,7 @@ return m({success:!1,error:"status_transition_failed"}),{content:[{type:"text",t
|
|
|
3113
3114
|
try{C=await f.getRequest(n)}catch(e){const t=(0,o.getErrorMessage)(e)
|
|
3114
3115
|
return m({success:!1,error:"fetch_request_failed"}),{content:[{type:"text",text:"Failed to fetch request criteria: "+t}],isError:!0}}const _=C?.acceptanceCriteria||[]
|
|
3115
3116
|
if(m({success:!0}),w){const e=g.completionMode
|
|
3116
|
-
return{content:[{type:"text",text:`Request ${n} moved to ready_for_review. ${"ask-me"===e?"A notification has been created asking the user to choose how to handle testing (auto-test, interactive-test, or skip).":`Tests are running automatically in the background (${e} mode).
|
|
3117
|
+
return{content:[{type:"text",text:`Request ${n} moved to ready_for_review. ${"ask-me"===e?"A notification has been created asking the user to choose how to handle testing (auto-test, interactive-test, or skip). Stop here; you will receive a report when testing finishes.":`Tests are running automatically in the background (${e} mode). Stop here; a tester subagent will run the test commands and post a report back to you when done.`}\n\n## When the report arrives\n- **Passed** \u2014 call \`merge\` with verification evidence for each acceptance criterion to finalize the request.\n- **Failed** \u2014 fix the issues and call \`complete\` again.\n\nDo NOT call merge, finalize, or any other tool BEFORE the report arrives \u2014 stop here and wait.`}]}}if(b)return{content:[{type:"text",text:`Request ${n} submitted for human review.${g.testsSkipped&&l?`\n\n_Tests skipped: ${l}_`:""}\n\nThe project is set to manual-review \u2014 a person will approve and merge from the UI. Do NOT call merge yourself.`}]}
|
|
3117
3118
|
const E=k?`\n\n_Skipped re-running tests: ${k}._\n`:"",S=y?`\n\n_No test processes are configured for this project; ${g.completionMode} mode is being treated as auto-merge._\n`:""
|
|
3118
3119
|
return 0===_.length?{content:[{type:"text",text:`Request ${n} is ready. ${E}${S}No acceptance criteria defined \u2014 call merge directly.`}]}:{content:[{type:"text",text:`Request ${n} completion started (${v}).${E}${S}\n\n## Next Step: Verify & Merge\n\n${(0,i.formatVerifyInstructions)(_)}\n\nUse the merge operation with your verification evidence to merge to main.`}]}}catch(r){let o="An unexpected error occurred",i=""
|
|
3119
3120
|
r instanceof n.ZodError?(o="Validation error",i=r.errors.map((e=>`${e.path.join(".")}: ${e.message}`)).join(", ")):r instanceof Error?(o=r.message,i=r.stack||""):i=r+"",p.error("Complete request failed",{error:o,details:i})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "alchemylab",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.13",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "AlchemyLab - agentic coding IDEm",
|
|
6
6
|
"author": "Testfire Software",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"ws": "^8.14.2",
|
|
50
50
|
"zod": "^3.22.4",
|
|
51
51
|
"zod-to-json-schema": "^3.22.3",
|
|
52
|
-
"alchemylab-opencode": "^1.0.
|
|
52
|
+
"alchemylab-opencode": "^1.0.17"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@types/chai": "^4.3.5",
|