alchemylab 1.4.12 → 1.4.14

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 CHANGED
@@ -946,18 +946,28 @@ module.exports=require("ws")},5124(module){"use strict"
946
946
  module.exports=require("winston")},5317(module){"use strict"
947
947
  module.exports=require("child_process")},5419(e,exports,t){"use strict"
948
948
  function r(e){return(0,o.join)((0,s.homedir)(),n.ALCHEMY_DIR_NAME,e?"opencode-dev":"opencode")}Object.defineProperty(exports,"__esModule",{value:!0}),exports.getGlobalConfigDir=r,exports.getOpencodeSpawnEnv=function(e,t){return{OPENCODE_CLIENT:"alchemy",OPENCODE_DATA_DIR:(0,o.join)(e,n.ALCHEMY_DIR_NAME,"opencode"),OPENCODE_CONFIG_DIR:r(t)}}
949
- const s=t(857),o=t(6928),n=t(4139)},5607(e,exports,t){"use strict"
950
- Object.defineProperty(exports,"__esModule",{value:!0}),exports.executeAction=async function(e,t,r){const s=f[e]
949
+ const s=t(857),o=t(6928),n=t(4139)},5454(e,exports){"use strict"
950
+ function t(e,t){let r=0
951
+ for(let t=0;t<e.length;t++)r=(r<<5)-r+e.charCodeAt(t)|0
952
+ return`${t+1}${String.fromCharCode(97+Math.abs(r)%26)}`}function r(e){const r=new Map
953
+ for(let s=0;s<e.length;s++){const o=t(e[s].id,s)
954
+ r.set(o,e[s])}return r}function s(e){if(0===e.length)return"No acceptance criteria defined."
955
+ const t=r(e),s=[]
956
+ for(const[e,r]of t){const t=(r.status||"unverified").toUpperCase()
957
+ s.push(`[${e}] [${t}] ${r.description}`)}return s.join("\n")}function o(e){const t=r(e),o=[]
958
+ for(const[e,r]of t)o.push(` {"key": "${e}", "evidence": "<your evidence for: ${r.description}>"}`)
959
+ return`Use the merge operation with evidence for each criterion:\n\n${s(e)}\n\nExample call:\n{\n "operation": "merge",\n "requestId": "<requestId>",\n "merge": {\n "verifications": [\n${o.join(",\n")}\n ]\n }\n}`}Object.defineProperty(exports,"__esModule",{value:!0}),exports.formatEvidenceInstructions=exports.formatCriteriaWithHashes=exports.buildCriteriaHashMap=exports.criterionHash=void 0,exports.criterionShortKey=t,exports.buildCriteriaKeyMap=r,exports.formatCriteriaWithKeys=s,exports.formatVerifyInstructions=o,exports.criterionHash=t,exports.buildCriteriaHashMap=r,exports.formatCriteriaWithHashes=s,exports.formatEvidenceInstructions=o},5607(e,exports,t){"use strict"
960
+ Object.defineProperty(exports,"__esModule",{value:!0}),exports.executeAction=async function(e,t,r){const s=y[e]
951
961
  if(!s)return{success:!1,message:"Unknown action type: "+e,error:"No handler registered for action type: "+e}
952
- try{return await s(t,r)}catch(t){const s=(0,p.getErrorMessage)(t)
962
+ try{return await s(t,r)}catch(t){const s=(0,m.getErrorMessage)(t)
953
963
  return r.logger.error("Action handler failed",{actionType:e,error:s}),{success:!1,message:"Action failed: "+s,error:s}}}
954
- const r=t(6636),s=t(7453),o=t(5700),n=t(848),i=t(9735),a=t(7767),c=t(9266),l=t(6555),d=t(3614),u=t(4654),h=t(7078),g=t(1405),p=t(8824),m=t(2272),f={revert_request:r.handleRevertRequestAction,"revert-request":r.handleRevertRequestAction,complete_request:s.handleCompleteRequestAction,"complete-request":s.handleCompleteRequestAction,"finalize-request":n.handleFinalizeRequestAction,"approve-request":i.handleApproveRequestAction,"approve-and-work-request":a.handleApproveAndWorkRequestAction,"restart-mcp":o.handleRestartMcpAction,"opencode-proxy":c.handleOpenCodeProxyAction,"run-tests":l.handleRunTestsAction,async"inject-prompt"(e,r){const{sessionId:s,message:o,kind:n,partPayload:i}=e
964
+ const r=t(6636),s=t(7453),o=t(5700),n=t(848),i=t(9735),a=t(7767),c=t(9266),l=t(6555),d=t(3614),u=t(4654),h=t(7078),g=t(7775),p=t(1405),m=t(8824),f=t(2272),y={revert_request:r.handleRevertRequestAction,"revert-request":r.handleRevertRequestAction,complete_request:s.handleCompleteRequestAction,"complete-request":s.handleCompleteRequestAction,"finalize-request":n.handleFinalizeRequestAction,"approve-request":i.handleApproveRequestAction,"approve-and-work-request":a.handleApproveAndWorkRequestAction,"restart-mcp":o.handleRestartMcpAction,"opencode-proxy":c.handleOpenCodeProxyAction,"run-tests":l.handleRunTestsAction,async"inject-prompt"(e,r){const{sessionId:s,message:o,kind:n,partPayload:i}=e
955
965
  if(!s||!o)return{success:!1,message:"sessionId and message are required",error:"missing_params"}
956
966
  const{getOpenCodeUrlFromConfig:a}=await Promise.resolve().then(t.t.bind(t,833,23)),c=a(r.projectPath)
957
967
  if(!c)return{success:!1,message:"Cannot read OpenCode config",error:"no_opencode"}
958
- try{const e=await fetch(`${c}/session/${s}/prompt_async`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({parts:[{type:"text",text:o,metadata:(0,m.partMeta)(n??"inserted-prompt",i)}],...r.cachedDefaultModel?(()=>{const[e,...t]=r.cachedDefaultModel.split("/")
968
+ try{const e=await fetch(`${c}/session/${s}/prompt_async`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({parts:[{type:"text",text:o,metadata:(0,f.partMeta)(n??"inserted-prompt",i)}],...r.cachedDefaultModel?(()=>{const[e,...t]=r.cachedDefaultModel.split("/")
959
969
  return{model:{providerID:e,modelID:t.join("/")}}})():{}}),signal:AbortSignal.timeout(1e4)})
960
- return e.ok?{success:!0,message:"Prompt injected into session "+s}:{success:!1,message:"prompt_async failed: "+e.status,error:"prompt_failed"}}catch(e){return{success:!1,message:(0,p.getErrorMessage)(e),error:"prompt_error"}}},"fork-filesystem":d.handleForkFilesystemAction,"prompt-and-wait":u.handlePromptAndWaitAction,"run-ab-prompt":u.handlePromptAndWaitAction,"spawn-auto-planner":h.handleSpawnAutoPlannerAction,"start-building":g.handleStartBuildingAction}},5671(e,exports,t){"use strict"
970
+ return e.ok?{success:!0,message:"Prompt injected into session "+s}:{success:!1,message:"prompt_async failed: "+e.status,error:"prompt_failed"}}catch(e){return{success:!1,message:(0,m.getErrorMessage)(e),error:"prompt_error"}}},"fork-filesystem":d.handleForkFilesystemAction,"prompt-and-wait":u.handlePromptAndWaitAction,"run-ab-prompt":u.handlePromptAndWaitAction,"spawn-auto-planner":h.handleSpawnAutoPlannerAction,"spawn-reviewer":g.handleSpawnReviewerAction,"start-building":p.handleStartBuildingAction}},5671(e,exports,t){"use strict"
961
971
  function r(e){const[t,...r]=e.split("/")
962
972
  return{provider:t||"anthropic",modelId:r.join("/")||e}}function s(e){const t={anthropic:!1,openai:!1}
963
973
  try{const s=(0,a.join)(e,"opencode.json")
@@ -1036,35 +1046,33 @@ return"idle"===t||"completed"===t}))
1036
1046
  return{sessions:s,statusMap:o,running:n,idle:i}}catch(e){return this.logger.debug("Failed to fetch OpenCode session state",{error:e+""}),null}}}},6540(e,exports){"use strict"
1037
1047
  var t
1038
1048
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.LogLevel=void 0,function(e){e.ERROR="error",e.WARN="warn",e.INFO="info",e.DEBUG="debug"}(t||(exports.LogLevel=t={}))},6555(e,exports,t){"use strict"
1039
- function r(e,process,t,r,s=!1,o,n){const i=process.config||{},a=(i.setup,i.commands||[]),c=o&&o.length>0?o.map((e=>a.find((t=>t.name===e)))).filter((e=>e)):a,l=new Set(n||[]),d=l.size>0?c.filter((e=>!l.has(e.name))):c,u=o&&o.length>0&&d.length<a.length||l.size>0,h=l.size>0?a.filter((e=>l.has(e.name))).map((e=>e.name)):[]
1040
- let g=`You are a test runner agent. Your job is to verify that request ${e.localId||e.id} ("${e.title}") passes all required tests.\n\n## Test Process: ${process.name}\n${process.description?`\n${process.description}\n`:""}\n${process.scope?`**Scope:** Files under \`${process.scope}\`\n`:""}\n## Project ID: ${r}\n${h.length>0?`\n_The coder opted out of these command(s) for this completion: **${h.join(", ")}**. Do NOT run them. They've been excluded from the list below._\n`:""}\n## Test Commands${u?" (partial run \u2014 only the commands listed below)":""}\nExecute these in order:\n`
1041
- for(let e=0;e<d.length;e++){const t=d[e]
1042
- g+=`\n### ${e+1}. ${t.name}${!1!==t.required?" (REQUIRED)":" (optional)"}\n- **Command:** \`${t.command}\`\n- **Timeout:** ${t.timeout||300}s\n`}if(g+=s?`\n## Instructions\n\nYou are in interactive mode. Present the test plan to the user and **wait for their direction** before running anything.\n\n1. Greet the user and list the test commands available for this process.\n2. Wait for the user to tell you to proceed (they may want to run specific commands, skip some, or run all).\n3. Use \`execute_test_command\` with processId="${process.id}" and the command name to run each command.\n4. After each command, explain the result and wait for the user before continuing.\n5. When the user is satisfied, call \`report_test_results\` with the overall status.\n\nYou are read-only \u2014 do NOT modify any source code. Your job is to verify, not fix.`:`\n## Instructions\n\nSetup commands have already been executed. Use the \`execute_test_command\` tool to run each test command:\n\n1. For each command above, call \`execute_test_command\` with processId="${process.id}" and the command name.\n2. Check if it passed or failed.\n3. If it fails: try to determine why \u2014 is it a real bug, a flaky test, or an environment issue? Retry once if it looks flaky.\n4. When all commands are done, call \`report_test_results\` with the overall status and per-command results.\n - If all REQUIRED commands passed \u2192 status "passed"\n - If any REQUIRED command failed \u2192 status "failed" with diagnosis of each failure\n\nYou are read-only \u2014 do NOT modify any source code. Your job is to verify, not fix.`,i.verifyAcceptanceCriteria&&e.acceptanceCriteria?.length>0){g+="\n\n## Acceptance Criteria Verification\n\nAfter running all commands, verify each acceptance criterion by reading code changes and running ad-hoc commands as needed. This is **advisory only** \u2014 it does not affect the overall pass/fail status.\n\nCriteria to verify:\n"
1043
- for(const t of e.acceptanceCriteria)g+=`- ${t}\n`
1044
- g+='\nFor each criterion, report in the `acceptanceCriteria` array of your results:\n- **criterion**: The criterion text\n- **status**: "verified" (confirmed in code), "unverified" (not found/not implemented), or "unclear" (ambiguous)\n- **evidence**: How you verified it, or why you couldn\'t\n'}return g}Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleRunTestsAction=async function(e,l){const{requestId:d,testProcessIds:u,parentSessionId:h,mode:g}=e,p="interactive"===g,{logger:m,apiClient:f,projectId:y,projectPath:v,trackChildPid:w}=l
1049
+ function r(e,process,t,r,s=!1,o,n){const i=process.config||{},c=(i.setup,i.commands||[]),l=o&&o.length>0?o.map((e=>c.find((t=>t.name===e)))).filter((e=>e)):c,d=new Set(n||[]),u=d.size>0?l.filter((e=>!d.has(e.name))):l,h=o&&o.length>0&&u.length<c.length||d.size>0,g=d.size>0?c.filter((e=>d.has(e.name))).map((e=>e.name)):[]
1050
+ let p=`You are a test runner agent. Your job is to verify that request ${e.localId||e.id} ("${e.title}") passes all required tests.\n\n## Test Process: ${process.name}\n${process.description?`\n${process.description}\n`:""}\n${process.scope?`**Scope:** Files under \`${process.scope}\`\n`:""}\n## Project ID: ${r}\n${g.length>0?`\n_The coder opted out of these command(s) for this completion: **${g.join(", ")}**. Do NOT run them. They've been excluded from the list below._\n`:""}\n## Test Commands${h?" (partial run \u2014 only the commands listed below)":""}\nExecute these in order:\n`
1051
+ for(let e=0;e<u.length;e++){const t=u[e]
1052
+ p+=`\n### ${e+1}. ${t.name}${!1!==t.required?" (REQUIRED)":" (optional)"}\n- **Command:** \`${t.command}\`\n- **Timeout:** ${t.timeout||300}s\n`}return p+=s?`\n## Instructions\n\nYou are in interactive mode. Present the test plan to the user and **wait for their direction** before running anything.\n\n1. Greet the user and list the test commands available for this process.\n2. Wait for the user to tell you to proceed (they may want to run specific commands, skip some, or run all).\n3. Use \`execute_test_command\` with processId="${process.id}" and the command name to run each command.\n4. After each command, explain the result and wait for the user before continuing.\n5. When the user is satisfied, call \`report_test_results\` with the overall status.\n\nYou are read-only \u2014 do NOT modify any source code. Your job is to verify, not fix.`:`\n## Instructions\n\nSetup commands have already been executed. Use the \`execute_test_command\` tool to run each test command:\n\n1. For each command above, call \`execute_test_command\` with processId="${process.id}" and the command name.\n2. Check if it passed or failed.\n3. If it fails: try to determine why \u2014 is it a real bug, a flaky test, or an environment issue? Retry once if it looks flaky.\n4. When all commands are done, call \`report_test_results\` with the overall status and per-command results.\n - If all REQUIRED commands passed \u2192 status "passed"\n - If any REQUIRED command failed \u2192 status "failed" with diagnosis of each failure\n\nYou are read-only \u2014 do NOT modify any source code. Your job is to verify, not fix.`,i.verifyAcceptanceCriteria&&e.acceptanceCriteria?.length>0&&(p+="\n\n## Acceptance Criteria Verification\n\nAfter running all commands, verify each acceptance criterion by running the relevant tests/commands (and reading code as needed). Criteria you confirm by execution are recorded as the test-dimension verdict.\n\nCriteria to verify (each is [key] [STATUS] text \u2014 cite the key):\n",p+=(0,a.formatCriteriaWithKeys)(e.acceptanceCriteria)+"\n",p+='\nFor each criterion, report in the `acceptanceCriteria` array of your results:\n- **key**: the criterion\'s [key] shown above\n- **status**: "verified" (proven by running it), "unverified" (not satisfied), or "unclear" (couldn\'t determine)\n- **evidence**: the command/output that proves it, or why you couldn\'t\n'),p}Object.defineProperty(exports,"__esModule",{value:!0}),exports.handleRunTestsAction=async function(e,a){const{requestId:d,testProcessIds:u,parentSessionId:h,mode:g}=e,p="interactive"===g,{logger:m,apiClient:f,projectId:y,projectPath:v,trackChildPid:w}=a
1045
1053
  if(!d)return{success:!1,message:"requestId is required",error:"Missing requestId in payload"}
1046
1054
  m.info("Starting run-tests action",{requestId:d,testProcessIds:u})
1047
- try{const l=await f.get(`/api/projects/${y}/requests/${d}`)
1048
- if(!l.success||!l.data)return{success:!1,message:`Request ${d} not found`,error:"Request not found"}
1049
- const g=l.data,I=g.branchName||"requests/"+d
1055
+ try{const a=await f.get(`/api/projects/${y}/requests/${d}`)
1056
+ if(!a.success||!a.data)return{success:!1,message:`Request ${d} not found`,error:"Request not found"}
1057
+ const g=a.data,I=g.branchName||"requests/"+d
1050
1058
  let E
1051
1059
  if(u?.length)E=(await Promise.all(u.map((e=>f.get(`/api/projects/${y}/test-processes/${e}`))))).filter((e=>e.success&&e.data)).map((e=>e.data))
1052
1060
  else{const e=await f.get(`/api/projects/${y}/test-processes/active`)
1053
1061
  E=e.success&&e.data?Array.isArray(e.data)?e.data:[e.data]:[]}if(0===E.length)return{success:!1,message:"No test processes configured for this project. Set up testing first.",error:"No test processes"}
1054
1062
  const b=Math.max(1,Math.min(8,E[0]?.config?.poolSize??2)),S=E[0]?.config?.fingerprint||[]
1055
1063
  let C
1056
- try{C=await(0,c.acquireSlot)(v,b)}catch(e){return{success:!1,message:"Could not acquire test worktree slot: "+(0,i.getErrorMessage)(e),error:(0,i.getErrorMessage)(e)}}const k=C.path,T=(0,n.simpleGit)(v)
1064
+ try{C=await(0,l.acquireSlot)(v,b)}catch(e){return{success:!1,message:"Could not acquire test worktree slot: "+(0,i.getErrorMessage)(e),error:(0,i.getErrorMessage)(e)}}const k=C.path,T=(0,n.simpleGit)(v)
1057
1065
  try{if((0,s.existsSync)(k))try{const e=(0,n.simpleGit)(k)
1058
1066
  await e.raw(["reset","--hard",I]),m.info("Reset pool slot to latest branch HEAD",{worktreePath:k,branchName:I,slotIndex:C.index})}catch(e){m.warn("Could not reset slot, recreating",{error:(0,i.getErrorMessage)(e)})
1059
1067
  try{await T.raw(["worktree","remove","--force",k])}catch{}}if(!(0,s.existsSync)(k)){(0,s.mkdirSync)((0,o.join)(v,".alchemy_lab","test-worktrees"),{recursive:!0})
1060
- try{if(!(await T.branch()).all.includes(I))return(0,c.releaseSlot)(k),{success:!1,message:`Branch ${I} not found`,error:"Branch not found"}
1061
- await T.raw(["worktree","add","--detach",k,I]),m.info("Created pool slot worktree (detached)",{worktreePath:k,branchName:I,slotIndex:C.index})}catch(e){if(!(e+"").includes("already exists"))return(0,c.releaseSlot)(k),{success:!1,message:"Failed to create pool slot worktree: "+(0,i.getErrorMessage)(e),error:(0,i.getErrorMessage)(e)}
1062
- m.info("Pool slot worktree already exists, using as-is",{worktreePath:k})}}}catch(e){throw(0,c.releaseSlot)(k),e}const _=await(0,c.computeFingerprint)(k,S),j=0===S.length||_!==C.storedFingerprint,A=0===S.length?null:_===C.storedFingerprint
1068
+ try{if(!(await T.branch()).all.includes(I))return(0,l.releaseSlot)(k),{success:!1,message:`Branch ${I} not found`,error:"Branch not found"}
1069
+ await T.raw(["worktree","add","--detach",k,I]),m.info("Created pool slot worktree (detached)",{worktreePath:k,branchName:I,slotIndex:C.index})}catch(e){if(!(e+"").includes("already exists"))return(0,l.releaseSlot)(k),{success:!1,message:"Failed to create pool slot worktree: "+(0,i.getErrorMessage)(e),error:(0,i.getErrorMessage)(e)}
1070
+ m.info("Pool slot worktree already exists, using as-is",{worktreePath:k})}}}catch(e){throw(0,l.releaseSlot)(k),e}const _=await(0,l.computeFingerprint)(k,S),j=0===S.length||_!==C.storedFingerprint,A=0===S.length?null:_===C.storedFingerprint
1063
1071
  j||m.info("Pool slot fingerprint matches; skipping setup",{worktreePath:k,slotIndex:C.index,fingerprintFiles:S})
1064
1072
  const{execSync:P}=await Promise.resolve().then(t.t.bind(t,5317,23))
1065
1073
  if(j){for(const e of E){const t=e.config?.setup||[]
1066
1074
  if(0!==t.length){m.info("Running setup commands for test process",{processName:e.name,commandCount:t.length,slotIndex:C.index})
1067
- for(const r of t)try{m.info("Setup command",{command:r,worktreePath:k}),P(r,{cwd:k,timeout:12e4,encoding:"utf-8",stdio:["pipe","pipe","pipe"],shell:"/bin/sh",env:{...process.env,FORCE_COLOR:"0"}})}catch(t){m.warn("Setup command failed",{command:r,processName:e.name,exitCode:t.status,stderr:(t.stderr||"").slice(-500)})}}}S.length>0&&await(0,c.writeSlotFingerprint)(k,_),await(0,c.writeWarmSnapshot)(k)}else{const{removed:e}=await(0,c.restoreToWarmSnapshot)(k)
1075
+ for(const r of t)try{m.info("Setup command",{command:r,worktreePath:k}),P(r,{cwd:k,timeout:12e4,encoding:"utf-8",stdio:["pipe","pipe","pipe"],shell:"/bin/sh",env:{...process.env,FORCE_COLOR:"0"}})}catch(t){m.warn("Setup command failed",{command:r,processName:e.name,exitCode:t.status,stderr:(t.stderr||"").slice(-500)})}}}S.length>0&&await(0,l.writeSlotFingerprint)(k,_),await(0,l.writeWarmSnapshot)(k)}else{const{removed:e}=await(0,l.restoreToWarmSnapshot)(k)
1068
1076
  e>0&&m.info("Pruned drift from warm slot",{worktreePath:k,slotIndex:C.index,removed:e})}let M
1069
1077
  try{const e=(0,n.simpleGit)(k)
1070
1078
  M=(await e.revparse(["HEAD"])).trim()}catch(e){m.warn("Failed to capture worktree HEAD; commit_sha will be NULL on this run",{worktreePath:k,error:(0,i.getErrorMessage)(e)})}const O=[],R=[],D=[]
@@ -1074,13 +1082,13 @@ for(const process of E){let t
1074
1082
  const s=e.testRunIds?.[O.length]
1075
1083
  try{s?(await f.patch(`/api/projects/${y}/test-runs/${s}`,$),t=(await f.get(`/api/projects/${y}/test-runs/${s}`)).data,O.push(t.id)):(t=(await f.post(`/api/projects/${y}/test-runs`,{requestId:d,testProcessId:process.id,parentSessionId:h,...$})).data,O.push(t.id))}catch(e){m.error("Failed to get/create test run record",{requestId:d,processId:process.id,preCreatedId:s,error:(0,i.getErrorMessage)(e)})
1076
1084
  continue}const o=r(g,process,0,y,p,e.commandNames,e.skipCommandNames),n=p?"tester":"auto-tester"
1077
- try{const e=await(0,a.spawnActionAgent)({agent:n,prompt:o,cwd:k,projectPath:v,projectId:y,title:`[${d}] Test: ${process.name}`,requestId:d,parentSessionId:h,allowParallel:!0,apiClient:f,logger:m,trackChildPid:w})
1085
+ try{const e=await(0,c.spawnActionAgent)({agent:n,prompt:o,cwd:k,projectPath:v,projectId:y,title:`[${d}] Test: ${process.name}`,requestId:d,parentSessionId:h,allowParallel:!0,apiClient:f,logger:m,trackChildPid:w})
1078
1086
  if(!e.success)throw Error(e.error||"Spawn failed")
1079
1087
  R.push(t.id),e.sessionId&&D.push(e.sessionId),e.pid&&(x=e.pid),e.sessionId&&(f.patch(`/api/projects/${y}/test-runs/${t.id}`,{testerSessionId:e.sessionId}).catch((e=>m.warn("Failed to set testerSessionId on test run",{error:(0,i.getErrorMessage)(e)}))),f.post(`/api/projects/${y}/session-test-process-associations`,{opencodeSessionId:e.sessionId,testProcessId:process.id}).catch((t=>m.warn("Failed to create session\u2194test-process association",{sessionId:e.sessionId,testProcessId:process.id,error:(0,i.getErrorMessage)(t)})))),m.info("Spawned test-runner agent",{testRunId:t.id,processName:process.name,worktreePath:k,pid:e.pid,sessionId:e.sessionId})}catch(e){m.error("Failed to spawn test-runner agent",{testRunId:t.id,error:(0,i.getErrorMessage)(e)})
1080
- try{await f.patch(`/api/projects/${y}/test-runs/${t.id}`,{status:"error",results:{steps:[],summary:"Failed to spawn test agent: "+(0,i.getErrorMessage)(e)}})}catch{}}}if(0===R.length)return(0,c.releaseSlot)(k),{success:!1,message:"Failed to spawn any test agents",error:"All agent spawns failed"}
1081
- for(const e of D);return(0,c.watchPidAndRelease)(x,k,12e5),{success:!0,message:`Started ${R.length} test run(s) for request ${d}`,data:{testRunIds:O,agentsSpawned:R.length,processCount:E.length,worktreePath:k,spawnedSessionIds:D}}}catch(e){const t=(0,i.getErrorMessage)(e)
1088
+ try{await f.patch(`/api/projects/${y}/test-runs/${t.id}`,{status:"error",results:{steps:[],summary:"Failed to spawn test agent: "+(0,i.getErrorMessage)(e)}})}catch{}}}if(0===R.length)return(0,l.releaseSlot)(k),{success:!1,message:"Failed to spawn any test agents",error:"All agent spawns failed"}
1089
+ for(const e of D);return(0,l.watchPidAndRelease)(x,k,12e5),{success:!0,message:`Started ${R.length} test run(s) for request ${d}`,data:{testRunIds:O,agentsSpawned:R.length,processCount:E.length,worktreePath:k,spawnedSessionIds:D}}}catch(e){const t=(0,i.getErrorMessage)(e)
1082
1090
  return m.error("run-tests action failed",{requestId:d,error:t}),{success:!1,message:"Failed to run tests: "+t,error:t}}}
1083
- const s=t(9896),o=t(6928),n=t(3807),i=t(8824),a=t(8148),c=t(489)},6563(e,exports,t){"use strict"
1091
+ const s=t(9896),o=t(6928),n=t(3807),i=t(8824),a=t(5454),c=t(8148),l=t(489)},6563(e,exports,t){"use strict"
1084
1092
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.TimerHookScheduler=void 0
1085
1093
  const r=t(6175),s=t(4627),o=t(4769)
1086
1094
  class n{constructor(e){this.tracking=new Map,this.inFlight=new Set,this.paused=new Set,this.stopped=new Set,this.cachedHooks=[],this.lastHookFetch=0,this.lastActivityTime=0,this.agentTypeCache=new Map,this.logger=e.logger,this.hookExecutor=e.hookExecutor,this.apiClient=e.apiClient,this.getOpenCodeUrl=e.getOpenCodeUrl,this.getDefaultModel=e.getDefaultModel,this.projectId=e.projectId,this.projectPath=e.projectPath,this.conditionEvaluator=new r.TimerConditionEvaluator(e.logger)}start(){this.tickTimer||(this.logger.info("Timer hook scheduler starting",{tickInterval:1e4}),setTimeout((()=>this.tick().catch((()=>{}))),3e3),this.tickTimer=setInterval((()=>{this.tick().catch((()=>{}))}),1e4))}stop(){this.tickTimer&&(clearInterval(this.tickTimer),this.tickTimer=void 0),this.tracking.clear(),this.inFlight.clear(),this.paused.clear(),this.stopped.clear(),this.cachedHooks=[],this.logger.info("Timer hook scheduler stopped")}pauseHook(e){return!this.stopped.has(e)&&(this.paused.add(e),this.logger.info("Timer hook paused",{hookId:e}),!0)}resumeHook(e){const t=this.paused.delete(e)
@@ -1394,7 +1402,21 @@ return{model:{providerID:e,modelID:r.join("/")}}})():{}})})
1394
1402
  i.ok?n.info("Pushed notification to CLI session",{sessionId:f}):n.warn("Failed to push notification to CLI session",{status:i.status})}}catch(e){n.warn("Could not push notification to CLI session (may be offline)",{error:(0,r.getErrorMessage)(e)})}}catch(e){n.warn("Failed to notify target session, spawning work agent instead",{requestId:o,error:(0,r.getErrorMessage)(e)})}if(!p)try{const e=await(0,s.spawnActionAgent)({agent:"work",prompt:h,cwd:c,projectPath:c,projectId:a,title:`[${o}] ${d.title}`,requestId:o,parentSessionId:g,apiClient:i,logger:n,trackChildPid:l})
1395
1403
  m=e.success,m?n.info("Spawned work agent for request",{requestId:o,sessionId:e.sessionId}):n.warn("Failed to spawn work agent",{requestId:o,error:e.error})}catch(e){n.warn("Failed to spawn work agent",{requestId:o,error:(0,r.getErrorMessage)(e)})}return n.info("Approve-and-work-request action completed",{requestId:o,notifiedSession:p,agentSpawned:m}),{success:!0,message:`Successfully approved request ${o}: ${d.title}${p?" \u2014 creating session notified":m?" \u2014 work agent spawned":""}`,data:{operationType:"approve-and-work",previousStatus:"pending_approval",newStatus:"open",notifiedSession:p,agentSpawned:m}}}catch(e){const t=(0,r.getErrorMessage)(e)
1396
1404
  return n.error("approve-and-work-request action failed",{requestId:o,error:t}),{success:!1,message:"Failed to approve request: "+t,error:t}}}
1397
- const r=t(8824),s=t(8148)},7904(e,exports){"use strict"
1405
+ const r=t(8824),s=t(8148)},7775(e,exports,t){"use strict"
1406
+ Object.defineProperty(exports,"__esModule",{value:!0}),exports.REVIEWER_FOCUSES=void 0,exports.handleSpawnReviewerAction=async function(e,t){const{requestId:n,parentSessionId:i,focus:a}=e,{logger:c,apiClient:l,projectId:d,projectPath:u,trackChildPid:h}=t
1407
+ if(!n||!i||!a)return{success:!1,message:"spawn-reviewer requires requestId, parentSessionId, and focus",error:"Missing payload fields"}
1408
+ if("string"!=typeof(g=a)||!exports.REVIEWER_FOCUSES.includes(g))return{success:!1,message:"Invalid focus: "+a,error:"Invalid focus"}
1409
+ var g
1410
+ c.info("Starting spawn-reviewer action",{requestId:n,focus:a,parentSessionId:i})
1411
+ let p=""
1412
+ if("CRITERIA"===a)try{const e=await l.get(`/api/projects/${d}/requests/${n}`)
1413
+ if(e.success&&e.data){const t=e.data,r=Array.isArray(t.acceptanceCriteria)?t.acceptanceCriteria:[],s=r.length>0?(0,o.formatCriteriaWithKeys)(r):"",n="string"==typeof t.completionSummary?t.completionSummary.trim():"",i=[]
1414
+ i.push(s?"Acceptance criteria to verify (shown as [key] [STATUS] text \u2014 record the ones you confirm satisfied via record_criteria_verification, citing the key):\n"+s:"This request has no acceptance criteria defined \u2014 report (none) for must_fix."),n&&i.push("Coder's completion summary (what they say they did):\n"+n),p=`\n${i.join("\n\n")}\n`}}catch(e){c.warn("spawn-reviewer: could not fetch request for CRITERIA context; proceeding without",{requestId:n,error:(0,s.getErrorMessage)(e)})}try{const e=function(e,t,r,s){const o=`[Session Context]\n- Request id: ${e}\n- Your focus: ${t}\n- Coder session to report to: ${r}`
1415
+ return"CRITERIA"===t?`${o}\n\nYou are the CRITERIA reviewer. You verify the diff actually delivers each acceptance criterion, RECORD your per-criterion verdict, then report. The other reviewers cover code quality; you cover whether the work is complete and correct against the spec.\n${s}\n1. Call \`request_diff\` to read the diff.\n2. Use \`read_files\` and \`search\` to confirm whether each criterion is satisfied by the change (cite file:line).\n3. For every criterion you confirm is satisfied, record it with \`record_criteria_verification\` { requestId: "${e}", verifications: [{ key, status, evidence }] } \u2014 record a verdict for EVERY criterion in a SINGLE call: status is "met" (evidence = file:line justification) or "unmet" (evidence = what is missing and how to fix it). Use the [key] shown next to each criterion.\n4. Any criterion you mark "unmet" is also a must_fix in your report. The server opens a rework task from the unmet criteria for the coder to address.\n5. Send ONE report via \`session_message_operations send\` with type "report", toSessionId "${r}", finish true, and a structured markdown summary (a per-criterion table + a must_fix section).\n6. Exit. Do not send follow-ups.`:`${o}\n\nYou are reviewing this request's diff with a single focus: **${t}**. Other dimensions are being reviewed in parallel by your peers \u2014 ignore them silently.\n\n1. Call \`request_diff\` to read the diff.\n2. Use \`read_files\` and \`search\` to gather context for any finding you intend to flag.\n3. Categorize each finding into must_fix / nice_to_have / out_of_scope per your agent instructions.\n4. Send ONE report via \`session_message_operations send\` with type "report", toSessionId "${r}", finish true, and the structured markdown summary.\n5. Exit. Do not send follow-ups.`}(n,a,i,p),t=`Reviewer (${a}) for ${n}`,s=await(0,r.spawnActionAgent)({agent:"CRITERIA"===a?"criteria-reviewer":"reviewer",prompt:e,cwd:u,projectPath:u,projectId:d,title:t,requestId:n,parentSessionId:i,allowParallel:!0,apiClient:l,logger:c,trackChildPid:h})
1416
+ return s.success&&s.sessionId?{success:!0,message:`Reviewer spawned: ${a} for ${n}`,data:{sessionId:s.sessionId,pid:s.pid,focus:a}}:{success:!1,message:`Failed to spawn reviewer (${a}): ${s.error}`,error:s.error||"Spawn failed"}}catch(e){const t=(0,s.getErrorMessage)(e)
1417
+ return c.error("spawn-reviewer action failed",{requestId:n,focus:a,error:t}),{success:!1,message:`Failed to spawn reviewer (${a}): ${t}`,error:t}}}
1418
+ const r=t(8148),s=t(8824),o=t(5454)
1419
+ exports.REVIEWER_FOCUSES=["REUSE","QUALITY","EFFICIENCY","CRITERIA"]},7904(e,exports){"use strict"
1398
1420
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.REPORT_STATUS_EMOJI=void 0,exports.formatReportNotification=function(e){const{messageCode:t,status:r,summary:s,details:o,fromSessionId:n,senderAgent:i}=e,a=[`${exports.REPORT_STATUS_EMOJI[r]||"\u{1f4cb}"} **Report \u2014 ${r}**`]
1399
1421
  return t&&a.push("**Report Code:** "+t),i&&a.push(n?`from: ${i} (${n})`:"from: "+i),a.push("",s||"Task completed"),o&&a.push("",o),t&&a.push("",`Use \`session_message_operations\` with \`reply\` operation and message code \`${t}\` to acknowledge.`),a.join("\n")},exports.REPORT_STATUS_EMOJI={success:"\u2705",partial:"\u26a0\ufe0f",failure:"\u274c",blocked:"\u{1f6ab}"}},7960(e,exports){"use strict"
1400
1422
  function t(){const e=process.argv[1]||""
@@ -1582,7 +1604,7 @@ for(const e of T){if(!e.enabled)continue
1582
1604
  const t={}
1583
1605
  for(const[r,s]of Object.entries(e.tools||{})){const e=s,n=r.replace(/-/g,"_")
1584
1606
  t[o.OPENCODE_BUILTIN_TOOLS.has(r)?r:`${d}_${n}`]=e}const r=_?.metadata?.bashToolAgents||{}
1585
- e.name in r&&(t.bash=!!r[e.name]),t.skill=!1,("subagent"===e.mode||"tester"===e.name||"auto-tester"===e.name)&&void 0===t.task&&(t.task=!1)
1607
+ e.name in r&&(t.bash=!!r[e.name]),t.skill=!1,("subagent"===e.mode||"tester"===e.name||"auto-tester"===e.name||"reviewer"===e.name||"criteria-reviewer"===e.name)&&void 0===t.task&&(t.task=!1)
1586
1608
  let s=""
1587
1609
  k&&(s+=k.trim()+"\n\n"),e.prompt&&(s+=e.prompt),e.promptAddition&&(s+="\n\n"+e.promptAddition),j.agent[e.name]={tools:t,...s?{prompt:s}:{},...e.description?{description:e.description}:{},mode:"utility"===e.mode?"primary":e.mode||"primary",...e.color?{color:e.color}:{}}}j.agent.general={...j.agent.general,disable:!0},j.agent.explore={...j.agent.explore,tools:{grep:!1,glob:!1,list:!1,read:!1,bash:!1,[d+"_search"]:!0,[d+"_read_files"]:!0,[d+"_view_asset"]:!0,[d+"_query_history"]:!0,webfetch:!0,websearch:!0,codesearch:!0}}
1588
1610
  const e=`---\ndescription: Fast codebase exploration and research subagent\nmode: subagent\n---\n\n${k?k.trim()+"\n\n":""}You are a codebase exploration agent. Your job is to find specific information and return concise, targeted answers.\n\n## Response Guidelines\n\n- **Be concise.** Return only what was asked for. A typical answer is 100-500 words.\n- **Lead with the answer.** State findings first, then supporting details.\n- **Reference specific files and lines.** Use \`file_path:line_number\` format.\n- **Don't exhaustively document everything you find.** Focus on what's relevant to the question.\n- **Use structured output** when listing multiple items (bullet points, not prose).\n\n## Strategy\n\n1. Start with \`search\` to find relevant files by name or content patterns\n2. Use \`read_files\` to read specific sections (use line ranges, don't read entire large files)\n3. Use \`query_history\` for understanding why code changed or tracing decisions\n\n## Anti-patterns to Avoid\n\n- Don't produce multi-thousand-word reports for simple questions\n- Don't read every file in a directory when a targeted search would suffice\n- Don't explain how tools work \u2014 just use them and report findings\n`