alchemylab 1.4.2 → 1.4.4

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/index.js CHANGED
@@ -1264,7 +1264,11 @@ try{if(o.default.existsSync(this.projectFilePath)){this.logger.debug("Updating e
1264
1264
  const t=JSON.parse(o.default.readFileSync(this.projectFilePath,"utf8"))
1265
1265
  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})
1266
1266
  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
1267
- 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)}}},1887(e,exports,t){"use strict"
1267
+ 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"
1268
+ 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
1269
+ 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
1270
+ 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}\``}}
1271
+ const t=3e3,r=1500},1887(e,exports,t){"use strict"
1268
1272
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.ProxyClient=void 0
1269
1273
  const r=t(9860),n=t(5674),o=t(4192),s=t(2161),i=t(8824)
1270
1274
  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)+"..."})
@@ -1351,12 +1355,17 @@ try{const{sessionIdentity:e}=await Promise.resolve().then(t.bind(t,6688)),r=e.ge
1351
1355
  if(p.push("session: "+(r?.sessionId||"NONE")),r?.sessionId){const t=await e.getSessionRequestId(o,s)
1352
1356
  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
1353
1357
  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}
1354
- const g=[]
1358
+ 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")
1359
+ 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
1355
1360
  for(const t of l){c.info("Executing test command",{name:t.name,command:t.command,timeout:t.timeout,cwd:h})
1356
- const r=await d(t.command,{cwd:h,timeoutMs:1e3*t.timeout,env:{...u.env,FORCE_COLOR:"0"}}),n=(r.stdout+(r.stderr?"\n"+r.stderr:"")).trim().slice(-4e3)
1357
- if(g.push({name:t.name,command:t.command,exitCode:r.exitCode,output:n,timedOut:r.timedOut}),"setup"===e.commandName&&0!==r.exitCode)break}const m=g.every((e=>0===e.exitCode)),f=g.map((e=>{const t=e.timedOut?"\u23f1 TIMEOUT":0===e.exitCode?"\u2705 PASS":`\u274c FAIL (exit ${e.exitCode})`
1358
- return`### ${e.name}: ${t}\n\`\`\`\n$ ${e.command}\n${e.output}\n\`\`\``})).join("\n\n")
1359
- return a({success:!0}),{content:[{type:"text",text:`## ${"setup"===e.commandName?"Setup":e.commandName} \u2014 ${m?"PASSED":"FAILED"}\n\n${f}`}]}}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")
1361
+ const r=`${t.name.replace(/[^a-zA-Z0-9._-]/g,"_")}-${Date.now()}`,n=m.join(w,r+".log")
1362
+ let o=null
1363
+ 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})
1364
+ o&&(o.write(`\n# exit: ${s.exitCode}${s.timedOut?" (timed out)":""}\n`),o.end())
1365
+ const i=(s.stdout+(s.stderr?"\n"+s.stderr:"")).trim(),{output:a,truncatedNote:l,logFooter:d}=I(i,{invocationId:r})
1366
+ 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})`
1367
+ return`### ${e.name}: ${t}\n\`\`\`\n$ ${e.command}\n${e.output}\n\`\`\`${e.truncatedNote}\n${e.logFooter}`})).join("\n\n")
1368
+ 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")
1360
1369
  try{const{sessionIdentity:u}=await Promise.resolve().then(t.bind(t,6688)),h=await u.getSessionRequestId(i,a,l?.sessionId)
1361
1370
  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}
1362
1371
  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]
@@ -2684,49 +2693,48 @@ const a=await r.updateDocument(e.planId,i),c=a.localId||a.id
2684
2693
  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}}}
2685
2694
  const r=t(8824)},4425(e,exports,t){"use strict"
2686
2695
  var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
2687
- Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleCommands=async function(e,t,r,l,d,u,h){const p=d.startTimer("tool_execution_time"),g=e.operation||"run"
2688
- try{const d=await r.proxyGet(`/api/projects/${t.projectId}/check-commands`),m=d?.data||d||[],f=m.filter((e=>e.isActive)),y=m.filter((e=>!e.isActive))
2689
- if("list"===g){if(0===m.length)return p({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,s.alchemyMeta)({kind:"list-commands"})}
2696
+ 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"
2697
+ 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))
2698
+ 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"})}
2690
2699
  const e=[]
2691
- return f.length>0&&(e.push(`## Available Commands (${f.length})\n`),e.push(...f.map((e=>`- **${e.name}**: \`${e.command}\`${e.cwd?` (in \`${e.cwd}\`)`:""}${e.description?" \u2014 "+e.description:""}`)))),y.length>0&&(e.push(`\n## Pending Approval (${y.length})\n`),e.push(...y.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'),p({success:!0}),{content:[{type:"text",text:e.join("\n")}],...(0,s.alchemyMeta)({kind:"list-commands"})}}if("setup"===g){const t=h?.sessionId,n=t?await a.sessionIdentity.getSessionRequestId(r,l,t):void 0
2692
- try{const r=await fetch((0,c.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)})
2700
+ 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=m?.sessionId,n=t?await d.sessionIdentity.getSessionRequestId(r,h,t):void 0
2701
+ 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)})
2693
2702
  if(!r.ok){const e=await r.text().catch((()=>""))
2694
- return p({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()
2695
- return o.success&&o.data?(p({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,s.alchemyMeta)({kind:"setup-commands"})}):(p({success:!1,error:"spawn_failed"}),{content:[{type:"text",text:"commands setup failed: "+(o.error||"unknown error")}],isError:!0})}catch(e){return p({success:!1,error:"spawn_failed"}),{content:[{type:"text",text:"commands setup failed: "+(0,i.getErrorMessage)(e)}],isError:!0}}}if("register"===g){if(!Array.isArray(e.commands)||0===e.commands.length)return p({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}
2696
- const n=function(e){return e?.sessionId||a.sessionIdentity.getIdentity()?.sessionId||null}(h)
2697
- if(!n)return p({success:!1,error:"no_session"}),{content:[{type:"text",text:"Error: no session context available to attribute approval notifications."}],isError:!0}
2698
- const o=new Set(m.map((e=>e.name.toLowerCase()))),c=[],d=[]
2703
+ 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()
2704
+ 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}
2705
+ const n=function(e){return e?.sessionId||d.sessionIdentity.getIdentity()?.sessionId||null}(m)
2706
+ if(!n)return f({success:!1,error:"no_session"}),{content:[{type:"text",text:"Error: no session context available to attribute approval notifications."}],isError:!0}
2707
+ const o=new Set(w.map((e=>e.name.toLowerCase()))),s=[],i=[]
2699
2708
  for(const t of e.commands){const e=t.name?.trim(),r=t.command?.trim(),n=t.description?.trim()
2700
- e&&r&&n?o.has(e.toLowerCase())?d.push({name:e,reason:"already exists"}):(o.add(e.toLowerCase()),c.push({name:e,command:r,description:n,cwd:t.cwd?.trim(),timeout:t.timeout})):d.push({name:e||"(unnamed)",reason:"missing required fields (name, command, description)"})}const u=await Promise.all(c.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:h?.sessionId,isActive:!1}),o=n?.data?.id||n?.id
2709
+ 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
2701
2710
  if(!o)throw Error("command create returned no id")
2702
- return{ok:!0,proposal:e,commandId:o}}catch(t){return{ok:!1,proposal:e,error:(0,i.getErrorMessage)(t)}}}))),g=u.filter((e=>e.ok))
2703
- for(const e of u)e.ok||d.push({name:e.proposal.name,reason:e.error})
2704
- const f=[]
2705
- if(g.length>0)try{const e=r.getSessionMessageClient()
2711
+ 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))
2712
+ for(const e of a)e.ok||i.push({name:e.proposal.name,reason:e.error})
2713
+ const p=[]
2714
+ if(u.length>0)try{const e=r.getSessionMessageClient()
2706
2715
  if(!e)throw Error("session message client unavailable")
2707
- await e.createQuestion({fromSessionId:n,questionType:"alternatives",question:`Command batch approval: ${g.length} command${1===g.length?"":"s"}`,options:[{id:"approve-all",label:"Approve all",description:`Activate all ${g.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:g.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"})
2708
- for(const e of g)f.push(e.proposal.name)}catch(e){await Promise.allSettled(g.map((e=>r.proxyDelete(`/api/projects/${t.projectId}/check-commands/${e.commandId}`).catch((t=>l.warn("Rollback delete after batch question failure",{commandId:e.commandId,error:(0,i.getErrorMessage)(t)}))))))
2709
- for(const t of g)d.push({name:t.proposal.name,reason:(0,i.getErrorMessage)(e)})}l.info("Register submitted commands for approval",{total:e.commands.length,submitted:f.length,skipped:d.length}),p({success:!0})
2710
- const y=[`## Submitted ${f.length} command${1===f.length?"":"s"} for approval${d.length?`, ${d.length} skipped`:""}`]
2711
- return f.length>0&&(y.push("","### Pending approval"),y.push(...f.map((e=>`- **${e}**`))),y.push("","The user will see one approval notification per command. Approved commands become runnable; denied ones are removed.")),d.length>0&&(y.push("",`### Skipped (${d.length})`),y.push(...d.map((e=>`- **${e.name}** \u2014 ${e.reason}`)))),y.push("",'Use `commands({ operation: "list" })` to see status.'),{content:[{type:"text",text:y.join("\n")}],...(0,s.alchemyMeta)({kind:"register-commands"})}}if("remove"===g){if(!e.name?.trim())return p({success:!1,error:"missing_name"}),{content:[{type:"text",text:"Error: `name` is required when removing a command."}],isError:!0}
2712
- const n=m.find((t=>t.name.toLowerCase()===e.name.toLowerCase()))
2713
- if(!n)return p({success:!1,error:"not_found"}),{content:[{type:"text",text:`Command "${e.name}" not found.`}],isError:!0}
2714
- try{return await r.proxyDelete(`/api/projects/${t.projectId}/check-commands/${n.id}`),l.info("Removed command",{name:n.name,id:n.id}),p({success:!0}),{content:[{type:"text",text:`Removed command **${n.name}**.`}]}}catch(e){return p({success:!1,error:"remove_failed"}),{content:[{type:"text",text:"Failed to remove command: "+(0,i.getErrorMessage)(e)}],isError:!0}}}if(!e.name){const e=f.map((e=>e.name)).join(", ")
2715
- return p({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 w=f.find((t=>t.name.toLowerCase()===e.name.toLowerCase()))
2716
- if(!w){const t=f.map((e=>e.name)).join(", ")
2717
- return p({success:!1,error:"not_found"}),{content:[{type:"text",text:`Command "${e.name}" not found.\n\nAvailable commands: ${t||"(none configured)"}`}],isError:!0}}let b=e.args?`${w.command} ${e.args}`:w.command
2718
- e.grep&&(b=`${b} 2>&1 | grep -i ${JSON.stringify(e.grep)}`)
2719
- const I=t.workspaceManager?.getCurrentContext(t.currentSessionId)?.basePath||t.basePath,k=w.cwd?o.default.resolve(I,w.cwd):I
2720
- l.info("Running command",{name:w.name,command:b,cwd:k,configuredCwd:w.cwd})
2721
- const v=await(0,n.runShellCommand)(b,{cwd:k,timeoutMs:1e3*(w.timeout||60),env:{...process.env,FORCE_COLOR:"0"}})
2722
- let C=v.stdout,_=v.stderr
2723
- const E=v.exitCode,S=v.timedOut,T=8e3,q=4e3
2724
- C.length>T&&(C="\u2026"+C.slice(-T)),_.length>q&&(_="\u2026"+_.slice(-q))
2725
- const P=S?"TIMEOUT":null,j=[C,_].filter(Boolean).join("\n")||"(no output)"
2726
- let R
2727
- if(u)try{const e=await u.createToolRecord({projectId:t.projectId,type:"run-command",metadata:{name:w.name,command:b,exitCode:E,timedOut:S,status:S?"timeout":0===E?"success":"failed",timestamp:(new Date).toISOString()}})
2728
- R=e?.id}catch(e){l.debug("Failed to create tool record for command run",{error:(0,i.getErrorMessage)(e)})}return p({success:!0}),{content:[{type:"text",text:`## ${w.name}${P?` \u2014 ${P} (killed after ${w.timeout}s)`:""}\n\`\`\`\n$ ${b}\n${j}\n\`\`\`\n\nExit code: ${E}`}],...(0,s.alchemyMeta)({kind:"run-command",toolRecordId:R})}}catch(e){return p({success:!1,error:"api_error"}),{content:[{type:"text",text:"Failed to run command: "+(0,i.getErrorMessage)(e)}],isError:!0}}}
2729
- const n=t(4600),o=r(t(6928)),s=t(2272),i=t(8824),a=t(6688),c=t(9414)},4466(e,exports,t){"use strict"
2716
+ 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"})
2717
+ 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)}))))))
2718
+ 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})
2719
+ const g=[`## Submitted ${p.length} command${1===p.length?"":"s"} for approval${i.length?`, ${i.length} skipped`:""}`]
2720
+ 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}
2721
+ const n=w.find((t=>t.name.toLowerCase()===e.name.toLowerCase()))
2722
+ if(!n)return f({success:!1,error:"not_found"}),{content:[{type:"text",text:`Command "${e.name}" not found.`}],isError:!0}
2723
+ 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(", ")
2724
+ 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()))
2725
+ if(!k){const t=b.map((e=>e.name)).join(", ")
2726
+ 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
2727
+ e.grep&&(v=`${v} 2>&1 | grep -i ${JSON.stringify(e.grep)}`)
2728
+ 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()}`,T=s.default.join(E,S+".log")
2729
+ let q=null
2730
+ try{i.default.mkdirSync(E,{recursive:!0}),q=i.default.createWriteStream(T,{flags:"w"}),q.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:T,error:(0,l.getErrorMessage)(e)})}h.info("Running command",{name:k.name,command:v,cwd:_,configuredCwd:k.cwd,logPath:T})
2731
+ const P=await(0,n.runTrackedShellCommand)(v,{cwd:_,timeoutMs:1e3*(k.timeout||60),env:{...process.env,FORCE_COLOR:"0"},onChunk:q?(e,t)=>{q?.write(t)}:void 0},{id:S,name:k.name,sessionId:m?.sessionId,logPath:T})
2732
+ q&&(q.write(`\n# exit: ${P.exitCode}${P.timedOut?" (timed out)":""}\n`),q.end())
2733
+ const{stdout:j,stderr:R,exitCode:x,timedOut:M}=P,{output:O,truncatedNote:$,logFooter:D}=(0,o.formatCommandOutput)(j,R,{invocationId:S}),A=M?"TIMEOUT":null
2734
+ let F
2735
+ if(g)try{const e=await g.createToolRecord({projectId:t.projectId,type:"run-command",metadata:{name:k.name,command:v,exitCode:x,timedOut:M,status:M?"timeout":0===x?"success":"failed",timestamp:(new Date).toISOString()}})
2736
+ 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${O}\n\`\`\`${$}\n\nExit code: ${x}\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}}}
2737
+ 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
2738
  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
2739
  const i=e.match(exports.TASK_REF_SHORT_PATTERN)
2732
2740
  if(!i)return e
@@ -2767,7 +2775,7 @@ if("interactive"===n)return"interactive"}catch(e){t.warn("Failed to read project
2767
2775
  if("ask"===e||"interactive"===e){h.info("Returning planner choice pending",{planningMode:e})
2768
2776
  try{const e=u?.sessionId||o.sessionIdentity.getIdentity()?.sessionId,t=d?.getSessionMessageClient()
2769
2777
  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}}})}
2778
+ 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
2779
  break}const r="auto-planner"
2772
2780
  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
2781
  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)
@@ -2780,15 +2788,20 @@ default:throw new r.ValidationError("Unknown operation: "+g)}p({success:!0}),l.i
2780
2788
  const y=a[g]??"planner-request"
2781
2789
  return(0,i.withAlchemyKind)(f,y)}catch(e){return h.error("Planner ops failed",{error:e instanceof Error?e.stack:e+""}),p({success:!1}),l.incrementCounter("planner_ops_errors",1),{content:[{type:"text",text:"Planner operation failed: "+(0,n.getErrorMessage)(e)}],isError:!0}}}
2782
2790
  const r=t(9860),n=t(8824),o=t(6688),s=t(1725),i=t(2272),a={request:"planner-request","check-status":"planner-check-status",list:"planner-list"}},4600(e,exports,t){"use strict"
2783
- Object.defineProperty(exports,"__esModule",{value:!0}),exports.execAsync=async function(e,t={}){return new Promise(((n,o)=>{const[s,...i]=e.split(" "),a=(0,r.spawn)(s,i,{cwd:t.cwd||process.cwd(),stdio:["pipe","pipe","pipe"]})
2791
+ async function r(e,r={}){const{spawn:n}=await Promise.resolve().then(t.t.bind(t,5317,23))
2792
+ return new Promise((t=>{const o="win32"===process.platform,s=n(e,{cwd:r.cwd??process.cwd(),env:r.env,shell:o?"cmd.exe":"/bin/sh",stdio:["pipe","pipe","pipe"],detached:!o}),i=[],a=[]
2793
+ let c=!1,l=!1
2794
+ const d=e=>{if(s.pid)try{o?s.kill(e):process.kill(-s.pid,e)}catch{}}
2795
+ let u,h
2796
+ r.timeoutMs&&r.timeoutMs>0&&(h=setTimeout((()=>{c=!0,d("SIGTERM"),u=setTimeout((()=>{l||d("SIGKILL")}),2e3)}),r.timeoutMs)),s.stdout?.on("data",(e=>{i.push(e),r.onChunk?.("stdout",e)})),s.stderr?.on("data",(e=>{a.push(e),r.onChunk?.("stderr",e)}))
2797
+ const p=e=>{l||(l=!0,h&&clearTimeout(h),u&&clearTimeout(u),t({stdout:Buffer.concat(i).toString("utf-8"),stderr:Buffer.concat(a).toString("utf-8"),exitCode:e,timedOut:c}))}
2798
+ s.on("close",(e=>p(e??(c?124:1)))),s.on("error",(()=>p(1)))}))}Object.defineProperty(exports,"__esModule",{value:!0}),exports.execAsync=async function(e,t={}){return new Promise(((r,o)=>{const[s,...i]=e.split(" "),a=(0,n.spawn)(s,i,{cwd:t.cwd||process.cwd(),stdio:["pipe","pipe","pipe"]})
2784
2799
  let c="",l=""
2785
- a.stdout?.on("data",(e=>{c+=e.toString()})),a.stderr?.on("data",(e=>{l+=e.toString()})),a.on("close",(e=>{n({stdout:c,stderr:l,exitCode:e||0})})),a.on("error",(t=>{o(Error(`Command failed: ${e}\nError: ${t.message}`))})),t.timeout&&setTimeout((()=>{a.kill(),o(Error("Command timed out: "+e))}),t.timeout)}))},exports.runShellCommand=async function(e,r={}){const{spawn:n}=await Promise.resolve().then(t.t.bind(t,5317,23))
2786
- return new Promise((t=>{const o="win32"===process.platform,s=n(e,{cwd:r.cwd??process.cwd(),env:r.env,shell:o?"cmd.exe":"/bin/sh",stdio:["pipe","pipe","pipe"]}),i=[],a=[]
2787
- let c,l,d=!1,u=!1
2788
- r.timeoutMs&&r.timeoutMs>0&&(l=setTimeout((()=>{d=!0,s.kill("SIGTERM"),c=setTimeout((()=>{u||s.kill("SIGKILL")}),2e3)}),r.timeoutMs)),s.stdout?.on("data",(e=>i.push(e))),s.stderr?.on("data",(e=>a.push(e)))
2789
- const h=e=>{u||(u=!0,l&&clearTimeout(l),c&&clearTimeout(c),t({stdout:Buffer.concat(i).toString("utf-8"),stderr:Buffer.concat(a).toString("utf-8"),exitCode:e,timedOut:d}))}
2790
- s.on("close",(e=>h(e??(d?124:1)))),s.on("error",(()=>h(1)))}))}
2791
- const r=t(5317)},4607(e,exports,t){"use strict"
2800
+ a.stdout?.on("data",(e=>{c+=e.toString()})),a.stderr?.on("data",(e=>{l+=e.toString()})),a.on("close",(e=>{r({stdout:c,stderr:l,exitCode:e||0})})),a.on("error",(t=>{o(Error(`Command failed: ${e}\nError: ${t.message}`))})),t.timeout&&setTimeout((()=>{a.kill(),o(Error("Command timed out: "+e))}),t.timeout)}))},exports.runShellCommand=r,exports.runTrackedShellCommand=async function(e,n,o){const{getActionServerBaseUrl:s}=await Promise.resolve().then(t.bind(t,9414)),i=s(),a=(new Date).toISOString(),c=n.cwd??process.cwd()
2801
+ fetch(i+"/api/commands/start",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({id:o.id,name:o.name,command:e,cwd:c,sessionId:o.sessionId,startedAt:a,logPath:o.logPath}),signal:AbortSignal.timeout(2e3)}).catch((()=>{}))
2802
+ const l=await r(e,n)
2803
+ return fetch(i+"/api/commands/end",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({id:o.id,exitCode:l.exitCode,timedOut:l.timedOut,finishedAt:(new Date).toISOString()}),signal:AbortSignal.timeout(2e3)}).catch((()=>{})),l}
2804
+ const n=t(5317)},4607(e,exports,t){"use strict"
2792
2805
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.def=void 0
2793
2806
  const r=t(6183),n=t(3306)
2794
2807
  exports.def={name:"replace_strings",description:'Replace text strings across multiple files and documents in a single operation.\n\nMODES:\n\u2022 \'unique\' (default, safer) - Fails if string appears multiple times\n\u2022 \'all\' - Replaces all occurrences\n\nOPERATIONS:\nEach operation specifies:\n\u2022 Target: \'path\' (for files) or \'documentId\' (for documents)\n\u2022 Replacements: Array of {old_str, new_str} pairs\n\u2022 Mode: \'unique\' or \'all\'\n\nEXAMPLES:\n- Replace in file: {"path": "file.ts", "replacements": [{"old_str": "foo", "new_str": "bar"}], "mode": "unique"}\n- Replace in document: {"documentId": "doc123", "replacements": [{"old_str": "old", "new_str": "new"}]}\n\nSupports multiple files/documents and multiple replacements per operation. All changes are committed atomically to Git for files.',schema:r.ReplaceStringsSchema,annotations:{title:"Replace Strings in Files/Documents",readOnlyHint:!1,destructiveHint:!0,idempotentHint:!1},hidden:!0,createsRecords:!0,handler(e,t){return(0,n.handleReplaceStrings)(e,t.context,t.apiClient.getTaskClient(),t.apiClient.getToolRecordClient(),t.apiClient.getRequestClient(),t.logger,t.metrics,t.mcpServer)}}},4628(e,exports){"use strict"
@@ -3779,7 +3792,7 @@ if(!s)return{content:[{type:"text",text:"\u274c No project context available. Pl
3779
3792
  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})
3780
3793
  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}
3781
3794
  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
3782
- let w=!1
3795
+ 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
3783
3796
  if(d.canBeActivePlan)try{const e=o.getProjectClient(),t=await e.getProject(s)
3784
3797
  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}}}
3785
3798
  const r=t(8824),n=t(3416)},6688(e,exports,t){"use strict"
@@ -4720,21 +4733,28 @@ if(r.startsWith("R")){const e=t[1],n=t[2],o=_.get(n)||{additions:0,deletions:0,b
4720
4733
  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}
4721
4734
  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))}}
4722
4735
  const o=t(3807),s=t(2272),i=t(8824),a=5e5},8291(e,exports,t){"use strict"
4723
- var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}}
4724
- Object.defineProperty(exports,"__esModule",{value:!0}),exports.validateAndNormalizePath=function(e,t){let r=e.replace(/\\/g,"/")
4725
- const s=function(e){return e.workspaceManager?e.workspaceManager.getCurrentContext(e.currentSessionId).basePath:e.basePath}(t)
4726
- if(n.default.isAbsolute(r)){const e=n.default.normalize(r)
4727
- if(e.startsWith(s))r=n.default.relative(s,e),r=r.replace(/\\/g,"/")
4728
- else{const i=t.basePath
4729
- if(!i||!e.startsWith(i)){const r=t.workspaceManager?.getCurrentContext(t.currentSessionId)?.mode||"unknown",n=t.workspaceManager?.getCurrentContext(t.currentSessionId)?.requestId||"none"
4730
- throw(0,o.createValidationError)(`File path is outside the allowed directory\n Requested path (absolute): ${e}\n Allowed base (current workspace): ${s}\n Allowed base (main repo): ${i||"N/A"}\n Workspace mode: ${r}\n Current request: ${n}\n Note: Use relative paths (e.g., 'src/file.txt') instead of absolute paths`)}r=n.default.relative(i,e),r=r.replace(/\\/g,"/")}}if(function(e){const t=e.replace(/\\/g,"/").replace(/^\//g,"")
4731
- for(const e of a)if(t===e||t.startsWith(e+"/"))return!0
4732
- for(const e of c)if(t===e||t.endsWith("/"+e))return!0
4733
- return!1}(r))throw(0,o.createValidationError)(`Access to '${r}' is not allowed - this directory contains system files`)
4734
- const i=n.default.resolve(s,r)
4735
- if(!i.startsWith(s)){const e=t.workspaceManager?.getCurrentContext(t.currentSessionId)?.mode||"unknown",n=t.workspaceManager?.getCurrentContext(t.currentSessionId)?.requestId||"none"
4736
- throw(0,o.createValidationError)(`File path is outside the allowed directory\n Requested path: ${r}\n Resolved to: ${i}\n Allowed base: ${s}\n Workspace mode: ${e}\n Current request: `+n)}return{normalizedPath:r,fullPath:i}},exports.validateTaskIdFormat=function(e,t,r,n){e&&(0,i.isTaskRef)(e)||(t.warn("Invalid task ID format provided",{taskId:e}),r.incrementCounter(n+"_invalid_task_format",1))}
4737
- const n=r(t(6928)),o=t(9860),s=t(4139),i=t(4466),a=[s.ALCHEMY_DIR_NAME,".opencode",".git"],c=[".mcp-server.lock"]},8309(e,exports,t){"use strict"
4736
+ 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}}
4737
+ Object.defineProperty(exports,"__esModule",{value:!0}),exports.validateAndNormalizePath=function(e,t){const n=function(e,t){if(!e.startsWith("log:"))return null
4738
+ const r=e.slice(4).replace(/^\/+/,""),n=r?`${i.ALCHEMY_DIR_NAME}/command-logs/${r}.log`:i.ALCHEMY_DIR_NAME+"/command-logs"
4739
+ 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.`)
4740
+ const a=o.default.resolve(t,n)
4741
+ if(!a.startsWith(t+o.default.sep))throw(0,s.createValidationError)("Log path resolves outside the workspace: "+e)
4742
+ return{normalizedPath:n,fullPath:a}}(e,r(t))
4743
+ if(n)return n
4744
+ let a=e.replace(/\\/g,"/")
4745
+ const d=r(t)
4746
+ if(o.default.isAbsolute(a)){const e=o.default.normalize(a)
4747
+ if(e.startsWith(d))a=o.default.relative(d,e),a=a.replace(/\\/g,"/")
4748
+ else{const r=t.basePath
4749
+ if(!r||!e.startsWith(r)){const n=t.workspaceManager?.getCurrentContext(t.currentSessionId)?.mode||"unknown",o=t.workspaceManager?.getCurrentContext(t.currentSessionId)?.requestId||"none"
4750
+ 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,"")
4751
+ for(const e of c)if(t===e||t.startsWith(e+"/"))return!0
4752
+ for(const e of l)if(t===e||t.endsWith("/"+e))return!0
4753
+ return!1}(a))throw(0,s.createValidationError)(`Access to '${a}' is not allowed - this directory contains system files`)
4754
+ const u=o.default.resolve(d,a)
4755
+ if(!u.startsWith(d)){const e=t.workspaceManager?.getCurrentContext(t.currentSessionId)?.mode||"unknown",r=t.workspaceManager?.getCurrentContext(t.currentSessionId)?.requestId||"none"
4756
+ 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))}
4757
+ 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"
4738
4758
  var r,n=this&&this.__createBinding||(Object.create?function(e,t,r,n){void 0===n&&(n=r)
4739
4759
  var o=Object.getOwnPropertyDescriptor(t,r)
4740
4760
  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=[]