alchemylab 1.4.3 → 1.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +130 -103
- package/dist/http-server.js +73 -65
- package/dist/index.js +635 -627
- package/dist/mcp-direct.js +73 -65
- package/package.json +1 -1
package/dist/mcp-direct.js
CHANGED
|
@@ -1161,7 +1161,11 @@ try{if(o.default.existsSync(this.projectFilePath)){this.logger.debug("Updating e
|
|
|
1161
1161
|
const t=JSON.parse(o.default.readFileSync(this.projectFilePath,"utf8"))
|
|
1162
1162
|
t.id=e,t.updatedAt=(new Date).toISOString(),o.default.writeFileSync(this.projectFilePath,JSON.stringify(t,null,2)),this.logger.info("Successfully updated project.json file",{path:this.projectFilePath,oldId:this.projectId,newId:e})}else this.logger.warn("project.json file does not exist, cannot update",{path:this.projectFilePath,projectId:e})
|
|
1163
1163
|
this.projectId=e,this.initialized=!0,this.logger.info("Project ID update completed successfully",{projectId:e,filePath:this.projectFilePath})}catch(t){const r=(0,c.getErrorMessage)(t),n=`Cannot start AlchemyMCP: Unable to update project.json file.\nFile: ${this.projectFilePath}\nProject ID: ${e}\n\nPlease check:\n\u2022 File permissions - ensure you can write to the alchemy_lab directory\n\u2022 Disk space - ensure sufficient space available\n\u2022 File not in use - close any editors that might have project.json open\n\u2022 Directory exists - alchemy_lab folder should be writable\n\nOriginal error: `+r
|
|
1164
|
-
throw this.logger.error("Critical: Project ID update failed, cannot start server",{error:t instanceof Error?t.stack:t+"",projectId:e,filePath:this.projectFilePath,directory:this.alchemyPath}),Error(n)}}isInitialized(){return this.initialized}createMiddleware(){return r(this.getProjectId(),this.logger)}}},
|
|
1164
|
+
throw this.logger.error("Critical: Project ID update failed, cannot start server",{error:t instanceof Error?t.stack:t+"",projectId:e,filePath:this.projectFilePath,directory:this.alchemyPath}),Error(n)}}isInitialized(){return this.initialized}createMiddleware(){return r(this.getProjectId(),this.logger)}}},1816(e,exports){"use strict"
|
|
1165
|
+
Object.defineProperty(exports,"__esModule",{value:!0}),exports.formatCommandOutput=function(e,n,o){const s=o.maxOut??t,i=o.maxErr??r,a=e.length>s,c=n.length>i,l=a||c
|
|
1166
|
+
return{output:[a?"\u2026"+e.slice(-s):e,c?"\u2026"+n.slice(-i):n].filter(Boolean).join("\n")||"(no output)",truncatedNote:l?`\n_(output truncated \u2014 view full with \`log:${o.invocationId}\`)_`:"",logFooter:`Log: \`log:${o.invocationId}\``}},exports.formatCombinedOutput=function(e,r){const n=r.maxOut??t,o=e.length>n
|
|
1167
|
+
return{output:o?"\u2026"+e.slice(-n):e,truncatedNote:o?`\n_(output truncated \u2014 view full with \`log:${r.invocationId}\`)_`:"",logFooter:`Log: \`log:${r.invocationId}\``}}
|
|
1168
|
+
const t=3e3,r=1500},1887(e,exports,t){"use strict"
|
|
1165
1169
|
Object.defineProperty(exports,"__esModule",{value:!0}),exports.ProxyClient=void 0
|
|
1166
1170
|
const r=t(9860),n=t(5674),o=t(4192),s=t(2161),i=t(8824)
|
|
1167
1171
|
class a extends o.BaseApiClient{constructor(e,t,r,n,o,s,i){super(e,t.child({component:"proxy-client"}),r,o,i),this.projectClient=n,this.connectionStatusManager=s,this.logger.info("Proxy client initialized")}setConnectionStatusManager(e){this.logger.debug("Setting connection status manager in ProxyClient"),this.connectionStatusManager=e,this.logger.info("Connection status manager set on "+this.constructor.name)}setAlchemyPath(e){this.logger.info(`Set alchemy path to ${e} on ${this.constructor.name}`)}isNetworkError(e){return"ECONNREFUSED"===e?.code||"ECONNRESET"===e?.code||"ETIMEDOUT"===e?.code||e?.message?.includes("network")||e?.message?.includes("connection")||"ConnectionError"===e?.name||503===e?.status||504===e?.status}isOffline(){return this.connectionStatusManager?.isOffline()||!1}async handlePotentialOfflineOperation(e,t,r){if(this.isOffline()){this.logger.info(`Operation ${e} attempted while offline`,{params:JSON.stringify(t).substring(0,100)+"..."})
|
|
@@ -1249,16 +1253,16 @@ if(p.push("session: "+(r?.sessionId||"NONE")),r?.sessionId){const t=await e.getS
|
|
|
1249
1253
|
if(p.push("reqId: "+(t||"NONE")),t){const e=await o.proxyGet(`/api/projects/${n.projectId}/requests/${t}/test-runs`),r=e?.data||e||[],s=Array.isArray(r)?r.find((e=>e.worktreePath||e.worktree_path)):null
|
|
1250
1254
|
p.push(`runs: ${Array.isArray(r)?r.length:0}, withWorktree: ${s?.id||"NONE"}, path: ${s?.worktreePath||s?.worktree_path||"NONE"}`),s&&(h=s.worktreePath||s.worktree_path,c.info("Using test worktree from request test run",{worktreePath:h,reqId:t}))}}}catch(e){c.warn("Failed to resolve test worktree",{error:(0,r.getErrorMessage)(e)})}if(!h)return a({success:!1,error:"no_worktree"}),{content:[{type:"text",text:`No test worktree found.\n\nDiagnostics:\n${p.length>0?p.map((e=>"\u2022 "+e)).join("\n"):"\u2022 No session identity found"}\n\nEnsure this session is linked to a test process and a test run has been created.`}],isError:!0}
|
|
1251
1255
|
const m=[],{default:g}=await Promise.resolve().then(t.t.bind(t,6928,23)),{default:f}=await Promise.resolve().then(t.t.bind(t,9896,23)),{ALCHEMY_DIR_NAME:y}=await Promise.resolve().then(t.bind(t,4139)),w=g.join(h,y,"command-logs")
|
|
1252
|
-
try{f.mkdirSync(w,{recursive:!0})}catch{}const{runTrackedShellCommand:b}=await Promise.resolve().then(t.bind(t,4600)),{
|
|
1256
|
+
try{f.mkdirSync(w,{recursive:!0})}catch{}const{runTrackedShellCommand:b}=await Promise.resolve().then(t.bind(t,4600)),{formatCombinedOutput:I}=await Promise.resolve().then(t.bind(t,1816)),{sessionIdentity:k}=await Promise.resolve().then(t.bind(t,6688)),v=k.getIdentity()?.sessionId
|
|
1253
1257
|
for(const t of l){c.info("Executing test command",{name:t.name,command:t.command,timeout:t.timeout,cwd:h})
|
|
1254
1258
|
const r=`${t.name.replace(/[^a-zA-Z0-9._-]/g,"_")}-${Date.now()}`,n=g.join(w,r+".log")
|
|
1255
1259
|
let o=null
|
|
1256
|
-
try{o=f.createWriteStream(n,{flags:"w"}),o.write(`# ${(new Date).toISOString()} starting: ${t.command}\n# cwd: ${h}\n# timeout: ${t.timeout}s\n\n`)}catch(e){c.warn("Failed to open command log file",{logPath:n,error:e+""})}const s=await b(t.command,{cwd:h,timeoutMs:1e3*t.timeout,env:{...u.env,FORCE_COLOR:"0"},onChunk:o?(e,t)=>{o?.write(t)}:void 0},{id:r,name:t.name,sessionId:
|
|
1260
|
+
try{o=f.createWriteStream(n,{flags:"w"}),o.write(`# ${(new Date).toISOString()} starting: ${t.command}\n# cwd: ${h}\n# timeout: ${t.timeout}s\n\n`)}catch(e){c.warn("Failed to open command log file",{logPath:n,error:e+""})}const s=await b(t.command,{cwd:h,timeoutMs:1e3*t.timeout,env:{...u.env,FORCE_COLOR:"0"},onChunk:o?(e,t)=>{o?.write(t)}:void 0},{id:r,name:t.name,sessionId:v,logPath:n})
|
|
1257
1261
|
o&&(o.write(`\n# exit: ${s.exitCode}${s.timedOut?" (timed out)":""}\n`),o.end())
|
|
1258
|
-
const i=(s.stdout+(s.stderr?"\n"+s.stderr:"")).trim()
|
|
1259
|
-
if(m.push({name:t.name,command:t.command,exitCode:s.exitCode,output:
|
|
1260
|
-
return`### ${e.name}: ${t}\n\`\`\`\n$ ${e.command}\n${e.output}\n
|
|
1261
|
-
return a({success:!0}),{content:[{type:"text",text:`## ${"setup"===e.commandName?"Setup":e.commandName} \u2014 ${
|
|
1262
|
+
const i=(s.stdout+(s.stderr?"\n"+s.stderr:"")).trim(),{output:a,truncatedNote:l,logFooter:d}=I(i,{invocationId:r})
|
|
1263
|
+
if(m.push({name:t.name,command:t.command,exitCode:s.exitCode,output:a,timedOut:s.timedOut,invocationId:r,truncatedNote:l,logFooter:d}),"setup"===e.commandName&&0!==s.exitCode)break}const C=m.every((e=>0===e.exitCode)),_=m.map((e=>{const t=e.timedOut?"\u23f1 TIMEOUT":0===e.exitCode?"\u2705 PASS":`\u274c FAIL (exit ${e.exitCode})`
|
|
1264
|
+
return`### ${e.name}: ${t}\n\`\`\`\n$ ${e.command}\n${e.output}\n\`\`\`${e.truncatedNote}\n${e.logFooter}`})).join("\n\n")
|
|
1265
|
+
return a({success:!0}),{content:[{type:"text",text:`## ${"setup"===e.commandName?"Setup":e.commandName} \u2014 ${C?"PASSED":"FAILED"}\n\n${_}`}]}}catch(e){return a({success:!1,error:"execution_error"}),{content:[{type:"text",text:"Failed to execute test command: "+(0,r.getErrorMessage)(e)}],isError:!0}}},exports.handleReportTestResults=async function(e,s,i,a,c,l){const d=c.startTimer("tool_execution_time")
|
|
1262
1266
|
try{const{sessionIdentity:u}=await Promise.resolve().then(t.bind(t,6688)),h=await u.getSessionRequestId(i,a,l?.sessionId)
|
|
1263
1267
|
if(!h)return d({success:!1,error:"no_request_association"}),{content:[{type:"text",text:"Cannot report test results: this session is not associated with a request."}],isError:!0}
|
|
1264
1268
|
const p=await i.proxyGet(`/api/projects/${s.projectId}/requests/${h}/test-runs`),m=p?.data||p||[],g=Array.isArray(m)?m:[],f=g.find((e=>"pending"===e.status||"running"===e.status))||g[0]
|
|
@@ -2036,7 +2040,7 @@ var g=t(6539)
|
|
|
2036
2040
|
Object.defineProperty(exports,"ClientFactory",{enumerable:!0,get(){return g.ClientFactory}})
|
|
2037
2041
|
const f=t(6539)
|
|
2038
2042
|
exports.default={ClientFactory:f.ClientFactory}},3416(e,exports){"use strict"
|
|
2039
|
-
Object.defineProperty(exports,"__esModule",{value:!0}),exports.KIND_META=exports.QUEST_STATUS_CLOSED=exports.QUEST_STATUS_OPEN=exports.QUEST_DEFAULTS=exports.PLAN_KINDS=void 0,exports.PLAN_KINDS=["plan","quest"],exports.QUEST_DEFAULTS={tickIntervalSeconds:1800,maxInFlight:1,maxPerDay:24,minIterations:3},exports.QUEST_STATUS_OPEN="open",exports.QUEST_STATUS_CLOSED="closed",exports.KIND_META={plan:{docType:"Plan",filenamePrefix:"PLAN-",label:"plan",idLabel:"Plan ID",headingCreated:"Plan Created",errorVerb:"create plan",alchemyKind:"plan-create",canBeActivePlan:!0},quest:{docType:"Quest",filenamePrefix:"QUEST-",label:"quest",idLabel:"Quest ID",headingCreated:"Quest Created",errorVerb:"create quest",alchemyKind:"quest-create",canBeActivePlan:!1}}},3420(e,exports,t){"use strict"
|
|
2043
|
+
Object.defineProperty(exports,"__esModule",{value:!0}),exports.KIND_META=exports.QUEST_STATUS_DRAFT=exports.QUEST_STATUS_CLOSED=exports.QUEST_STATUS_OPEN=exports.QUEST_DEFAULTS=exports.PLAN_KINDS=void 0,exports.PLAN_KINDS=["plan","quest"],exports.QUEST_DEFAULTS={tickIntervalSeconds:1800,maxInFlight:1,maxPerDay:24,minIterations:3},exports.QUEST_STATUS_OPEN="open",exports.QUEST_STATUS_CLOSED="closed",exports.QUEST_STATUS_DRAFT="draft",exports.KIND_META={plan:{docType:"Plan",filenamePrefix:"PLAN-",label:"plan",idLabel:"Plan ID",headingCreated:"Plan Created",errorVerb:"create plan",alchemyKind:"plan-create",canBeActivePlan:!0},quest:{docType:"Quest",filenamePrefix:"QUEST-",label:"quest",idLabel:"Quest ID",headingCreated:"Quest Created",errorVerb:"create quest",alchemyKind:"quest-create",canBeActivePlan:!1}}},3420(e,exports,t){"use strict"
|
|
2040
2044
|
Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleGetTaskInformation=async function(e,t,i,a,c){const l=a.child({tool:"get_task_information"})
|
|
2041
2045
|
l.info("Getting task information",{args:e})
|
|
2042
2046
|
const d=c.startTimer("tool_handler_duration")
|
|
@@ -2458,51 +2462,48 @@ const a=await r.updateDocument(e.planId,i),c=a.localId||a.id
|
|
|
2458
2462
|
return o.info("Plan appended",{planId:c,appendedLength:e.content.length}),{content:[{type:"text",text:`\u2705 **Plan Appended**\n\n**Plan ID:** ${c}\n**Appended:** ${e.content.length} characters\n**Total Length:** ${s.length} characters`}]}}catch(e){return o.error("Failed to append to plan",{error:(0,r.getErrorMessage)(e)}),{content:[{type:"text",text:"\u274c **Failed to append to plan:** "+(0,r.getErrorMessage)(e)}],isError:!0}}}
|
|
2459
2463
|
const r=t(8824)},4425(e,exports,t){"use strict"
|
|
2460
2464
|
var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
|
|
2461
|
-
Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleCommands=async function(e,t,r,
|
|
2462
|
-
try{const
|
|
2463
|
-
if("list"===
|
|
2465
|
+
Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleCommands=async function(e,t,r,h,p,m,g){const f=p.startTimer("tool_execution_time"),y=e.operation||"run"
|
|
2466
|
+
try{const p=await r.proxyGet(`/api/projects/${t.projectId}/check-commands`),w=p?.data||p||[],b=w.filter((e=>e.isActive)),I=w.filter((e=>!e.isActive))
|
|
2467
|
+
if("list"===y){if(0===w.length)return f({success:!0}),{content:[{type:"text",text:'No commands configured for this project yet.\n\nUse `commands({ operation: "register", commands: [{ name: "typecheck", command: "tsc --noEmit", description: "..." }] })` to add one, or `commands({ operation: "setup" })` to spawn the architect for an interview.'}],...(0,c.alchemyMeta)({kind:"list-commands"})}
|
|
2464
2468
|
const e=[]
|
|
2465
|
-
return
|
|
2466
|
-
try{const r=await fetch((0,
|
|
2469
|
+
return b.length>0&&(e.push(`## Available Commands (${b.length})\n`),e.push(...b.map((e=>`- **${e.name}**: \`${e.command}\`${e.cwd?` (in \`${e.cwd}\`)`:""}${e.description?" \u2014 "+e.description:""}`)))),I.length>0&&(e.push(`\n## Pending Approval (${I.length})\n`),e.push(...I.map((e=>`- **${e.name}**: \`${e.command}\`${e.cwd?` (in \`${e.cwd}\`)`:""} \u2014 \u23f3 waiting for user approval`)))),e.push('\nRun: `commands({ name: "name" })`\nRegister: `commands({ operation: "register", commands: [{ name, command, description, cwd? }, ...] })` \u2014 submits 1+ commands for approval. Single-item arrays work for one command; multiples bundle into one batch notification.\nRemove: `commands({ operation: "remove", name: "..." })`\nSetup (interactive): `commands({ operation: "setup" })` \u2014 spawns the command-architect for guided setup'),f({success:!0}),{content:[{type:"text",text:e.join("\n")}],...(0,c.alchemyMeta)({kind:"list-commands"})}}if("setup"===y){const t=g?.sessionId,n=t?await d.sessionIdentity.getSessionRequestId(r,h,t):void 0
|
|
2470
|
+
try{const r=await fetch((0,u.getActionServerBaseUrl)()+"/api/setup-commands",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({requestId:n,parentSessionId:t,context:e.context}),signal:AbortSignal.timeout(15e3)})
|
|
2467
2471
|
if(!r.ok){const e=await r.text().catch((()=>""))
|
|
2468
|
-
return
|
|
2469
|
-
return o.success&&o.data?(
|
|
2470
|
-
const n=function(e){return e?.sessionId||
|
|
2471
|
-
if(!n)return
|
|
2472
|
-
const o=new Set(
|
|
2472
|
+
return f({success:!1,error:"spawn_failed"}),{content:[{type:"text",text:`commands setup failed: action server returned ${r.status}: ${e.slice(0,300)}`}],isError:!0}}const o=await r.json()
|
|
2473
|
+
return o.success&&o.data?(f({success:!0}),{content:[{type:"text",text:`Commands setup session started.\nWorktree: ${o.data.worktreePath} (branch: ${o.data.branch})\n\nThe command-architect agent is now interviewing the user. You will receive a notification on your next turn when configuration completes \u2014 continue with other work in the meantime.`}],...(0,c.alchemyMeta)({kind:"spawn-subagent",spawnedSessionId:o.data.sessionId,agentType:"command-architect",description:"Commands setup"})}):(f({success:!1,error:"spawn_failed"}),{content:[{type:"text",text:"commands setup failed: "+(o.error||"unknown error")}],isError:!0})}catch(e){return f({success:!1,error:"spawn_failed"}),{content:[{type:"text",text:"commands setup failed: "+(0,l.getErrorMessage)(e)}],isError:!0}}}if("register"===y){if(!Array.isArray(e.commands)||0===e.commands.length)return f({success:!1,error:"missing_commands"}),{content:[{type:"text",text:"Error: `commands` array is required and must be non-empty for the register operation."}],isError:!0}
|
|
2474
|
+
const n=function(e){return e?.sessionId||d.sessionIdentity.getIdentity()?.sessionId||null}(g)
|
|
2475
|
+
if(!n)return f({success:!1,error:"no_session"}),{content:[{type:"text",text:"Error: no session context available to attribute approval notifications."}],isError:!0}
|
|
2476
|
+
const o=new Set(w.map((e=>e.name.toLowerCase()))),s=[],i=[]
|
|
2473
2477
|
for(const t of e.commands){const e=t.name?.trim(),r=t.command?.trim(),n=t.description?.trim()
|
|
2474
|
-
e&&r&&n?o.has(e.toLowerCase())?i.push({name:e,reason:"already exists"}):(o.add(e.toLowerCase()),s.push({name:e,command:r,description:n,cwd:t.cwd?.trim(),timeout:t.timeout})):i.push({name:e||"(unnamed)",reason:"missing required fields (name, command, description)"})}const
|
|
2478
|
+
e&&r&&n?o.has(e.toLowerCase())?i.push({name:e,reason:"already exists"}):(o.add(e.toLowerCase()),s.push({name:e,command:r,description:n,cwd:t.cwd?.trim(),timeout:t.timeout})):i.push({name:e||"(unnamed)",reason:"missing required fields (name, command, description)"})}const a=await Promise.all(s.map((async e=>{try{const n=await r.proxyPost(`/api/projects/${t.projectId}/check-commands`,{name:e.name,command:e.command,description:e.description,timeout:e.timeout||60,cwd:e.cwd||void 0,addedBySessionId:g?.sessionId,isActive:!1}),o=n?.data?.id||n?.id
|
|
2475
2479
|
if(!o)throw Error("command create returned no id")
|
|
2476
|
-
return{ok:!0,proposal:e,commandId:o}}catch(t){return{ok:!1,proposal:e,error:(0,
|
|
2477
|
-
for(const e of
|
|
2480
|
+
return{ok:!0,proposal:e,commandId:o}}catch(t){return{ok:!1,proposal:e,error:(0,l.getErrorMessage)(t)}}}))),u=a.filter((e=>e.ok))
|
|
2481
|
+
for(const e of a)e.ok||i.push({name:e.proposal.name,reason:e.error})
|
|
2478
2482
|
const p=[]
|
|
2479
|
-
if(
|
|
2483
|
+
if(u.length>0)try{const e=r.getSessionMessageClient()
|
|
2480
2484
|
if(!e)throw Error("session message client unavailable")
|
|
2481
|
-
await e.createQuestion({fromSessionId:n,questionType:"alternatives",question:`Command batch approval: ${
|
|
2482
|
-
for(const e of
|
|
2483
|
-
for(const t of
|
|
2484
|
-
const
|
|
2485
|
-
return p.length>0&&(
|
|
2486
|
-
const n=
|
|
2487
|
-
if(!n)return
|
|
2488
|
-
try{return await r.proxyDelete(`/api/projects/${t.projectId}/check-commands/${n.id}`),
|
|
2489
|
-
return
|
|
2490
|
-
if(!
|
|
2491
|
-
return
|
|
2492
|
-
e.grep&&(
|
|
2493
|
-
const
|
|
2494
|
-
let
|
|
2495
|
-
try{
|
|
2496
|
-
const
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
if(p)try{const e=await p.createToolRecord({projectId:t.projectId,type:"run-command",metadata:{name:I.name,command:k,exitCode:R,timedOut:M,status:M?"timeout":0===R?"success":"failed",timestamp:(new Date).toISOString()}})
|
|
2504
|
-
A=e?.id}catch(e){u.debug("Failed to create tool record for command run",{error:(0,c.getErrorMessage)(e)})}return g({success:!0}),{content:[{type:"text",text:`## ${I.name}${O?` \u2014 ${O} (killed after ${I.timeout}s)`:""}\n\`\`\`\n$ ${k}\n${D}\n\`\`\`\n\nExit code: ${R}\nFull log: ${S}`}],...(0,a.alchemyMeta)({kind:"run-command",toolRecordId:A})}}catch(e){return g({success:!1,error:"api_error"}),{content:[{type:"text",text:"Failed to run command: "+(0,c.getErrorMessage)(e)}],isError:!0}}}
|
|
2505
|
-
const n=t(4600),o=r(t(6928)),s=r(t(9896)),i=t(4139),a=t(2272),c=t(8824),l=t(6688),d=t(9414)},4466(e,exports,t){"use strict"
|
|
2485
|
+
await e.createQuestion({fromSessionId:n,questionType:"alternatives",question:`Command batch approval: ${u.length} command${1===u.length?"":"s"}`,options:[{id:"approve-all",label:"Approve all",description:`Activate all ${u.length} commands`},{id:"deny-all",label:"Deny all",description:"Delete all proposed commands"},{id:"select",label:"Select\u2026",description:"Pick which to approve individually"}],context:JSON.stringify({type:"command-approval-batch",commands:u.map((e=>({commandId:e.commandId,name:e.proposal.name,command:e.proposal.command,description:e.proposal.description,cwd:e.proposal.cwd})))}),category:"action-required",priority:"high"})
|
|
2486
|
+
for(const e of u)p.push(e.proposal.name)}catch(e){await Promise.allSettled(u.map((e=>r.proxyDelete(`/api/projects/${t.projectId}/check-commands/${e.commandId}`).catch((t=>h.warn("Rollback delete after batch question failure",{commandId:e.commandId,error:(0,l.getErrorMessage)(t)}))))))
|
|
2487
|
+
for(const t of u)i.push({name:t.proposal.name,reason:(0,l.getErrorMessage)(e)})}h.info("Register submitted commands for approval",{total:e.commands.length,submitted:p.length,skipped:i.length}),f({success:!0})
|
|
2488
|
+
const m=[`## Submitted ${p.length} command${1===p.length?"":"s"} for approval${i.length?`, ${i.length} skipped`:""}`]
|
|
2489
|
+
return p.length>0&&(m.push("","### Pending approval"),m.push(...p.map((e=>`- **${e}**`))),m.push("","The user will see one approval notification per command. Approved commands become runnable; denied ones are removed.")),i.length>0&&(m.push("",`### Skipped (${i.length})`),m.push(...i.map((e=>`- **${e.name}** \u2014 ${e.reason}`)))),m.push("",'Use `commands({ operation: "list" })` to see status.'),{content:[{type:"text",text:m.join("\n")}],...(0,c.alchemyMeta)({kind:"register-commands"})}}if("remove"===y){if(!e.name?.trim())return f({success:!1,error:"missing_name"}),{content:[{type:"text",text:"Error: `name` is required when removing a command."}],isError:!0}
|
|
2490
|
+
const n=w.find((t=>t.name.toLowerCase()===e.name.toLowerCase()))
|
|
2491
|
+
if(!n)return f({success:!1,error:"not_found"}),{content:[{type:"text",text:`Command "${e.name}" not found.`}],isError:!0}
|
|
2492
|
+
try{return await r.proxyDelete(`/api/projects/${t.projectId}/check-commands/${n.id}`),h.info("Removed command",{name:n.name,id:n.id}),f({success:!0}),{content:[{type:"text",text:`Removed command **${n.name}**.`}]}}catch(e){return f({success:!1,error:"remove_failed"}),{content:[{type:"text",text:"Failed to remove command: "+(0,l.getErrorMessage)(e)}],isError:!0}}}if(!e.name){const e=b.map((e=>e.name)).join(", ")
|
|
2493
|
+
return f({success:!1,error:"missing_name"}),{content:[{type:"text",text:"Error: `name` is required when running a command.\n\nAvailable commands: "+(e||"(none configured)")}],isError:!0}}const k=b.find((t=>t.name.toLowerCase()===e.name.toLowerCase()))
|
|
2494
|
+
if(!k){const t=b.map((e=>e.name)).join(", ")
|
|
2495
|
+
return f({success:!1,error:"not_found"}),{content:[{type:"text",text:`Command "${e.name}" not found.\n\nAvailable commands: ${t||"(none configured)"}`}],isError:!0}}let v=e.args?`${k.command} ${e.args}`:k.command
|
|
2496
|
+
e.grep&&(v=`${v} 2>&1 | grep -i ${JSON.stringify(e.grep)}`)
|
|
2497
|
+
const C=t.workspaceManager?.getCurrentContext(t.currentSessionId)?.basePath||t.basePath,_=k.cwd?s.default.resolve(C,k.cwd):C,E=s.default.join(C,a.ALCHEMY_DIR_NAME,"command-logs"),S=`${k.name.replace(/[^a-zA-Z0-9._-]/g,"_")}-${Date.now()}`,q=s.default.join(E,S+".log")
|
|
2498
|
+
let T=null
|
|
2499
|
+
try{i.default.mkdirSync(E,{recursive:!0}),T=i.default.createWriteStream(q,{flags:"w"}),T.write(`# ${(new Date).toISOString()} starting: ${v}\n# cwd: ${_}\n# timeout: ${k.timeout||60}s\n\n`)}catch(e){h.warn("Failed to open command log file",{logPath:q,error:(0,l.getErrorMessage)(e)})}h.info("Running command",{name:k.name,command:v,cwd:_,configuredCwd:k.cwd,logPath:q})
|
|
2500
|
+
const x=await(0,n.runTrackedShellCommand)(v,{cwd:_,timeoutMs:1e3*(k.timeout||60),env:{...process.env,FORCE_COLOR:"0"},onChunk:T?(e,t)=>{T?.write(t)}:void 0},{id:S,name:k.name,sessionId:g?.sessionId,logPath:q})
|
|
2501
|
+
T&&(T.write(`\n# exit: ${x.exitCode}${x.timedOut?" (timed out)":""}\n`),T.end())
|
|
2502
|
+
const{stdout:P,stderr:R,exitCode:M,timedOut:$}=x,{output:j,truncatedNote:O,logFooter:D}=(0,o.formatCommandOutput)(P,R,{invocationId:S}),A=$?"TIMEOUT":null
|
|
2503
|
+
let F
|
|
2504
|
+
if(m)try{const e=await m.createToolRecord({projectId:t.projectId,type:"run-command",metadata:{name:k.name,command:v,exitCode:M,timedOut:$,status:$?"timeout":0===M?"success":"failed",timestamp:(new Date).toISOString()}})
|
|
2505
|
+
F=e?.id}catch(e){h.debug("Failed to create tool record for command run",{error:(0,l.getErrorMessage)(e)})}return f({success:!0}),{content:[{type:"text",text:`## ${k.name}${A?` \u2014 ${A} (killed after ${k.timeout}s)`:""}\n\`\`\`\n$ ${v}\n${j}\n\`\`\`${O}\n\nExit code: ${M}\n${D} (pass to read_files or search)`}],...(0,c.alchemyMeta)({kind:"run-command",toolRecordId:F})}}catch(e){return f({success:!1,error:"api_error"}),{content:[{type:"text",text:"Failed to run command: "+(0,l.getErrorMessage)(e)}],isError:!0}}}
|
|
2506
|
+
const n=t(4600),o=t(1816),s=r(t(6928)),i=r(t(9896)),a=t(4139),c=t(2272),l=t(8824),d=t(6688),u=t(9414)},4466(e,exports,t){"use strict"
|
|
2506
2507
|
function r(e,t){return`${e}#${t.toString().padStart(2,"0")}`}async function n(e,t,n,s){if(exports.TASK_REF_PATTERN.test(e))return e
|
|
2507
2508
|
const i=e.match(exports.TASK_REF_SHORT_PATTERN)
|
|
2508
2509
|
if(!i)return e
|
|
@@ -2543,7 +2544,7 @@ if("interactive"===n)return"interactive"}catch(e){t.warn("Failed to read project
|
|
|
2543
2544
|
if("ask"===e||"interactive"===e){h.info("Returning planner choice pending",{planningMode:e})
|
|
2544
2545
|
try{const e=u?.sessionId||o.sessionIdentity.getIdentity()?.sessionId,t=d?.getSessionMessageClient()
|
|
2545
2546
|
if(t&&e){const r=await o.sessionIdentity.getParentSessionId(e,d,h),n=c.description||c.prompt
|
|
2546
|
-
await t.createQuestion({fromSessionId:e,toSessionId:r||void 0,questionType:"alternatives",question:"Planning requested: "+n,options:[{id:"auto",label:"Auto Plan",description:"Spawn an autonomous planner agent"},{id:"manual",label:"Manual Plan",description:"Open the interactive plan editor"},{id:"cancel",label:"Skip",description:"Dismiss this planning request"}],context:c.prompt,category:"action-required",priority:"high"}),h.info("planner-request: notification created",{fromSessionId:e})}else h.warn("planner-request: skipping notification",{hasClient:!!t,hasCallerSessionId:!!e})}catch(e){h.warn("planner-request: failed to create notification",{error:(0,n.getErrorMessage)(e)})}f={content:[{type:"text",text:"Planning request submitted. The user will choose the planning approach. This is non-blocking \u2014 continue with your other work."}],...(0,i.alchemyMeta)({pendingAction:{kind:"planner-choice",payload:{prompt:c.prompt,description:c.description}}})}
|
|
2547
|
+
await t.createQuestion({fromSessionId:e,toSessionId:r||void 0,questionType:"alternatives",question:"Planning requested: "+n,options:[{id:"auto",label:"Auto Plan",description:"Spawn an autonomous planner agent"},{id:"manual",label:"Manual Plan",description:"Open the interactive plan editor"},{id:"cancel",label:"Skip",description:"Dismiss this planning request"}],context:{type:"planner-request",prompt:c.prompt,description:c.description},category:"action-required",priority:"high"}),h.info("planner-request: notification created",{fromSessionId:e})}else h.warn("planner-request: skipping notification",{hasClient:!!t,hasCallerSessionId:!!e})}catch(e){h.warn("planner-request: failed to create notification",{error:(0,n.getErrorMessage)(e)})}f={content:[{type:"text",text:"Planning request submitted. The user will choose the planning approach. This is non-blocking \u2014 continue with your other work."}],...(0,i.alchemyMeta)({pendingAction:{kind:"planner-choice",payload:{prompt:c.prompt,description:c.description}}})}
|
|
2547
2548
|
break}const r="auto-planner"
|
|
2548
2549
|
if(h.info("Spawning planner",{planningMode:e,agent:r}),f=await(0,s.handleSpawn)({agent:r,prompt:c.prompt,title:c.description},t,h,d,u),!f.isError&&f.content.length>0){const t=f.content[0].text
|
|
2549
2550
|
f.content[0].text=`Planning mode: ${e} (agent: ${r}). Planning is running in the background \u2014 continue with your other work.\n${t}`}break}case"check-status":f=await(0,s.handleCheckStatus)(g,t,h,d)
|
|
@@ -3547,7 +3548,7 @@ if(!s)return{content:[{type:"text",text:"\u274c No project context available. Pl
|
|
|
3547
3548
|
const i=e.name.replace(/\.md$/i,""),a=`${d.filenamePrefix}${i}.md`,l=o.getDocumentClient(),p=await l.listDocuments({type:d.docType,filename:a,limit:1,offset:0})
|
|
3548
3549
|
if(p&&p.length>0)return{content:[{type:"text",text:`\u274c A ${d.label} with filename "${a}" already exists (${p[0].localId||p[0].id}). Choose a different name.`}],isError:!0}
|
|
3549
3550
|
const m=u?{criteria:e.criteria||"",status:n.QUEST_STATUS_OPEN,tickIntervalSeconds:e.tickIntervalSeconds??n.QUEST_DEFAULTS.tickIntervalSeconds,maxInFlight:e.maxInFlight??n.QUEST_DEFAULTS.maxInFlight,maxPerDay:e.maxPerDay??n.QUEST_DEFAULTS.maxPerDay,minIterations:e.minIterations??n.QUEST_DEFAULTS.minIterations}:void 0,g=await l.createDocument(void 0,{filename:a,content:e.content,type:d.docType,...m&&{metadata:m},...c&&{cliOrigin:c}}),f=g.localId||g.id,y=g.id
|
|
3550
|
-
let w=!1
|
|
3551
|
+
if(c?.sessionId)try{await o.proxyPost(`/api/projects/${s}/session-plan-associations`,{opencodeSessionId:c.sessionId,documentId:y})}catch(e){h.warn("Failed to create session-plan association",{error:(0,r.getErrorMessage)(e)})}let w=!1
|
|
3551
3552
|
if(d.canBeActivePlan)try{const e=o.getProjectClient(),t=await e.getProject(s)
|
|
3552
3553
|
t&&!t.activePlanId&&(await e.updateProject(s,{activePlanId:y}),w=!0,h.info("Auto-set as active plan",{planId:f,projectId:s}))}catch(e){h.warn("Failed to auto-set active plan",{error:(0,r.getErrorMessage)(e)})}return h.info(""+d.headingCreated,{planId:f,filename:a}),{content:[{type:"text",text:`\u2705 **${d.headingCreated}**\n\n**${d.idLabel}:** ${f}\n**Filename:** ${a}\n**Created:** `+new Date(g.createdAt).toLocaleString()+(w?"\n\n\u{1f4cc} _Automatically set as the active project plan._":"")}]}}catch(e){return h.error("Failed to "+d.errorVerb,{error:(0,r.getErrorMessage)(e)}),{content:[{type:"text",text:`\u274c **Failed to ${d.errorVerb}:** ${(0,r.getErrorMessage)(e)}`}],isError:!0}}}
|
|
3553
3554
|
const r=t(8824),n=t(3416)},6688(e,exports,t){"use strict"
|
|
@@ -4429,21 +4430,28 @@ if(r.startsWith("R")){const e=t[1],n=t[2],o=_.get(n)||{additions:0,deletions:0,b
|
|
|
4429
4430
|
Number.isFinite(s)&&(i.similarity=s),o.binary&&(i.binary=!0),E.push(i)}else{const e=t[1],n=_.get(e)||{additions:0,deletions:0,binary:!1},o={path:e,status:"A"===r?"added":"D"===r?"deleted":"modified",additions:n.additions,deletions:n.deletions}
|
|
4430
4431
|
n.binary&&(o.binary=!0),E.push(o)}}return E.sort(((e,t)=>e.path.localeCompare(t.path))),u({success:!0}),l.incrementCounter("request_diff_total",1,{format:"summary"}),r({baseSha:b,headSha:I,branch:y?null:w,files:E})}catch(e){return d.error("request_diff failed",{error:e instanceof Error?e.stack:e+""}),u({success:!1}),l.incrementCounter("request_diff_errors",1),n("request_diff failed: "+(0,i.getErrorMessage)(e))}}
|
|
4431
4432
|
const o=t(3807),s=t(2272),i=t(8824),a=5e5},8291(e,exports,t){"use strict"
|
|
4432
|
-
var
|
|
4433
|
-
Object.defineProperty(exports,"__esModule",{value:!0}),exports.validateAndNormalizePath=function(e,t){
|
|
4434
|
-
const
|
|
4435
|
-
if(
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4433
|
+
function r(e){return e.workspaceManager?e.workspaceManager.getCurrentContext(e.currentSessionId).basePath:e.basePath}var n=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
|
|
4434
|
+
Object.defineProperty(exports,"__esModule",{value:!0}),exports.validateAndNormalizePath=function(e,t){const n=function(e,t){if(!e.startsWith("log:"))return null
|
|
4435
|
+
const r=e.slice(4).replace(/^\/+/,""),n=r?`${i.ALCHEMY_DIR_NAME}/command-logs/${r}.log`:i.ALCHEMY_DIR_NAME+"/command-logs"
|
|
4436
|
+
if(r&&(!/^[a-zA-Z0-9._-]+$/.test(r)||r.includes("..")))throw(0,s.createValidationError)(`Invalid log id in '${e}'. Expected alphanumeric characters, dots, underscores, hyphens.`)
|
|
4437
|
+
const a=o.default.resolve(t,n)
|
|
4438
|
+
if(!a.startsWith(t+o.default.sep))throw(0,s.createValidationError)("Log path resolves outside the workspace: "+e)
|
|
4439
|
+
return{normalizedPath:n,fullPath:a}}(e,r(t))
|
|
4440
|
+
if(n)return n
|
|
4441
|
+
let a=e.replace(/\\/g,"/")
|
|
4442
|
+
const d=r(t)
|
|
4443
|
+
if(o.default.isAbsolute(a)){const e=o.default.normalize(a)
|
|
4444
|
+
if(e.startsWith(d))a=o.default.relative(d,e),a=a.replace(/\\/g,"/")
|
|
4445
|
+
else{const r=t.basePath
|
|
4446
|
+
if(!r||!e.startsWith(r)){const n=t.workspaceManager?.getCurrentContext(t.currentSessionId)?.mode||"unknown",o=t.workspaceManager?.getCurrentContext(t.currentSessionId)?.requestId||"none"
|
|
4447
|
+
throw(0,s.createValidationError)(`File path is outside the allowed directory\n Requested path (absolute): ${e}\n Allowed base (current workspace): ${d}\n Allowed base (main repo): ${r||"N/A"}\n Workspace mode: ${n}\n Current request: ${o}\n Note: Use relative paths (e.g., 'src/file.txt') instead of absolute paths`)}a=o.default.relative(r,e),a=a.replace(/\\/g,"/")}}if(function(e){const t=e.replace(/\\/g,"/").replace(/^\//g,"")
|
|
4448
|
+
for(const e of c)if(t===e||t.startsWith(e+"/"))return!0
|
|
4449
|
+
for(const e of l)if(t===e||t.endsWith("/"+e))return!0
|
|
4450
|
+
return!1}(a))throw(0,s.createValidationError)(`Access to '${a}' is not allowed - this directory contains system files`)
|
|
4451
|
+
const u=o.default.resolve(d,a)
|
|
4452
|
+
if(!u.startsWith(d)){const e=t.workspaceManager?.getCurrentContext(t.currentSessionId)?.mode||"unknown",r=t.workspaceManager?.getCurrentContext(t.currentSessionId)?.requestId||"none"
|
|
4453
|
+
throw(0,s.createValidationError)(`File path is outside the allowed directory\n Requested path: ${a}\n Resolved to: ${u}\n Allowed base: ${d}\n Workspace mode: ${e}\n Current request: `+r)}return{normalizedPath:a,fullPath:u}},exports.validateTaskIdFormat=function(e,t,r,n){e&&(0,a.isTaskRef)(e)||(t.warn("Invalid task ID format provided",{taskId:e}),r.incrementCounter(n+"_invalid_task_format",1))}
|
|
4454
|
+
const o=n(t(6928)),s=t(9860),i=t(4139),a=t(4466),c=[i.ALCHEMY_DIR_NAME,".opencode",".git"],l=[".mcp-server.lock"]},8309(e,exports,t){"use strict"
|
|
4447
4455
|
var r,n=this&&this.__createBinding||(Object.create?function(e,t,r,n){void 0===n&&(n=r)
|
|
4448
4456
|
var o=Object.getOwnPropertyDescriptor(t,r)
|
|
4449
4457
|
o&&!("get"in o?!t.__esModule:o.writable||o.configurable)||(o={enumerable:!0,get(){return t[r]}}),Object.defineProperty(e,n,o)}:function(e,t,r,n){void 0===n&&(n=r),e[n]=t[r]}),o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),s=this&&this.__importStar||(r=function(e){return r=Object.getOwnPropertyNames||function(e){var t=[]
|
|
@@ -4869,7 +4877,7 @@ if(n(parseInt(e.replace("v","").split(".")[0],10)))return"node"}catch{}const e=p
|
|
|
4869
4877
|
if((0,i.existsSync)(t))try{const e=(0,i.readdirSync)(t).filter((e=>n(parseInt(e.replace("v","").split(".")[0],10)))).sort(((e,t)=>{const r=parseInt(e.replace("v","").split(".")[0],10)
|
|
4870
4878
|
return parseInt(t.replace("v","").split(".")[0],10)-r}))
|
|
4871
4879
|
if(e.length>0){const r=(0,s.join)(t,e[0],"bin","node")
|
|
4872
|
-
if((0,i.existsSync)(r))return r}}catch{}throw Error(`AlchemyLab requires Node.js 22 or 24+ to run the OpenCode server (Node 23 is not supported).\nYour current version: ${process.version}\nInstall a compatible version: nvm install 22 (or download from https://nodejs.org)`)}(),prependArgs:["--no-warnings",(0,s.join)(e,t)]}}Object.defineProperty(exports,"__esModule",{value:!0}),exports.resolveServeCommand=function(){const e=r()
|
|
4880
|
+
if((0,i.existsSync)(r))return r}}catch{}throw Error(`AlchemyLab requires Node.js 22 or 24+ to run the OpenCode server (Node 23 is not supported).\nYour current version: ${process.version}\nInstall a compatible version: nvm install 22 (or download from https://nodejs.org)`)}(),prependArgs:["--no-warnings",(0,s.join)(e,t)]}}Object.defineProperty(exports,"__esModule",{value:!0}),exports.findOpencodeDir=r,exports.resolveServeCommand=function(){const e=r()
|
|
4873
4881
|
return e?o((0,s.join)(e,"dist"),"serve.js"):{command:"opencode",prependArgs:["serve"]}},exports.resolveRunCommand=function(){const e=r()
|
|
4874
4882
|
return e?o((0,s.join)(e,"dist"),"run.js"):{command:"opencode",prependArgs:[]}}
|
|
4875
4883
|
const s=t(6928),i=t(9896),a=t(5317)},9072(e,exports,t){"use strict"
|