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/http-server.js
CHANGED
|
@@ -1262,7 +1262,11 @@ try{if(o.default.existsSync(this.projectFilePath)){this.logger.debug("Updating e
|
|
|
1262
1262
|
const t=JSON.parse(o.default.readFileSync(this.projectFilePath,"utf8"))
|
|
1263
1263
|
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})
|
|
1264
1264
|
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
|
|
1265
|
-
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)}}},
|
|
1265
|
+
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"
|
|
1266
|
+
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
|
|
1267
|
+
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
|
|
1268
|
+
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}\``}}
|
|
1269
|
+
const t=3e3,r=1500},1887(e,exports,t){"use strict"
|
|
1266
1270
|
Object.defineProperty(exports,"__esModule",{value:!0}),exports.ProxyClient=void 0
|
|
1267
1271
|
const r=t(9860),n=t(5674),o=t(4192),s=t(2161),i=t(8824)
|
|
1268
1272
|
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)+"..."})
|
|
@@ -1350,16 +1354,16 @@ if(p.push("session: "+(r?.sessionId||"NONE")),r?.sessionId){const t=await e.getS
|
|
|
1350
1354
|
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
|
|
1351
1355
|
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}
|
|
1352
1356
|
const g=[],{default:m}=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=m.join(h,y,"command-logs")
|
|
1353
|
-
try{f.mkdirSync(w,{recursive:!0})}catch{}const{runTrackedShellCommand:I}=await Promise.resolve().then(t.bind(t,4600)),{
|
|
1357
|
+
try{f.mkdirSync(w,{recursive:!0})}catch{}const{runTrackedShellCommand:I}=await Promise.resolve().then(t.bind(t,4600)),{formatCombinedOutput:b}=await Promise.resolve().then(t.bind(t,1816)),{sessionIdentity:k}=await Promise.resolve().then(t.bind(t,6688)),v=k.getIdentity()?.sessionId
|
|
1354
1358
|
for(const t of l){c.info("Executing test command",{name:t.name,command:t.command,timeout:t.timeout,cwd:h})
|
|
1355
1359
|
const r=`${t.name.replace(/[^a-zA-Z0-9._-]/g,"_")}-${Date.now()}`,n=m.join(w,r+".log")
|
|
1356
1360
|
let o=null
|
|
1357
|
-
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 I(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:
|
|
1361
|
+
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 I(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})
|
|
1358
1362
|
o&&(o.write(`\n# exit: ${s.exitCode}${s.timedOut?" (timed out)":""}\n`),o.end())
|
|
1359
|
-
const i=(s.stdout+(s.stderr?"\n"+s.stderr:"")).trim()
|
|
1360
|
-
if(g.push({name:t.name,command:t.command,exitCode:s.exitCode,output:
|
|
1361
|
-
return`### ${e.name}: ${t}\n\`\`\`\n$ ${e.command}\n${e.output}\n
|
|
1362
|
-
return a({success:!0}),{content:[{type:"text",text:`## ${"setup"===e.commandName?"Setup":e.commandName} \u2014 ${
|
|
1363
|
+
const i=(s.stdout+(s.stderr?"\n"+s.stderr:"")).trim(),{output:a,truncatedNote:l,logFooter:d}=b(i,{invocationId:r})
|
|
1364
|
+
if(g.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=g.every((e=>0===e.exitCode)),_=g.map((e=>{const t=e.timedOut?"\u23f1 TIMEOUT":0===e.exitCode?"\u2705 PASS":`\u274c FAIL (exit ${e.exitCode})`
|
|
1365
|
+
return`### ${e.name}: ${t}\n\`\`\`\n$ ${e.command}\n${e.output}\n\`\`\`${e.truncatedNote}\n${e.logFooter}`})).join("\n\n")
|
|
1366
|
+
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")
|
|
1363
1367
|
try{const{sessionIdentity:u}=await Promise.resolve().then(t.bind(t,6688)),h=await u.getSessionRequestId(i,a,l?.sessionId)
|
|
1364
1368
|
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}
|
|
1365
1369
|
const p=await i.proxyGet(`/api/projects/${s.projectId}/requests/${h}/test-runs`),g=p?.data||p||[],m=Array.isArray(g)?g:[],f=m.find((e=>"pending"===e.status||"running"===e.status))||m[0]
|
|
@@ -2224,7 +2228,7 @@ var m=t(6539)
|
|
|
2224
2228
|
Object.defineProperty(exports,"ClientFactory",{enumerable:!0,get(){return m.ClientFactory}})
|
|
2225
2229
|
const f=t(6539)
|
|
2226
2230
|
exports.default={ClientFactory:f.ClientFactory}},3416(e,exports){"use strict"
|
|
2227
|
-
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"
|
|
2231
|
+
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"
|
|
2228
2232
|
Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleGetTaskInformation=async function(e,t,i,a,c){const l=a.child({tool:"get_task_information"})
|
|
2229
2233
|
l.info("Getting task information",{args:e})
|
|
2230
2234
|
const d=c.startTimer("tool_handler_duration")
|
|
@@ -2682,51 +2686,48 @@ const a=await r.updateDocument(e.planId,i),c=a.localId||a.id
|
|
|
2682
2686
|
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}}}
|
|
2683
2687
|
const r=t(8824)},4425(e,exports,t){"use strict"
|
|
2684
2688
|
var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
|
|
2685
|
-
Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleCommands=async function(e,t,r,
|
|
2686
|
-
try{const
|
|
2687
|
-
if("list"===
|
|
2689
|
+
Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleCommands=async function(e,t,r,h,p,g,m){const f=p.startTimer("tool_execution_time"),y=e.operation||"run"
|
|
2690
|
+
try{const p=await r.proxyGet(`/api/projects/${t.projectId}/check-commands`),w=p?.data||p||[],I=w.filter((e=>e.isActive)),b=w.filter((e=>!e.isActive))
|
|
2691
|
+
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"})}
|
|
2688
2692
|
const e=[]
|
|
2689
|
-
return
|
|
2690
|
-
try{const r=await fetch((0,
|
|
2693
|
+
return I.length>0&&(e.push(`## Available Commands (${I.length})\n`),e.push(...I.map((e=>`- **${e.name}**: \`${e.command}\`${e.cwd?` (in \`${e.cwd}\`)`:""}${e.description?" \u2014 "+e.description:""}`)))),b.length>0&&(e.push(`\n## Pending Approval (${b.length})\n`),e.push(...b.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=m?.sessionId,n=t?await d.sessionIdentity.getSessionRequestId(r,h,t):void 0
|
|
2694
|
+
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)})
|
|
2691
2695
|
if(!r.ok){const e=await r.text().catch((()=>""))
|
|
2692
|
-
return
|
|
2693
|
-
return o.success&&o.data?(
|
|
2694
|
-
const n=function(e){return e?.sessionId||
|
|
2695
|
-
if(!n)return
|
|
2696
|
-
const o=new Set(
|
|
2696
|
+
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()
|
|
2697
|
+
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}
|
|
2698
|
+
const n=function(e){return e?.sessionId||d.sessionIdentity.getIdentity()?.sessionId||null}(m)
|
|
2699
|
+
if(!n)return f({success:!1,error:"no_session"}),{content:[{type:"text",text:"Error: no session context available to attribute approval notifications."}],isError:!0}
|
|
2700
|
+
const o=new Set(w.map((e=>e.name.toLowerCase()))),s=[],i=[]
|
|
2697
2701
|
for(const t of e.commands){const e=t.name?.trim(),r=t.command?.trim(),n=t.description?.trim()
|
|
2698
|
-
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
|
|
2702
|
+
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:m?.sessionId,isActive:!1}),o=n?.data?.id||n?.id
|
|
2699
2703
|
if(!o)throw Error("command create returned no id")
|
|
2700
|
-
return{ok:!0,proposal:e,commandId:o}}catch(t){return{ok:!1,proposal:e,error:(0,
|
|
2701
|
-
for(const e of
|
|
2704
|
+
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))
|
|
2705
|
+
for(const e of a)e.ok||i.push({name:e.proposal.name,reason:e.error})
|
|
2702
2706
|
const p=[]
|
|
2703
|
-
if(
|
|
2707
|
+
if(u.length>0)try{const e=r.getSessionMessageClient()
|
|
2704
2708
|
if(!e)throw Error("session message client unavailable")
|
|
2705
|
-
await e.createQuestion({fromSessionId:n,questionType:"alternatives",question:`Command batch approval: ${
|
|
2706
|
-
for(const e of
|
|
2707
|
-
for(const t of
|
|
2708
|
-
const
|
|
2709
|
-
return p.length>0&&(
|
|
2710
|
-
const n=
|
|
2711
|
-
if(!n)return
|
|
2712
|
-
try{return await r.proxyDelete(`/api/projects/${t.projectId}/check-commands/${n.id}`),
|
|
2713
|
-
return
|
|
2714
|
-
if(!
|
|
2715
|
-
return
|
|
2716
|
-
e.grep&&(
|
|
2717
|
-
const
|
|
2718
|
-
let
|
|
2719
|
-
try{
|
|
2720
|
-
const
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
if(p)try{const e=await p.createToolRecord({projectId:t.projectId,type:"run-command",metadata:{name:b.name,command:k,exitCode:j,timedOut:R,status:R?"timeout":0===j?"success":"failed",timestamp:(new Date).toISOString()}})
|
|
2728
|
-
A=e?.id}catch(e){u.debug("Failed to create tool record for command run",{error:(0,c.getErrorMessage)(e)})}return m({success:!0}),{content:[{type:"text",text:`## ${b.name}${O?` \u2014 ${O} (killed after ${b.timeout}s)`:""}\n\`\`\`\n$ ${k}\n${D}\n\`\`\`\n\nExit code: ${j}\nFull log: ${S}`}],...(0,a.alchemyMeta)({kind:"run-command",toolRecordId:A})}}catch(e){return m({success:!1,error:"api_error"}),{content:[{type:"text",text:"Failed to run command: "+(0,c.getErrorMessage)(e)}],isError:!0}}}
|
|
2729
|
-
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"
|
|
2709
|
+
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"})
|
|
2710
|
+
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)}))))))
|
|
2711
|
+
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})
|
|
2712
|
+
const g=[`## Submitted ${p.length} command${1===p.length?"":"s"} for approval${i.length?`, ${i.length} skipped`:""}`]
|
|
2713
|
+
return p.length>0&&(g.push("","### Pending approval"),g.push(...p.map((e=>`- **${e}**`))),g.push("","The user will see one approval notification per command. Approved commands become runnable; denied ones are removed.")),i.length>0&&(g.push("",`### Skipped (${i.length})`),g.push(...i.map((e=>`- **${e.name}** \u2014 ${e.reason}`)))),g.push("",'Use `commands({ operation: "list" })` to see status.'),{content:[{type:"text",text:g.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}
|
|
2714
|
+
const n=w.find((t=>t.name.toLowerCase()===e.name.toLowerCase()))
|
|
2715
|
+
if(!n)return f({success:!1,error:"not_found"}),{content:[{type:"text",text:`Command "${e.name}" not found.`}],isError:!0}
|
|
2716
|
+
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=I.map((e=>e.name)).join(", ")
|
|
2717
|
+
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=I.find((t=>t.name.toLowerCase()===e.name.toLowerCase()))
|
|
2718
|
+
if(!k){const t=I.map((e=>e.name)).join(", ")
|
|
2719
|
+
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
|
|
2720
|
+
e.grep&&(v=`${v} 2>&1 | grep -i ${JSON.stringify(e.grep)}`)
|
|
2721
|
+
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")
|
|
2722
|
+
let T=null
|
|
2723
|
+
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})
|
|
2724
|
+
const P=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:m?.sessionId,logPath:q})
|
|
2725
|
+
T&&(T.write(`\n# exit: ${P.exitCode}${P.timedOut?" (timed out)":""}\n`),T.end())
|
|
2726
|
+
const{stdout:x,stderr:j,exitCode:R,timedOut:M}=P,{output:$,truncatedNote:O,logFooter:D}=(0,o.formatCommandOutput)(x,j,{invocationId:S}),A=M?"TIMEOUT":null
|
|
2727
|
+
let F
|
|
2728
|
+
if(g)try{const e=await g.createToolRecord({projectId:t.projectId,type:"run-command",metadata:{name:k.name,command:v,exitCode:R,timedOut:M,status:M?"timeout":0===R?"success":"failed",timestamp:(new Date).toISOString()}})
|
|
2729
|
+
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${$}\n\`\`\`${O}\n\nExit code: ${R}\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}}}
|
|
2730
|
+
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"
|
|
2730
2731
|
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
|
|
2731
2732
|
const i=e.match(exports.TASK_REF_SHORT_PATTERN)
|
|
2732
2733
|
if(!i)return e
|
|
@@ -2767,7 +2768,7 @@ if("interactive"===n)return"interactive"}catch(e){t.warn("Failed to read project
|
|
|
2767
2768
|
if("ask"===e||"interactive"===e){h.info("Returning planner choice pending",{planningMode:e})
|
|
2768
2769
|
try{const e=u?.sessionId||o.sessionIdentity.getIdentity()?.sessionId,t=d?.getSessionMessageClient()
|
|
2769
2770
|
if(t&&e){const r=await o.sessionIdentity.getParentSessionId(e,d,h),n=c.description||c.prompt
|
|
2770
|
-
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}}})}
|
|
2771
|
+
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}}})}
|
|
2771
2772
|
break}const r="auto-planner"
|
|
2772
2773
|
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
|
|
2773
2774
|
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)(m,t,h,d)
|
|
@@ -3750,7 +3751,7 @@ if(!s)return{content:[{type:"text",text:"\u274c No project context available. Pl
|
|
|
3750
3751
|
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})
|
|
3751
3752
|
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}
|
|
3752
3753
|
const g=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,m=await l.createDocument(void 0,{filename:a,content:e.content,type:d.docType,...g&&{metadata:g},...c&&{cliOrigin:c}}),f=m.localId||m.id,y=m.id
|
|
3753
|
-
let w=!1
|
|
3754
|
+
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
|
|
3754
3755
|
if(d.canBeActivePlan)try{const e=o.getProjectClient(),t=await e.getProject(s)
|
|
3755
3756
|
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(m.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}}}
|
|
3756
3757
|
const r=t(8824),n=t(3416)},6688(e,exports,t){"use strict"
|
|
@@ -4667,21 +4668,28 @@ if(r.startsWith("R")){const e=t[1],n=t[2],o=_.get(n)||{additions:0,deletions:0,b
|
|
|
4667
4668
|
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}
|
|
4668
4669
|
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:I,headSha:b,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))}}
|
|
4669
4670
|
const o=t(3807),s=t(2272),i=t(8824),a=5e5},8291(e,exports,t){"use strict"
|
|
4670
|
-
var
|
|
4671
|
-
Object.defineProperty(exports,"__esModule",{value:!0}),exports.validateAndNormalizePath=function(e,t){
|
|
4672
|
-
const
|
|
4673
|
-
if(
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4671
|
+
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}}
|
|
4672
|
+
Object.defineProperty(exports,"__esModule",{value:!0}),exports.validateAndNormalizePath=function(e,t){const n=function(e,t){if(!e.startsWith("log:"))return null
|
|
4673
|
+
const r=e.slice(4).replace(/^\/+/,""),n=r?`${i.ALCHEMY_DIR_NAME}/command-logs/${r}.log`:i.ALCHEMY_DIR_NAME+"/command-logs"
|
|
4674
|
+
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.`)
|
|
4675
|
+
const a=o.default.resolve(t,n)
|
|
4676
|
+
if(!a.startsWith(t+o.default.sep))throw(0,s.createValidationError)("Log path resolves outside the workspace: "+e)
|
|
4677
|
+
return{normalizedPath:n,fullPath:a}}(e,r(t))
|
|
4678
|
+
if(n)return n
|
|
4679
|
+
let a=e.replace(/\\/g,"/")
|
|
4680
|
+
const d=r(t)
|
|
4681
|
+
if(o.default.isAbsolute(a)){const e=o.default.normalize(a)
|
|
4682
|
+
if(e.startsWith(d))a=o.default.relative(d,e),a=a.replace(/\\/g,"/")
|
|
4683
|
+
else{const r=t.basePath
|
|
4684
|
+
if(!r||!e.startsWith(r)){const n=t.workspaceManager?.getCurrentContext(t.currentSessionId)?.mode||"unknown",o=t.workspaceManager?.getCurrentContext(t.currentSessionId)?.requestId||"none"
|
|
4685
|
+
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,"")
|
|
4686
|
+
for(const e of c)if(t===e||t.startsWith(e+"/"))return!0
|
|
4687
|
+
for(const e of l)if(t===e||t.endsWith("/"+e))return!0
|
|
4688
|
+
return!1}(a))throw(0,s.createValidationError)(`Access to '${a}' is not allowed - this directory contains system files`)
|
|
4689
|
+
const u=o.default.resolve(d,a)
|
|
4690
|
+
if(!u.startsWith(d)){const e=t.workspaceManager?.getCurrentContext(t.currentSessionId)?.mode||"unknown",r=t.workspaceManager?.getCurrentContext(t.currentSessionId)?.requestId||"none"
|
|
4691
|
+
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))}
|
|
4692
|
+
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"
|
|
4685
4693
|
var r,n=this&&this.__createBinding||(Object.create?function(e,t,r,n){void 0===n&&(n=r)
|
|
4686
4694
|
var o=Object.getOwnPropertyDescriptor(t,r)
|
|
4687
4695
|
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=[]
|
|
@@ -5085,7 +5093,7 @@ if(n(parseInt(e.replace("v","").split(".")[0],10)))return"node"}catch{}const e=p
|
|
|
5085
5093
|
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)
|
|
5086
5094
|
return parseInt(t.replace("v","").split(".")[0],10)-r}))
|
|
5087
5095
|
if(e.length>0){const r=(0,s.join)(t,e[0],"bin","node")
|
|
5088
|
-
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()
|
|
5096
|
+
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()
|
|
5089
5097
|
return e?o((0,s.join)(e,"dist"),"serve.js"):{command:"opencode",prependArgs:["serve"]}},exports.resolveRunCommand=function(){const e=r()
|
|
5090
5098
|
return e?o((0,s.join)(e,"dist"),"run.js"):{command:"opencode",prependArgs:[]}}
|
|
5091
5099
|
const s=t(6928),i=t(9896),a=t(5317)},9072(e,exports,t){"use strict"
|