alchemylab 1.0.0
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/LICENSE.txt +33 -0
- package/dist/478.js +109 -0
- package/dist/60.js +14 -0
- package/dist/800.js +23 -0
- package/dist/939.js +13 -0
- package/dist/994.js +126 -0
- package/dist/cli.js +899 -0
- package/dist/http-server.js +5659 -0
- package/dist/index.js +5731 -0
- package/dist/mcp-cli-bridge.js +88 -0
- package/dist/mcp-direct.js +5436 -0
- package/package.json +67 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,899 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
(()=>{"use strict"
|
|
3
|
+
function e(t){var r=n[t]
|
|
4
|
+
if(void 0!==r)return r.exports
|
|
5
|
+
var module=n[t]={exports:{}}
|
|
6
|
+
return s[t].call(module.exports,module,module.exports,e),module.exports}var t,r,o,s={848(e,exports,t){Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleFinalizeRequestAction=async function(e,t){const{requestId:s}=e,{logger:n,apiClient:i,projectPath:a,projectId:c}=t
|
|
7
|
+
if(!s)return{success:!1,message:"requestId is required",error:"Missing requestId in payload"}
|
|
8
|
+
n.info("Starting finalize-request action",{requestId:s})
|
|
9
|
+
try{const e=await i.post(`/api/projects/${c}/requests/${s}/validate-completion`,{source:"ui"})
|
|
10
|
+
if(!e.success||!e.data)return{success:!1,message:"Server validation failed: "+(e.data?.reason||"Unknown error"),error:"Validation failed"}
|
|
11
|
+
const t=e.data
|
|
12
|
+
if(!t.canComplete){let e="Cannot finalize request: "+t.reason
|
|
13
|
+
return t.blockedByTasks?.length>0&&(e+="\n\nTasks that must be completed first:\n",t.blockedByTasks.forEach(((t,r)=>{e+=`${r+1}. ${t.taskId}: ${t.title} (${t.status})\n`}))),{success:!1,message:e,error:t.reason}}const l=await i.get(`/api/projects/${c}/requests/${s}`),d=l?.data,u="requests/"+s,g=(0,r.simpleGit)(a),h=await g.status(),p=await g.branch(),f=p.all.includes("main")?"main":"master"
|
|
14
|
+
if(h.current!==f&&await g.checkout(f),!p.all.includes(u))return{success:!1,message:`Branch '${u}' not found. The request may not have any file changes to merge.`,error:"Branch not found"}
|
|
15
|
+
let m,y=!1
|
|
16
|
+
try{const e=d?.title?`Merge ${u}-${d.title}`:"Merge "+u
|
|
17
|
+
await g.merge([u,"--no-ff","-m",e])
|
|
18
|
+
const t=await g.log({maxCount:1})
|
|
19
|
+
m=t.latest?.hash,n.info("Git merge completed",{requestId:s,mergeCommit:m?.substring(0,8)})}catch(e){const t=(0,o.getErrorMessage)(e)
|
|
20
|
+
if(t.toLowerCase().includes("conflict")){try{await g.merge(["--abort"])}catch(e){n.error("CRITICAL: git merge --abort failed \u2014 repo may be in inconsistent state",{requestId:s,error:e instanceof Error?e.message:e+""})}return{success:!1,message:`Merge conflicts detected for request ${s}. Please resolve conflicts before finalizing.`,error:"Merge conflicts",data:{hasConflicts:!0}}}return{success:!1,message:"Merge failed: "+t,error:t}}try{await g.deleteLocalBranch(u,!0),y=!0}catch{}try{await i.post(`/api/projects/${c}/requests/${s}/finalize-completion`,{summary:"Finalized request "+s,acceptanceEvidence:[{evidence:"Finalized via GUI after review"}],mergeResult:{mergeCommitHash:m||"unknown",cleanedUp:y,method:"action-server-merge"}})}catch(e){const t=(0,o.getErrorMessage)(e)
|
|
21
|
+
return n.error("CRITICAL: Merge succeeded but server finalization failed",{requestId:s,mergeCommitHash:m,error:t}),{success:!1,message:`Code was merged (commit: ${m?.substring(0,8)}) but server finalization failed: ${t}`,error:t,data:{mergeCommitHash:m,partialSuccess:!0}}}return{success:!0,message:`Successfully finalized request ${s}: ${d?.title||s}`,data:{mergeCommitHash:m,branchDeleted:y}}}catch(e){const t=(0,o.getErrorMessage)(e)
|
|
22
|
+
return n.error("finalize-request action failed",{requestId:s,error:t}),{success:!1,message:"Failed to finalize request: "+t,error:t}}}
|
|
23
|
+
const r=t(3807),o=t(8824)},857(module){module.exports=require("os")},1152(e,exports){function t(e=!0){return new r(e)}Object.defineProperty(exports,"__esModule",{value:!0}),exports.createMetricsCollector=t,exports.createNullMetricsCollector=function(){return t(!1)}
|
|
24
|
+
class r{constructor(e=!0){this.enabled=e,this.counters=new Map,this.gauges=new Map,this.histograms=new Map}getKey(e,t){const r=Object.entries(t).map((([e,t])=>`${e}="${t+""}"`)).join(",")
|
|
25
|
+
return r?`${e}{${r}}`:e}incrementCounter(e,t=1,r={}){if(!this.enabled)return
|
|
26
|
+
const o=this.getKey(e,r),s=this.counters.get(o)||0
|
|
27
|
+
this.counters.set(o,s+t)}setGauge(e,t,r={}){if(!this.enabled)return
|
|
28
|
+
const o=this.getKey(e,r)
|
|
29
|
+
this.gauges.set(o,t)}observeHistogram(e,t,r={}){if(!this.enabled)return
|
|
30
|
+
const o=this.getKey(e,r),s=this.histograms.get(o)||[]
|
|
31
|
+
s.push(t),this.histograms.set(o,s)}recordValue(e,t,r={}){this.observeHistogram(e,t,r)}startTimer(e){if(!this.enabled)return()=>{}
|
|
32
|
+
const t=Date.now()
|
|
33
|
+
return(r={})=>{const o=Date.now()-t
|
|
34
|
+
this.observeHistogram(e,o,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){Object.defineProperty(exports,"__esModule",{value:!0}),exports.UnifiedLauncher=void 0
|
|
35
|
+
const r=t(5317),o=t(8824)
|
|
36
|
+
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:s}=e,{installPath:n,nodePath:i}=this.installContext
|
|
37
|
+
this.logger.info("UnifiedLauncher: Starting launch",{projectPath:t,installPath:n})
|
|
38
|
+
try{const o=await this.configurer.configureOpenCode(n,t,i,r,s)
|
|
39
|
+
return o.success?(this.logger.info("UnifiedLauncher: Configuration complete",{configPath:o.configPath}),this.launchBackground(e,o)):(this.logger.error("UnifiedLauncher: Configuration failed",{error:o.error}),{success:!1,error:o.error})}catch(e){return this.logger.error("UnifiedLauncher: Launch failed",{error:(0,o.getErrorMessage)(e)}),{success:!1,error:(0,o.getErrorMessage)(e)}}}async launchBackground(e,t){const{projectPath:o,envVars:s}=e
|
|
40
|
+
try{(0,r.execFileSync)("which",["opencode"],{stdio:"pipe"})}catch{return{success:!1,error:"OpenCode not found in PATH."}}return this.logger.info("UnifiedLauncher: Launching opencode serve in background",{command:"opencode serve",cwd:o}),new Promise((e=>{const n=(0,r.spawn)("opencode",["serve"],{stdio:["ignore","pipe","pipe"],cwd:o,detached:!0,env:{...process.env,...s}})
|
|
41
|
+
let i="",a=!1
|
|
42
|
+
const c=r=>{i+=r
|
|
43
|
+
const o=i.match(/listening on http:\/\/[^:]+:(\d+)/)
|
|
44
|
+
if(o&&!a){const r=parseInt(o[1],10)
|
|
45
|
+
a=!0,this.logger.info("UnifiedLauncher: Discovered opencode serve port",{pid:n.pid,port:r}),n.unref(),e({success:!0,pid:n.pid,port:r,configPath:t.configPath})}}
|
|
46
|
+
n.stdout?.on("data",(e=>c(e.toString()))),n.stderr?.on("data",(e=>c(e.toString()))),setTimeout((()=>{a||(a=!0,this.logger.warn("UnifiedLauncher: Timeout waiting for opencode serve port",{outputBuffer:i.slice(0,500)}),n.unref(),e({success:!0,pid:n.pid,configPath:t.configPath,error:"Could not determine port from opencode serve output"}))}),1e4),n.on("error",(t=>{a||(a=!0,this.logger.error("UnifiedLauncher: opencode serve failed to start",{error:t.message}),e({success:!1,error:"Failed to start opencode serve: "+t.message}))})),n.on("exit",(t=>{a||(a=!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){var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
|
|
47
|
+
Object.defineProperty(exports,"__esModule",{value:!0}),exports.TokenManager=void 0
|
|
48
|
+
const o=r(t(6928)),s=r(t(1943)),n=r(t(6982)),i=r(t(857)),a=t(4139),c=t(9067),l=t(8824),d="alchemy-lab"
|
|
49
|
+
exports.TokenManager=class{constructor(e,t,r={}){this.projectId=null,this.encryptionKey=null,this.pendingAuthCheck=!1,this.tempAuthCode=null,this.CACHE_TTL_MS=6e4,this.TOKEN_CACHE={manualToken:null,encryptedToken:null,hasValidToken:null},this.initPromise=null,this.initialized=!1,this.logger=e.child({component:"token-manager"}),this.remoteServerUrl=r.remoteServerUrl||"https://api.alchemylabcode.com",this.allowInteractiveAuth=!1!==r.allowInteractiveAuth,void 0!==r.cacheTtlMs&&r.cacheTtlMs>0&&(this.CACHE_TTL_MS=r.cacheTtlMs),this.serverInstallDir=this.getModuleDir(),this.tokenDirPath=o.default.join(this.serverInstallDir,a.TOKEN_DIR_NAME),this.authFlow=new c.ExternalAuthFlow(e,this.remoteServerUrl),this.logger.info("TokenManager initialized",{workingDir:t,serverInstallDir:this.serverInstallDir,tokenDirPath:this.tokenDirPath,allowInteractiveAuth:this.allowInteractiveAuth,cacheTtlMs:this.CACHE_TTL_MS}),this.generateEncryptionKey()}getModuleDir(){const e=__dirname||process.cwd(),t=i.default.homedir()
|
|
50
|
+
return e.includes("node_modules")?o.default.join(t,".alchemy-mcp"):o.default.join(t,".alchemy-mcp-local")}async initialize(){return this.initPromise||(this.initPromise=this.ensureTokenDirectoryExists().then((()=>{this.initialized=!0,this.logger.info("TokenManager initialization completed")})).catch((e=>{throw this.logger.error("TokenManager initialization failed",{error:e instanceof Error?e.stack:e+""}),Error("TokenManager initialization failed")}))),this.initPromise}getStableMachineId(){try{const e=t(857),r=o.default.join(this.serverInstallDir,".machine-id")
|
|
51
|
+
try{const e=t(9896).readFileSync(r,"utf8").trim()
|
|
52
|
+
if(e&&e.length>0)return e}catch{}const s=e.platform(),i=e.arch(),a=e.homedir(),c=e.cpus(),l=`${s}-${i}-${c&&c.length>0?c[0].model:"unknown"}-${a}`,d=n.default.createHash("sha256").update(l).digest("hex")
|
|
53
|
+
try{t(9896).writeFileSync(r,d,"utf8"),this.logger.debug("Created new machine ID file",{path:r})}catch(e){this.logger.warn("Could not persist machine ID, will regenerate on restart",{error:e instanceof Error?e.message:e+""})}return d}catch(e){return this.logger.error("Error getting stable machine ID",{error:(0,l.getErrorMessage)(e)}),n.default.randomBytes(32).toString("hex")}}generateEncryptionKey(){try{const e=t(857).platform(),r=this.getStableMachineId(),o=`${d}-${e}-${r}`
|
|
54
|
+
this.encryptionKey=n.default.createHash("sha256").update(o).digest(),this.logger.debug("Generated encryption key using stable machine ID")}catch(e){this.logger.error("Error generating encryption key",{error:(0,l.getErrorMessage)(e)}),this.encryptionKey=Buffer.from(d.repeat(4),"utf8")}}generateLegacyEncryptionKey(){try{const e=t(857).platform(),r=t(857).hostname(),o=`${d}-${e}-${r}`
|
|
55
|
+
return n.default.createHash("sha256").update(o).digest()}catch(e){return this.logger.error("Error generating legacy encryption key",{error:(0,l.getErrorMessage)(e)}),Buffer.from(d.repeat(4),"utf8")}}async ensureTokenDirectoryExists(){try{await s.default.mkdir(this.tokenDirPath,{recursive:!0}),this.logger.debug("Token directory ensured exists",{path:this.tokenDirPath})}catch(e){throw this.logger.error("Error creating token directory",{path:this.tokenDirPath,error:(0,l.getErrorMessage)(e)}),Error("Error creating token directory: "+this.tokenDirPath)}}setProjectId(e){this.projectId=e,this.logger.info("Set project ID for token manager",{projectId:e})}getAccountKey(){return"default-user-"+n.default.createHash("sha256").update(this.remoteServerUrl).digest("hex").substring(0,8)}getTokenFilePath(e){const t=e.replace(/[^a-zA-Z0-9]/g,"_")
|
|
56
|
+
return o.default.join(this.tokenDirPath,t+".token")}invalidateCache(){this.logger.debug("Invalidating all token caches"),this.TOKEN_CACHE.manualToken=null,this.TOKEN_CACHE.encryptedToken=null,this.TOKEN_CACHE.hasValidToken=null}async checkForManualToken(){const e=Date.now(),t=o.default.join(this.tokenDirPath,"manual-token.json")
|
|
57
|
+
if(this.TOKEN_CACHE.manualToken&&e<this.TOKEN_CACHE.manualToken.expiresAt){const t=this.TOKEN_CACHE.manualToken
|
|
58
|
+
return this.logger.debug("Using cached manual token check result",{ageSecs:Math.round((e-t.timestamp)/1e3),exists:t.exists,hasToken:null!==t.value}),t.exists&&t.value?t.value:null}this.TOKEN_CACHE.manualToken&&e-(this.TOKEN_CACHE.manualToken.lastCheck||0)<=2*this.CACHE_TTL_MS||this.logger.debug("Checking for manual token file",{tokenFilePath:t})
|
|
59
|
+
try{let r=!1,o=null
|
|
60
|
+
try{await s.default.stat(t),r=!0,this.TOKEN_CACHE.manualToken&&e-(this.TOKEN_CACHE.manualToken.lastCheck||0)<=2*this.CACHE_TTL_MS||this.logger.debug("Manual token file exists")}catch{this.TOKEN_CACHE.manualToken&&e-(this.TOKEN_CACHE.manualToken.lastCheck||0)<=2*this.CACHE_TTL_MS||this.logger.debug("Manual token file does not exist")}if(r)try{const r=await s.default.readFile(t,"utf8"),n=JSON.parse(r)
|
|
61
|
+
new Date(n.expiresAt)>new Date?(o=n,this.TOKEN_CACHE.manualToken&&e-(this.TOKEN_CACHE.manualToken.lastCheck||0)<=2*this.CACHE_TTL_MS||this.logger.debug("Valid manual token found",{username:o.username,expiresAt:o.expiresAt})):this.logger.debug("Manual token has expired",{expiresAt:n.expiresAt})}catch(e){this.logger.error("Error parsing manual token file",{error:e instanceof Error?e.message:e+""})}return this.TOKEN_CACHE.manualToken={value:o,timestamp:e,expiresAt:e+this.CACHE_TTL_MS,lastCheck:e,exists:r},o}catch(e){return this.logger.error("Error checking for manual token",{error:(0,l.getErrorMessage)(e)}),null}}encrypt(e){if(!this.encryptionKey)throw Error("Encryption key not available")
|
|
62
|
+
const t=n.default.randomBytes(16),r=n.default.createCipheriv("aes-256-cbc",this.encryptionKey,t)
|
|
63
|
+
let o=r.update(e,"utf8","hex")
|
|
64
|
+
return o+=r.final("hex"),`${t.toString("hex")}:${o}`}decrypt(e){if(!this.encryptionKey)throw Error("Encryption key not available")
|
|
65
|
+
try{return this.decryptWithKey(e,this.encryptionKey)}catch(t){this.logger.warn("Failed to decrypt with current key, trying legacy hostname-based key",{error:t instanceof Error?t.message:t+""})
|
|
66
|
+
try{const t=this.generateLegacyEncryptionKey(),r=this.decryptWithKey(e,t)
|
|
67
|
+
return this.logger.info("Successfully decrypted with legacy key, will re-encrypt with new key on next store"),r}catch(e){this.logger.error("Failed to decrypt with both current and legacy keys",{primaryError:t instanceof Error?t.message:t+"",legacyError:e instanceof Error?e.message:e+""})
|
|
68
|
+
const r=this.getAccountKey(),o=this.getTokenFilePath(r)
|
|
69
|
+
try{s.default.rename(o,o+".corrupt").catch((()=>this.logger.warn("Could not backup corrupted token file")))}catch(e){this.logger.error("Error backing up corrupted file: "+e)}throw Error("Could not decrypt token file with current or legacy key - it may be corrupted")}}}decryptWithKey(e,t){try{const[r,o]=e.split(":")
|
|
70
|
+
if(!r||!o)throw Error("Invalid encrypted data format")
|
|
71
|
+
const s=Buffer.from(r,"hex"),i=n.default.createDecipheriv("aes-256-cbc",t,s)
|
|
72
|
+
let a=i.update(o,"hex","utf8")
|
|
73
|
+
return a+=i.final("utf8"),a}catch(e){throw e}}async storeToken(e){!this.initPromise&&this.initialized?await this.initPromise:this.initialized||await this.initialize()
|
|
74
|
+
try{const t=this.getAccountKey(),r=this.getTokenFilePath(t),o=JSON.stringify(e),n=this.encrypt(o)
|
|
75
|
+
await s.default.writeFile(r,n,"utf8"),this.logger.info("Token stored securely",{username:e.username,expiresAt:e.expiresAt}),this.invalidateCache()}catch(e){throw this.logger.error("Error storing token",{error:(0,l.getErrorMessage)(e)}),Error("Failed to store token securely")}}async getToken(){!this.initPromise&&this.initialized?await this.initPromise:this.initialized||await this.initialize()
|
|
76
|
+
try{if(this.hasPendingAuth()&&this.tempAuthCode){const e=await this.checkAuthStatus(this.tempAuthCode)
|
|
77
|
+
if(e)return e}const e=await this.checkForManualToken()
|
|
78
|
+
if(e)return e
|
|
79
|
+
const t=Date.now()
|
|
80
|
+
if(this.TOKEN_CACHE.encryptedToken&&t<this.TOKEN_CACHE.encryptedToken.expiresAt){const e=this.TOKEN_CACHE.encryptedToken
|
|
81
|
+
return this.logger.debug("Using cached encrypted token",{ageSecs:Math.round((t-e.timestamp)/1e3),hasToken:null!==e.value}),e.value}const r=this.getAccountKey(),o=this.getTokenFilePath(r)
|
|
82
|
+
try{await s.default.stat(o)}catch{return this.logger.debug("No encrypted token file found"),this.TOKEN_CACHE.encryptedToken={value:null,timestamp:t,expiresAt:t+this.CACHE_TTL_MS,lastCheck:t,exists:!1},this.allowInteractiveAuth&&!this.hasPendingAuth()?(this.logger.debug("No stored token found, starting authentication flow"),this.startAuthenticationFlow().catch((e=>{this.logger.error("Failed to start authentication flow",{error:e instanceof Error?e.message:e+""})}))):this.logger.debug("Interactive authentication disabled or already pending"),null}try{const e=await s.default.readFile(o,"utf8"),t=this.decrypt(e),r=JSON.parse(t),n=new Date(r.expiresAt),i=new Date,a=n.getTime()-i.getTime(),c=a/36e5
|
|
83
|
+
return this.logger.info("Token expiration check details",{tokenExpiresAt:r.expiresAt,currentTime:i.toISOString(),isExpired:i>=n,timeUntilExpiryMs:a,timeUntilExpiryHours:Math.round(100*c)/100,username:r.username,systemTimezone:Intl.DateTimeFormat().resolvedOptions().timeZone}),n>i?(this.logger.info("Retrieved valid token from storage",{username:r.username,expiresAt:r.expiresAt}),this.TOKEN_CACHE.encryptedToken={value:r,timestamp:Date.now(),expiresAt:Date.now()+this.CACHE_TTL_MS,lastCheck:Date.now(),exists:!0},r):(this.logger.info("Stored token has expired",{expiresAt:r.expiresAt,expiredByMs:i.getTime()-n.getTime(),expiredByHours:Math.round((i.getTime()-n.getTime())/36e5*100)/100}),this.TOKEN_CACHE.encryptedToken={value:null,timestamp:Date.now(),expiresAt:Date.now()+this.CACHE_TTL_MS,lastCheck:Date.now(),exists:!1},null)}catch(e){this.logger.error("Error reading/decrypting token file",{error:(0,l.getErrorMessage)(e),path:o})
|
|
84
|
+
try{const e=o+".backup"
|
|
85
|
+
await s.default.rename(o,e),this.logger.info("Moved corrupted token file to backup",{from:o,to:e})}catch(e){this.logger.error("Error creating backup of corrupted token file",{error:e instanceof Error?e.message:e+""})}return this.TOKEN_CACHE.encryptedToken={value:null,timestamp:Date.now(),expiresAt:Date.now()+this.CACHE_TTL_MS,lastCheck:Date.now(),exists:!1},null}}catch(e){return this.logger.error("Error retrieving token",{error:(0,l.getErrorMessage)(e)}),null}}async removeToken(){!this.initPromise&&this.initialized?await this.initPromise:this.initialized||await this.initialize()
|
|
86
|
+
try{const e=this.getAccountKey(),t=this.getTokenFilePath(e)
|
|
87
|
+
try{await s.default.stat(t)}catch{return this.logger.info("No token file to remove"),!1}return await s.default.unlink(t),this.logger.info("Token removed from secure storage"),this.invalidateCache(),!0}catch(e){return this.logger.error("Error removing token",{error:(0,l.getErrorMessage)(e)}),!1}}async hasValidToken(){const e=Date.now()
|
|
88
|
+
if(this.TOKEN_CACHE.hasValidToken&&e<this.TOKEN_CACHE.hasValidToken.expiresAt){const t=this.TOKEN_CACHE.hasValidToken
|
|
89
|
+
return this.logger.debug("Using cached hasValidToken result",{ageSecs:Math.round((e-t.timestamp)/1e3),hasToken:t.value}),t.value}this.logger.debug("Checking if valid token exists (cache miss or expired)")
|
|
90
|
+
const t=null!==await this.getToken()
|
|
91
|
+
return this.TOKEN_CACHE.hasValidToken={value:t,timestamp:e,expiresAt:e+this.CACHE_TTL_MS,lastCheck:e,exists:t},t}getTokenDirectoryPath(){return this.tokenDirPath}getManualTokenFilePath(){return o.default.join(this.tokenDirPath,"manual-token.json")}logTokenLocationInstructions(){const e=this.getManualTokenFilePath()
|
|
92
|
+
console.log("========================================"),console.log("To use MCP server, you need to authenticate."),console.log("Token file location: "+e),console.log("You can either:"),console.log('\n1. Use the authenticate tool (recommended)\n2. Manually place a token file at the above location\n3. Set up automatic authentication flow\n\nFor manual token placement, create a JSON file with:\n{\n "token": "your_token_here",\n "userId": "your_user_id",\n "username": "your_username", \n "role": "user",\n "expiresAt": "2025-12-31T23:59:59.000Z"\n}\n'),console.log("For more information, see the authentication documentation."),console.log("========================================")}async startAuthenticationFlow(){this.pendingAuthCheck=!0
|
|
93
|
+
try{const{tempCode:e,authUrl:t}=await this.authFlow.startAuthFlow()
|
|
94
|
+
return this.tempAuthCode=e,this.logger.info("Started external authentication flow",{tempCode:e,authUrl:t}),this.authFlow.startPolling(e,(async t=>{this.logger.info("Authentication flow completed",{tempCode:e.substring(0,4)+"***",username:t.username}),await this.storeToken(t),this.pendingAuthCheck=!1,this.tempAuthCode=null,this.invalidateCache(),console.log("Authentication successful!"),console.log(`Welcome, ${t.username}!`),console.log("You can now use all MCP tools and features."),console.log("========================================")}),(()=>{this.logger.info("Authentication flow timed out",{tempCode:e.substring(0,4)+"***"}),this.pendingAuthCheck=!1,this.tempAuthCode=null,console.log("Authentication flow timed out."),console.log("Please try again or manually place a token file."),console.log("Token file location:"),console.log(""+this.getManualTokenFilePath()),console.log("========================================")}),(t=>{this.logger.error("Authentication flow error",{tempCode:e.substring(0,4)+"***",error:t}),this.pendingAuthCheck=!1,this.tempAuthCode=null,console.log("Authentication flow error."),console.log("Error: "+t),console.log("Please try again or manually place a token file."),console.log(""+this.getManualTokenFilePath()),console.log("========================================")})),{tempCode:e,authUrl:t}}catch(e){throw this.logger.error("Error starting authentication flow",{error:(0,l.getErrorMessage)(e)}),this.pendingAuthCheck=!1,this.tempAuthCode=null,e}}hasPendingAuth(){return this.pendingAuthCheck||!!this.tempAuthCode}getAuthStatus(){return this.hasPendingAuth()?{hasToken:!1,isPendingAuth:!0,tempCode:this.tempAuthCode,authUrl:this.authFlow.getAuthUrl(this.tempAuthCode)}:{hasToken:!1,isPendingAuth:!1}}async checkAuthStatus(e){if(!e)return null
|
|
95
|
+
try{const t=this.authFlow.getPollingStatus()
|
|
96
|
+
if(t.status===c.PollingStatus.AUTHENTICATED&&t.tokenInfo)return this.logger.info("Authentication polling has token available",{tempCode:e.substring(0,4)+"***",username:t.tokenInfo.username}),t.tokenInfo
|
|
97
|
+
if(t.status===c.PollingStatus.ERROR||t.status===c.PollingStatus.TIMEOUT)return this.pendingAuthCheck=!1,this.tempAuthCode=null,null
|
|
98
|
+
const r=this.authFlow.getPollingUrl(e),o=await fetch(r)
|
|
99
|
+
if(o.ok){const e=await o.json()
|
|
100
|
+
if(e.authenticated&&e.token){const t=this.authFlow.parseTokenResponse(e)
|
|
101
|
+
if(t)return await this.storeToken(t),this.pendingAuthCheck=!1,this.tempAuthCode=null,this.invalidateCache(),t}}return null}catch(e){return this.logger.error("Error checking authentication status",{error:(0,l.getErrorMessage)(e)}),this.pendingAuthCheck=!1,this.tempAuthCode=null,null}}async validateTokenWithRemoteServer(){this.logger.debug("Validating stored token with remote server")
|
|
102
|
+
try{const e=await this.getToken()
|
|
103
|
+
if(!e)return this.logger.debug("No token available for validation"),!1
|
|
104
|
+
const t=this.remoteServerUrl+"/auth/validate"
|
|
105
|
+
this.logger.debug("Making token validation request",{validationUrl:t})
|
|
106
|
+
const r=await fetch(t,{method:"GET",headers:{Authorization:"Bearer "+e.token,"Content-Type":"application/json"},signal:AbortSignal.timeout(1e4)})
|
|
107
|
+
if(r.ok){const e=await r.json()
|
|
108
|
+
return this.logger.info("Token validation successful",{status:e.status||"unknown",username:e.username||"unknown"}),!0}return this.logger.info("Token validation failed - token may be invalid or expired",{status:r.status,statusText:r.statusText}),this.invalidateCache(),!1}catch(e){return e instanceof Error&&"AbortError"===e.name?(this.logger.info("Token validation request timed out - assuming offline or server unavailable"),!0):(this.logger.info("Token validation request failed",{error:(0,l.getErrorMessage)(e)}),!0)}}async hasValidAndWorkingToken(){return!!await this.hasValidToken()&&await this.validateTokenWithRemoteServer()}async isGoogleOAuthAvailable(){return await this.authFlow.isGoogleOAuthAvailable()}async startGoogleOAuthFlow(){return await this.authFlow.startGoogleOAuthFlow()}async startGoogleOAuthPolling(e,t,r,o,s){return await this.authFlow.startGoogleOAuthPolling(e,t,r,o,s)}}},1943(module){module.exports=require("fs/promises")},2096(e,exports,t){var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
|
|
109
|
+
Object.defineProperty(exports,"__esModule",{value:!0}),exports.ProjectConfigService=void 0
|
|
110
|
+
const o=r(t(1943)),s=r(t(6928)),n=r(t(857)),i=t(4139),a=t(8824)
|
|
111
|
+
exports.ProjectConfigService=class{constructor(e){this.logger=e.child({component:"project-config"})}async readProjectId(e){try{const t=s.default.join(e,i.ALCHEMY_DIR_NAME,"project.json"),r=await o.default.readFile(t,"utf-8"),n=JSON.parse(r)
|
|
112
|
+
return n.id?n.id+"":null}catch(e){return this.logger.debug("Could not read project ID",{error:(0,a.getErrorMessage)(e)}),null}}async readProjectConfig(e){try{const t=s.default.join(e,i.ALCHEMY_DIR_NAME,"config.json"),r=await o.default.readFile(t,"utf-8")
|
|
113
|
+
return JSON.parse(r)}catch(e){return this.logger.debug("Could not read project config",{error:(0,a.getErrorMessage)(e)}),{}}}async saveMultiAgentConfig(e,t,r){try{const n=s.default.join(e,i.ALCHEMY_DIR_NAME),a=s.default.join(n,"config.json")
|
|
114
|
+
await o.default.mkdir(n,{recursive:!0})
|
|
115
|
+
let c={}
|
|
116
|
+
try{const e=await o.default.readFile(a,"utf-8")
|
|
117
|
+
c=JSON.parse(e)}catch(e){this.logger.debug("No existing config.json, creating new one")}c.multiAgent=!1!==t,void 0!==r&&(c.autoJournal=r),c.lockTimeout||(c.lockTimeout=3600),c.worktreeCleanupDelay||(c.worktreeCleanupDelay=900),c.maxWorktreeAge||(c.maxWorktreeAge=24),c.cleanupInterval||(c.cleanupInterval=15),void 0===c.preserveBranchesOnCleanup&&(c.preserveBranchesOnCleanup=!0),c.maxConcurrentSessions||(c.maxConcurrentSessions=10),c.sessionTimeoutWarning||(c.sessionTimeoutWarning=45),await o.default.writeFile(a,JSON.stringify(c,null,2),"utf-8"),this.logger.info("Saved multiAgent config",{configPath:a,multiAgent:c.multiAgent})}catch(e){this.logger.error("Failed to save multiAgent config",{error:(0,a.getErrorMessage)(e)})}}getToolPreferencesPath(){const e=n.default.homedir()
|
|
118
|
+
return s.default.join(e,".alchemy_lab","tool-preferences.json")}async loadToolPreferences(e){const t=this.getToolPreferencesPath()
|
|
119
|
+
try{const r=await o.default.readFile(t,"utf-8"),s=JSON.parse(r)
|
|
120
|
+
return e&&s[e]?s[e]:e?null:s}catch(t){return this.logger.debug("No user preferences found",{error:(0,a.getErrorMessage)(t)}),e?null:{}}}async saveToolPreferences(e,t,r,n){const i=this.getToolPreferencesPath(),a=s.default.dirname(i)
|
|
121
|
+
this.logger.info("Saving user tool preferences",{clientType:e,preset:t,disabledToolsCount:r?.length||0,allowedToolsCount:n?.length||0}),await o.default.mkdir(a,{recursive:!0})
|
|
122
|
+
let c={}
|
|
123
|
+
try{const e=await o.default.readFile(i,"utf-8")
|
|
124
|
+
c=JSON.parse(e)}catch(e){}c[e]={preset:t,disabledTools:r||[],allowedAlchemyTools:n||[],updatedAt:(new Date).toISOString()},await o.default.writeFile(i,JSON.stringify(c,null,2),"utf-8"),this.logger.info("User tool preferences saved",{clientType:e,prefsFile:i})}}},2366(e,exports,t){var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
|
|
125
|
+
Object.defineProperty(exports,"__esModule",{value:!0}),exports.AlchemyActionServer=void 0
|
|
126
|
+
const o=r(t(7252)),s=r(t(8577)),n=t(4192),i=t(1152),a=r(t(6928)),c=r(t(9896)),l=r(t(857)),d=t(5607),u=t(8984),g=t(8524),h=t(8824),p=t(2096),f=t(7210),m=t(5317)
|
|
127
|
+
class y{updateAuthToken(e){this.options.authToken=e,this.apiClient.authToken=e,this.configurer.authToken=e}constructor(e,t){this.directReportDedup=new Map,this.settingsSyncCounter=0,this.isPolling=!1,this.isProcessingAction=!1,this.openCodeUrlCache=null,this.trackedChildPids=new Set,this.sseEventBuffer=[],this.cachedProjectConfig=null,this.logger=e,this.options=t,this.port=t.port||3456,this.startTime=new Date
|
|
128
|
+
const r=(0,i.createMetricsCollector)(!1)
|
|
129
|
+
this.apiClient=new n.BaseApiClient(t.remoteServerUrl,e,r,t.authToken),this.configurer=new u.MCPClientConfigurer(e,t.remoteServerUrl,t.authToken),this.cliBridge=new g.CLIBridgeWebSocket(e),(0,g.setCLIBridge)(this.cliBridge),this.hookExecutor=new f.HookExecutor(e,"",t.projectPath,this.apiClient,{cachedDefaultModel:this.cachedDefaultModel,trackChildPid:e=>this.trackedChildPids.add(e)}),this.app=this.createApp()}createApp(){const e=(0,o.default)()
|
|
130
|
+
return e.use((0,s.default)()),e.use(o.default.json({limit:"1mb"})),e.get("/health",this.handleHealth.bind(this)),e.get("/status",this.handleStatus.bind(this)),e.get("/opencode",this.handleOpenCodeConfig.bind(this)),e.post("/sync-agents",this.handleSyncAgents.bind(this)),e.post("/api/generate-journal",this.handleGenerateJournal.bind(this)),e.get("/api/event-hooks/by-event/:eventType",this.handleEventHooksProxy.bind(this)),e.post("/api/hook-triggers",this.handleHookTriggersProxy.bind(this)),e.get("/api/hook-iterations/:sessionId/:hookId",this.handleGetHookIteration.bind(this)),e.post("/api/hook-iterations/:sessionId/:hookId/increment",this.handleIncrementHookIteration.bind(this)),e.get("/api/project-settings",((e,t)=>{t.json({success:!0,data:{defaultModel:this.cachedDefaultModel||null}})})),e.post("/api/sync-settings",(async(e,t)=>{await this.syncProjectSettings(),t.json({success:!0,data:{defaultModel:this.cachedDefaultModel||null}})})),e.get("/api/agents/by-name/:agentName",this.handleGetAgentByName.bind(this)),e.get("/api/sessions/:sessionId/agent-type",this.handleGetSessionAgentType.bind(this)),e.get("/api/sessions/:sessionId/parent",this.handleGetSessionParent.bind(this)),e.get("/api/sessions/:sessionId/ancestors",(async(e,t)=>{try{const{sessionId:r}=e.params,o=this.projectId
|
|
131
|
+
if(!o)return void t.status(400).json({success:!1,error:"No project"})
|
|
132
|
+
const s=await this.apiClient.get(`/api/projects/${o}/cli-sessions/external/opencode/${encodeURIComponent(r)}/ancestors`),n=(s?.data||s||[]).map((e=>({externalSessionId:e.externalSessionId,agentType:e.agentType,title:e.title})))
|
|
133
|
+
t.json({success:!0,ancestors:n})}catch(e){t.status(500).json({success:!1,error:(0,h.getErrorMessage)(e)})}})),e.post("/api/hook-event",(async(e,t)=>{try{const{eventType:r,sessionId:o,variables:s,timing:n}=e.body
|
|
134
|
+
if(!r)return void t.status(400).json({success:!1,error:"eventType is required"})
|
|
135
|
+
const i=await this.hookExecutor.processEvent({eventType:r,sessionId:o||"",variables:s||{},timing:n})
|
|
136
|
+
t.json({success:!0,data:i})}catch(e){this.logger.error("Hook event processing failed",{error:(0,h.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,h.getErrorMessage)(e)})}})),e.post("/api/direct-report",this.handleDirectReport.bind(this)),e.post("/api/cli-bridge/command",this.handleCLIBridgeCommand.bind(this)),e.get("/api/cli-bridge/status",this.handleCLIBridgeStatus.bind(this)),e.use("/api/opencode",((e,t)=>this.handleOpenCodeProxy(e,t))),e}handleHealth(e,t){t.json({status:"healthy",uptime:Math.floor((Date.now()-this.startTime.getTime())/1e3),timestamp:(new Date).toISOString()})}handleStatus(e,t){t.json({status:"running",uptime:Math.floor((Date.now()-this.startTime.getTime())/1e3),startTime:this.startTime.toISOString(),projectId:this.projectId,serverId:this.serverId,isPolling:this.isPolling,port:this.port,timestamp:(new Date).toISOString()})}handleOpenCodeConfig(e,t){const r=a.default.join(this.options.projectPath,".alchemy_lab","opencode.json")
|
|
137
|
+
try{if(!c.default.existsSync(r))return void t.status(404).json({error:"OpenCode config not found",message:"OpenCode serve may not be running"})
|
|
138
|
+
const e=c.default.readFileSync(r,"utf-8"),o=JSON.parse(e)
|
|
139
|
+
t.json({port:o.port,pid:o.pid,startedAt:o.startedAt,baseUrl:"http://localhost:"+o.port,directory:this.options.projectPath,proxyEnabled:!!this.options.proxyOpenCode})}catch(e){this.logger.error("Failed to read OpenCode config",{error:(0,h.getErrorMessage)(e)}),t.status(500).json({error:"Failed to read OpenCode config",message:(0,h.getErrorMessage)(e)})}}async handleSyncAgents(e,t){try{const e=this.readProjectConfig()
|
|
140
|
+
if(!e?.id)return void t.status(400).json({success:!1,error:"No project configuration found"})
|
|
141
|
+
this.logger.info("Syncing agents from remote",{projectId:e.id})
|
|
142
|
+
const r=await this.configurer.fetchAgentsFromRemote(e.id)
|
|
143
|
+
if(!r||0===r.length)return void t.json({success:!0,message:"No agents to sync",count:0})
|
|
144
|
+
const o=await this.configurer.fetchProjectFromRemote(e.id)
|
|
145
|
+
this.logger.debug("Fetched project for global prompt",{projectId:e.id,hasProject:!!o,hasGlobalSystemPrompt:!!o?.metadata?.globalSystemPrompt})
|
|
146
|
+
const s=o?.metadata?.globalSystemPrompt
|
|
147
|
+
s?this.logger.info("Using global system prompt from project metadata",{projectId:e.id,promptLength:s.length}):this.logger.debug("No global system prompt found in project metadata")
|
|
148
|
+
const n=a.default.join(this.options.projectPath,".opencode","agent")
|
|
149
|
+
await c.default.promises.mkdir(n,{recursive:!0})
|
|
150
|
+
let i=0
|
|
151
|
+
for(const e of r){if(!e.enabled)continue
|
|
152
|
+
const t="alchemy",r=this.configurer.generateAgentMarkdown(e,t,"anthropic/claude-sonnet-4-20250514",s),o=a.default.join(n,e.name+".md")
|
|
153
|
+
await c.default.promises.writeFile(o,r,"utf8"),i++,this.logger.info("Synced agent file",{name:e.name,path:o,mode:e.mode})}try{const e=a.default.join(this.options.projectPath,"opencode.json")
|
|
154
|
+
if(c.default.existsSync(e)){const t=c.default.readFileSync(e,"utf-8"),s=JSON.parse(t),n=o?.metadata?.bashToolAgents||{}
|
|
155
|
+
if(s.agent){for(const e of r)e.enabled&&s.agent[e.name]&&e.name in n&&(s.agent[e.name].tools.bash=!!n[e.name])
|
|
156
|
+
c.default.writeFileSync(e,JSON.stringify(s,null,2),"utf-8"),this.logger.info("Updated opencode.json agent tools",{bashOverrides:Object.keys(n).length})}}}catch(e){this.logger.warn("Failed to update opencode.json agent tools",{error:(0,h.getErrorMessage)(e)})}let l=!1
|
|
157
|
+
try{const e=a.default.join(this.options.projectPath,".alchemy_lab","opencode.json")
|
|
158
|
+
if(c.default.existsSync(e)){const t=c.default.readFileSync(e,"utf-8"),r=JSON.parse(t)
|
|
159
|
+
if(r.pid)try{process.kill(r.pid,"SIGTERM"),this.logger.info("Killed existing OpenCode process",{pid:r.pid}),await new Promise((e=>setTimeout(e,500)))}catch(e){"ESRCH"!==e?.code&&this.logger.warn("Could not kill OpenCode",{error:(0,h.getErrorMessage)(e)})}const o=(0,m.spawn)("opencode",["serve"],{stdio:["ignore","pipe","pipe"],cwd:this.options.projectPath,detached:!0,env:process.env}),s=new Promise((e=>{let t=""
|
|
160
|
+
const r=setTimeout((()=>{this.logger.warn("Timeout waiting for OpenCode port, killing process")
|
|
161
|
+
try{o.kill("SIGTERM")}catch{}e(null)}),1e4)
|
|
162
|
+
o.stdout?.on("data",(s=>{t+=s.toString()
|
|
163
|
+
const n=t.match(/listening on http:\/\/[^:]+:(\d+)/)
|
|
164
|
+
n&&(clearTimeout(r),o.unref(),e({port:parseInt(n[1],10),pid:o.pid}))})),o.on("error",(()=>{clearTimeout(r)
|
|
165
|
+
try{o.kill("SIGTERM")}catch{}e(null)}))})),n=await s
|
|
166
|
+
n&&(this.trackChildPid(n.pid),c.default.writeFileSync(e,JSON.stringify({port:n.port,pid:n.pid,startedAt:(new Date).toISOString()},null,2)),this.logger.info("Restarted OpenCode",{port:n.port,pid:n.pid}),l=!0)}}catch(e){this.logger.warn("Could not restart OpenCode",{error:(0,h.getErrorMessage)(e)})}t.json({success:!0,message:`Synced ${i} agent(s) to local files${l?" - OpenCode reloading":""}`,count:i,restarted:l})}catch(e){this.logger.error("Failed to sync agents",{error:(0,h.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,h.getErrorMessage)(e)})}}async handleGenerateJournal(e,t){try{const{requestId:r}=e.body
|
|
167
|
+
if(!r)return void t.status(400).json({success:!1,error:"requestId is required"})
|
|
168
|
+
if(!/^req\d+$/.test(r))return void t.status(400).json({success:!1,error:"Invalid requestId format"})
|
|
169
|
+
this.logger.info("Generating journal for request",{requestId:r})
|
|
170
|
+
const o=`Generate a journal entry for request ${r}.\n\nSteps:\n1. Use the generate_request_journal tool with requestId: ${r}\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-${r}.md using document_operations (operation: create, type: Report)\n\nKeep it story-focused and informative.`,s=(0,m.spawn)("opencode",["run","--agent","journal",o],{detached:!0,stdio:"ignore",cwd:this.options.projectPath})
|
|
171
|
+
s.pid&&this.trackChildPid(s.pid),s.unref(),t.json({success:!0,message:"Journal generation started for "+r})}catch(e){this.logger.error("Failed to generate journal",{error:(0,h.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,h.getErrorMessage)(e)})}}async handleEventHooksProxy(e,t){try{const{eventType:r}=e.params,{sessionId:o}=e.query,s=this.projectId
|
|
172
|
+
if(!s)return void t.status(400).json({success:!1,error:"Project not registered"})
|
|
173
|
+
this.logger.info("Proxying event hooks request",{projectId:s,eventType:r,sessionId:o})
|
|
174
|
+
const n=o?"?sessionId="+encodeURIComponent(o):"",i=await this.apiClient.get(`/api/projects/${s}/event-hooks/by-event/${r}${n}`)
|
|
175
|
+
t.json(i)}catch(e){this.logger.error("Failed to fetch event hooks",{error:(0,h.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,h.getErrorMessage)(e)})}}async handleHookTriggersProxy(e,t){try{const r=this.projectId
|
|
176
|
+
if(!r)return void t.status(400).json({success:!1,error:"Project not registered"})
|
|
177
|
+
const{hookId:o,hookName:s,eventType:n,executionMode:i,agent:a}=e.body
|
|
178
|
+
this.logger.debug("Proxying hook trigger notification",{projectId:r,hookName:s,eventType:n})
|
|
179
|
+
const c=await this.apiClient.post(`/api/projects/${r}/hook-triggers`,{hookId:o,hookName:s,eventType:n,executionMode:i,agent:a})
|
|
180
|
+
t.json(c)}catch(e){this.logger.debug("Failed to proxy hook trigger",{error:(0,h.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,h.getErrorMessage)(e)})}}async handleGetHookIteration(e,t){try{const r=this.readProjectConfig()
|
|
181
|
+
if(!r?.id)return void t.status(400).json({success:!1,error:"No project configured"})
|
|
182
|
+
const{sessionId:o,hookId:s}=e.params,n=await this.apiClient.get(`/api/projects/${r.id}/hook-iterations/${o}/${s}`)
|
|
183
|
+
t.json(n)}catch(e){this.logger.debug("Failed to get hook iteration",{error:(0,h.getErrorMessage)(e)}),t.json({success:!0,data:{iterationCount:0}})}}async handleIncrementHookIteration(e,t){try{const r=this.readProjectConfig()
|
|
184
|
+
if(!r?.id)return void t.status(400).json({success:!1,error:"No project configured"})
|
|
185
|
+
const{sessionId:o,hookId:s}=e.params,n=await this.apiClient.post(`/api/projects/${r.id}/hook-iterations/${o}/${s}/increment`,{})
|
|
186
|
+
t.json(n)}catch(e){this.logger.debug("Failed to increment hook iteration",{error:(0,h.getErrorMessage)(e)}),t.json({success:!0,data:{iterationCount:0}})}}async handleDirectReport(e,t){try{const r=this.projectId
|
|
187
|
+
if(!r)return void t.status(400).json({success:!1,error:"Project not registered"})
|
|
188
|
+
const{sessionId:o,requestId:s,summary:n,status:i}=e.body
|
|
189
|
+
if(!o)return void t.status(400).json({success:!1,error:"sessionId is required"})
|
|
190
|
+
this.logger.info("Direct report creation",{sessionId:o,requestId:s,status:i})
|
|
191
|
+
const a=[]
|
|
192
|
+
try{const e=await this.apiClient.get(`/api/projects/${r}/cli-sessions/external/opencode/${encodeURIComponent(o)}/ancestors`),t=e?.data||e||[]
|
|
193
|
+
let s=o
|
|
194
|
+
for(const e of t)a.push({ancestorId:e.externalSessionId,childId:s}),s=e.externalSessionId}catch(e){this.logger.warn("Failed to fetch ancestor chain",{sessionId:o,error:(0,h.getErrorMessage)(e)})}if(0===a.length)return void t.status(404).json({success:!1,error:"No parent session found"})
|
|
195
|
+
let c,l=n
|
|
196
|
+
if(!l&&s)try{const e=await this.apiClient.get(`/api/projects/${r}/requests/${s}`),t=e?.data||e
|
|
197
|
+
c=t?.title
|
|
198
|
+
const o=t?.completionSummary
|
|
199
|
+
o?l=o:c&&(l="Done: "+c)}catch(e){this.logger.debug("Could not fetch request details for summary",{requestId:s,error:(0,h.getErrorMessage)(e)})}l||(l="Completed request "+(s||"unknown"))
|
|
200
|
+
const d=s||o
|
|
201
|
+
if(this.directReportDedup.size>=1e3){const e=this.directReportDedup.keys().next().value
|
|
202
|
+
void 0!==e&&this.directReportDedup.delete(e)}this.directReportDedup.has(d)||this.directReportDedup.set(d,new Set)
|
|
203
|
+
const u=this.directReportDedup.get(d),g=a.filter((({ancestorId:e})=>!u.has(e)||(this.logger.info("Direct report dedup: already notified",{ancestorId:e,dedupKey:d}),!1))),p=await Promise.allSettled(g.map((async({ancestorId:e,childId:t})=>{const o=await this.apiClient.post(`/api/projects/${r}/session-messages/reports`,{fromSessionId:t,toSessionId:e,status:i||"success",summary:l}),s=o?.data||o
|
|
204
|
+
return u.add(e),this.logger.info("Direct report created for ancestor",{messageCode:s?.messageCode,ancestorId:e,fromChild:t}),{externalSessionId:e,messageCode:s?.messageCode,fromSessionId:t}}))),f=[]
|
|
205
|
+
for(const e of p)"fulfilled"===e.status&&f.push(e.value)
|
|
206
|
+
for(const e of p)"rejected"===e.status&&this.logger.warn("Failed to create report for ancestor",{error:(0,h.getErrorMessage)(e.reason)})
|
|
207
|
+
if(setTimeout((()=>{this.directReportDedup.delete(d)}),3e5),s)try{const e=await this.apiClient.get(`/api/projects/${r}/session-request-associations/by-session/${encodeURIComponent(o)}?activeOnly=true`),t=e?.data||e||[],s=(await Promise.allSettled(t.map((e=>this.apiClient.post(`/api/projects/${r}/session-request-associations/end`,{sessionId:o,requestLocalId:e.requestLocalId}))))).filter((e=>"rejected"===e.status)).length
|
|
208
|
+
this.logger.info("Released request associations",{sessionId:o,count:t.length,failCount:s})}catch(e){this.logger.warn("Failed to release associations",{sessionId:o,error:(0,h.getErrorMessage)(e)})}t.json({success:!0,data:{notifiedSessions:f,report:f[0]?{messageCode:f[0].messageCode}:void 0,parentExternalSessionId:a[0]?.ancestorId||null}})}catch(e){this.logger.error("Direct report creation failed",{error:(0,h.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,h.getErrorMessage)(e)})}}async handleGetSessionAgentType(e,t){try{const{sessionId:r}=e.params,o=this.readProjectConfig()
|
|
209
|
+
if(o?.id)try{const e=await this.apiClient.get(`/api/projects/${o.id}/cli-sessions/external/opencode/${encodeURIComponent(r)}`),s=e?.data||e
|
|
210
|
+
if(this.logger.info("Agent type DB lookup",{sessionId:r,dbAgentType:s?.agentType||null,dbId:s?.id||s?.localId||null}),s?.agentType)return void t.json({success:!0,agentType:s.agentType,source:"db"})}catch(e){this.logger.info("Agent type DB lookup failed",{sessionId:r,error:(0,h.getErrorMessage)(e)})}if(this.cliBridge.isUIConnected())try{const e=await this.cliBridge.sendCommand("get-session-status",{sessionId:r},5e3)
|
|
211
|
+
if(this.logger.info("Agent type bridge lookup",{sessionId:r,bridgeAgentType:e?.data?.agentType||null,bridgeSuccess:e?.success}),e.success&&e.data?.agentType)return void t.json({success:!0,agentType:e.data.agentType,source:"bridge"})}catch(e){this.logger.info("Agent type bridge lookup failed",{sessionId:r,error:(0,h.getErrorMessage)(e)})}this.logger.info("Agent type not found",{sessionId:r}),t.json({success:!0,agentType:null,source:"unknown"})}catch(e){this.logger.debug("Failed to get session agent type",{error:(0,h.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,h.getErrorMessage)(e)})}}async handleGetSessionParent(e,t){try{const{sessionId:r}=e.params,o=this.readProjectConfig()
|
|
212
|
+
if(o?.id)try{const e=await this.apiClient.get(`/api/projects/${o.id}/cli-sessions/resolve/${encodeURIComponent(r)}`),s=e?.data?.parent?.externalSessionId||""
|
|
213
|
+
return void t.json({success:!0,parentSessionId:s})}catch(e){this.logger.info("Parent session DB lookup failed",{sessionId:r,error:(0,h.getErrorMessage)(e)})}t.json({success:!0,parentSessionId:""})}catch(e){this.logger.debug("Failed to get parent session",{error:(0,h.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,h.getErrorMessage)(e)})}}async handleOpenCodeProxy(e,t){try{if(!this.openCodeUrlCache||Date.now()-this.openCodeUrlCache.timestamp>3e4){const e=a.default.join(this.options.projectPath,".alchemy_lab","opencode.json")
|
|
214
|
+
if(!c.default.existsSync(e))return void t.status(502).json({error:"OpenCode not running"})
|
|
215
|
+
const r=JSON.parse(c.default.readFileSync(e,"utf-8"))
|
|
216
|
+
this.openCodeUrlCache={url:"http://localhost:"+r.port,timestamp:Date.now()}}const r=`${this.openCodeUrlCache.url}/${e.path.replace(/^\//,"")}${e.originalUrl.includes("?")?e.originalUrl.substring(e.originalUrl.indexOf("?")):""}`,o=e.headers.accept?.includes("text/event-stream")
|
|
217
|
+
if(o){const o=await fetch(r,{headers:{Accept:"text/event-stream"}})
|
|
218
|
+
if(!o.ok||!o.body)return void t.status(o.status).json({error:"SSE upstream error"})
|
|
219
|
+
t.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-cache",Connection:"keep-alive"})
|
|
220
|
+
const s=o.body.getReader()
|
|
221
|
+
return(async()=>{try{for(;;){const{done:e,value:r}=await s.read()
|
|
222
|
+
if(e)break
|
|
223
|
+
t.write(r)}}catch{}t.end()})(),void e.on("close",(()=>{try{s.cancel()}catch{}}))}const s={"Content-Type":e.headers["content-type"]||"application/json"},n={method:e.method,headers:s}
|
|
224
|
+
"GET"!==e.method&&"HEAD"!==e.method&&e.body&&(n.body=JSON.stringify(e.body))
|
|
225
|
+
const i=await fetch(r,n),l=i.headers.get("content-type")||"",d=l.includes("application/json")?await i.json():await i.text()
|
|
226
|
+
t.status(i.status),l&&t.setHeader("Content-Type",l),"string"==typeof d?t.send(d):t.json(d)}catch(r){this.logger.error("OpenCode proxy error",{error:(0,h.getErrorMessage)(r),path:e.params[0]}),t.status(502).json({error:"OpenCode proxy error",message:(0,h.getErrorMessage)(r)})}}async handleGetAgentByName(e,t){try{const r=this.readProjectConfig()
|
|
227
|
+
if(!r?.id)return void t.status(400).json({success:!1,error:"No project configured"})
|
|
228
|
+
const{agentName:o}=e.params,s=await this.apiClient.get(`/api/projects/${r.id}/agents`),n=(s?.data||s||[]).find((e=>e.name===o))
|
|
229
|
+
if(!n)return void t.status(404).json({success:!1,error:`Agent '${o}' not found`})
|
|
230
|
+
t.json({success:!0,data:n})}catch(e){this.logger.debug("Failed to get agent by name",{error:(0,h.getErrorMessage)(e)}),t.status(500).json({success:!1,error:(0,h.getErrorMessage)(e)})}}readProjectConfig(){if(this.cachedProjectConfig&&Date.now()-this.cachedProjectConfig.timestamp<y.PROJECT_CONFIG_TTL)return this.cachedProjectConfig.config
|
|
231
|
+
const e=a.default.join(this.options.projectPath,".alchemy_lab","project.json")
|
|
232
|
+
try{if(!c.default.existsSync(e))return this.cachedProjectConfig={config:null,timestamp:Date.now()},null
|
|
233
|
+
const t=c.default.readFileSync(e,"utf-8"),r=JSON.parse(t)
|
|
234
|
+
return this.cachedProjectConfig={config:r,timestamp:Date.now()},r}catch(e){return this.logger.error("Failed to read project config",{error:(0,h.getErrorMessage)(e)}),this.cachedProjectConfig={config:null,timestamp:Date.now()},null}}getOpenCodeUrl(){if(this.openCodeUrlCache&&3e4>Date.now()-this.openCodeUrlCache.timestamp)return this.openCodeUrlCache.url
|
|
235
|
+
const e=a.default.join(this.options.projectPath,".alchemy_lab","opencode.json")
|
|
236
|
+
try{if(!c.default.existsSync(e))return null
|
|
237
|
+
const t=c.default.readFileSync(e,"utf-8"),r=JSON.parse(t)
|
|
238
|
+
if(r.port){const e="http://127.0.0.1:"+r.port
|
|
239
|
+
return this.openCodeUrlCache={url:e,timestamp:Date.now()},e}}catch{}return null}async handleSessionCommand(e,t){const r=this.getOpenCodeUrl()
|
|
240
|
+
if(!r)return{success:!1,error:"OpenCode server not available. Is 'opencode serve' running?"}
|
|
241
|
+
try{switch(e){case"list-sessions":{const[e,o]=await Promise.all([fetch(r+"/session"),fetch(r+"/session/status")])
|
|
242
|
+
if(!e.ok)return{success:!1,error:"Failed to list sessions: "+e.status}
|
|
243
|
+
const s=await e.json(),n=o.ok?await o.json():{}
|
|
244
|
+
let i={}
|
|
245
|
+
if(this.projectId)try{const e=await this.apiClient.get(`/api/projects/${this.projectId}/cli-sessions`,{limit:200}),t=e?.data||e||[]
|
|
246
|
+
for(const e of t)e.externalSessionId&&e.agentType&&(i[e.externalSessionId]=e.agentType)}catch{}let a=s.map((e=>({id:e.id,title:e.title||"(untitled)",parentId:e.parentID||void 0,status:n[e.id]?.type||"idle",agentType:i[e.id]||void 0,time:e.time})))
|
|
247
|
+
if(t.status&&(a=a.filter((e=>e.status===t.status))),t.agentType&&(a=a.filter((e=>e.agentType===t.agentType))),t.search){const e=t.search.toLowerCase()
|
|
248
|
+
a=a.filter((t=>t.title?.toLowerCase().includes(e)))}return t.limit&&(a=a.slice(0,t.limit)),{success:!0,data:a}}case"get-messages":{const e=t.sessionId
|
|
249
|
+
if(!e)return{success:!1,error:"sessionId is required"}
|
|
250
|
+
const o=t.limit||50,s=await fetch(`${r}/session/${e}/message?limit=${o}`)
|
|
251
|
+
return s.ok?{success:!0,data:(await s.json()).map((e=>({role:e.info.role,time:e.info.time?.created,parts:e.parts||[]})))}:404===s.status?{success:!1,error:"Session not found"}:{success:!1,error:"Failed to get messages: "+s.status}}case"send-message":{const e=t.sessionId
|
|
252
|
+
if(!e)return{success:!1,error:"sessionId is required"}
|
|
253
|
+
if(!t.message)return{success:!1,error:"message is required"}
|
|
254
|
+
const o={parts:[{type:"text",text:t.message}]}
|
|
255
|
+
if(t.agent&&(o.agent=t.agent),t.variant&&(o.variant=t.variant),t.system&&(o.system=t.system),this.cachedDefaultModel){const[e,...t]=this.cachedDefaultModel.split("/")
|
|
256
|
+
o.model={providerID:e,modelID:t.join("/")}}const s=await fetch(`${r}/session/${e}/prompt_async`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(o)})
|
|
257
|
+
if(!s.ok){const e=await s.text().catch((()=>"Unknown error"))
|
|
258
|
+
return{success:!1,error:`Failed to send message: ${s.status} ${e}`}}return{success:!0,data:{messageId:null}}}case"create-session":{const e={}
|
|
259
|
+
t.parentId&&(e.parentID=t.parentId),t.title&&(e.title=t.title)
|
|
260
|
+
const o=await fetch(r+"/session",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)})
|
|
261
|
+
if(!o.ok)return{success:!1,error:"Failed to create session: "+o.status}
|
|
262
|
+
const s=await o.json()
|
|
263
|
+
return{success:!0,data:{id:s.id,title:s.title||t.title||"(untitled)",status:"idle",agentType:t.agent||void 0}}}case"get-session-status":{const e=t.sessionId
|
|
264
|
+
if(!e)return{success:!1,error:"sessionId is required"}
|
|
265
|
+
const[o,s]=await Promise.all([fetch(r+"/session/status"),fetch(r+"/session")]),n=o.ok?await o.json():{}
|
|
266
|
+
let i,a
|
|
267
|
+
if(s.ok){const t=(await s.json()).find((t=>t.id===e))
|
|
268
|
+
i=t?.title}if(this.projectId)try{const t=await this.apiClient.get(`/api/projects/${this.projectId}/cli-sessions/external/opencode/${encodeURIComponent(e)}`),r=t?.data||t
|
|
269
|
+
r?.agentType&&(a=r.agentType)}catch{}return{success:!0,data:{id:e,title:i||"(untitled)",status:n[e]?.type||"idle",agentType:a}}}default:return{success:!1,error:"Unknown session action: "+e}}}catch(e){return{success:!1,error:(0,h.getErrorMessage)(e)}}}async register(){const e=this.readProjectConfig()
|
|
270
|
+
if(!e?.id)throw Error("No project configuration found")
|
|
271
|
+
this.projectId=e.id,this.hookExecutor.setProjectId(this.projectId)
|
|
272
|
+
const t={projectId:this.projectId,projectPath:this.options.projectPath,serverPort:this.port,hostname:l.default.hostname(),platform:process.platform}
|
|
273
|
+
this.logger.info("Registering with remote",{projectId:this.projectId})
|
|
274
|
+
const r=await this.apiClient.post("/api/servers/register",t)
|
|
275
|
+
r.success&&r.data?.serverId?(this.serverId=r.data.serverId,this.logger.info("Registered successfully",{serverId:this.serverId})):this.logger.warn("Registration failed, continuing without remote connection",{error:r.error?.message||"Unknown error"})}startHeartbeat(){this.serverId&&(this.heartbeatInterval=setInterval((async()=>{try{await this.apiClient.post(`/api/servers/${this.serverId}/heartbeat`,{timestamp:(new Date).toISOString()})}catch(e){this.logger.warn("Heartbeat failed",{error:(0,h.getErrorMessage)(e)})}}),3e4))}startSSEEventForwarder(){this.logger.info("Starting SSE event forwarder"),this.connectToOpenCodeSSE(),this.sseForwarderInterval=setInterval((async()=>{await this.flushSSEEvents()}),2e3)}stopSSEEventForwarder(){this.sseForwarderInterval&&(clearInterval(this.sseForwarderInterval),this.sseForwarderInterval=void 0),this.sseAbortController&&(this.sseAbortController.abort(),this.sseAbortController=void 0),this.flushSSEEvents().catch((()=>{}))}async connectToOpenCodeSSE(){const e=a.default.join(this.options.projectPath,".alchemy_lab","opencode.json")
|
|
276
|
+
if(!c.default.existsSync(e))return this.logger.debug("SSE forwarder: opencode.json not found, will retry"),void setTimeout((()=>this.connectToOpenCodeSSE()),5e3)
|
|
277
|
+
let t
|
|
278
|
+
try{t=JSON.parse(c.default.readFileSync(e,"utf-8")).port}catch{return void setTimeout((()=>this.connectToOpenCodeSSE()),5e3)}const r=`http://localhost:${t}/event`
|
|
279
|
+
this.logger.info("SSE forwarder connecting",{url:r}),this.sseAbortController=new AbortController
|
|
280
|
+
try{const e=await fetch(r,{headers:{Accept:"text/event-stream"},signal:this.sseAbortController.signal})
|
|
281
|
+
if(!e.ok||!e.body)return this.logger.warn("SSE forwarder: upstream error",{status:e.status}),void setTimeout((()=>this.connectToOpenCodeSSE()),5e3)
|
|
282
|
+
const t=e.body.getReader(),o=new TextDecoder
|
|
283
|
+
let s="";(async()=>{try{for(;;){const{done:e,value:r}=await t.read()
|
|
284
|
+
if(e)break
|
|
285
|
+
s+=o.decode(r,{stream:!0})
|
|
286
|
+
const n=s.split("\n")
|
|
287
|
+
s=n.pop()||""
|
|
288
|
+
for(const e of n)if(e.startsWith("data: "))try{const t=JSON.parse(e.slice(6))
|
|
289
|
+
this.sseEventBuffer.push({type:t.type||"unknown",data:t}),this.handleSSEHookEvent(t).catch((()=>{}))}catch{}}}catch(e){"AbortError"===e?.name||(e?.message+"").includes("terminated")||this.logger.debug("SSE forwarder stream ended",{error:(0,h.getErrorMessage)(e)})}this.sseForwarderInterval&&(this.logger.info("SSE forwarder reconnecting in 3s"),setTimeout((()=>this.connectToOpenCodeSSE()),3e3))})()}catch(e){"AbortError"===e?.name||(e?.message+"").includes("terminated")||(this.logger.debug("SSE forwarder connection failed",{error:(0,h.getErrorMessage)(e)}),this.sseForwarderInterval&&setTimeout((()=>this.connectToOpenCodeSSE()),5e3))}}async handleSSEHookEvent(e){const t=e?.type
|
|
290
|
+
if(t&&this.projectId)if("session.idle"===t){const t=e?.properties?.sessionID||e?.sessionID||""
|
|
291
|
+
if(!t)return
|
|
292
|
+
this.logger.debug("SSE hook: session_idle",{sessionId:t}),await this.hookExecutor.processEvent({eventType:"session_idle",sessionId:t,variables:{sessionId:t}})}else if("session.created"===t){const t=e?.properties?.info?.id||e?.info?.id||""
|
|
293
|
+
if(!t)return
|
|
294
|
+
setTimeout((async()=>{this.logger.debug("SSE hook: session_created (delayed)",{sessionId:t}),await this.hookExecutor.processEvent({eventType:"session_created",sessionId:t,variables:{sessionId:t}})}),500)}}async flushSSEEvents(){if(0===this.sseEventBuffer.length||!this.projectId)return
|
|
295
|
+
const e=this.sseEventBuffer.splice(0)
|
|
296
|
+
try{await this.apiClient.post(`/api/projects/${this.projectId}/opencode-events`,{events:e})}catch(t){this.logger.debug("SSE event flush failed",{error:(0,h.getErrorMessage)(t),count:e.length}),this.sseEventBuffer.unshift(...e),this.sseEventBuffer.length>500&&this.sseEventBuffer.splice(0,this.sseEventBuffer.length-500)}}startPolling(){this.projectId&&(this.isPolling=!0,this.logger.info("Starting action polling",{projectId:this.projectId}),this.pollingInterval=setInterval((async()=>{await this.pollOnce()}),3e3),this.pollOnce())}async pollOnce(){if(this.isPolling&&this.projectId)try{const e=await this.apiClient.get(`/api/projects/${this.projectId}/actions/pending`)
|
|
297
|
+
if(e.success&&e.data?.settings){const{defaultModel:t,autoJournal:r}=e.data.settings
|
|
298
|
+
if(void 0!==t&&(this.cachedDefaultModel=t,this.hookExecutor.setDefaultModel(t)),void 0!==r&&r!==this.cachedAutoJournal){this.logger.info("autoJournal setting changed",{oldValue:this.cachedAutoJournal,newValue:r})
|
|
299
|
+
const e=new p.ProjectConfigService(this.logger)
|
|
300
|
+
await e.saveMultiAgentConfig(this.options.projectPath,void 0,r),this.cachedAutoJournal=r}}if(e.success&&e.data?.actions?.length>0){const t=[],r=[]
|
|
301
|
+
for(const o of e.data.actions)"opencode-proxy"===o.type||"run-tests"===o.type?t.push(o):r.push(o)
|
|
302
|
+
for(const e of t)this.processAction(e).catch((t=>this.logger.debug("Proxy action failed",{actionId:e.id,error:(0,h.getErrorMessage)(t)})))
|
|
303
|
+
if(r.length>0&&!this.isProcessingAction){this.isProcessingAction=!0
|
|
304
|
+
try{for(const e of r)await this.processAction(e)}finally{this.isProcessingAction=!1}}}}catch(e){this.logger.debug("Poll failed",{error:(0,h.getErrorMessage)(e)})}}async syncProjectSettings(){try{const e=await this.apiClient.get("/api/projects/"+this.projectId)
|
|
305
|
+
if(e.success&&e.data?.metadata){const{autoJournal:t,defaultModel:r}=e.data.metadata
|
|
306
|
+
if(void 0!==r&&(this.cachedDefaultModel=r,this.hookExecutor.setDefaultModel(r)),void 0!==t&&t!==this.cachedAutoJournal){this.logger.info("autoJournal setting changed, syncing to local config",{oldValue:this.cachedAutoJournal,newValue:t})
|
|
307
|
+
const e=new p.ProjectConfigService(this.logger)
|
|
308
|
+
await e.saveMultiAgentConfig(this.options.projectPath,void 0,t),this.cachedAutoJournal=t}}}catch(e){this.logger.debug("Settings sync failed",{error:(0,h.getErrorMessage)(e)})}}async processAction(e){this.logger.info("Processing action",{actionId:e.id,type:e.type})
|
|
309
|
+
const t=Date.now()
|
|
310
|
+
try{const r={projectPath:this.options.projectPath,projectId:this.projectId,logger:this.logger,apiClient:this.apiClient,trackChildPid:e=>this.trackChildPid(e)},o=await(0,d.executeAction)(e.type,e.payload||{},r)
|
|
311
|
+
await this.apiClient.post(`/api/actions/${e.id}/result`,{success:o.success,result:o.data,error:o.message||o.error,duration:Date.now()-t}),this.logger.info("Action processed",{actionId:e.id,type:e.type,success:o.success,duration:Date.now()-t})}catch(r){const o=(0,h.getErrorMessage)(r)
|
|
312
|
+
await this.apiClient.post(`/api/actions/${e.id}/result`,{success:!1,error:o,duration:Date.now()-t}),this.logger.error("Action failed",{actionId:e.id,type:e.type,error:o})}}async handleCLIBridgeCommand(e,t){try{const{action:r,params:o,timeout:s}=e.body
|
|
313
|
+
if(!r)return void t.status(400).json({success:!1,error:"action is required"})
|
|
314
|
+
if(["list-notifications","get-notification","respond-to-notification"].includes(r)){const e=await this.handleNotificationCommand(r,o||{})
|
|
315
|
+
return void t.json(e)}if(["list-sessions","get-messages","send-message","create-session","get-session-status"].includes(r)){const e=await this.handleSessionCommand(r,o||{})
|
|
316
|
+
return void t.json(e)}if(["list-requests","get-request"].includes(r)){const e=await this.handleRequestCommand(r,o||{})
|
|
317
|
+
return void t.json(e)}const n=await this.cliBridge.sendCommand(r,o||{},s)
|
|
318
|
+
t.json(n)}catch(e){t.status(500).json({success:!1,error:(0,h.getErrorMessage)(e)})}}async handleNotificationCommand(e,t){if(!this.projectId)return{success:!1,error:"Project not registered yet"}
|
|
319
|
+
const r=this.projectId
|
|
320
|
+
try{switch(e){case"list-notifications":{const e={limit:t.limit||30}
|
|
321
|
+
return t.sessionId&&(e.sessionId=t.sessionId),t.messageType&&(e.messageType=t.messageType),t.status&&(e.status=t.status),t.readStatus&&(e.readStatus=t.readStatus),{success:!0,data:(await this.apiClient.get(`/api/projects/${r}/session-messages`,e)).data}}case"get-notification":return t.messageCode?{success:!0,data:(await this.apiClient.get(`/api/projects/${r}/session-messages/by-code/${encodeURIComponent(t.messageCode)}`)).data}:{success:!1,error:"messageCode is required"}
|
|
322
|
+
case"respond-to-notification":{if(!t.messageCode)return{success:!1,error:"messageCode is required"}
|
|
323
|
+
const e=t.messageCode
|
|
324
|
+
return{success:!0,data:(await this.apiClient.post(`/api/projects/${r}/session-messages/${encodeURIComponent(e)}/answer`,{respondedBySessionId:t.respondedBySessionId||"cli-bridge",text:t.responseText,selectedOptions:t.selectedOptions,notes:t.notes})).data}}default:return{success:!1,error:"Unknown notification action: "+e}}}catch(e){return{success:!1,error:(0,h.getErrorMessage)(e)}}}async handleRequestCommand(e,t){if(!this.projectId)return{success:!1,error:"Project not registered yet"}
|
|
325
|
+
const r=this.projectId
|
|
326
|
+
try{switch(e){case"list-requests":{const e={}
|
|
327
|
+
return t.status&&(e.status=t.status),t.type&&(e.type=t.type),t.limit&&(e.limit=t.limit),t.offset&&(e.offset=t.offset),t.sortBy&&(e.sortBy=t.sortBy),t.sortDirection&&(e.sortDirection=t.sortDirection),{success:!0,data:(await this.apiClient.get(`/api/projects/${r}/requests`,e)).data}}case"get-request":{if(!t.requestId)return{success:!1,error:"requestId is required"}
|
|
328
|
+
const e=encodeURIComponent(t.requestId),[o,s]=await Promise.all([this.apiClient.get(`/api/projects/${r}/requests/${e}`),this.apiClient.get(`/api/projects/${r}/tasks`,{requestId:t.requestId})])
|
|
329
|
+
return{success:!0,data:{...o.data,tasks:s.data||[]}}}default:return{success:!1,error:"Unknown request action: "+e}}}catch(e){return{success:!1,error:(0,h.getErrorMessage)(e)}}}handleCLIBridgeStatus(e,t){t.json({uiConnected:this.cliBridge.isUIConnected()})}async start(){this.logger.info("Starting AlchemyActionServer",{projectPath:this.options.projectPath,port:this.port})
|
|
330
|
+
for(let e=0;10>e;e++)try{await new Promise(((e,t)=>{try{this.server=this.app.listen(this.port,"127.0.0.1",(()=>{this.logger.info("HTTP server started",{port:this.port}),this.cliBridge.attach(this.server),e()})),this.server.on("error",(e=>{e.code,t(e)}))}catch(e){t(e)}}))
|
|
331
|
+
break}catch(t){if("EADDRINUSE"===t?.code&&9>e){this.logger.info(`Port ${this.port} in use, trying ${this.port+1}`),this.port++
|
|
332
|
+
continue}throw Error(`Port ${this.port} is already in use (tried 10 ports)`)}try{await this.register(),this.startHeartbeat()}catch(e){this.logger.warn("Remote registration failed, continuing in local-only mode",{error:(0,h.getErrorMessage)(e)})}this.startPolling(),this.options.proxyOpenCode&&this.projectId&&this.startSSEEventForwarder()}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,h.getErrorMessage)(t)})}}this.trackedChildPids.clear()
|
|
333
|
+
try{const e=a.default.join(this.options.projectPath,".alchemy_lab","opencode.json")
|
|
334
|
+
if(c.default.existsSync(e)){const t=c.default.readFileSync(e,"utf-8"),r=JSON.parse(t)
|
|
335
|
+
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,h.getErrorMessage)(e)})}}}}catch{}}async stop(){if(this.logger.info("Stopping AlchemyActionServer..."),this.killTrackedProcesses(),this.stopSSEEventForwarder(),this.isPolling=!1,this.pollingInterval&&clearInterval(this.pollingInterval),this.heartbeatInterval&&clearInterval(this.heartbeatInterval),this.serverId)try{await this.apiClient.post(`/api/servers/${this.serverId}/disconnect`)}catch{}this.cliBridge.close(),await new Promise((e=>{if(this.server){const t=setTimeout((()=>{this.logger.warn("Graceful shutdown timed out after 10s, forcing close"),e()}),1e4)
|
|
336
|
+
this.server.close((()=>{clearTimeout(t),e()}))}else e()})),this.logger.info("AlchemyActionServer stopped")}getPort(){return this.port}isRunning(){return!!this.server}}exports.AlchemyActionServer=y,y.PROJECT_CONFIG_TTL=3e4},2782(e,exports,t){function r(){return"true"===process.env.ENABLE_DEBUG_TOOLS?.toLowerCase()}function o(e={}){const t={...function(e){const t={}
|
|
337
|
+
if(!r())return t.level=(0,l.isDevBuild)()?c.LogLevel.INFO:c.LogLevel.ERROR,t.logToFile=!1,t
|
|
338
|
+
if("true"===process.env.DEBUG_LOGGING?.toLowerCase()){t.level=c.LogLevel.DEBUG,t.logToFile=!0
|
|
339
|
+
const r=e||process.cwd()
|
|
340
|
+
t.logFilePath=i.default.join(r,"logs","debug.log")}const o=process.env.DEBUG_LEVEL?.toLowerCase()
|
|
341
|
+
return o&&Object.values(c.LogLevel).includes(o)&&(t.level=o),t}(e.baseDir),...e},{level:s=c.LogLevel.INFO,serviceName:h="mcp-service",logToFile:p=!1,logFilePath:f="./logs/app.log",console=!0}=t,m=[]
|
|
342
|
+
if(console&&m.push(new n.default.transports.Console({format:d,stderrLevels:["error","warn","info","debug"]})),p){!function(e){const t=i.default.dirname(e)
|
|
343
|
+
a.default.existsSync(t)||a.default.mkdirSync(t,{recursive:!0})}(f)
|
|
344
|
+
const e=s===c.LogLevel.DEBUG&&"true"===process.env.DEBUG_LOGGING?.toLowerCase()
|
|
345
|
+
m.push(new n.default.transports.File({filename:f,format:e?g:u}))}const y=n.default.createLogger({level:s,defaultMeta:{service:h},transports:m})
|
|
346
|
+
return s===c.LogLevel.DEBUG&&p&&r()&&y.info("Enhanced logging initialized",{level:s,serviceName:h,logToFile:p,logFilePath:f,debugLogging:process.env.DEBUG_LOGGING}),{error(e,t){y.error(e,t)},warn(e,t){y.warn(e,t)},info(e,t){y.info(e,t)},debug(e,t){y.debug(e,t)},child(e){const r=y.child(e)
|
|
347
|
+
return{error(e,t){r.error(e,t)},warn(e,t){r.warn(e,t)},info(e,t){r.info(e,t)},debug(e,t){r.debug(e,t)},child(e){return o({...t,serviceName:`${h}:${e.component||"nested"}`})}}}}}var s=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
|
|
348
|
+
Object.defineProperty(exports,"__esModule",{value:!0}),exports.createLogger=o,exports.createEnvLogger=function(e={}){return o(e)},exports.isDebugToolsEnabled=r,exports.isDebugLoggingEnabled=function(){return!!r()&&"true"===process.env.DEBUG_LOGGING?.toLowerCase()},exports.getDebugLevelFromEnv=function(){if(!r())return c.LogLevel.INFO
|
|
349
|
+
const e=process.env.DEBUG_LEVEL?.toLowerCase()
|
|
350
|
+
return e&&Object.values(c.LogLevel).includes(e)?e:c.LogLevel.INFO}
|
|
351
|
+
const n=s(t(5124)),i=s(t(6928)),a=s(t(9896)),c=t(6540),l=t(7960),d=n.default.format.combine(n.default.format.timestamp(),n.default.format.colorize(),n.default.format.printf((({level:e,message:t,timestamp:r,...o})=>`${r} [${e}]: ${t}${Object.keys(o).length?" "+JSON.stringify(o,null,0):""}`))),u=n.default.format.combine(n.default.format.timestamp(),n.default.format.json()),g=n.default.format.combine(n.default.format.timestamp(),n.default.format.printf((({level:e,message:t,timestamp:r,service:o,component:s,...n})=>{const i=o?`[${o}]`:"",a=s?`[${s}]`:"",c=Object.keys(n).length?" "+JSON.stringify(n,null,2):""
|
|
352
|
+
return`${r} ${i}${a} [${e.toUpperCase()}]: ${t}${c}`}))),h=o({logToFile:!1,console:!0,level:c.LogLevel.INFO})
|
|
353
|
+
exports.default=h},3077(e,exports,t){Object.defineProperty(exports,"__esModule",{value:!0}),exports.HookExecutor=void 0
|
|
354
|
+
const r=t(8740),o=t(4627),s=t(4769)
|
|
355
|
+
exports.HookExecutor=class{constructor(e,t,o,s,n){this.logger=e,this.cache=new r.HookCache(e,t,s),this.ctx={logger:e,projectId:t,projectPath:o,apiClient:s,cachedDefaultModel:n?.cachedDefaultModel,trackChildPid:n?.trackChildPid}}setDefaultModel(e){this.ctx.cachedDefaultModel=e}setProjectId(e){this.ctx.projectId=e,this.cache.setProjectId(e)}async processEvent(e){const{eventType:t,sessionId:r,variables:s,timing:n}=e,i={triggered:0,hooks:[]}
|
|
356
|
+
if(!this.ctx.projectId)return this.logger.debug("No project ID, skipping hook event",{eventType:t}),i
|
|
357
|
+
try{const e=await this.cache.getHooksForEvent(t,r)
|
|
358
|
+
if(0===e.length)return i
|
|
359
|
+
const a=new Set,c=e.filter((e=>!a.has(e.id)&&(a.add(e.id),!0))),l=n||"after",d=c.filter((e=>(e.timing||"after")===l))
|
|
360
|
+
if(0===d.length)return i
|
|
361
|
+
const u={...s}
|
|
362
|
+
if(r&&(!u.parentSessionId||!u.agentType))try{const e=await this.ctx.apiClient.get(`/api/projects/${this.ctx.projectId}/cli-sessions/external/opencode/${encodeURIComponent(r)}`),t=e?.data||e
|
|
363
|
+
if(t?.agentType&&!u.agentType&&(u.agentType=t.agentType),!u.parentSessionId&&t?.parentSessionId){const e=await this.ctx.apiClient.get(`/api/projects/${this.ctx.projectId}/cli-sessions/${t.parentSessionId}`),r=e?.data||e
|
|
364
|
+
r?.externalSessionId&&(u.parentSessionId=r.externalSessionId)}}catch(e){this.logger.warn("Failed to enrich hook variables with session context",{sessionId:r,error:e+""})}const g=d.filter((e=>(0,o.evaluateCondition)(e.condition,u)))
|
|
365
|
+
if(0===g.length)return i
|
|
366
|
+
for(const e of g){await this.executeHook(e,u,r),i.triggered++,i.hooks.push(e.name)
|
|
367
|
+
const t=(0,o.determineEventToEmit)(e,u)
|
|
368
|
+
t&&await this.processCustomEvent(t,{...u,sessionId:r||""},0)}"session_idle"!==t&&"session_created"!==t&&r&&this.cache.preWarm("session_idle",r)}catch(e){this.logger.error("Error processing hook event",{eventType:t,error:e+""})}return i}async executeHook(e,t,r){const n=(0,o.interpolatePrompt)(e.promptTemplate,t),i=t.requestId
|
|
369
|
+
if(this.logger.info("Executing hook",{name:e.name,action:e.action,target:e.target,eventType:e.eventType,sessionId:r}),this.notifyHookTrigger(e).catch((()=>{})),"inject_prompt"===e.action)switch(e.target){case"same_session":default:await(0,s.executeSameSession)(n,r,this.ctx)
|
|
370
|
+
break
|
|
371
|
+
case"parent_session":case"ancestor_chain":await(0,s.executeInjectPrompt)(e,e.promptTemplate,r,t,this.ctx)}else await(0,s.executeBackground)(e,n,r,i,this.ctx)}async processCustomEvent(e,t,r){if(r>=10)return void this.logger.warn("Custom event chain depth exceeded",{eventName:e,chainDepth:r})
|
|
372
|
+
const n=e.startsWith("custom:")?e:"custom:"+e,i=await this.cache.getHooksForEvent(n,t.sessionId)
|
|
373
|
+
if(0===i.length)return
|
|
374
|
+
const a={...t,chainDepth:r+1+"",triggerEvent:n},c=i.filter((e=>(0,o.evaluateCondition)(e.condition,a)))
|
|
375
|
+
for(const e of c){const t=(0,o.interpolatePrompt)(e.promptTemplate,a),i=a.sessionId||""
|
|
376
|
+
if(this.logger.info("Executing custom event hook",{name:e.name,event:n,chainDepth:r}),"inject_prompt"===e.action)switch(e.target){case"same_session":default:await(0,s.executeSameSession)(t,i,this.ctx)
|
|
377
|
+
break
|
|
378
|
+
case"parent_session":case"ancestor_chain":await(0,s.executeInjectPrompt)(e,e.promptTemplate,i,a,this.ctx)}else await(0,s.executeBackground)(e,t,i,a.requestId,this.ctx)
|
|
379
|
+
const c=(0,o.determineEventToEmit)(e,a)
|
|
380
|
+
c&&await this.processCustomEvent(c,a,r+1)}}async notifyHookTrigger(e){try{await this.ctx.apiClient.post(`/api/projects/${this.ctx.projectId}/hook-triggers`,{hookId:e.id,hookName:e.name,eventType:e.eventType,action:e.action,target:e.target,agent:e.agent})}catch{}}}},3444(e,exports,t){var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
|
|
381
|
+
Object.defineProperty(exports,"__esModule",{value:!0}),exports.LogLevel=exports.defaultLogger=exports.isDebugToolsEnabled=exports.getDebugLevelFromEnv=exports.isDebugLoggingEnabled=exports.createEnvLogger=exports.createLogger=void 0
|
|
382
|
+
const o=t(2782)
|
|
383
|
+
Object.defineProperty(exports,"createLogger",{enumerable:!0,get(){return o.createLogger}}),Object.defineProperty(exports,"createEnvLogger",{enumerable:!0,get(){return o.createEnvLogger}}),Object.defineProperty(exports,"isDebugLoggingEnabled",{enumerable:!0,get(){return o.isDebugLoggingEnabled}}),Object.defineProperty(exports,"getDebugLevelFromEnv",{enumerable:!0,get(){return o.getDebugLevelFromEnv}}),Object.defineProperty(exports,"isDebugToolsEnabled",{enumerable:!0,get(){return o.isDebugToolsEnabled}})
|
|
384
|
+
const s=r(t(2782))
|
|
385
|
+
exports.defaultLogger=s.default
|
|
386
|
+
const n=t(6540)
|
|
387
|
+
Object.defineProperty(exports,"LogLevel",{enumerable:!0,get(){return n.LogLevel}}),exports.default=s.default},3785(module){module.exports=require("readline")},3807(module){module.exports=require("simple-git")},3962(e,exports,t){function r(){if(n)return n
|
|
388
|
+
const e=process.argv[1]||""
|
|
389
|
+
let t=""
|
|
390
|
+
if(e.includes("node_modules")){const r=e.lastIndexOf("/dist/")
|
|
391
|
+
r>0&&(t=e.substring(0,r+5))}else{const r=e.lastIndexOf("/dist-dev/")
|
|
392
|
+
if(r>0)t=e.substring(0,r+9)
|
|
393
|
+
else{const r=e.lastIndexOf("/dist/")
|
|
394
|
+
r>0&&(t=e.substring(0,r+5))}}if(!t){const e=process.cwd()
|
|
395
|
+
t=e.includes("alchemy-local")||e.includes("alchemy")?s.default.join(e,"dist-dev"):s.default.join(e,"dist")}const r=t.includes("dist-dev")||process.env.REMOTE_SERVER_URL?.includes("localhost")||!1,o=r?s.default.join(t,"mcp-direct.js"):t,i=process.env.REMOTE_SERVER_URL||"https://api.alchemylabcode.com"
|
|
396
|
+
return n={installPath:t,scriptPath:o,nodePath:process.execPath,isDevMode:r,serverName:"alchemy",remoteServerUrl:i},n}var o=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
|
|
397
|
+
Object.defineProperty(exports,"__esModule",{value:!0}),exports.getInstallContext=r,exports.clearInstallContextCache=function(){n=null},exports.isDevMode=function(e){return e?e.includes("dist-dev")||process.env.REMOTE_SERVER_URL?.includes("localhost")||!1:r().isDevMode},exports.getServerName=function(e){return e??r().isDevMode,"alchemy"}
|
|
398
|
+
const s=o(t(6928))
|
|
399
|
+
let n=null},4139(e,exports){Object.defineProperty(exports,"__esModule",{value:!0}),exports.ALCHEMY_DIR_NAME=exports.TOKEN_DIR_NAME=void 0,exports.TOKEN_DIR_NAME="alchemy-key",exports.ALCHEMY_DIR_NAME=".alchemy_lab"},4192(e,exports,t){Object.defineProperty(exports,"__esModule",{value:!0}),exports.BaseApiClient=void 0
|
|
400
|
+
const r=t(7163)
|
|
401
|
+
exports.BaseApiClient=class{constructor(e,t,o,s,n){this.failureCount=0,this.circuitState="closed",this.circuitResetTimeout=null,this.baseUrl=e,this.logger=t,this.metrics=o,this.authToken=s,this.options={retryAttempts:3,retryDelay:1e3,timeout:1e4,circuitBreakerThreshold:5,circuitBreakerResetTime:3e4,...n},this.logger.debug("BaseApiClient constructor options:",{providedOptions:n?JSON.stringify(n):"undefined",mergedOptions:JSON.stringify(this.options)})
|
|
402
|
+
try{(0,r.debugTokenAccess)(this,"BaseApiClient constructor")}catch(e){}}setAuthToken(e){this.authToken=e||void 0,this.logger.debug("Auth token updated",{hasToken:!!e,tokenLength:e?e.length:0})}getAuthToken(){return this.authToken}async requestWithRetry(e,t,r,o,s){if("open"===this.circuitState)throw this.logger.warn(`Circuit breaker open - rejecting request to ${e} ${t}`),Error("Service unavailable - circuit breaker open")
|
|
403
|
+
const n=t.startsWith("/")?t:"/"+t
|
|
404
|
+
let i=`${this.baseUrl}${n}`
|
|
405
|
+
if(s&&Object.keys(s).length>0){const e=Object.entries(s).filter((([e,t])=>null!=t)).map((([e,t])=>Array.isArray(t)?t.map((t=>`${encodeURIComponent(e)}=${encodeURIComponent(t+"")}`)).join("&"):`${encodeURIComponent(e)}=${encodeURIComponent(t+"")}`)).join("&")
|
|
406
|
+
i=`${i}${i.includes("?")?"&":"?"}${e}`}const a={"Content-Type":"application/json",Accept:"application/json",...o}
|
|
407
|
+
this.authToken?(a.Authorization="Bearer "+this.authToken,this.logger.debug("Using auth token for request",{method:e,path:n,tokenLength:this.authToken.length})):this.logger.warn(`No auth token available for ${e} ${t}`,{operation:"api-request",method:e,path:t,authStatus:"missing"})
|
|
408
|
+
const c=process.env.SERVICE_SHARED_SECRET||"dev-service-secret-change-in-production"
|
|
409
|
+
a["X-Service-Secret"]=c
|
|
410
|
+
let l=0
|
|
411
|
+
const d=this.options.retryAttempts,u={method:e,path:n},g=this.metrics.startTimer("api_request_duration"),h=e=>g({...u,...e})
|
|
412
|
+
for(;d>l;){l++
|
|
413
|
+
try{this.logger.debug(`Making ${e} request to ${i}`,{attempt:l,maxAttempts:d,hasAuth:!!a.Authorization,hasServiceIdentifier:!!a["X-Client-Type"]})
|
|
414
|
+
const t={method:e,headers:{...a},signal:AbortSignal.timeout(this.options.timeout||1e4)}
|
|
415
|
+
if("GET"!==e&&void 0!==r&&(t.body=JSON.stringify(r)),this.logger.debug("Request details",{method:e,url:i}),t.headers){const e={}
|
|
416
|
+
Object.entries(t.headers).forEach((([t,r])=>{"authorization"===t.toLowerCase()?e[t]="Bearer [redacted]":e[t]=r})),this.logger.debug("Request headers",{headers:e})}if("GET"!==e&&t.body){const e="string"==typeof t.body?t.body:JSON.stringify(t.body)
|
|
417
|
+
this.logger.debug("Request body",{bodyPreview:e.substring(0,200)+(e.length>200?"...":""),bodyLength:e.length})}const o=await fetch(i,t)
|
|
418
|
+
if(!o.ok){if(this.incrementFailureCount(),this.logger.error("API request failed with status "+o.status,{method:e,url:i,status:o.status,statusText:o.statusText,attempt:l}),401===o.status)throw h({success:!1,status:o.status,retries:l-1}),Error("Authentication failed - invalid or expired token")
|
|
419
|
+
if(429===o.status){const e=o.headers.get("Retry-After"),t=e?1e3*parseInt(e):2*(this.options.retryDelay||1e3)
|
|
420
|
+
if(this.logger.warn(`Rate limited (429), retrying after ${t}ms`,{attempt:l,maxAttempts:d,retryAfter:e}),d>l){await new Promise((e=>setTimeout(e,t)))
|
|
421
|
+
continue}}if(o.status>=500&&d>l){const e=(this.options.retryDelay||1e3)*Math.pow(2,l-1)
|
|
422
|
+
this.logger.warn(`Server error ${o.status}, retry in ${e}ms`,{attempt:l,maxAttempts:d}),await new Promise((t=>setTimeout(t,e)))
|
|
423
|
+
continue}let t=`API request failed with status ${o.status}: ${o.statusText}`,r={}
|
|
424
|
+
try{const e=await o.json()
|
|
425
|
+
if(e&&"object"==typeof e&&!1===e.success&&e.error){const o=e.error
|
|
426
|
+
o.message&&(t=o.message),r={code:o.code,statusCode:o.statusCode,retryable:o.retryable,details:o.details}}else e&&"object"==typeof e&&e.message&&(t=e.message)}catch(e){try{const e=await o.text()
|
|
427
|
+
e&&e.trim()&&(t=`${t}. Response: ${e.substring(0,200)}`)}catch(t){this.logger.debug("Failed to parse error response",{jsonError:e instanceof Error?e.message:e+"",textError:t instanceof Error?t.message:t+""})}}h({success:!1,status:o.status,retries:l-1})
|
|
428
|
+
const s=Error(t)
|
|
429
|
+
throw s.status=o.status,s.statusText=o.statusText,s.method=e,s.url=i,s.errorDetails=r,s}this.resetFailureCount()
|
|
430
|
+
const s=await o.json()
|
|
431
|
+
if(null===s||"object"!=typeof s)throw Error("Invalid API response: expected object, got "+typeof s)
|
|
432
|
+
if(s&&"object"==typeof s&&!("success"in s)){this.logger.warn("Non-standard API response detected - wrapping in standard format",{responseKeys:Object.keys(s)})
|
|
433
|
+
const e={success:!0,data:s}
|
|
434
|
+
return h({success:!0,status:o.status,retries:l-1,standardFormat:!1}),e}if(s&&"object"==typeof s&&"success"in s&&!1===s.success){const e=s
|
|
435
|
+
throw this.logger.error("API returned error response",{errorCode:e.error?.code,errorMessage:e.error?.message,statusCode:e.error?.statusCode}),h({success:!1,status:o.status,retries:l-1,errorCode:e.error?.code}),Error(`API error: ${e.error?.code}: ${e.error?.message}`)}if(s&&"object"==typeof s&&"success"in s&&!0===s.success&&"data"in s){const t=s
|
|
436
|
+
return h({success:!0,status:o.status,retries:l-1,standardFormat:!0}),this.logger.debug("API request succeeded with status "+o.status,{method:e,url:i,status:o.status,retries:l-1,hasMetadata:"metadata"in t}),t}return this.logger.warn("Invalid API response format: Unexpected structure",{responseType:typeof s,responseKeys:"object"==typeof s?Object.keys(s):[]}),{success:!0,data:s}}catch(t){if(l>=d)throw this.incrementFailureCount(),this.logger.error(`API request failed after ${l} attempts`,{method:e,url:i,error:t.message||t+"",status:t.status}),h({success:!1,error:!0,retries:l-1}),t
|
|
437
|
+
const r=Math.min((this.options.retryDelay||1e3)*Math.pow(2,l-1),5e3)
|
|
438
|
+
this.logger.warn(`API request failed, retrying in ${r}ms`,{method:e,url:i,error:t.message||t+"",attempt:l,maxAttempts:d}),await new Promise((e=>setTimeout(e,r)))}}throw Error("Unexpected end of requestWithRetry")}incrementFailureCount(){this.failureCount++,this.metrics.setGauge("api_failure_count",this.failureCount),"half-open"!==this.circuitState&&(this.failureCount<this.options.circuitBreakerThreshold||"closed"!==this.circuitState)||this.openCircuit()}resetFailureCount(){const e="closed"!==this.circuitState
|
|
439
|
+
this.failureCount=0,this.circuitState="closed",this.metrics.setGauge("api_failure_count",0),this.metrics.setGauge("circuit_breaker_open",0),this.circuitResetTimeout&&(clearTimeout(this.circuitResetTimeout),this.circuitResetTimeout=null),e&&this.logger.info("Circuit breaker closed after successful request")}openCircuit(){this.circuitState="open",this.metrics.setGauge("circuit_breaker_open",1),this.logger.warn(`Circuit breaker opened after ${this.failureCount} failures`),this.circuitResetTimeout&&clearTimeout(this.circuitResetTimeout)
|
|
440
|
+
const e=this.options.circuitBreakerResetTime||5e3
|
|
441
|
+
this.circuitResetTimeout=setTimeout((()=>{this.circuitState="half-open",this.logger.info("Circuit breaker half-open, next request will probe")}),e)}async get(e,t,r){return this.requestWithRetry("GET",e,void 0,r,t)}async post(e,t,r){return this.requestWithRetry("POST",e,t,r)}async put(e,t,r){return this.requestWithRetry("PUT",e,t,r)}async delete(e,t){return this.requestWithRetry("DELETE",e,void 0,t)}async patch(e,t,r){return this.requestWithRetry("PATCH",e,t,r)}}},4627(e,exports){function t(e,t){try{return function(e){const t=r.get(e)
|
|
442
|
+
if(t)return t
|
|
443
|
+
let o=e.replace(/[.+^${}()|[\]\\]/g,"\\$&").replace(/\*\*/g,"{{GLOBSTAR}}").replace(/\*/g,"[^/]*").replace(/\?/g,".").replace(/\{\{GLOBSTAR\}\}/g,".*")
|
|
444
|
+
const s=RegExp(`^${o}$`)
|
|
445
|
+
return r.size>500&&r.clear(),r.set(e,s),s}(t).test(e)}catch{return!1}}Object.defineProperty(exports,"__esModule",{value:!0}),exports.evaluateCondition=function(e,r){if(!e||!e.conditions||0===e.conditions.length)return!0
|
|
446
|
+
const o=e.conditions.map((e=>{const o=r[e.field]||""
|
|
447
|
+
return"paths"===e.field&&o.includes(", ")?function(e,r,o){switch(r){case"matches":return e.some((e=>t(e,o)))
|
|
448
|
+
case"not_matches":return e.every((e=>!t(e,o)))
|
|
449
|
+
case"contains":return e.some((e=>e.includes(o)))
|
|
450
|
+
case"not_contains":return e.every((e=>!e.includes(o)))
|
|
451
|
+
case"equals":return e.some((e=>e===o))
|
|
452
|
+
case"not_equals":return e.every((e=>e!==o))
|
|
453
|
+
default:return!0}}(o.split(", "),e.operator,e.value):function(e,r,o){switch(r){case"matches":return t(e,o)
|
|
454
|
+
case"not_matches":return!t(e,o)
|
|
455
|
+
case"contains":return e.includes(o)
|
|
456
|
+
case"not_contains":return!e.includes(o)
|
|
457
|
+
case"equals":return e===o
|
|
458
|
+
case"not_equals":return e!==o
|
|
459
|
+
default:return!0}}(o,e.operator,e.value)}))
|
|
460
|
+
return"OR"===e.logic?o.some((e=>e)):o.every((e=>e))},exports.interpolatePrompt=function(e,t){let r=e
|
|
461
|
+
for(const[e,o]of Object.entries(t))r=r.replace(RegExp(`\\{\\{${e}\\}\\}`,"g"),o)
|
|
462
|
+
return r},exports.determineEventToEmit=function(e,t){if(e.emitEventOnCondition){const{condition:r,event:s}=e.emitEventOnCondition,n=t[r.field]||""
|
|
463
|
+
let i=!1
|
|
464
|
+
switch(r.operator){case"contains":i=n.includes(r.value)
|
|
465
|
+
break
|
|
466
|
+
case"not_contains":i=!n.includes(r.value)
|
|
467
|
+
break
|
|
468
|
+
case"equals":i=n===r.value
|
|
469
|
+
break
|
|
470
|
+
case"not_equals":i=n!==r.value
|
|
471
|
+
break
|
|
472
|
+
case"matches":try{o.has(r.value)||(o.set(r.value,RegExp(r.value)),o.size>500&&o.clear()),i=o.get(r.value).test(n)}catch{i=!1}break
|
|
473
|
+
case"not_matches":try{o.has(r.value)||(o.set(r.value,RegExp(r.value)),o.size>500&&o.clear()),i=!o.get(r.value).test(n)}catch{i=!0}}if(i)return s}return e.emitEvent||null},exports.createToolCallHash=function(e,t){return`${e}:${t?.substring(0,100)||""}`},exports.isDuplicateToolCall=function(e,t=Date.now()){const r=s.get(e)
|
|
474
|
+
if(r&&n>t-r)return!0
|
|
475
|
+
for(const[e,r]of s.entries())t-r>2*n&&s.delete(e)
|
|
476
|
+
return s.set(e,t),!1},exports.tryMarkMessageProcessed=function(e,t){if(!t)return!0
|
|
477
|
+
if(i.get(e)===t)return!1
|
|
478
|
+
if(i.set(e,t),i.size>100){const e=i.keys().next().value
|
|
479
|
+
e&&i.delete(e)}return!0}
|
|
480
|
+
const r=new Map,o=new Map,s=new Map,n=2e3,i=new Map},4769(e,exports,t){function r(e){return(0,s.getOpenCodeServerUrl)(e.projectPath,e.logger)}async function o(e,t,o){const s=r(o)
|
|
481
|
+
if(!s||!t)return!1
|
|
482
|
+
try{const r=await async function(e,t){try{const r=await t.apiClient.get(`/api/projects/${t.projectId}/cli-sessions/external/opencode/${encodeURIComponent(e)}`),o=r?.data||r
|
|
483
|
+
return o?.agentType||""}catch{return""}}(t,o)||void 0
|
|
484
|
+
let n
|
|
485
|
+
if(o.cachedDefaultModel){const[e,...t]=o.cachedDefaultModel.split("/")
|
|
486
|
+
n={providerID:e,modelID:t.join("/")}}const i=`${s}/session/${t}/prompt_async`,a=await fetch(i,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({parts:[{type:"text",text:e}],...r?{agent:r}:{},...n?{model:n}:{}})})
|
|
487
|
+
return a.ok?(o.logger.debug("Message sent to session",{sessionId:t}),!0):(o.logger.warn("Failed to send message to session",{sessionId:t,status:a.status}),!1)}catch(e){return o.logger.warn("Error sending message to session",{sessionId:t,error:e+""}),!1}}Object.defineProperty(exports,"__esModule",{value:!0}),exports.executeBackground=async function(e,t,r,o,n){const i=await(0,s.spawnActionAgent)({agent:e.agent,prompt:t,cwd:n.projectPath,projectPath:n.projectPath,projectId:n.projectId,title:"Hook: "+e.name,requestId:o,parentSessionId:"independent_session"===e.target?void 0:r||void 0,apiClient:n.apiClient,logger:n.logger,trackChildPid:n.trackChildPid})
|
|
488
|
+
i.success||n.logger.warn("Background hook spawn failed",{hook:e.name,error:i.error})},exports.executeSameSession=async function(e,t,r){await o(e,t,r)},exports.executeInjectPrompt=async function(e,r,s,n,i){let a=[]
|
|
489
|
+
if("ancestor_chain"===e.target)try{const e=await i.apiClient.get(`/api/projects/${i.projectId}/cli-sessions/external/opencode/${encodeURIComponent(s)}/ancestors`)
|
|
490
|
+
a=(e?.data||e||[]).map((e=>({externalSessionId:e.externalSessionId,agentType:e.agentType,title:e.title}))),i.logger.debug("inject_prompt: ancestors fetched",{sessionId:s,count:a.length,ancestors:a.map((e=>e.externalSessionId))})}catch(e){i.logger.warn("inject_prompt: ancestors fetch failed, falling back to parent",{sessionId:s,error:e+""})}if(0===a.length)try{const e=await i.apiClient.get(`/api/projects/${i.projectId}/cli-sessions/external/opencode/${encodeURIComponent(s)}/parent`),t=e?.data?.parentExternalSessionId||e?.parentExternalSessionId
|
|
491
|
+
t&&(a=[{externalSessionId:t}])}catch{}if(0===a.length)return void i.logger.debug("inject_prompt: no ancestors found",{sessionId:s})
|
|
492
|
+
const c=a.map((e=>`${e.agentType||"agent"}${e.title?" "+e.title:""} (${e.externalSessionId})`)).join(" \u2192 "),{interpolatePrompt:l}=await Promise.resolve().then(t.bind(t,4627)),d={...n,chainInfo:c}
|
|
493
|
+
i.logger.debug("inject_prompt: interpolating",{variables:Object.keys(d),chainInfo:c,sessionId:n.sessionId,agentType:n.agentType})
|
|
494
|
+
const u=l(r,d)
|
|
495
|
+
for(const e of a)i.logger.debug("inject_prompt: sending to ancestor",{targetSessionId:e.externalSessionId,agentType:e.agentType}),await o("\x3c!-- inserted-prompt --\x3e"+u,e.externalSessionId,i)},exports.executeNotification=async function(e,t){const o=r(t)
|
|
496
|
+
if(o)try{const t=o+"/tui/toast"
|
|
497
|
+
await fetch(t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({title:"Background Work Complete",message:e,variant:"success",duration:8e3})})}catch(e){t.logger.warn("Failed to send toast notification",{error:e+""})}},exports.executeDirectReport=async function(e,t,r){try{const s="http://localhost:3456/api/direct-report",n=await fetch(s,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sessionId:e,requestId:t||void 0,status:"success"})})
|
|
498
|
+
if(!n.ok)return void r.logger.warn("Direct report failed",{sessionId:e,status:n.status})
|
|
499
|
+
const i=await n.json()
|
|
500
|
+
if(!i.success||!i.data)return void r.logger.warn("Direct report returned failure",{sessionId:e})
|
|
501
|
+
const{notifiedSessions:a,report:c,parentExternalSessionId:l}=i.data,d=a||(l?[{externalSessionId:l}]:[])
|
|
502
|
+
for(const e of d){const t=c?.content?.reportStatus||"success",s=[`\x3c!-- inserted-prompt --\x3e${{success:"\u2705",partial:"\u26a0\ufe0f",failure:"\u274c",blocked:"\u{1f6ab}"}[t]||"\u{1f4cb}"} **Worker Report \u2014 ${t}**`,"",c?.content?.summary||"Task completed",...c?.content?.details?["",c.content.details]:[]].join("\n")
|
|
503
|
+
await o(s,e.externalSessionId,r)}}catch(t){r.logger.warn("Error in direct report",{sessionId:e,error:t+""})}}
|
|
504
|
+
const s=t(8148)},5086(module){module.exports=require("ws")},5124(module){module.exports=require("winston")},5317(module){module.exports=require("child_process")},5607(e,exports,t){Object.defineProperty(exports,"__esModule",{value:!0}),exports.executeAction=async function(e,t,r){const o=u[e]
|
|
505
|
+
if(!o)return{success:!1,message:"Unknown action type: "+e,error:"No handler registered for action type: "+e}
|
|
506
|
+
try{return await o(t,r)}catch(t){const o=(0,d.getErrorMessage)(t)
|
|
507
|
+
return r.logger.error("Action handler failed",{actionType:e,error:o}),{success:!1,message:"Action failed: "+o,error:o}}}
|
|
508
|
+
const r=t(6636),o=t(7453),s=t(5700),n=t(848),i=t(9735),a=t(7767),c=t(9266),l=t(6555),d=t(8824),u={revert_request:r.handleRevertRequestAction,"revert-request":r.handleRevertRequestAction,complete_request:o.handleCompleteRequestAction,"complete-request":o.handleCompleteRequestAction,"finalize-request":n.handleFinalizeRequestAction,"approve-request":i.handleApproveRequestAction,"approve-and-work-request":a.handleApproveAndWorkRequestAction,"restart-mcp":s.handleRestartMcpAction,"opencode-proxy":c.handleOpenCodeProxyAction,"run-tests":l.handleRunTestsAction}},5700(e,exports,t){var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
|
|
509
|
+
Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleRestartMcpAction=async function(e,t){const{logger:r,projectPath:n}=t,i=e.reason||"Manual restart requested"
|
|
510
|
+
r.info("Executing restart-mcp action",{reason:i})
|
|
511
|
+
try{const e=function(e){return s.default.join(e,".alchemy_lab","mcp-restart-trigger")}(n),t=s.default.dirname(e)
|
|
512
|
+
await o.default.mkdir(t,{recursive:!0})
|
|
513
|
+
const a=JSON.stringify({timestamp:(new Date).toISOString(),reason:i},null,2)
|
|
514
|
+
return await o.default.writeFile(e,a,"utf-8"),r.info("MCP restart triggered",{triggerPath:e,reason:i}),{success:!0,message:"MCP restart triggered: "+i,data:{triggerPath:e}}}catch(e){const t=e instanceof Error?e.message:e+""
|
|
515
|
+
return r.error("Failed to trigger MCP restart",{error:t}),{success:!1,message:"Failed to trigger MCP restart: "+t,error:t}}}
|
|
516
|
+
const o=r(t(1943)),s=r(t(6928))},6540(e,exports){var t
|
|
517
|
+
Object.defineProperty(exports,"__esModule",{value:!0}),exports.LogLevel=void 0,function(e){e.ERROR="error",e.WARN="warn",e.INFO="info",e.DEBUG="debug"}(t||(exports.LogLevel=t={}))},6555(e,exports,t){function r(e,process,t,r,o=!1){const s=process.config||{},n=(s.setup,s.commands||[])
|
|
518
|
+
let i=`You are a test runner agent. Your job is to verify that request ${e.localId||e.id} ("${e.title}") passes all required tests.\n\n## Test Process: ${process.name}\n${process.description?`\n${process.description}\n`:""}\n${process.scope?`**Scope:** Files under \`${process.scope}\`\n`:""}\n## Project ID: ${r}\n\n## Test Commands\nExecute these in order:\n`
|
|
519
|
+
for(let e=0;e<n.length;e++){const t=n[e]
|
|
520
|
+
i+=`\n### ${e+1}. ${t.name}${!1!==t.required?" (REQUIRED)":" (optional)"}\n- **Command:** \`${t.command}\`\n- **Timeout:** ${t.timeout||300}s\n`}if(i+=o?`\n## Instructions\n\nYou are in interactive mode. Present the test plan to the user and **wait for their direction** before running anything.\n\n1. Greet the user and list the test commands available for this process.\n2. Wait for the user to tell you to proceed (they may want to run specific commands, skip some, or run all).\n3. Use \`execute_test_command\` with processId="${process.id}" and the command name to run each command.\n4. After each command, explain the result and wait for the user before continuing.\n5. When the user is satisfied, call \`report_test_results\` with the overall status.\n\nYou are read-only \u2014 do NOT modify any source code. Your job is to verify, not fix.`:`\n## Instructions\n\nSetup commands have already been executed. Use the \`execute_test_command\` tool to run each test command:\n\n1. For each command above, call \`execute_test_command\` with processId="${process.id}" and the command name.\n2. Check if it passed or failed.\n3. If it fails: try to determine why \u2014 is it a real bug, a flaky test, or an environment issue? Retry once if it looks flaky.\n4. When all commands are done, call \`report_test_results\` with the overall status and per-command results.\n - If all REQUIRED commands passed \u2192 status "passed"\n - If any REQUIRED command failed \u2192 status "failed" with diagnosis of each failure\n\nYou are read-only \u2014 do NOT modify any source code. Your job is to verify, not fix.`,s.verifyAcceptanceCriteria&&e.acceptanceCriteria?.length>0){i+="\n\n## Acceptance Criteria Verification\n\nAfter running all commands, verify each acceptance criterion by reading code changes and running ad-hoc commands as needed. This is **advisory only** \u2014 it does not affect the overall pass/fail status.\n\nCriteria to verify:\n"
|
|
521
|
+
for(const t of e.acceptanceCriteria)i+=`- ${t}\n`
|
|
522
|
+
i+='\nFor each criterion, report in the `acceptanceCriteria` array of your results:\n- **criterion**: The criterion text\n- **status**: "verified" (confirmed in code), "unverified" (not found/not implemented), or "unclear" (ambiguous)\n- **evidence**: How you verified it, or why you couldn\'t\n'}return i}Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleRunTestsAction=async function(e,d){const{requestId:u,testProcessIds:g,parentSessionId:h,mode:p}=e,f="interactive"===p,{logger:m,apiClient:y,projectId:v,projectPath:w,trackChildPid:C}=d
|
|
523
|
+
if(!u)return{success:!1,message:"requestId is required",error:"Missing requestId in payload"}
|
|
524
|
+
m.info("Starting run-tests action",{requestId:u,testProcessIds:g})
|
|
525
|
+
try{const d=await y.get(`/api/projects/${v}/requests/${u}`)
|
|
526
|
+
if(!d.success||!d.data)return{success:!1,message:`Request ${u} not found`,error:"Request not found"}
|
|
527
|
+
const p=d.data,I=p.branchName||"requests/"+u
|
|
528
|
+
let E
|
|
529
|
+
if(g?.length)E=(await Promise.all(g.map((e=>y.get(`/api/projects/${v}/test-processes/${e}`))))).filter((e=>e.success&&e.data)).map((e=>e.data))
|
|
530
|
+
else{const e=await y.get(`/api/projects/${v}/test-processes/active`)
|
|
531
|
+
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"}
|
|
532
|
+
const k=(0,s.join)(w,c,l),b=(0,s.join)(k,u),j=(0,n.simpleGit)(w)
|
|
533
|
+
if((0,o.existsSync)(b))try{const e=(0,n.simpleGit)(b)
|
|
534
|
+
await e.raw(["reset","--hard",I]),m.info("Reset test worktree to latest branch HEAD",{worktreePath:b,branchName:I})}catch(e){m.warn("Could not reset test worktree, recreating",{error:(0,i.getErrorMessage)(e)})
|
|
535
|
+
try{await j.raw(["worktree","remove","--force",b])}catch{}}if(!(0,o.existsSync)(b)){(0,o.mkdirSync)(k,{recursive:!0})
|
|
536
|
+
try{if(!(await j.branch()).all.includes(I))return{success:!1,message:`Branch ${I} not found`,error:"Branch not found"}
|
|
537
|
+
await j.raw(["worktree","add","--detach",b,I]),m.info("Created test worktree (detached)",{worktreePath:b,branchName:I})}catch(e){if(!(e+"").includes("already exists"))return{success:!1,message:"Failed to create test worktree: "+(0,i.getErrorMessage)(e),error:(0,i.getErrorMessage)(e)}
|
|
538
|
+
m.info("Test worktree already exists, using as-is",{worktreePath:b})}}const{execSync:T}=await Promise.resolve().then(t.t.bind(t,5317,23))
|
|
539
|
+
for(const e of E){const t=e.config?.setup||[]
|
|
540
|
+
if(0!==t.length){m.info("Running setup commands for test process",{processName:e.name,commandCount:t.length})
|
|
541
|
+
for(const r of t)try{m.info("Setup command",{command:r,worktreePath:b}),T(r,{cwd:b,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)})}}}const S=[],P=[]
|
|
542
|
+
for(const process of E){let t
|
|
543
|
+
const o=e.testRunIds?.[S.length]
|
|
544
|
+
try{o?(await y.patch(`/api/projects/${v}/test-runs/${o}`,{worktreePath:b}),t=(await y.get(`/api/projects/${v}/test-runs/${o}`)).data,S.push(t.id)):(t=(await y.post(`/api/projects/${v}/test-runs`,{requestId:u,testProcessId:process.id,worktreePath:b,parentSessionId:h})).data,S.push(t.id))}catch(e){m.error("Failed to get/create test run record",{requestId:u,processId:process.id,preCreatedId:o,error:(0,i.getErrorMessage)(e)})
|
|
545
|
+
continue}const s=r(p,process,0,v,f),n=f?"tester":"auto-tester"
|
|
546
|
+
try{const e=await(0,a.spawnActionAgent)({agent:n,prompt:s,cwd:b,projectPath:w,projectId:v,title:`[${u}] Test: ${process.name}`,requestId:u,parentSessionId:h,allowParallel:!0,apiClient:y,logger:m,trackChildPid:C})
|
|
547
|
+
if(!e.success)throw Error(e.error||"Spawn failed")
|
|
548
|
+
P.push(t.id),e.sessionId&&y.patch(`/api/projects/${v}/test-runs/${t.id}`,{testerSessionId:e.sessionId}).catch((e=>m.warn("Failed to set testerSessionId on test run",{error:(0,i.getErrorMessage)(e)}))),m.info("Spawned test-runner agent",{testRunId:t.id,processName:process.name,worktreePath:b,pid:e.pid,sessionId:e.sessionId})}catch(e){m.error("Failed to spawn test-runner agent",{testRunId:t.id,error:(0,i.getErrorMessage)(e)})
|
|
549
|
+
try{await y.patch(`/api/projects/${v}/test-runs/${t.id}`,{status:"error",results:{steps:[],summary:"Failed to spawn test agent: "+(0,i.getErrorMessage)(e)}})}catch{}}}return 0===P.length?{success:!1,message:"Failed to spawn any test agents",error:"All agent spawns failed"}:{success:!0,message:`Started ${P.length} test run(s) for request ${u}`,data:{testRunIds:S,agentsSpawned:P.length,processCount:E.length,worktreePath:b}}}catch(e){const t=(0,i.getErrorMessage)(e)
|
|
550
|
+
return m.error("run-tests action failed",{requestId:u,error:t}),{success:!1,message:"Failed to run tests: "+t,error:t}}}
|
|
551
|
+
const o=t(9896),s=t(6928),n=t(3807),i=t(8824),a=t(8148),c=".alchemy_lab",l="test-worktrees"},6636(e,exports,t){Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleRevertRequestAction=async function(e,t){const{requestId:s,reason:n}=e,{logger:i,apiClient:a,projectPath:c,projectId:l}=t
|
|
552
|
+
if(!s)return{success:!1,message:"requestId is required",error:"Missing requestId in payload"}
|
|
553
|
+
if(!n)return{success:!1,message:"reason is required",error:"Missing reason in payload"}
|
|
554
|
+
i.info("Starting revert_request action",{requestId:s,reason:n})
|
|
555
|
+
try{const e=await a.post(`/api/projects/${l}/requests/${s}/validate-revert`,{})
|
|
556
|
+
if(!e.success||!e.data)return{success:!1,message:"Server validation failed: "+(e.data?.reason||"Unknown error"),error:"Validation failed"}
|
|
557
|
+
const t=e.data
|
|
558
|
+
if(!t.canRevert){let e="Cannot revert request: "+t.reason
|
|
559
|
+
return t.lifoViolation?.blockingRequests?.length>0&&(e+="\n\nRequests that must be reverted first:\n",t.lifoViolation.blockingRequests.forEach(((t,r)=>{e+=`${r+1}. ${t.requestId}: ${t.title}\n`}))),{success:!1,message:e,error:t.reason}}if("cancel"===t.operationType){const e=await a.post(`/api/projects/${l}/requests/${s}/finalize-revert`,{reason:n,operationType:"cancel"})
|
|
560
|
+
return e.success?{success:!0,message:"Successfully canceled request "+s,data:{operationType:"cancel",reason:n}}:{success:!1,message:"Failed to finalize cancel operation",error:e.error?.message||"Finalize failed"}}let d=t.mergeCommitHash
|
|
561
|
+
if(!d){i.info("No merge commit hash from server, searching git log",{requestId:s})
|
|
562
|
+
const e=await async function(e,t){try{const o=(0,r.simpleGit)(t),s=(await o.raw(["log","--merges","--format=%H %s","HEAD"])).trim().split("\n").filter((e=>e.trim())),n=[`Merge requests/${e}-`,`Merge requests/${e} `,"Merge branch 'requests/"+e,"requests/"+e]
|
|
563
|
+
for(const e of s){const[t,...r]=e.split(" "),o=r.join(" ")
|
|
564
|
+
for(const e of n)if(o.includes(e))return{found:!0,commitHash:t}}return{found:!1,message:"No merge commit found for request "+e}}catch(e){return{found:!1,message:"Error searching for merge commit: "+(0,o.getErrorMessage)(e)}}}(s,c)
|
|
565
|
+
if(!e.found||!e.commitHash)return{success:!1,message:"Could not find merge commit for request "+s,error:"Merge commit not found"}
|
|
566
|
+
d=e.commitHash}const u=(0,r.simpleGit)(c),g=await u.status(),h=(await u.branch()).all.includes("main")?"main":"master"
|
|
567
|
+
let p
|
|
568
|
+
g.current!==h&&(i.info("Switching to default branch",{from:g.current,to:h}),await u.checkout(h))
|
|
569
|
+
try{i.info("Executing git revert",{mergeCommitHash:d.substring(0,8)}),await u.raw(["revert","-m","1","--no-edit",d])
|
|
570
|
+
const e=await u.log({maxCount:1})
|
|
571
|
+
p=e.latest?.hash||"",i.info("Git revert completed",{originalCommit:d.substring(0,8),revertCommit:p.substring(0,8)})}catch(e){const t=(0,o.getErrorMessage)(e)
|
|
572
|
+
i.error("Git revert failed",{requestId:s,error:t})
|
|
573
|
+
try{await u.raw(["revert","--abort"])}catch(e){i.error("CRITICAL: git revert --abort failed \u2014 repo may be in inconsistent state",{requestId:s,error:e instanceof Error?e.message:e+""})}return{success:!1,message:"Git revert failed: "+t,error:t}}const f=await a.post(`/api/projects/${l}/requests/${s}/finalize-revert`,{reason:n,operationType:"revert",revertCommitHash:p,originalMergeCommitHash:d})
|
|
574
|
+
return f.success?{success:!0,message:"Successfully reverted request "+s,data:{operationType:"revert",originalMergeCommit:d,revertCommitHash:p,reason:n}}:(i.error("CRITICAL: Git revert succeeded but finalization failed",{requestId:s,revertCommitHash:p,error:f.error}),{success:!1,message:`CRITICAL: Git revert succeeded (commit: ${p.substring(0,8)}) but server finalization failed`,error:"Finalization failed"})}catch(e){const t=(0,o.getErrorMessage)(e)
|
|
575
|
+
return i.error("Revert_request action failed",{requestId:s,error:t}),{success:!1,message:"Failed to revert request: "+t,error:t}}}
|
|
576
|
+
const r=t(3807),o=t(8824)},6800(e,exports,t){function r(e){return new Promise((t=>{const r=s.default.createServer()
|
|
577
|
+
r.once("error",(e=>{e.code,t(!1)})),r.once("listening",(()=>{r.close((()=>{t(!0)}))})),r.listen(e)}))}var o=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
|
|
578
|
+
Object.defineProperty(exports,"__esModule",{value:!0}),exports.isPortAvailable=r,exports.findAvailablePort=async function(e,t=10){for(let o=0;t>o;o++){const t=e+o
|
|
579
|
+
if(await r(t))return t}throw Error(`No available port found after ${t} attempts starting from port ${e}`)}
|
|
580
|
+
const s=o(t(9278))},6895(module){module.exports=require("chalk")},6928(module){module.exports=require("path")},6982(module){module.exports=require("crypto")},7163(e,exports){Object.defineProperty(exports,"__esModule",{value:!0}),exports.debugTokenAccess=function(){},exports.compareTokens=function(){},exports.debugTokenStorage=function(){},exports.disableTokenDebugging=function(){},exports.enableTokenDebugging=function(){},new WeakMap},7210(e,exports,t){Object.defineProperty(exports,"__esModule",{value:!0}),exports.tryMarkMessageProcessed=exports.createToolCallHash=exports.isDuplicateToolCall=exports.determineEventToEmit=exports.interpolatePrompt=exports.evaluateCondition=exports.HookCache=exports.HookExecutor=void 0
|
|
581
|
+
var r=t(3077)
|
|
582
|
+
Object.defineProperty(exports,"HookExecutor",{enumerable:!0,get(){return r.HookExecutor}})
|
|
583
|
+
var o=t(8740)
|
|
584
|
+
Object.defineProperty(exports,"HookCache",{enumerable:!0,get(){return o.HookCache}})
|
|
585
|
+
var s=t(4627)
|
|
586
|
+
Object.defineProperty(exports,"evaluateCondition",{enumerable:!0,get(){return s.evaluateCondition}}),Object.defineProperty(exports,"interpolatePrompt",{enumerable:!0,get(){return s.interpolatePrompt}}),Object.defineProperty(exports,"determineEventToEmit",{enumerable:!0,get(){return s.determineEventToEmit}}),Object.defineProperty(exports,"isDuplicateToolCall",{enumerable:!0,get(){return s.isDuplicateToolCall}}),Object.defineProperty(exports,"createToolCallHash",{enumerable:!0,get(){return s.createToolCallHash}}),Object.defineProperty(exports,"tryMarkMessageProcessed",{enumerable:!0,get(){return s.tryMarkMessageProcessed}})},7252(module){module.exports=require("express")},7453(e,exports,t){Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleCompleteRequestAction=async function(e,t){const{requestId:s,reason:n}=e,{logger:i,apiClient:a,projectPath:c,projectId:l}=t
|
|
587
|
+
if(!s)return{success:!1,message:"requestId is required",error:"Missing requestId in payload"}
|
|
588
|
+
i.info("Starting complete_request action",{requestId:s,reason:n})
|
|
589
|
+
try{const e=await a.post(`/api/projects/${l}/requests/${s}/validate-completion`,{})
|
|
590
|
+
if(!e.success||!e.data)return{success:!1,message:"Server validation failed: "+(e.data?.reason||"Unknown error"),error:"Validation failed"}
|
|
591
|
+
const c=e.data
|
|
592
|
+
if(!c.canComplete){let e="Cannot complete request: "+c.reason
|
|
593
|
+
return c.lifoViolation?.blockingRequests?.length>0&&(e+="\n\nRequests that must be re-completed first:\n",c.lifoViolation.blockingRequests.forEach(((t,r)=>{e+=`${r+1}. ${t.requestId}: ${t.title}\n`}))),c.blockedByTasks?.length>0&&(e+="\n\nTasks that must be completed first:\n",c.blockedByTasks.forEach(((t,r)=>{e+=`${r+1}. ${t.taskId}: ${t.title} (${t.status})\n`}))),{success:!1,message:e,error:c.reason}}if("revert-removal"!==c.mergeStrategy)return{success:!1,message:"Action server only handles reverted request re-completion. Got merge strategy: "+c.mergeStrategy,error:"Unsupported merge strategy: "+c.mergeStrategy}
|
|
594
|
+
if(!c.revertInfo?.revertCommitHash)return{success:!1,message:"Server validated but did not provide revert commit hash",error:"Missing revert commit hash"}
|
|
595
|
+
const d=await async function(e,t,s){const{logger:n,projectPath:i}=s
|
|
596
|
+
n.info("Performing revert removal",{requestId:e,revertCommitHash:t.substring(0,8)})
|
|
597
|
+
const a=(0,r.simpleGit)(i),c=(await a.branch()).all.includes("main")?"main":"master",l=await a.status()
|
|
598
|
+
if(l.current!==c&&(n.info("Switching to default branch",{from:l.current,to:c}),await a.checkout(c)),(await a.status()).files.length>0)return{success:!1,message:"Cannot re-complete request: working directory has uncommitted changes",error:"Uncommitted changes present"}
|
|
599
|
+
if((await a.revparse(["HEAD"])).trim()!==t){const e=await a.raw(["rev-list","--count",t+"..HEAD"]),r=parseInt(e.trim(),10)
|
|
600
|
+
if(r>0)return{success:!1,message:`Cannot re-complete: main branch has ${r} commit(s) since the revert. Create a new request instead.`,error:"Main branch has changed since revert"}}try{const r=(await a.raw(["rev-parse",t+"^"])).trim()
|
|
601
|
+
await a.reset(["--hard",r])
|
|
602
|
+
const o=await a.revparse(["HEAD"])
|
|
603
|
+
if(o.trim()!==r)throw Error(`Git reset verification failed: expected ${r.substring(0,8)}, got ${o.substring(0,8)}`)
|
|
604
|
+
return n.info("Revert removal succeeded",{requestId:e,removedCommit:t.substring(0,8),restoredTo:r.substring(0,8)}),{success:!0,message:"Successfully re-completed request "+e,data:{operationType:"revert-removal",removedRevertCommit:t,restoredToCommit:r}}}catch(t){const r=(0,o.getErrorMessage)(t)
|
|
605
|
+
return n.error("Revert removal failed",{requestId:e,error:r}),{success:!1,message:"Failed to remove revert commit: "+r,error:r}}}(s,c.revertInfo.revertCommitHash,t)
|
|
606
|
+
if(!d.success)return d
|
|
607
|
+
try{await a.post(`/api/projects/${l}/requests/${s}/finalize-completion`,{summary:n||"Re-completed from UI",acceptanceEvidence:[{evidence:"Re-completed reverted request via GUI action"}],mergeResult:{mergeCommitHash:d.data.restoredToCommit,method:"revert-removal"}})}catch(e){return i.error("Merge succeeded but finalization failed",{requestId:s,error:(0,o.getErrorMessage)(e)}),{success:!1,message:"Git revert removal succeeded but server finalization failed: "+(0,o.getErrorMessage)(e),error:"Finalization failed"}}return d}catch(e){const t=(0,o.getErrorMessage)(e)
|
|
608
|
+
return i.error("Complete_request action failed",{requestId:s,error:t}),{success:!1,message:"Failed to complete request: "+t,error:t}}}
|
|
609
|
+
const r=t(3807),o=t(8824)},7767(e,exports,t){Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleApproveAndWorkRequestAction=async function(e,t){const{requestId:s}=e,{logger:n,apiClient:i,projectId:a,projectPath:c,trackChildPid:l}=t
|
|
610
|
+
if(!s)return{success:!1,message:"requestId is required",error:"Missing requestId in payload"}
|
|
611
|
+
n.info("Starting approve-and-work-request action",{requestId:s})
|
|
612
|
+
try{const e=await i.get(`/api/projects/${a}/requests/${s}`)
|
|
613
|
+
if(!e.success||!e.data)return{success:!1,message:`Request ${s} not found`,error:"Request not found"}
|
|
614
|
+
const t=e.data
|
|
615
|
+
if("pending_approval"!==t.status)return{success:!1,message:`Cannot approve request with status '${t.status}'. Only requests in 'pending_approval' status can be approved.`,error:"Invalid status: "+t.status}
|
|
616
|
+
if(!(await i.patch(`/api/projects/${a}/requests/${s}`,{status:"open"})).success)return{success:!1,message:"Failed to update request status",error:"Update failed"}
|
|
617
|
+
const d=Array.isArray(t.acceptanceCriteria)?t.acceptanceCriteria.map((e=>e.description||e)).join("\n- "):"",u=`Work on request ${s}: ${t.title}\n\nDescription: ${t.description}\n\n${d?"Acceptance Criteria:\n- "+d:""}\n\nThis request has just been approved. Please begin implementation.`
|
|
618
|
+
let g=!1,h=!1
|
|
619
|
+
if(t.cliOrigin?.sessionId)try{await i.post(`/api/projects/${a}/session-messages/reports`,{fromSessionId:"gui-approval",toSessionId:t.cliOrigin.sessionId,status:"success",summary:`Request ${s} "${t.title}" has been approved. Please begin implementation.`,details:u}),g=!0,n.info("Stored session report for creating session",{requestId:s,sessionId:t.cliOrigin.sessionId})
|
|
620
|
+
try{const e=(0,o.getOpenCodeServerUrl)(c,n)
|
|
621
|
+
if(e){const r=`\u2705 [Request Approved]\n\nRequest ${s} "${t.title}" has been approved.\nPlease begin implementation.\n\nDetails:\n${u}`,o=`${e}/session/${t.cliOrigin.sessionId}/prompt_async`,i=await fetch(o,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({parts:[{type:"text",text:r}]})})
|
|
622
|
+
i.ok?n.info("Pushed notification to CLI session",{sessionId:t.cliOrigin.sessionId}):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 creating session, spawning work agent instead",{requestId:s,error:(0,r.getErrorMessage)(e)})}if(!g)try{const e=await(0,o.spawnActionAgent)({agent:"work",prompt:u,cwd:c,projectPath:c,projectId:a,title:`[${s}] ${t.title}`,requestId:s,apiClient:i,logger:n,trackChildPid:l})
|
|
623
|
+
h=e.success,h?n.info("Spawned work agent for request",{requestId:s,sessionId:e.sessionId}):n.warn("Failed to spawn work agent",{requestId:s,error:e.error})}catch(e){n.warn("Failed to spawn work agent",{requestId:s,error:(0,r.getErrorMessage)(e)})}return n.info("Approve-and-work-request action completed",{requestId:s,notifiedSession:g,agentSpawned:h}),{success:!0,message:`Successfully approved request ${s}: ${t.title}${g?" \u2014 creating session notified":h?" \u2014 work agent spawned":""}`,data:{operationType:"approve-and-work",previousStatus:"pending_approval",newStatus:"open",notifiedSession:g,agentSpawned:h}}}catch(e){const t=(0,r.getErrorMessage)(e)
|
|
624
|
+
return n.error("approve-and-work-request action failed",{requestId:s,error:t}),{success:!1,message:"Failed to approve request: "+t,error:t}}}
|
|
625
|
+
const r=t(8824),o=t(8148)},7960(e,exports){function t(){const e=process.argv[1]||""
|
|
626
|
+
if(e.includes("/dist-dev/")||e.includes("\\dist-dev\\"))return!0
|
|
627
|
+
if(e.includes("/dist/")||e.includes("\\dist\\"))return!1
|
|
628
|
+
const t=process.cwd()
|
|
629
|
+
return!(!t.includes("/dist-dev")&&!t.includes("\\dist-dev"))}Object.defineProperty(exports,"__esModule",{value:!0}),exports.isDevBuild=t,exports.getBuildMode=function(){return t()?"development":"production"},exports.enableDebugMode=function(){return"true"===process.env.ENABLE_DEBUG_TOOLS&&(process.env.DEBUG_LOGGING="true",!0)}},8035(e,exports,t){function r(e,t,r){try{const o=`[${(new Date).toISOString()}] [detached-subagent] ${t}${r?" "+JSON.stringify(r):""}\n`,s=(0,a.join)(e||process.cwd(),".alchemy_lab","spawn-debug.log");(0,i.appendFileSync)(s,o)}catch{}}function o(e){const{agent:t,prompt:o,cwd:c,parentServerUrl:l,parentSessionId:d,sessionId:u,model:g,initialBranch:h,serverUrl:p,onSpawn:f,onError:m}=e
|
|
630
|
+
try{const n={...process.env}
|
|
631
|
+
l&&(n.OPENCODE_PARENT_SERVER=l),h&&(n.ALCHEMY_INITIAL_BRANCH=h),e.allowParallel&&(n.ALCHEMY_ALLOW_PARALLEL="1")
|
|
632
|
+
const d=new Date,m=`${(d.getHours()+"").padStart(2,"0")}${(d.getMinutes()+"").padStart(2,"0")}${(d.getSeconds()+"").padStart(2,"0")}`,y=Math.random().toString(36).substring(2,7)
|
|
633
|
+
let v
|
|
634
|
+
if(n.ALCHEMY_SESSION_TAG=`mcp-${m}-${y}`,r(c,"Spawn env setup",{agent:t,initialBranch:h||"none",hasParentServer:!!l,sessionId:u||"new",attachMode:!!p}),v=p&&u&&o?["run","--attach",p,"--session",u,"--agent",t,o]:u&&o?["run","--session",u,"--agent",t,o]:o?["run","--agent",t,o]:["run","--agent",t],g){const e=v.findIndex((e=>e===o))
|
|
635
|
+
e>0?v.splice(e,0,"--model",g):v.push("--model",g)}r(c,"Spawning opencode",{args:v.map((e=>e===o?`[prompt:${e.length}chars]`:e)),model:g||"not specified",promptLength:o?.length,promptPreview:o?.slice(0,100),cwd:c,hasSessionId:!!u,hasParentServerUrl:!!l})
|
|
636
|
+
const w=(0,a.join)(c||process.cwd(),".alchemy_lab"),C=(0,a.join)(w,"spawn-stderr.log")
|
|
637
|
+
let I
|
|
638
|
+
try{I=(0,i.openSync)(C,"a")}catch{}const E=(0,s.spawn)("opencode",v,{detached:!0,stdio:["ignore","ignore",I??"ignore"],cwd:c||process.cwd(),env:n})
|
|
639
|
+
return E.unref(),f&&f(E.pid),{success:!0,pid:E.pid}}catch(e){const t=(0,n.getErrorMessage)(e)
|
|
640
|
+
return m&&e instanceof Error&&m(e),{success:!1,error:t}}}Object.defineProperty(exports,"__esModule",{value:!0}),exports.spawnDetachedSubagent=o,exports.spawnJournalSubagent=function(e,t,r,s){return o({agent:"journal",prompt:`Generate a journal entry for request ${e}.\n\nSteps:\n1. Use ${t}__generate_request_journal tool with requestId: ${e}\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-${e}.md using ${t}__document_operations (operation: create, type: Report)\n\nKeep it story-focused and informative.`,cwd:r,parentServerUrl:s})},exports.spawnWorkSubagent=function(e,t,r){return o({agent:"work",prompt:e,cwd:t,parentServerUrl:r})}
|
|
641
|
+
const s=t(5317),n=t(8824),i=t(9896),a=t(6928)},8148(e,exports,t){function r(e,t){try{const t=(0,s.join)(e,a,"opencode.json"),r=(0,o.readFileSync)(t,"utf8"),n=JSON.parse(r)
|
|
642
|
+
if(n.port)return"http://127.0.0.1:"+n.port}catch(e){t.debug("Could not read opencode.json for server URL",{error:(0,i.getErrorMessage)(e)})}return null}Object.defineProperty(exports,"__esModule",{value:!0}),exports.getOpenCodeServerUrl=r,exports.spawnActionAgent=async function(e){const{agent:t,prompt:o,cwd:s,projectPath:a,title:c,requestId:l,parentSessionId:d,allowParallel:u,apiClient:g,logger:h,trackChildPid:p}=e,f=r(a,h)
|
|
643
|
+
let m,y=null
|
|
644
|
+
f?y=await async function(e,t,r,o,s,n,a,c){try{const l=await fetch(e+"/session",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({title:t,...s?{parentID:s}:{}})})
|
|
645
|
+
if(!l.ok)return c.warn("Failed to pre-create session via HTTP",{status:l.status}),null
|
|
646
|
+
const d=await l.json()
|
|
647
|
+
if(!d.id)return c.warn("Session created but no ID returned"),null
|
|
648
|
+
const u=d.id
|
|
649
|
+
c.info("Pre-created agent session",{sessionId:u,title:t,agent:r})
|
|
650
|
+
try{await a.post(`/api/projects/${n}/cli-sessions`,{externalSessionId:u,providerId:"opencode",...s?{parentExternalSessionId:s}:{},agentType:r,status:"active"}),c.info("Registered session in alchemy DB",{sessionId:u,agent:r,parentSessionId:s})}catch(e){c.warn("Failed to register session in DB",{error:(0,i.getErrorMessage)(e)})}if(o&&n){try{await a.post(`/api/projects/${n}/session-request-associations`,{opencodeSessionId:u,requestLocalId:o}),c.info("Created session-request association",{sessionId:u,requestId:o})}catch(e){c.warn("Failed to create session-request association",{error:(0,i.getErrorMessage)(e)})}if("coder"===r)try{await a.patch(`/api/projects/${n}/requests/${o}`,{cliOrigin:{providerId:"opencode",sessionId:u,createdAt:(new Date).toISOString()}}),c.info("Updated request cliOrigin to coder session",{sessionId:u,requestId:o})}catch(e){c.warn("Failed to update request cliOrigin",{error:(0,i.getErrorMessage)(e)})}}return u}catch(e){return c.warn("Failed to pre-create session",{error:(0,i.getErrorMessage)(e)}),null}}(f,c,t,l,d,e.projectId,g,h):h.warn("No OpenCode server URL \u2014 agent session will not be visible in session list")
|
|
651
|
+
try{const e=await fetch("http://localhost:3456/api/project-settings")
|
|
652
|
+
if(e.ok){const r=await e.json()
|
|
653
|
+
r.data?.defaultModel&&(m=r.data.defaultModel,h.info("Using user default model for agent spawn",{agent:t,model:m}))}}catch{}const v=(0,n.spawnDetachedSubagent)({agent:t,prompt:o,cwd:s,sessionId:y||void 0,serverUrl:f||void 0,model:m,allowParallel:u??!1,onSpawn(e){e&&p&&p(e)}})
|
|
654
|
+
return v.success?(h.info("Spawned action agent",{agent:t,sessionId:y,pid:v.pid,requestId:l}),{success:!0,sessionId:y,pid:v.pid}):{success:!1,sessionId:y,error:v.error||"Spawn failed"}}
|
|
655
|
+
const o=t(9896),s=t(6928),n=t(8035),i=t(8824),a=".alchemy_lab"},8524(e,exports,t){var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
|
|
656
|
+
Object.defineProperty(exports,"__esModule",{value:!0}),exports.CLIBridgeWebSocket=void 0,exports.setCLIBridge=function(e){n=e},exports.getCLIBridge=function(){return n}
|
|
657
|
+
const o=t(5086),s=r(t(6982))
|
|
658
|
+
exports.CLIBridgeWebSocket=class{constructor(e,t=3e4){this.wss=null,this.uiClient=null,this.pendingCommands=new Map,this.heartbeatInterval=null,this.logger=e,this.defaultTimeout=t}attach(e){this.wss=new o.WebSocketServer({server:e,path:"/ws/cli-bridge"}),this.wss.on("connection",(e=>{this.logger.info("[CLIBridge] UI client connected"),this.uiClient&&(this.logger.warn("[CLIBridge] Replacing existing UI client connection"),this.uiClient.close(1e3,"Replaced by new connection")),this.uiClient=e,this.startHeartbeat(),e.on("message",(e=>{try{const t=JSON.parse(e.toString())
|
|
659
|
+
this.handleResponse(t)}catch(e){this.logger.error("[CLIBridge] Failed to parse message from UI",{error:e instanceof Error?e.message:e+""})}})),e.on("close",(()=>{this.logger.info("[CLIBridge] UI client disconnected"),this.uiClient===e&&(this.uiClient=null),this.stopHeartbeat()
|
|
660
|
+
for(const[e,t]of this.pendingCommands)clearTimeout(t.timer),t.resolve({id:e,success:!1,error:"UI client disconnected during command. The command may have been partially executed."}),this.pendingCommands.delete(e)})),e.on("error",(e=>{this.logger.error("[CLIBridge] WebSocket error",{error:e.message})}))})),this.logger.info("[CLIBridge] WebSocket server attached at /ws/cli-bridge")}isUIConnected(){return null!==this.uiClient&&this.uiClient.readyState===o.WebSocket.OPEN}async sendCommand(e,t={},r){if(!this.isUIConnected())return{id:"",success:!1,error:"alchemy-ui is not connected. Please open alchemy-ui in your browser first."}
|
|
661
|
+
const o=s.default.randomUUID(),n={id:o,action:e,params:t}
|
|
662
|
+
return new Promise(((t,s)=>{const i=setTimeout((()=>{this.pendingCommands.delete(o),t({id:o,success:!1,error:`Command '${e}' timed out after ${(r||this.defaultTimeout)/1e3}s`})}),r||this.defaultTimeout)
|
|
663
|
+
this.pendingCommands.set(o,{resolve:t,reject:s,timer:i})
|
|
664
|
+
try{this.uiClient.send(JSON.stringify(n))}catch(e){clearTimeout(i),this.pendingCommands.delete(o),t({id:o,success:!1,error:"Failed to send command: "+(e instanceof Error?e.message:e+"")})}}))}handleResponse(e){const t=this.pendingCommands.get(e.id)
|
|
665
|
+
t?(clearTimeout(t.timer),this.pendingCommands.delete(e.id),t.resolve(e)):this.logger.warn("[CLIBridge] Received response for unknown command",{id:e.id})}startHeartbeat(){this.stopHeartbeat(),this.heartbeatInterval=setInterval((()=>{this.uiClient&&this.uiClient.readyState===o.WebSocket.OPEN&&this.uiClient.ping()}),3e4)}stopHeartbeat(){this.heartbeatInterval&&(clearInterval(this.heartbeatInterval),this.heartbeatInterval=null)}close(){this.stopHeartbeat()
|
|
666
|
+
for(const[e,t]of this.pendingCommands)clearTimeout(t.timer),t.reject(Error("Bridge shutting down")),this.pendingCommands.delete(e)
|
|
667
|
+
this.uiClient&&(this.uiClient.close(1e3,"Server shutting down"),this.uiClient=null),this.wss&&(this.wss.close(),this.wss=null)}}
|
|
668
|
+
let n=null},8577(module){module.exports=require("cors")},8740(e,exports){Object.defineProperty(exports,"__esModule",{value:!0}),exports.HookCache=void 0,exports.HookCache=class{constructor(e,t,r){this.cache=new Map,this.logger=e,this.projectId=t,this.apiClient=r}setProjectId(e){this.projectId=e}getCacheKey(e,t){return t?`${e}:session:${t}`:e+":all"}async getHooksForEvent(e,t,r){const o=this.getCacheKey(e,t)
|
|
669
|
+
if(!r){const e=this.cache.get(o)
|
|
670
|
+
if(e&&6e4>Date.now()-e.time)return e.hooks.filter((e=>e.enabled))}try{const r=t?"?sessionId="+encodeURIComponent(t):"",s=await this.apiClient.get(`/api/projects/${this.projectId}/event-hooks/by-event/${e}${r}`),n=(s?.data||s||[]).map((e=>({id:e.id||e.hookId,name:e.name||e.hookName,eventType:e.eventType||e.hookEventType,agent:e.agent||e.hookAgent,promptTemplate:e.promptTemplate||e.hookPromptTemplate,condition:e.condition||e.hookCondition||{},enabled:!1!==e.enabled,action:e.action||e.hookAction||void 0,target:e.target||e.hookTarget||void 0,timing:e.timing||e.hookTiming||"after",maxIterations:e.maxIterations||e.hookMaxIterations||void 0,emitEvent:e.emitEvent||e.hookEmitEvent||void 0,emitEventOnCondition:e.emitEventOnCondition||e.hookEmitEventOnCondition||void 0})))
|
|
671
|
+
return this.cache.set(o,{hooks:n,time:Date.now()}),n.filter((e=>e.enabled))}catch(t){return this.logger.warn("Failed to fetch hooks",{eventType:e,error:t+""}),[]}}preWarm(e,t){this.getHooksForEvent(e,t).catch((()=>{}))}clear(){this.cache.clear()}}},8824(e,exports){Object.defineProperty(exports,"__esModule",{value:!0}),exports.getErrorMessage=function(e){return e instanceof Error?e.message:"string"==typeof e?e:null===e?"null error":void 0===e?"undefined error":e+""},exports.getErrorStack=function(e){return e instanceof Error?e.stack||e.message:e+""},exports.formatErrorForLog=function(e){return e instanceof Error?{message:e.message,stack:e.stack}:{message:e+""}},exports.isError=function(e){return e instanceof Error}},8984(e,exports,t){Object.defineProperty(exports,"__esModule",{value:!0}),exports.MCPClientConfigurer=void 0
|
|
672
|
+
const r=t(8824)
|
|
673
|
+
class o{constructor(e,t,r){this.logger=e,this.remoteServerUrl=t||process.env.REMOTE_SERVER_URL,this.authToken=r}async seedAgentsFromRemote(e){const t=this.remoteServerUrl
|
|
674
|
+
if(!t)return this.logger.debug("No REMOTE_SERVER_URL set, cannot initialize agents"),!1
|
|
675
|
+
try{const r=t+"/api/agents/seed"
|
|
676
|
+
this.logger.info("Ensuring built-in agents exist",{url:r})
|
|
677
|
+
const o={"Content-Type":"application/json","X-Client-Type":"alchemy-local-service","X-Service-Secret":process.env.SERVICE_SHARED_SECRET||"dev-service-secret-change-in-production"}
|
|
678
|
+
this.authToken&&(o.Authorization="Bearer "+this.authToken)
|
|
679
|
+
const s=await fetch(r,{method:"POST",headers:o})
|
|
680
|
+
s.ok||this.logger.warn("Failed to seed global agents",{status:s.status,statusText:s.statusText})
|
|
681
|
+
const n=`${t}/api/projects/${e}/agents/initialize`
|
|
682
|
+
this.logger.info("Initializing project agent activations",{url:n})
|
|
683
|
+
const i=await fetch(n,{method:"POST",headers:o})
|
|
684
|
+
if(!i.ok)return this.logger.warn("Failed to initialize project agents",{status:i.status,statusText:i.statusText}),!1
|
|
685
|
+
const a=await i.json()
|
|
686
|
+
return this.logger.info("Initialized project agent activations",{initialized:a.data?.initialized||0}),!0}catch(e){return this.logger.warn("Error initializing agents from remote",{error:(0,r.getErrorMessage)(e)}),!1}}async fetchAgentsFromRemote(e){const t=this.remoteServerUrl
|
|
687
|
+
if(!t)return this.logger.debug("No REMOTE_SERVER_URL set, cannot fetch agents from remote"),null
|
|
688
|
+
try{const r=`${t}/api/projects/${e}/agents/enabled`
|
|
689
|
+
this.logger.info("Fetching agents from remote",{url:r})
|
|
690
|
+
const o={"Content-Type":"application/json"}
|
|
691
|
+
this.authToken&&(o.Authorization="Bearer "+this.authToken)
|
|
692
|
+
const s=await fetch(r,{method:"GET",headers:o})
|
|
693
|
+
if(!s.ok)return this.logger.warn("Failed to fetch agents from remote",{status:s.status,statusText:s.statusText}),null
|
|
694
|
+
const n=await s.json(),i=n.data||n
|
|
695
|
+
return this.logger.info("Fetched agents from remote",{count:i.length}),i}catch(e){return this.logger.warn("Error fetching agents from remote",{error:(0,r.getErrorMessage)(e)}),null}}async fetchProjectFromRemote(e){const t=this.remoteServerUrl
|
|
696
|
+
if(!t)return this.logger.debug("No REMOTE_SERVER_URL set, cannot fetch project from remote"),null
|
|
697
|
+
try{const r=await fetch(`${t}/api/projects/${e}`,{headers:{"Content-Type":"application/json",...this.authToken?{Authorization:"Bearer "+this.authToken}:{}}})
|
|
698
|
+
if(!r.ok)return this.logger.warn("Failed to fetch project from remote",{projectId:e,status:r.status,statusText:r.statusText}),null
|
|
699
|
+
const o=await r.json()
|
|
700
|
+
return o.data||o}catch(t){return this.logger.error("Error fetching project from remote",{projectId:e,error:(0,r.getErrorMessage)(t)}),null}}generateAgentMarkdown(e,t,r,s){const n=Object.entries(e.tools||{}).map((([e,r])=>{const s=e.replace(/-/g,"_")
|
|
701
|
+
return` ${o.OPENCODE_BUILTIN_TOOLS.has(e)?e:`${t}_${s}`}: ${r}`})).join("\n")
|
|
702
|
+
let i=`---\ndescription: "${e.description||e.name}"\nmode: "${"utility"===e.mode?"primary":e.mode||"primary"}"\nmodel: "${r}"\n`
|
|
703
|
+
if(e.color&&(i+=`color: "${e.color}"\n`),i+=`tools:\n${n}\n`,e.opencodePermission&&Object.keys(e.opencodePermission).length>0){i+="permission:\n"
|
|
704
|
+
for(const[t,r]of Object.entries(e.opencodePermission))if("object"==typeof r&&null!==r){i+=` ${t}:\n`
|
|
705
|
+
for(const[e,t]of Object.entries(r))i+=` ${e}: ${t}\n`}else i+=` ${t}: ${r}\n`}return void 0!==e.temperature&&(i+=`temperature: ${e.temperature}\n`),e.name.endsWith("-thinking")&&(i+="thinking:\n type: enabled\n budgetTokens: 10000\n"),"journal"===e.name&&(i+=`mcp:\n ${t}:\n env:\n ALCHEMY_MODE: "journal"\n`),i+="---\n\n",s&&(i+=s.trim()+"\n\n"),e.prompt&&(i+=e.prompt),e.promptAddition&&(i+="\n\n"+e.promptAddition),i}async configureOpenCode(e,s,n="node",i,a){try{const r=await Promise.resolve().then(t.t.bind(t,1943,23)),c=await Promise.resolve().then(t.t.bind(t,6928,23)),l=e.includes("dist-dev")||process.env.REMOTE_SERVER_URL?.includes("localhost"),d="alchemy",u=l?c.join(e,"mcp-direct.js"):e
|
|
706
|
+
this.logger.info("Configuring OpenCode",{isDevMode:l,serverName:d,scriptPath:u,projectPath:s})
|
|
707
|
+
const g=c.join(s,"opencode.json")
|
|
708
|
+
let h=null,p=null,f=null,m=null,y=!1,v=!1
|
|
709
|
+
const w=c.join(s,".alchemy_lab","config.json")
|
|
710
|
+
if(!i){try{const e=await r.readFile(w,"utf-8"),t=JSON.parse(e)
|
|
711
|
+
t?.openCodeTools?(h=t.openCodeTools,this.logger.info("Found tool settings in project config",{projectConfigTools:h})):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(g,"utf-8"),t=JSON.parse(e)
|
|
712
|
+
t?.mode?.alchemy?.tools&&(f=t.mode.alchemy.tools,this.logger.info("Found existing tool settings in opencode.json",{existingTools:f})),t?.model&&(m=t.model),t?.mode?.journal&&(y=!0)}catch(e){this.logger.info("No existing opencode.json found, will use defaults")}}const C=i?.openCodeTools||h||f||{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},I={tools:C},E=i?.openCodeModel||p||m||"anthropic/claude-opus-4-5"
|
|
713
|
+
i?.openCodeModel?I.model=i.openCodeModel:p?I.model=p:m&&(I.model=m)
|
|
714
|
+
const k=c.join(s,".opencode","agent")
|
|
715
|
+
await r.mkdir(k,{recursive:!0})
|
|
716
|
+
const b=E||"anthropic/claude-opus-4-5-20251101"
|
|
717
|
+
let j,T=null,S=null
|
|
718
|
+
if(i?.projectId)if(await this.seedAgentsFromRemote(i.projectId),S=await this.fetchProjectFromRemote(i.projectId),j=S?.metadata?.globalSystemPrompt,j&&this.logger.info("Using global system prompt from project metadata",{projectId:i.projectId,promptLength:j.length}),T=await this.fetchAgentsFromRemote(i.projectId),T&&T.length>0){this.logger.info("Using agents from remote database",{count:T.length})
|
|
719
|
+
for(const e of T){if(!e.enabled)continue
|
|
720
|
+
const t=this.generateAgentMarkdown(e,d,b,j),o=c.join(k,e.name+".md")
|
|
721
|
+
await r.writeFile(o,t,"utf8"),this.logger.info(`Created ${e.name} agent file from remote config`,{agentPath:o,mode:e.mode})}}else this.logger.warn("No agents found - check remote server or enable agents via UI")
|
|
722
|
+
else this.logger.warn("No projectId provided - agents must be configured via database")
|
|
723
|
+
const P={$schema:"https://opencode.ai/config.json",model:E,mcp:{[d]:{type:"local",command:[n,u,s],enabled:!0}}}
|
|
724
|
+
if(l&&process.env.REMOTE_SERVER_URL&&(P.mcp[d].environment={REMOTE_SERVER_URL:process.env.REMOTE_SERVER_URL}),T&&T.length>0){P.agent={}
|
|
725
|
+
for(const e of T){if(!e.enabled)continue
|
|
726
|
+
const t={}
|
|
727
|
+
for(const[r,s]of Object.entries(e.tools||{})){const e=s,n=r.replace(/-/g,"_")
|
|
728
|
+
t[o.OPENCODE_BUILTIN_TOOLS.has(r)?r:`${d}_${n}`]=e}const r=S?.metadata?.bashToolAgents||{}
|
|
729
|
+
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)
|
|
730
|
+
let s=""
|
|
731
|
+
j&&(s+=j.trim()+"\n\n"),e.prompt&&(s+=e.prompt),e.promptAddition&&(s+="\n\n"+e.promptAddition),P.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}:{}}}this.logger.info("Added agent tools to opencode.json",{agentCount:Object.keys(P.agent).length})}const A=[]
|
|
732
|
+
if(A.push("file://"+c.join(s,".opencode","plugin","inject-session.js")),A.push("file://"+c.join(s,".opencode","plugin","event-hooks.js")),A.push("file://"+c.join(s,".opencode","plugin","anthropic-auth.js")),A.push("file://"+c.join(s,".opencode","plugin","system-logger.js")),A.length>0&&(P.plugin=A),await r.writeFile(g,JSON.stringify(P,null,2),"utf8"),this.logger.info("OpenCode configured successfully",{configPath:g,plugins:A}),v&&!i)try{let e={}
|
|
733
|
+
try{const t=await r.readFile(w,"utf-8")
|
|
734
|
+
e=JSON.parse(t)}catch{}e.openCodeTools||(e.openCodeTools=C,await r.writeFile(w,JSON.stringify(e,null,2),"utf8"),this.logger.info("Created project config with default tool settings",{projectConfigPath:w}))}catch(e){this.logger.warn("Failed to create project config",{error:e instanceof Error?e.message:"Unknown error"})}try{const{syncPlugins:r}=await t.e(939).then(t.t.bind(t,9939,23)),o=await r(s,l??!1,this.logger,e)
|
|
735
|
+
o.synced.length>0&&this.logger.info("Synced plugins during OpenCode config",{synced:o.synced}),o.errors.length>0&&this.logger.warn("Some plugins failed to sync",{errors:o.errors})}catch(e){this.logger.warn("Plugin sync failed during OpenCode config",{error:e instanceof Error?e.message:"Unknown error"})}return{success:!0,configPath:g}}catch(e){return this.logger.error("Failed to configure OpenCode",{error:(0,r.getErrorMessage)(e)}),{success:!1,error:(0,r.getErrorMessage)(e)}}}}exports.MCPClientConfigurer=o,o.OPENCODE_BUILTIN_TOOLS=new Set(["glob","grep","ls","list","view","read","write","edit","patch","diagnostics","sourcegraph","bash","fetch","agent","task","question","todoread","todowrite"])},9067(e,exports,t){var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
|
|
736
|
+
Object.defineProperty(exports,"__esModule",{value:!0}),exports.ExternalAuthFlow=exports.PollingStatus=void 0
|
|
737
|
+
const o=r(t(6982)),s=t(8824)
|
|
738
|
+
var n
|
|
739
|
+
!function(e){e.NOT_STARTED="not_started",e.POLLING="polling",e.AUTHENTICATED="authenticated",e.TIMEOUT="timeout",e.ERROR="error"}(n||(exports.PollingStatus=n={})),exports.ExternalAuthFlow=class{constructor(e,t,r=5e3,o=9e5){this.pollingStatus=n.NOT_STARTED,this.pollingStartTime=0,this.pollingIntervalId=null,this.tokenResult=null,this.pollingError=null,this.logger=e.child({component:"auth-flow"}),this.remoteServerUrl=t,this.pollingIntervalMs=r,this.maxPollingTimeMs=o}getAuthUrl(e){let t=this.remoteServerUrl
|
|
740
|
+
t=t.includes("api.alchemylabcode.com")?t.replace("api.alchemylabcode.com","alchemylabcode.com"):t.match(/localhost:\d+/)?t.replace(/localhost:\d+/,"localhost:3000"):t.replace(/\/\/api\./g,"//")
|
|
741
|
+
const r=`${t}/auth?mcp_code=${e}`
|
|
742
|
+
return this.logger.info("Generated auth URL",{url:r,frontendUrl:t,backendUrl:this.remoteServerUrl}),r}getPollingUrl(e){const t=`${this.remoteServerUrl}/auth/check-auth?code=${e}`
|
|
743
|
+
return this.logger.info("Generated polling URL",{url:t}),t}async requestTemporaryCode(){const e=this.remoteServerUrl+"/auth/generate-code"
|
|
744
|
+
this.logger.info("Requesting temporary code from remote server",{url:e})
|
|
745
|
+
try{const t=await fetch(e)
|
|
746
|
+
if(!t.ok){const e=await t.text().catch((()=>"Failed to read error response body"))
|
|
747
|
+
throw Error(`Error requesting temporary code: ${t.status} ${t.statusText}\nResponse: ${e}`)}const r=await t.json()
|
|
748
|
+
if(!r.success||!r.code)throw Error("Invalid response from server: "+JSON.stringify(r))
|
|
749
|
+
const o=r.code
|
|
750
|
+
return this.logger.info("Received temporary code from server",{code:o}),o}catch(e){throw this.logger.error("Failed to request temporary code",{error:(0,s.getErrorMessage)(e)}),e}}async startAuthFlow(){const e=await this.requestTemporaryCode(),t=this.getAuthUrl(e),r=this.getPollingUrl(e)
|
|
751
|
+
return this.logger.info("Starting external authentication flow",{tempCode:e,authUrl:t,pollingUrl:r,remoteServerUrl:this.remoteServerUrl}),{tempCode:e,authUrl:t,pollingUrl:r}}parseTokenResponse(e){try{return e&&e.token&&e.expiresAt?{token:e.token,userId:e.userId||"unknown",username:e.username||"unknown",role:e.role||"user",expiresAt:e.expiresAt}:null}catch(e){return this.logger.error("Error parsing token response",{error:(0,s.getErrorMessage)(e)}),null}}startPolling(e,t,r,o){this.pollingStatus=n.POLLING,this.pollingStartTime=Date.now(),this.tokenResult=null,this.pollingError=null,this.stopPolling(),this.logger.info("Starting polling for authentication",{tempCode:e.substring(0,4)+"***",interval:this.pollingIntervalMs,maxTime:this.maxPollingTimeMs})
|
|
752
|
+
const i=async()=>{try{const o=Date.now()-this.pollingStartTime
|
|
753
|
+
if(o>this.maxPollingTimeMs)return this.logger.warn("Authentication polling timed out",{tempCode:e.substring(0,4)+"***",elapsedTime:o,maxTime:this.maxPollingTimeMs}),this.pollingStatus=n.TIMEOUT,this.stopPolling(),void(r&&r())
|
|
754
|
+
const i=this.getPollingUrl(e)
|
|
755
|
+
this.logger.debug("Polling for authentication status",{tempCode:e,url:i})
|
|
756
|
+
try{const r=await fetch(i)
|
|
757
|
+
if(!r.ok){const e=await r.text().catch((()=>"Failed to read error response body"))
|
|
758
|
+
throw Error(`Error polling for authentication: ${r.status} ${r.statusText}\nResponse: ${e}`)}const o=await r.json()
|
|
759
|
+
if(this.logger.debug("Polling response",{data:o}),o.authenticated&&o.token){this.logger.info("Authentication successful",{tempCode:e.substring(0,4)+"***",userId:o.userId})
|
|
760
|
+
const r=this.parseTokenResponse(o)
|
|
761
|
+
if(!r)throw Error("Invalid token response from server")
|
|
762
|
+
this.pollingStatus=n.AUTHENTICATED,this.tokenResult=r,this.stopPolling(),t&&t(r)}else this.logger.debug("Not authenticated yet, continuing to poll",{tempCode:e.substring(0,4)+"***"})}catch(e){const t=(0,s.getErrorMessage)(e)
|
|
763
|
+
throw Error("Error during polling: "+t)}}catch(t){const r=(0,s.getErrorMessage)(t)
|
|
764
|
+
this.logger.error("Error polling for authentication",{tempCode:e.substring(0,4)+"***",error:r}),this.pollingStatus=n.ERROR,this.pollingError=r,this.stopPolling(),o&&o(r)}}
|
|
765
|
+
return i(),this.pollingIntervalId=setInterval(i,this.pollingIntervalMs),()=>this.stopPolling()}stopPolling(){this.pollingIntervalId&&(clearInterval(this.pollingIntervalId),this.pollingIntervalId=null,this.logger.debug("Stopped polling for authentication",{status:this.pollingStatus}))}getPollingStatus(){const e={status:this.pollingStatus}
|
|
766
|
+
return this.pollingStatus===n.AUTHENTICATED&&this.tokenResult?e.tokenInfo=this.tokenResult:this.pollingStatus===n.ERROR&&this.pollingError&&(e.error=this.pollingError),this.pollingStartTime>0&&(e.elapsedTimeMs=Date.now()-this.pollingStartTime),e}resetPollingState(){this.stopPolling(),this.pollingStatus=n.NOT_STARTED,this.pollingStartTime=0,this.tokenResult=null,this.pollingError=null}async startGoogleOAuthFlow(){this.logger.info("Starting Google OAuth authentication flow")
|
|
767
|
+
try{const e=o.default.randomBytes(16).toString("hex"),t=`${this.remoteServerUrl}/api/auth/google/auth-url?state=${e}`,r=await fetch(t)
|
|
768
|
+
if(!r.ok){const e=await r.text().catch((()=>"Failed to read error response body"))
|
|
769
|
+
throw Error(`Error getting Google OAuth URL: ${r.status} ${r.statusText}\nResponse: ${e}`)}const s=await r.json()
|
|
770
|
+
if(!s.success||!s.data||!s.data.authUrl)throw Error("Invalid response from server: "+JSON.stringify(s))
|
|
771
|
+
const n=s.data.authUrl
|
|
772
|
+
return this.logger.info("Received Google OAuth URL from server",{authUrl:n,state:e}),{authUrl:n,state:e}}catch(e){throw this.logger.error("Failed to start Google OAuth flow",{error:(0,s.getErrorMessage)(e)}),e}}async isGoogleOAuthAvailable(){try{const e=this.remoteServerUrl+"/api/auth/google/status",t=await fetch(e)
|
|
773
|
+
if(!t.ok)return!1
|
|
774
|
+
const r=await t.json()
|
|
775
|
+
return r.success&&r.data&&!0===r.data.enabled}catch(e){return this.logger.debug("Error checking Google OAuth availability",{error:(0,s.getErrorMessage)(e)}),!1}}async startGoogleOAuthPolling(e,t,r,o,i){return this.logger.info("Starting Google OAuth polling",{state:t}),this.pollingStatus=n.POLLING,this.pollingStartTime=Date.now(),this.tokenResult=null,this.pollingError=null,this.logger.info("Google OAuth authentication URLs ready for user interaction"),this.pollingIntervalId=setInterval((async()=>{try{if(Date.now()-this.pollingStartTime>this.maxPollingTimeMs)return this.logger.info("Google OAuth polling timed out"),this.pollingStatus=n.TIMEOUT,this.stopPolling(),void(o&&o())
|
|
776
|
+
const e=`${this.remoteServerUrl}/api/auth/google/check-status?state=${t}`,s=await fetch(e)
|
|
777
|
+
if(!s.ok)return void this.logger.debug("Polling response not ok",{status:s.status})
|
|
778
|
+
const i=await s.json()
|
|
779
|
+
if(this.logger.debug("Google OAuth polling response",{data:i}),i.success&&i.data&&i.data.authenticated&&i.data.token){this.logger.info("Google OAuth authentication completed")
|
|
780
|
+
const e=this.parseTokenResponse(i.data)
|
|
781
|
+
e&&(this.pollingStatus=n.AUTHENTICATED,this.tokenResult=e,this.stopPolling(),r&&r(e))}else this.logger.debug("Not authenticated yet, continuing to poll",{hasSuccess:!!i.success,hasData:!!i.data,authenticated:i.data?.authenticated})}catch(e){this.logger.error("Error during Google OAuth polling",{error:(0,s.getErrorMessage)(e)}),this.pollingStatus=n.ERROR,this.pollingError=(0,s.getErrorMessage)(e),this.stopPolling(),i&&i(this.pollingError)}}),this.pollingIntervalMs),{authUrl:e,state:t}}}},9266(e,exports,t){var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
|
|
782
|
+
Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleOpenCodeProxyAction=async function(e,t){const{method:r,path:i,body:a,headers:c}=e
|
|
783
|
+
if(!r||!i)return{success:!1,message:"method and path are required in proxy payload"}
|
|
784
|
+
const l=function(e){const t=o.default.join(e,".alchemy_lab","opencode.json")
|
|
785
|
+
if(!s.default.existsSync(t))return null
|
|
786
|
+
try{return"http://localhost:"+JSON.parse(s.default.readFileSync(t,"utf-8")).port}catch{return null}}(t.projectPath)
|
|
787
|
+
if(!l)return{success:!1,message:"OpenCode not running (no opencode.json found)"}
|
|
788
|
+
const d=`${l}${i.startsWith("/")?i:"/"+i}`
|
|
789
|
+
try{const e={"Content-Type":"application/json",...c},t={method:r.toUpperCase(),headers:e}
|
|
790
|
+
"GET"!==r.toUpperCase()&&"HEAD"!==r.toUpperCase()&&void 0!==a&&(t.body="string"==typeof a?a:JSON.stringify(a))
|
|
791
|
+
const o=await fetch(d,t),s=o.headers.get("content-type")||""
|
|
792
|
+
let n
|
|
793
|
+
return n=s.includes("application/json")?await o.json():await o.text(),{success:!0,message:"Proxy request completed",data:{status:o.status,contentType:s,body:n}}}catch(e){return{success:!1,message:"Proxy request failed: "+(0,n.getErrorMessage)(e),error:(0,n.getErrorMessage)(e)}}}
|
|
794
|
+
const o=r(t(6928)),s=r(t(9896)),n=t(8824)},9278(module){module.exports=require("net")},9735(e,exports,t){Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleApproveRequestAction=async function(e,t){const{requestId:o}=e,{logger:s,apiClient:n,projectId:i}=t
|
|
795
|
+
if(!o)return{success:!1,message:"requestId is required",error:"Missing requestId in payload"}
|
|
796
|
+
s.info("Starting approve-request action",{requestId:o})
|
|
797
|
+
try{const e=await n.get(`/api/projects/${i}/requests/${o}`)
|
|
798
|
+
if(!e.success||!e.data)return{success:!1,message:`Request ${o} not found`,error:"Request not found"}
|
|
799
|
+
const t=e.data
|
|
800
|
+
return"pending_approval"!==t.status?{success:!1,message:`Cannot approve request with status '${t.status}'. Only requests in 'pending_approval' status can be approved.`,error:"Invalid status: "+t.status}:(await n.patch(`/api/projects/${i}/requests/${o}`,{status:"open"})).success?(s.info("Approve-request action completed",{requestId:o}),{success:!0,message:`Successfully approved request ${o}: ${t.title}`,data:{operationType:"approve",previousStatus:"pending_approval",newStatus:"open"}}):{success:!1,message:"Failed to update request status",error:"Update failed"}}catch(e){const t=(0,r.getErrorMessage)(e)
|
|
801
|
+
return s.error("approve-request action failed",{requestId:o,error:t}),{success:!1,message:"Failed to approve request: "+t,error:t}}}
|
|
802
|
+
const r=t(8824)},9896(module){module.exports=require("fs")}},n={}
|
|
803
|
+
e.m=s,r=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,e.t=function(o,s){if(1&s&&(o=this(o)),8&s)return o
|
|
804
|
+
if("object"==typeof o&&o){if(4&s&&o.__esModule)return o
|
|
805
|
+
if(16&s&&"function"==typeof o.then)return o}var n=Object.create(null)
|
|
806
|
+
e.r(n)
|
|
807
|
+
var i={}
|
|
808
|
+
t=t||[null,r({}),r([]),r(r)]
|
|
809
|
+
for(var a=2&s&&o;"object"==typeof a&&!~t.indexOf(a);a=r(a))Object.getOwnPropertyNames(a).forEach((e=>i[e]=()=>o[e]))
|
|
810
|
+
return i.default=()=>o,e.d(n,i),n},e.d=(exports,t)=>{for(var r in t)e.o(t,r)&&!e.o(exports,r)&&Object.defineProperty(exports,r,{enumerable:!0,get:t[r]})},e.f={},e.e=t=>Promise.all(Object.keys(e.f).reduce(((r,o)=>(e.f[o](t,r),r)),[])),e.u=e=>e+".js",e.o=(e,t)=>({}.hasOwnProperty.call(e,t)),e.r=exports=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(exports,"__esModule",{value:!0})},o={27:1},e.f.require=t=>{o[t]||(t=>{var r=t.modules,s=t.ids,n=t.runtime
|
|
811
|
+
for(var i in r)e.o(r,i)&&(e.m[i]=r[i])
|
|
812
|
+
n&&n(e)
|
|
813
|
+
for(var a=0;a<s.length;a++)o[s[a]]=1})(require("./"+e.u(t)))}
|
|
814
|
+
var i={};(()=>{function t(){const e=[(0,g.join)(__dirname,"..","package.json"),(0,g.join)(__dirname,"..","..","package.json")]
|
|
815
|
+
for(const t of e)try{const e=JSON.parse((0,d.readFileSync)(t,"utf-8"))
|
|
816
|
+
if(e.version)return e.version}catch{}return"unknown"}function r(){const e=t()
|
|
817
|
+
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 --proxy-opencode Proxy OpenCode API through action server (for remote UI)\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\n alchemy serve --proxy-opencode # Serve with OpenCode API proxy\n\nFor more information, visit: https://alchemylabcode.com/\n`)}function o(e){const t=(0,g.join)(e,E.ALCHEMY_DIR_NAME,"project.json")
|
|
818
|
+
return(0,d.existsSync)(t)}function s(e){const t=(0,p.platform)(),r="darwin"===t?"open":"win32"===t?"cmd":"xdg-open",o="win32"===t?["/c","start","",e]:[e];(0,h.spawn)(r,o,{detached:!0,stdio:"ignore"}).unref()}async function n(e,t=3e5){return new Promise((r=>{const o=setInterval((async()=>{await e.hasValidToken()&&(clearInterval(o),r(!0))}),2e3)
|
|
819
|
+
setTimeout((()=>{clearInterval(o),r(!1)}),t)}))}async function a(t){if(process.env.REMOTE_SERVER_URL)return process.env.REMOTE_SERVER_URL
|
|
820
|
+
try{const{createConfigManager:r}=await e.e(60).then(e.t.bind(e,9060,23)),o=r(t)
|
|
821
|
+
return(await o.getInstallationConfig()).remoteServerUrl||"https://api.alchemylabcode.com"}catch{return"https://api.alchemylabcode.com"}}async function c(e,t,r,o,s="cli-init"){try{const n=(0,g.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:s}})})
|
|
822
|
+
if(i.ok){const e=await i.json()
|
|
823
|
+
return e.data?.id||e.id}o.info(`Project registration failed (HTTP ${i.status})`)}catch(e){o.info("Project registration failed",{error:(0,C.getErrorMessage)(e)})}}Object.defineProperty(i,"__esModule",{value:!0})
|
|
824
|
+
const l=e(3444),d=e(9896),u=e(1943),g=e(6928),h=e(5317),p=e(857),f=e(1253),m=e(8984),y=e(2366),v=e(3962),w=e(1187),C=e(8824),I=e(6800),E=e(4139);(async function(){const i=process.argv.slice(2),p=i.includes("--proxy-opencode")
|
|
825
|
+
let k
|
|
826
|
+
const b=i.indexOf("--port");-1!==b&&i[b+1]&&(k=parseInt(i[b+1],10),isNaN(k)&&(console.error("\u274c Invalid port number"),process.exit(1)))
|
|
827
|
+
const j=i.filter(((e,t)=>"--proxy-opencode"!==e&&"--port"!==e&&(-1===b||t!==b+1)))
|
|
828
|
+
if(0===j.length)return void await async function(r={}){const i=process.cwd(),l=(0,g.join)(i,E.ALCHEMY_DIR_NAME)
|
|
829
|
+
await(0,u.mkdir)(l,{recursive:!0})
|
|
830
|
+
const p=(0,g.join)(l,"server.log"),{createLogger:k}=await Promise.resolve().then(e.t.bind(e,2782,23)),b=k({serviceName:"alchemy-server",console:!1,logToFile:!0,logFilePath:p,baseDir:i}),j=await a(b),T=new f.TokenManager(b,i,{remoteServerUrl:j,allowInteractiveAuth:!1})
|
|
831
|
+
await T.initialize()
|
|
832
|
+
let S=await T.getToken()
|
|
833
|
+
if(!S?.token){console.log(""),console.log("\u{1f511} Not logged in. Opening browser to authenticate...")
|
|
834
|
+
const e=await T.startAuthenticationFlow()
|
|
835
|
+
console.log("\u{1f310} "+e.authUrl),s(e.authUrl),console.log(""),console.log("\u{1f446} Browser window opened. Complete login there."),console.log("\u23f1\ufe0f Waiting for authentication..."),await n(T)||(console.error("\u274c Authentication timed out. Run `alchemy` again to retry."),process.exit(1)),S=await T.getToken(),console.log(`\u2705 Welcome, ${S?.username||"user"}!`),console.log("")}let P=S?.token
|
|
836
|
+
await async function(e,t,r,s){if(o(e))return
|
|
837
|
+
console.log("\u{1f4c1} Initializing AlchemyLab project..."),console.log("")
|
|
838
|
+
const n=(0,g.join)(e,E.ALCHEMY_DIR_NAME)
|
|
839
|
+
let i
|
|
840
|
+
await(0,u.mkdir)(n,{recursive:!0}),r?(console.log(`\u{1f4cb} Registering project "${(0,g.basename)(e)||"Local Project"}"...`),i=await c(t,r,e,s),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")
|
|
841
|
+
const a=(0,g.join)(n,"project.json");(0,d.writeFileSync)(a,JSON.stringify({id:i||null,name:(0,g.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,j,P,b)
|
|
842
|
+
const A=(0,g.join)(i,E.ALCHEMY_DIR_NAME,"project.json")
|
|
843
|
+
let M
|
|
844
|
+
try{const e=JSON.parse((0,d.readFileSync)(A,"utf-8"))
|
|
845
|
+
if(M=e.id||void 0,!1===e.registeredRemotely&&P){const t=await c(j,P,i,b,"cli-init-retry")
|
|
846
|
+
t&&(e.id=t,e.registeredRemotely=!0,e.updatedAt=(new Date).toISOString(),(0,d.writeFileSync)(A,JSON.stringify(e,null,2),"utf-8"),M=t,console.log("\u2705 Project registered: "+t))}}catch{b.warn("Could not read project.json")}await async function(t){try{return void(0,h.execFileSync)("which",["opencode"],{stdio:"pipe"})}catch{}let r="latest"
|
|
847
|
+
try{const e=(0,g.join)(t,E.ALCHEMY_DIR_NAME,"config.json")
|
|
848
|
+
if((0,d.existsSync)(e)){const t=JSON.parse((0,d.readFileSync)(e,"utf-8"))
|
|
849
|
+
t.opencodeVersion&&(r=t.opencodeVersion)}}catch{}console.log(""),console.log("\u{1f4e6} OpenCode not found in PATH. Installing...")
|
|
850
|
+
const o="latest"===r?"latest":"v"+r
|
|
851
|
+
try{(0,h.execFileSync)("which",["go"],{stdio:"pipe"}),console.log(" Running: go install github.com/sst/opencode@"+o),(0,h.execFileSync)("go",["install","github.com/sst/opencode@"+o],{stdio:"inherit",timeout:12e4})
|
|
852
|
+
try{return(0,h.execFileSync)("which",["opencode"],{stdio:"pipe"}),void console.log("\u2705 OpenCode installed successfully via go install")}catch{console.log("\u26a0\ufe0f go install completed but opencode not found in PATH"),console.log(" Ensure $(go env GOPATH)/bin is in your PATH")}}catch{}try{const{platform:t,arch:o}=await Promise.resolve().then(e.t.bind(e,857,23)),s=t(),n=o(),i="darwin"===s?"darwin":"linux"===s?"linux":null,a="arm64"===n?"arm64":"x64"===n?"amd64":null
|
|
853
|
+
if(i&&a){const e=`opencode_${i}_${a}`,t="latest"===r?"https://github.com/sst/opencode/releases/latest/download/"+e:`https://github.com/sst/opencode/releases/download/v${r}/${e}`
|
|
854
|
+
console.log(" Downloading from: "+t)
|
|
855
|
+
const o=(0,g.join)(process.env.HOME||"/usr/local/bin",".local","bin")
|
|
856
|
+
await(0,u.mkdir)(o,{recursive:!0})
|
|
857
|
+
const s=(0,g.join)(o,"opencode")
|
|
858
|
+
return(0,h.execFileSync)("curl",["-fsSL","-o",s,t],{stdio:"inherit",timeout:6e4}),(0,h.execFileSync)("chmod",["+x",s],{stdio:"pipe"}),(process.env.PATH||"").split(":").includes(o)||(console.log(` \u26a0\ufe0f Add ${o} to your PATH:`),console.log(` export PATH="${o}:$PATH"`),console.log(""),process.env.PATH=`${o}:${process.env.PATH}`),void console.log("\u2705 OpenCode installed successfully")}}catch(e){console.log("\u26a0\ufe0f Binary download failed: "+(0,C.getErrorMessage)(e))}console.log(""),console.log("\u26a0\ufe0f Could not auto-install OpenCode."),console.log(""),console.log(" Install manually:"),console.log(" go install github.com/sst/opencode@latest"),console.log(" Or download from:"),console.log(" https://github.com/sst/opencode/releases"),console.log("")}(i)
|
|
859
|
+
const O=t()
|
|
860
|
+
console.log(["\n .oo 8 8 o 8 \n .P 8 8 8 8 8 \n .P 8 8 .oPYo. 8oPYo. .oPYo. ooYoYo. o o 8 .oPYo. 8oPYo. \n oPooo8 8 8 ' 8 8 8oooo8 8' 8 8 8 8 8 .oooo8 8 8 \n .P 8 8 8 . 8 8 8. 8 8 8 8 8 8 8 8 8 8 \n.P 8 8 `YooP' 8 8 `Yooo' 8 8 8 `YooP8 8oooo `YooP8 `YooP' \n..:::::....:.....:..:::..:.....:..:..:..:....8 ......:.....::.....:","::::::::::::::::::::::::::::::::::::::::::ooP'.::::::::::::::::::: v"+O,"::::::::::::::::::::::::::::::::::::::::::...::::::::::::::::::::::\n"].join("\n")),console.log("\u{1f4c1} Project: "+i)
|
|
861
|
+
const _=r.port||await(0,I.findAvailablePort)(3456),x=new y.AlchemyActionServer(b,{projectPath:i,remoteServerUrl:j,port:_,authToken:P,proxyOpenCode:r.proxyOpenCode})
|
|
862
|
+
try{await x.start(),r.proxyOpenCode&&console.log("\u{1f500} OpenCode API proxy enabled")
|
|
863
|
+
let t=process.env.FRONTEND_URL
|
|
864
|
+
if(!t)if(j.includes("api.alchemylabcode.com"))t="https://alchemylabcode.com"
|
|
865
|
+
else{try{const e=await fetch(j+"/health")
|
|
866
|
+
if(e.ok){const r=await e.json()
|
|
867
|
+
t=r?.data?.frontendUrl}}catch{}t||(t=j.replace(/:\d+/,":3000"))}console.log(""),console.log(" Action server .... port "+x.getPort())
|
|
868
|
+
try{const e=(0,v.getInstallContext)(),t=new m.MCPClientConfigurer(b,j,P),r=new w.UnifiedLauncher(b,e,t),o=await r.launch({projectPath:i,settings:M?{projectId:M}:void 0})
|
|
869
|
+
if(o.success&&o.port){o.pid&&x.trackChildPid(o.pid),console.log(" OpenCode serve ... port "+o.port)
|
|
870
|
+
const e=(0,g.join)(i,E.ALCHEMY_DIR_NAME,"opencode.json");(0,d.writeFileSync)(e,JSON.stringify({port:o.port,pid:o.pid,startedAt:(new Date).toISOString()},null,2)),b.info("Wrote OpenCode config",{path:e,port:o.port})}else{if(!o.success)throw Error(o.error||"Launch failed")
|
|
871
|
+
console.log(" OpenCode serve ... started")}}catch(e){b.warn("Failed to launch OpenCode serve",{error:(0,C.getErrorMessage)(e)}),console.log(" OpenCode serve ... failed (run manually: opencode serve)")}console.log(""),console.log(` \u2192 Alchemy UI: \x1b]8;;${t}\x07${t}\x1b]8;;\x07`),console.log("")
|
|
872
|
+
let o=!1,a=null
|
|
873
|
+
const c=async()=>{o||(o=!0,a&&clearInterval(a),console.log(""),console.log("Shutting down..."),await x.stop(),process.exit(0))}
|
|
874
|
+
process.on("SIGTERM",c),process.on("SIGINT",c)
|
|
875
|
+
const{InteractiveShell:l}=await e.e(994).then(e.t.bind(e,5994,23)),u=new l({projectPath:i,logger:b,remoteServerUrl:j,authToken:P,projectId:M})
|
|
876
|
+
a=setInterval((async()=>{try{if(await T.hasValidToken())return
|
|
877
|
+
console.log(""),console.log("\u26a0\ufe0f Auth token expired. Opening browser to re-authenticate...")
|
|
878
|
+
const e=await T.startAuthenticationFlow()
|
|
879
|
+
if(console.log("\u{1f310} "+e.authUrl),s(e.authUrl),console.log("\u23f1\ufe0f Waiting for re-authentication..."),await n(T)){const e=await T.getToken()
|
|
880
|
+
e?.token&&(P=e.token,x.updateAuthToken(P)),console.log("\u2705 Re-authenticated successfully.")}else console.log("\u274c Re-authentication timed out. Some features may not work until you restart.")}catch(e){b.warn("Token watchdog check failed",{error:(0,C.getErrorMessage)(e)})}}),3e5),await u.start(c)}catch(e){b.error("Failed to start server",{error:(0,C.getErrorMessage)(e)}),console.error("\u274c Failed to start: "+(0,C.getErrorMessage)(e)),process.exit(1)}}({proxyOpenCode:p,port:k})
|
|
881
|
+
const T=j[0].toLowerCase()
|
|
882
|
+
switch("--help"!==T&&"-h"!==T||(r(),process.exit(0)),"--version"!==T&&"-v"!==T||(console.log("alchemy v"+t()),process.exit(0)),T){case"serve":await async function(e={}){const r=process.cwd()
|
|
883
|
+
o(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))
|
|
884
|
+
const s=(0,l.createEnvLogger)({serviceName:"alchemy-server",console:!0,baseDir:r}),n=t()
|
|
885
|
+
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)
|
|
886
|
+
const i=await a(s),c=new f.TokenManager(s,r,{remoteServerUrl:i,allowInteractiveAuth:!1})
|
|
887
|
+
await c.initialize()
|
|
888
|
+
const d=await c.getToken(),u=d?.token
|
|
889
|
+
u||console.log("\u26a0\ufe0f No auth token. Some features may be limited. Run `alchemy` to authenticate.")
|
|
890
|
+
const g=e.port||await(0,I.findAvailablePort)(3456),h=new y.AlchemyActionServer(s,{projectPath:r,remoteServerUrl:i,port:g,authToken:u,proxyOpenCode:e.proxyOpenCode})
|
|
891
|
+
try{await h.start(),console.log(""),console.log("\u2705 Action server running!"),e.proxyOpenCode&&console.log(" \u{1f500} OpenCode API proxy: enabled"),console.log(` Health: http://127.0.0.1:${h.getPort()}/health`),console.log(""),console.log("Press Ctrl+C to stop the server."),console.log("")
|
|
892
|
+
let t=!1
|
|
893
|
+
const r=async()=>{t||(t=!0,console.log(""),console.log("Shutting down action server..."),await h.stop(),process.exit(0))}
|
|
894
|
+
process.on("SIGTERM",r),process.on("SIGINT",r),await new Promise((()=>{}))}catch(e){s.error("Failed to start server",{error:(0,C.getErrorMessage)(e)}),console.error("\u274c Failed to start: "+(0,C.getErrorMessage)(e)),process.exit(1)}}({proxyOpenCode:p,port:k})
|
|
895
|
+
break
|
|
896
|
+
case"logout":await async function(){const e=process.cwd(),t=(0,l.createEnvLogger)({serviceName:"alchemy-cli",console:!1,baseDir:e}),r=await a(t),o=new f.TokenManager(t,e,{remoteServerUrl:r,allowInteractiveAuth:!1})
|
|
897
|
+
await o.initialize(),await o.hasValidToken()?await o.removeToken()?console.log("Logged out successfully."):(console.error("Failed to remove auth token."),process.exit(1)):console.log("Not currently logged in.")}()
|
|
898
|
+
break
|
|
899
|
+
default:console.error("\u274c Unknown command: "+T),console.log(""),r(),process.exit(1)}})().catch((e=>{console.error("\u{1f4a5} Unexpected error:",e),process.exit(1)}))})(),module.exports=i})()
|