alchemylab 3.0.8 → 3.0.9

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
@@ -5583,29 +5583,39 @@ if(!e)return null
5583
5583
  if(Date.now()>=d)return e
5584
5584
  await new Promise(e=>setTimeout(e,s))}}catch{return null}}
5585
5585
  const c=t(81618),l=e=>`${(e/1024**3).toFixed(1)} GB`,u=new Set(["experiment","rung1","deps","egress-proxy","service"])
5586
- exports.IDLE_GRACE_MS=3e5,exports.kz=6e4,exports.Lt=3e4,exports.ROOM_WAIT_MS=12e4,Promise.resolve(),exports.FY=exports.ROOM_WAIT_MS+6*c.DOCKER_OP_TIMEOUT_MS,exports.TS=["light","standard","heavy"]},44876(e,exports,t){"use strict"
5587
- async function r(e,t,r){const n=await(0,u.declaredRuntimeFloor)(t,t=>{try{return a.readFileSync(c.join(e,t),"utf8")}catch{return null}}),i=(0,u.imageRuntime)(r)
5588
- return n&&i?(0,u.floorImage)(i,n):r}var n,i=this&&this.__createBinding||(Object.create?function(e,t,r,n){void 0===n&&(n=r)
5586
+ exports.IDLE_GRACE_MS=3e5,exports.kz=6e4,exports.Lt=3e4,exports.ROOM_WAIT_MS=12e4,Promise.resolve(),exports.FY=exports.ROOM_WAIT_MS+6*c.DOCKER_OP_TIMEOUT_MS,exports.TS=["light","standard","heavy"]},77580(e,exports){"use strict"
5587
+ exports.HOST_BROWSER_WORDS=void 0,exports.networkWords=function(e,t={}){const r=function(e){return"pure-host"===e?{egress:"open",allowCaps:!1,allowRoot:!1,allowDockerSocket:!1,commandGate:["human"],hostExec:"raw"}:"host"===e?{egress:"none",allowCaps:!1,allowRoot:!1,allowDockerSocket:!1,commandGate:["agent","human"],hostExec:"sandboxed"}:"unsafe"===e?{egress:"open",allowCaps:!0,allowRoot:!0,allowDockerSocket:!0,commandGate:["human"],hostExec:!1}:"privileged"===e?{egress:"none",allowCaps:!0,allowRoot:!0,allowDockerSocket:!1,commandGate:["human"],hostExec:!1}:"egress-allowlist"===e?{egress:"allowlist",allowCaps:!1,allowRoot:!1,allowDockerSocket:!1,commandGate:[],hostExec:!1}:{egress:"none",allowCaps:!1,allowRoot:!1,allowDockerSocket:!1,commandGate:[],hostExec:!1}}(e)
5588
+ return"raw"===r.hostExec?"this computer's own network, unsandboxed: its addresses, the local network and the internet":"sandboxed"===r.hostExec?"darwin"===(t.platform??process.platform)?"connections on this computer only (localhost, so a server it starts is reachable from the host browser), no internet":"only its own loopback address (127.0.0.1), no internet":"open"===r.egress?"a private network address and the open internet":"allowlist"===r.egress?"a private network address; the internet only through a proxy, to the listed hosts":t.services?"a private network shared with its services, no internet":"only its own loopback address (127.0.0.1): no network address, no internet"},exports.HOST_BROWSER_WORDS="The host browser (drive_site, read_site, capture_site) is a real Chrome on the user's computer, outside every box: it has the computer's graphics card and network (the internet, pages served on this computer such as preview_site's, and direct links between its own pages)."},44876(e,exports,t){"use strict"
5589
+ function r(e){const t='SETUPTOOLS_SCM_PRETEND_VERSION="${SETUPTOOLS_SCM_PRETEND_VERSION:-0.0.0}" /work/.venv/bin/pip install -q --no-cache-dir'
5590
+ return`{ if [ -f pyproject.toml ] || [ -f setup.py ]; then ${[...e?[`${t} -e ".[${e}]"`]:[],`${t} -e .`,'echo "the project could not be installed; its tests import it from /work"'].join(" || ")}; fi; }`}function n(e){if(!e)return null
5591
+ const t=new Set
5592
+ let r=""
5593
+ for(const n of e.split(/\r?\n/)){const e=n.trim(),i=/^\[([^\[\]]+)\]$/.exec(e)
5594
+ if(i){r=i[1].trim()
5595
+ continue}const o=/^"?([A-Za-z0-9_.-]+)"?\s*=/.exec(e)?.[1]
5596
+ o&&("project.optional-dependencies"===r?t.add(o):"project"===r&&o.startsWith("optional-dependencies.")&&t.add(o.slice(22)))}return f.find(e=>t.has(e))??null}function i(e){try{return u.readFileSync(e,"utf8")}catch{return null}}async function o(e,t,r){const n=await(0,h.declaredRuntimeFloor)(t,t=>{try{return u.readFileSync(d.join(e,t),"utf8")}catch{return null}}),i=(0,h.imageRuntime)(r)
5597
+ return n&&i?(0,h.floorImage)(i,n):r}var s,a=this&&this.__createBinding||(Object.create?function(e,t,r,n){void 0===n&&(n=r)
5589
5598
  var i=Object.getOwnPropertyDescriptor(t,r)
5590
- i&&!("get"in i?!t.__esModule:i.writable||i.configurable)||(i={enumerable:!0,get:function(){return t[r]}}),Object.defineProperty(e,n,i)}: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||(n=function(e){return n=Object.getOwnPropertyNames||function(e){var t=[]
5599
+ i&&!("get"in i?!t.__esModule:i.writable||i.configurable)||(i={enumerable:!0,get:function(){return t[r]}}),Object.defineProperty(e,n,i)}:function(e,t,r,n){void 0===n&&(n=r),e[n]=t[r]}),c=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),l=this&&this.__importStar||(s=function(e){return s=Object.getOwnPropertyNames||function(e){var t=[]
5591
5600
  for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[t.length]=r)
5592
- return t},n(e)},function(e){if(e&&e.__esModule)return e
5601
+ return t},s(e)},function(e){if(e&&e.__esModule)return e
5593
5602
  var t={}
5594
- if(null!=e)for(var r=n(e),s=0;s<r.length;s++)"default"!==r[s]&&i(t,e,r[s])
5595
- return o(t,e),t})
5596
- Object.defineProperty(exports,"__esModule",{value:!0}),exports.PYTHON_MANIFESTS=exports.NODE_IMAGE=exports.PYTHON_IMAGE=void 0,exports.defaultBoxFor=async function(e){if(!e)return{language:null}
5603
+ if(null!=e)for(var r=s(e),n=0;n<r.length;n++)"default"!==r[n]&&a(t,e,r[n])
5604
+ return c(t,e),t})
5605
+ Object.defineProperty(exports,"__esModule",{value:!0}),exports.PYTHON_MANIFESTS=exports.NODE_IMAGE=exports.PYTHON_IMAGE=void 0,exports.pythonTestExtra=n,exports.defaultBoxFor=async function(e){if(!e)return{language:null}
5597
5606
  let t=[]
5598
- try{t=a.readdirSync(e)}catch{return{language:null}}const n=e=>t.includes(e)
5599
- if(exports.PYTHON_MANIFESTS.some(n)||n("Pipfile")||!n("package.json")&&t.some(e=>e.endsWith(".py")))return{language:"python",image:await r(e,"python",exports.PYTHON_IMAGE),artifacts:[{path:".venv",prepare:d,manifests:exports.PYTHON_MANIFESTS,env:{PATH:"/work/.venv/bin:/usr/local/bin:/usr/local/sbin:/usr/sbin:/usr/bin:/sbin:/bin",VIRTUAL_ENV:"/work/.venv"}}]}
5600
- if(n("package.json")){const t=function(e){let t
5601
- try{t=JSON.parse(a.readFileSync(c.join(e,"package.json"),"utf8"))}catch{return}if(!["dependencies","devDependencies","optionalDependencies"].some(e=>t&&"object"==typeof t[e]&&Object.keys(t[e]).length>0))return
5602
- const r=t=>a.existsSync(c.join(e,t))
5607
+ try{t=u.readdirSync(e)}catch{return{language:null}}const s=e=>t.includes(e)
5608
+ if(exports.PYTHON_MANIFESTS.some(s)||s("Pipfile")||!s("package.json")&&t.some(e=>e.endsWith(".py")))return{language:"python",image:await o(e,"python",exports.PYTHON_IMAGE),artifacts:[{path:".venv",prepare:(a=n(s("pyproject.toml")?i(d.join(e,"pyproject.toml")):null),["python -m venv /work/.venv","/work/.venv/bin/pip install -q --no-cache-dir pytest","if [ -f requirements.txt ]; then /work/.venv/bin/pip install -q --no-cache-dir -r requirements.txt; fi","if [ -f requirements-dev.txt ]; then /work/.venv/bin/pip install -q --no-cache-dir -r requirements-dev.txt; fi",r(a)].join(" && ")),manifests:exports.PYTHON_MANIFESTS,env:{PATH:"/work/.venv/bin:/usr/local/bin:/usr/local/sbin:/usr/sbin:/usr/bin:/sbin:/bin",VIRTUAL_ENV:"/work/.venv"}}]}
5609
+ var a
5610
+ if(s("package.json")){const t=function(e){let t
5611
+ try{t=JSON.parse(u.readFileSync(d.join(e,"package.json"),"utf8"))}catch{return}if(!["dependencies","devDependencies","optionalDependencies"].some(e=>t&&"object"==typeof t[e]&&Object.keys(t[e]).length>0))return
5612
+ const r=t=>u.existsSync(d.join(e,t))
5603
5613
  return r("pnpm-lock.yaml")?"corepack enable >/dev/null 2>&1; pnpm install --frozen-lockfile":r("yarn.lock")?"corepack enable >/dev/null 2>&1; yarn install --frozen-lockfile":r("package-lock.json")||r("npm-shrinkwrap.json")?"npm ci":"npm install"}(e)
5604
- return{language:"node",image:await r(e,"node",exports.NODE_IMAGE),...t?{install:t}:{}}}return{language:null}},exports.defaultBoxIdentity=function(e){const t=JSON.stringify([e.image??"",e.install??"",e.artifacts??[]])
5605
- return`default-box:${(0,l.createHash)("sha256").update(t).digest("hex").slice(0,16)}`}
5606
- const a=s(t(79896)),c=s(t(16928)),l=t(76982),u=t(56844)
5614
+ return{language:"node",image:await o(e,"node",exports.NODE_IMAGE),...t?{install:t}:{}}}return{language:null}},exports.defaultBoxIdentity=function(e){const t=JSON.stringify([e.image??"",e.install??"",e.artifacts??[]])
5615
+ return`default-box:${(0,p.createHash)("sha256").update(t).digest("hex").slice(0,16)}`}
5616
+ const u=l(t(79896)),d=l(t(16928)),p=t(76982),h=t(56844)
5607
5617
  exports.PYTHON_IMAGE="python:3.12-slim",exports.NODE_IMAGE="node:20-alpine",exports.PYTHON_MANIFESTS=["pyproject.toml","setup.py","setup.cfg","requirements.txt","requirements-dev.txt"]
5608
- const d=["python -m venv /work/.venv","/work/.venv/bin/pip install -q --no-cache-dir pytest","if [ -f requirements.txt ]; then /work/.venv/bin/pip install -q --no-cache-dir -r requirements.txt; fi","if [ -f requirements-dev.txt ]; then /work/.venv/bin/pip install -q --no-cache-dir -r requirements-dev.txt; fi",'{ if [ -f pyproject.toml ] || [ -f setup.py ]; then /work/.venv/bin/pip install -q --no-cache-dir -e . || echo "the project could not be installed; its tests import it from /work"; fi; }'].join(" && ")},29603(e,exports,t){"use strict"
5618
+ const f=["test","tests","testing","dev"]},29603(e,exports,t){"use strict"
5609
5619
  exports._i=exports.pl=exports.Ld=exports.Ci=exports.QB=void 0,exports.normalizeArtifactPath=function(e){const t=e.startsWith("/")?e:`/work/${e.replace(/^\.\//,"")}`
5610
5620
  if(t.split("/").some(e=>".."===e))return null
5611
5621
  const n=t.replace(/\/+$/,"")||"/"
@@ -5653,35 +5663,35 @@ const o=(0,n.createHash)("sha256")
5653
5663
  for(const n of t)try{const t=await r.promises.readFile((0,i.join)(e,n),"utf-8")
5654
5664
  o.update(n).update("\0").update(t).update("\n")}catch{o.update(n).update("\0:MISSING\n")}return o.digest("hex")}
5655
5665
  const r=t(79896),n=t(76982),i=t(16928)},83129(e,exports,t){"use strict"
5656
- function r(e){const t=f.get(e.image)
5666
+ function r(e){return f.find(t=>t.image===e)}function n(e){const t=m.get(e.image)
5657
5667
  if(t)return t.work
5658
- const r=(async()=>{const t=await(0,u.docker)(["image","inspect","--format",`{{ index .Config.Labels "${p}" }}`,e.image],u.DOCKER_OP_TIMEOUT_MS)
5668
+ const r=(async()=>{const t=await(0,d.docker)(["image","inspect","--format",`{{ index .Config.Labels "${h}" }}`,e.image],d.DOCKER_OP_TIMEOUT_MS)
5659
5669
  if(t.spawnError)return t.spawnError
5660
5670
  if(0===t.exit&&t.stdout.trim()===e.recipeHash)return
5661
- const r=a.mkdtempSync(l.join(c.tmpdir(),"alchemylab-image-"))
5662
- try{a.writeFileSync(l.join(r,"Dockerfile"),e.dockerfile)
5663
- const t=await(0,u.docker)(["build","--label",`${p}=${e.recipeHash}`,"-t",e.image,r],exports.BUILD_TIMEOUT_MS)
5671
+ const r=c.mkdtempSync(u.join(l.tmpdir(),"alchemylab-image-"))
5672
+ try{c.writeFileSync(u.join(r,"Dockerfile"),e.dockerfile)
5673
+ const t=await(0,d.docker)(["build","--label",`${h}=${e.recipeHash}`,"-t",e.image,r],exports.BUILD_TIMEOUT_MS)
5664
5674
  if(0!==t.exit){const r=(t.stderr||t.stdout||"").trim().split("\n").slice(-12).join("\n")
5665
- return`building ${e.image} failed${t.timedOut?" (timed out)":""}: ${r}`}return}finally{a.rmSync(r,{recursive:!0,force:!0})}})()
5666
- return f.set(e.image,{work:r,since:Date.now()}),r.then(t=>{t?m.set(e.image,{error:t,at:Date.now()}):m.delete(e.image)},t=>{m.set(e.image,{error:t instanceof Error?t.message:String(t),at:Date.now()})}).finally(()=>f.delete(e.image)),r}var n,i=this&&this.__createBinding||(Object.create?function(e,t,r,n){void 0===n&&(n=r)
5675
+ return`building ${e.image} failed${t.timedOut?" (timed out)":""}: ${r}`}return}finally{c.rmSync(r,{recursive:!0,force:!0})}})()
5676
+ return m.set(e.image,{work:r,since:Date.now()}),r.then(t=>{t?g.set(e.image,{error:t,at:Date.now()}):g.delete(e.image)},t=>{g.set(e.image,{error:t instanceof Error?t.message:String(t),at:Date.now()})}).finally(()=>m.delete(e.image)),r}var i,o=this&&this.__createBinding||(Object.create?function(e,t,r,n){void 0===n&&(n=r)
5667
5677
  var i=Object.getOwnPropertyDescriptor(t,r)
5668
- i&&!("get"in i?!t.__esModule:i.writable||i.configurable)||(i={enumerable:!0,get:function(){return t[r]}}),Object.defineProperty(e,n,i)}: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||(n=function(e){return n=Object.getOwnPropertyNames||function(e){var t=[]
5678
+ i&&!("get"in i?!t.__esModule:i.writable||i.configurable)||(i={enumerable:!0,get:function(){return t[r]}}),Object.defineProperty(e,n,i)}:function(e,t,r,n){void 0===n&&(n=r),e[n]=t[r]}),s=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),a=this&&this.__importStar||(i=function(e){return i=Object.getOwnPropertyNames||function(e){var t=[]
5669
5679
  for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[t.length]=r)
5670
- return t},n(e)},function(e){if(e&&e.__esModule)return e
5680
+ return t},i(e)},function(e){if(e&&e.__esModule)return e
5671
5681
  var t={}
5672
- if(null!=e)for(var r=n(e),s=0;s<r.length;s++)"default"!==r[s]&&i(t,e,r[s])
5673
- return o(t,e),t})
5674
- Object.defineProperty(exports,"__esModule",{value:!0}),exports.BUILD_TIMEOUT_MS=void 0,exports.localRecipeFor=function(e){return h.find(t=>t.image===e)},exports.ensureLocalImage=r,exports.localImageState=async function(e){const t=f.get(e.image)
5682
+ if(null!=e)for(var r=i(e),n=0;n<r.length;n++)"default"!==r[n]&&o(t,e,r[n])
5683
+ return s(t,e),t})
5684
+ Object.defineProperty(exports,"__esModule",{value:!0}),exports.BUILD_TIMEOUT_MS=void 0,exports.localRecipeFor=r,exports.registerLocalRecipe=function(e){r(e.image)||f.push(e)},exports.ensureLocalImage=n,exports.localImageState=async function(e){const t=m.get(e.image)
5675
5685
  if(t)return{state:"building",since:t.since}
5676
- const r=await(0,u.docker)(["image","inspect","--format",`{{ index .Config.Labels "${p}" }}`,e.image],u.DOCKER_OP_TIMEOUT_MS)
5686
+ const r=await(0,d.docker)(["image","inspect","--format",`{{ index .Config.Labels "${h}" }}`,e.image],d.DOCKER_OP_TIMEOUT_MS)
5677
5687
  if(r.spawnError)return{state:"no-docker",error:"Docker is not installed on this machine (no docker command)"}
5678
5688
  if(0===r.exit&&r.stdout.trim()===e.recipeHash)return{state:"ready"}
5679
5689
  if(/cannot connect to the docker daemon|is the docker daemon running|error during connect/i.test(r.stderr))return{state:"no-docker",error:"Docker is not running on this machine"}
5680
- const n=m.get(e.image)
5681
- return n?{state:"failed",...n}:{state:"missing"}},exports.startLocalImageBuild=function(e){return m.delete(e.image),r(e).catch(()=>{}),{since:f.get(e.image)?.since??Date.now()}}
5682
- const a=s(t(73024)),c=s(t(48161)),l=s(t(76760)),u=t(81618),d=t(44853),p="dev.alchemylab.recipe"
5690
+ const n=g.get(e.image)
5691
+ return n?{state:"failed",...n}:{state:"missing"}},exports.startLocalImageBuild=function(e){return g.delete(e.image),n(e).catch(()=>{}),{since:m.get(e.image)?.since??Date.now()}}
5692
+ const c=a(t(73024)),l=a(t(48161)),u=a(t(76760)),d=t(81618),p=t(44853),h="dev.alchemylab.recipe"
5683
5693
  exports.BUILD_TIMEOUT_MS=15e5
5684
- const h=[{image:d.OFFICE_BOX_IMAGE,dockerfile:d.OFFICE_BOX_DOCKERFILE,recipeHash:d.OFFICE_BOX_RECIPE_HASH}],f=new Map,m=new Map},90915(e,exports,t){"use strict"
5694
+ const f=[{image:p.OFFICE_BOX_IMAGE,dockerfile:p.OFFICE_BOX_DOCKERFILE,recipeHash:p.OFFICE_BOX_RECIPE_HASH}],m=new Map,g=new Map},90915(e,exports,t){"use strict"
5685
5695
  exports.snapshotLine=function(e){return e&&"current"!==e.state?"refreshed"===e.state?`This box's files were brought up to ${e.branch} at ${e.to.slice(0,9)} before the run (they were ${e.from.slice(0,9)}).`:`This box's files are ${e.branch} at ${e.from.slice(0,9)}, older than ${e.branch} now (${e.to.slice(0,9)}): ${e.reason}. experiment_stop it to run on ${e.branch} as it is now.`:""},t(94662)},60560(e,exports,t){"use strict"
5686
5696
  exports.readGoalNumber=function(e,t){if(e)return(0,r.readMetric)(e,t)
5687
5697
  for(const e of[t.stdout??"",t.stderr??""]){const t=e.split("\n").map(e=>e.trim()).filter(Boolean).pop()
@@ -6698,7 +6708,9 @@ return"worktree"===t&&w.test(n)?null:"worktree"===t&&n.startsWith("/")?`it start
6698
6708
  let E=e.box?.trim(),C=""
6699
6709
  if((v||I)&&!E){const r=e.env?.trim(),{handleExperimentStart:n}=await Promise.resolve().then(()=>t(82567)),o=await n({...r?{env:r}:{},...e.size?{size:e.size}:{},forCommand:v||I,why:e.why},i,a,d,p,y),s=String(o?.content?.[0]?.text??""),c=/Handle: `([^`]+)`/.exec(s)?.[1]
6700
6710
  if(!c)return b({success:!1,error:"env_start_failed"}),{content:[{type:"text",text:`Not run: the box${r?` on "${r}"`:""} could not be started.\n\n${s}`}],isError:!0}
6701
- E=c,C=r?`(Box \`${c}\` on ${r}: pass box: "${c}" to later runs, or env again.)\n`:`(Box \`${c}\`, this project's default: pass box: "${c}" to later runs.)\n`}if(E){const t=r(v,"worktree")
6711
+ E=c
6712
+ const l=/^Network: .*$/m.exec(s)?.[0]
6713
+ C=(r?`(Box \`${c}\` on ${r}: pass box: "${c}" to later runs, or env again.)\n`:`(Box \`${c}\`, this project's default: pass box: "${c}" to later runs.)\n`)+(l?`${l}\n`:"")}if(E){const t=r(v,"worktree")
6702
6714
  if(t)return b({success:!1,error:"cd_prefix"}),{content:[{type:"text",text:`Not run: ${t}. The box already runs at the worktree root.`}],isError:!0}
6703
6715
  let p=e.cwd?.trim().replace(/\/+$/,"")
6704
6716
  const A=p?n(p):null
@@ -7045,12 +7057,12 @@ return i(t,e),t})
7045
7057
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.environmentDefs=void 0
7046
7058
  const s=o(t(76183)),a=t(56819),c=e=>(t,r)=>e(t,r.context,r.apiClient,r.logger,r.metrics,r.cliOrigin)
7047
7059
  exports.environmentDefs=[{name:"environment_list",description:"List the environments (confined runtimes) configured for this project.",schema:s.EnvironmentListSchema,annotations:{title:"List Environments",readOnlyHint:!0},handler:c(a.handleEnvironmentList)},{name:"environment_get",description:"Get the full config of one environment (image, install, services, connection).",schema:s.EnvironmentGetSchema,annotations:{title:"Get Environment",readOnlyHint:!0},handler:c(a.handleEnvironmentGet)},{name:"environment_create",description:"Create an environment: how to stand up a confined runtime for this project (image, deps install, services/compose, connection). The first environment for a project becomes its primary. Saves are verify-gated: the exact config must have passed environment_verify in this session (skipVerify + a reason is the logged escape hatch for genuinely unverifiable configs).",schema:s.EnvironmentCreateSchema,annotations:{title:"Create Environment",readOnlyHint:!1},handler:c(a.handleEnvironmentCreate)},{name:"environment_update",description:"Update an existing environment. Provided fields are replaced; config replaces the existing config wholesale. A new config is verify-gated like environment_create (metadata-only updates are not).",schema:s.EnvironmentUpdateSchema,annotations:{title:"Update Environment",readOnlyHint:!1},handler:c(a.handleEnvironmentUpdate)},{name:"environment_delete",description:"Delete an environment. Test processes referencing it fall back to the LLM path until reassigned.",schema:s.EnvironmentDeleteSchema,annotations:{title:"Delete Environment",readOnlyHint:!1,destructiveHint:!0},handler:c(a.handleEnvironmentDelete)},{name:"environment_verify",description:"Verify a PROPOSED environment by standing it up confined (install + services + a smoke command) in a throwaway worktree, before saving it. Returns whether it stands up + diagnostics. A pass unlocks environment_create / environment_update for that exact config in this session.",schema:s.EnvironmentVerifySchema,annotations:{title:"Verify Environment",readOnlyHint:!1},handler:c(a.handleEnvironmentVerify)},{name:"environment_setup",description:"Ask for a verified project environment when environment_list has nothing matching the toolchain you need (for making or rendering Word, Excel, PowerPoint or PDF files, ask for the office preset). The ask joins the project's setup queue: it starts a setup run, or joins the one running, whose setup architect builds and verifies the environment. From a request, your request waits on the run and you are woken with its outcome and the architect's answer to your ask.",schema:s.EnvironmentSetupSchema,annotations:{title:"Set Up Environment",readOnlyHint:!1},handler:c(a.handleEnvironmentSetup)},{name:"environment_provision",description:"Provision an environment's disposable delivery stack (a running system to verify changes against) from its config.delivery, contained and health-gated. Waits for the user's approval; refused for the stack you run on.",schema:s.EnvironmentProvisionSchema,annotations:{title:"Provision Delivery Stack",readOnlyHint:!1},handler:c(a.handleEnvironmentProvision)},{name:"environment_teardown",description:"Tear down an environment's delivery stack; named volumes are kept. Waits for the user's approval.",schema:s.EnvironmentTeardownSchema,annotations:{title:"Tear Down Delivery Stack",readOnlyHint:!1},handler:c(a.handleEnvironmentTeardown)},{name:"environment_status",description:"Read-only status of an environment's delivery stack: which services are up + the health probe. No approval.",schema:s.EnvironmentStatusSchema,annotations:{title:"Delivery Stack Status",readOnlyHint:!0},handler:c(a.handleEnvironmentStatus)},{name:"environment_verify_stack",description:"Verify a running delivery stack against the checks declared on its environment (each runs inside a service and must pass). Read-only, no approval.",schema:s.EnvironmentVerifyStackSchema,annotations:{title:"Verify Delivery Stack",readOnlyHint:!0},handler:c(a.handleEnvironmentVerifyStack)}]},56819(e,exports,t){"use strict"
7048
- function r(e){return null===e||"object"!=typeof e?JSON.stringify(e):Array.isArray(e)?`[${e.map(r).join(",")}]`:`{${Object.keys(e).filter(t=>void 0!==e[t]).sort().map(t=>`${JSON.stringify(t)}:${r(e[t])}`).join(",")}}`}function n(e){return(0,u.createHash)("sha256").update(r(e??{})).digest("hex")}async function i(e,t,r,i){const o=await(0,b.callerSessionKey)(i)
7060
+ function r(e){return null===e||"object"!=typeof e?JSON.stringify(e):Array.isArray(e)?`[${e.map(r).join(",")}]`:`{${Object.keys(e).filter(t=>void 0!==e[t]).sort().map(t=>`${JSON.stringify(t)}:${r(e[t])}`).join(",")}}`}function n(e){return(0,u.createHash)("sha256").update(r(e??{})).digest("hex")}async function i(e,t,r,i){const o=await(0,v.callerSessionKey)(i)
7049
7061
  if(t.skipVerify){const n=(t.skipVerifyReason||"").trim()
7050
- return n?(r.warn("environment save: skipVerify used -- saving UNVERIFIED config",{op:e,sessionKey:o,reason:n}),{ok:!0,note:`\n\n\u26a0\ufe0f Saved UNVERIFIED (skipVerify) -- reason logged: ${n}`}):{ok:!1,error:"Not saved: skipVerify requires skipVerifyReason. Say WHY this config genuinely cannot pass environment_verify from here (e.g. it needs credentials, external services, or hardware the confined verifier lacks) -- the reason is logged. If it CAN be verified, run environment_verify with this exact config instead."}}return function(e,t){return v.get(e)?.has(n(t))??!1}(o,t.config)?{ok:!0}:{ok:!1,error:`Not saved: this exact config has not passed environment_verify in this session.\n\nRun environment_verify with this EXACT config first -- it stands the environment up confined (image + install + services + smoke) in a throwaway worktree, so broken configs are caught before they become the project's runtime. When it passes, re-run ${e} with the same config (any edit to the config needs a fresh verify).\n\nGenuinely unverifiable from here (needs secrets, external services, or hardware the confined runner lacks)? Pass skipVerify: true with skipVerifyReason explaining why -- the skip is logged.`}}function o(e,t){let r=I.get(e)
7051
- r||(r=new Set,I.set(e,r))
7052
- for(const e of t)e&&r.add(e)}function s(e){const t=e.config||{},r=[t.image?`image: ${t.image}`:null,t.install?`install: \`${t.install}\``:null,t.composeFile?`services: ${t.composeFile}`:null,t.connEnv?`conn: ${Object.keys(t.connEnv).join(", ")}`:null,t.delivery?.targetPath?`delivery: ${String(t.delivery.targetPath).split("/").filter(Boolean).pop()||t.delivery.targetPath}`:null].filter(Boolean).join(" \xb7 ")
7053
- return`**${e.name}** (${e.id})${e.isPrimary?" \u2b50 primary":""}${e.scope?` [scope: ${e.scope}]`:""}\n ${r||"(empty config)"}${e.description?`\n ${e.description}`:""}`}function a(e){const t=(Array.isArray(e?.artifacts)?e.artifacts:[]).filter(e=>e&&"string"==typeof e.prepare&&e.prepare.trim()&&!(Array.isArray(e.manifests)&&e.manifests.length>0))
7062
+ return n?(r.warn("environment save: skipVerify used -- saving UNVERIFIED config",{op:e,sessionKey:o,reason:n}),{ok:!0,note:`\n\n\u26a0\ufe0f Saved UNVERIFIED (skipVerify) -- reason logged: ${n}`}):{ok:!1,error:"Not saved: skipVerify requires skipVerifyReason. Say WHY this config genuinely cannot pass environment_verify from here (e.g. it needs credentials, external services, or hardware the confined verifier lacks) -- the reason is logged. If it CAN be verified, run environment_verify with this exact config instead."}}return function(e,t){return I.get(e)?.has(n(t))??!1}(o,t.config)?{ok:!0}:{ok:!1,error:`Not saved: this exact config has not passed environment_verify in this session.\n\nRun environment_verify with this EXACT config first -- it stands the environment up confined (image + install + services + smoke) in a throwaway worktree, so broken configs are caught before they become the project's runtime. When it passes, re-run ${e} with the same config (any edit to the config needs a fresh verify).\n\nGenuinely unverifiable from here (needs secrets, external services, or hardware the confined runner lacks)? Pass skipVerify: true with skipVerifyReason explaining why -- the skip is logged.`}}function o(e,t){let r=E.get(e)
7063
+ r||(r=new Set,E.set(e,r))
7064
+ for(const e of t)e&&r.add(e)}function s(e){const t=e.config||{},r=[t.image?`image: ${t.image}`:null,t.install?`install: \`${t.install}\``:null,t.composeFile?`services: ${t.composeFile}`:null,t.connEnv?`conn: ${Object.keys(t.connEnv).join(", ")}`:null,t.delivery?.targetPath?`delivery: ${String(t.delivery.targetPath).split("/").filter(Boolean).pop()||t.delivery.targetPath}`:null].filter(Boolean).join(" \xb7 "),n=`network: ${(0,g.networkWords)(t.confinement,{services:!!t.composeFile})}`
7065
+ return`**${e.name}** (${e.id})${e.isPrimary?" \u2b50 primary":""}${e.scope?` [scope: ${e.scope}]`:""}\n ${r||"(empty config)"}\n ${n}${e.description?`\n ${e.description}`:""}`}function a(e){const t=(Array.isArray(e?.artifacts)?e.artifacts:[]).filter(e=>e&&"string"==typeof e.prepare&&e.prepare.trim()&&!(Array.isArray(e.manifests)&&e.manifests.length>0))
7054
7066
  if(0===t.length)return""
7055
7067
  const r=t.map(e=>`\`${e.path??e.target??"?"}\``).join(", ")
7056
7068
  return`\n\n\u26a0\ufe0f ${1===t.length?"Artifact":"Artifacts"} ${r} ${1===t.length?"declares":"declare"} no \`manifests\`, so ${1===t.length?"it is":"they are"} rebuilt on EVERY check and experiment start (the install runs again each time). Declare the files that decide the dependencies (e.g. \`pyproject.toml\`, \`requirements.txt\`, a lockfile) and the result is cached until one of them changes. If the prepare command names every package itself, declare the file it lives in or any stable project file.`}function c(e){return function(e){const t=e?.connEnv
@@ -7059,60 +7071,62 @@ const r=/(?:^|[\/@,=\s])(localhost|127(?:\.\d{1,3}){3}|\[?::1\]?)(?=$|[:\/,\s?#]
7059
7071
  return 0===n.length?null:`Not accepted: connEnv ${n.join(", ")} ${1===n.length?"points":"point"} at localhost. Inside the box localhost is the box itself, not your services. Use the compose SERVICE NAME and its internal port, e.g. DATABASE_URL=postgres://user:pass@postgres:5432/app.`}(e)??function(e){const t=(Array.isArray(e?.artifacts)?e.artifacts:[]).map(e=>"string"==typeof e?.path?e.path.trim():"").filter(e=>e&&null===(0,m.normalizeArtifactPath)(e))
7060
7072
  return 0===t.length?null:`Not accepted: ${1===t.length?"the artifact at":"the artifacts at"} ${t.map(e=>`"${e}"`).join(", ")} would shadow a system directory (/usr, /lib, /etc, /bin, ...) or leave its folder, so the box never mounts ${1===t.length?"it":"them"}. Put installed tools at their own absolute path, such as /tools or /opt/<name>, and point PATH there.`}(e)}async function l(e,r,n){try{const{sessionIdentity:i}=await Promise.resolve().then(()=>t(6688)),o=await i.getSessionRequestId(r,n,e.currentSessionId)
7061
7073
  if(o)return{requestId:o}
7062
- const s=(0,y.askingRequestOf)(await(0,y.callerSetupRun)(r,e.projectId,e.currentSessionId))
7074
+ const s=(0,w.askingRequestOf)(await(0,w.callerSetupRun)(r,e.projectId,e.currentSessionId))
7063
7075
  return s?{requestId:s}:{}}catch{return{}}}exports.handleEnvironmentList=async function(e,r,n,i,a,c){const l=a.startTimer("tool_execution_time")
7064
7076
  try{const e=await n.proxyGet(`/api/projects/${r.projectId}/environments`),i=e?.data||e||[],a=Array.isArray(i)?i:[]
7065
- l({success:!0}),o(await(0,b.callerSessionKey)(c),a.map(e=>e?.id))
7077
+ l({success:!0}),o(await(0,v.callerSessionKey)(c),a.map(e=>e?.id))
7066
7078
  const u=await async function(){try{const{actionServerFetch:e}=await Promise.resolve().then(()=>t(79414)),r=await e("/api/capabilities",{signal:AbortSignal.timeout(8e3)}),n=(await r.json().catch(()=>({})))?.data??{}
7067
- return`Host capabilities: Docker ${!0===n.docker?"available":"UNAVAILABLE (no daemon reachable: `confined` environments and sealed images cannot run here; use the narrowest host level and expect the Tests gate to need sign-off)"} \xb7 OS sandbox for the \`host\` level: ${n.osSandbox?`${n.osSandbox}`:"none"}${n.gpu?` \xb7 graphics card: ${n.gpu.box?"in boxes":"not in boxes"}, ${n.gpu.host?"in the `host` level":"not in the `host` level"}`:""} \xb7 platform ${n.platform??process.platform}`}catch{return"Host capabilities: unknown (the action server did not answer); assume Docker may be unavailable and verify before proposing a sealed environment."}}(),d=(0,h.presetsLine)(a.map(e=>e?.config?.image).filter(Boolean))
7079
+ return`Host capabilities: Docker ${!0===n.docker?"available":"UNAVAILABLE (no daemon reachable: `confined` environments and sealed images cannot run here; use the narrowest host level and expect the Tests gate to need sign-off)"} \xb7 OS sandbox for the \`host\` level: ${n.osSandbox?`${n.osSandbox}`:"none"}${n.gpu?` \xb7 graphics card: ${n.gpu.box?"in boxes":"not in boxes"}, ${n.gpu.host?"in the `host` level":"not in the `host` level"}`:""} \xb7 platform ${n.platform??process.platform}\nNetwork by level: sealed and privileged boxes: ${(0,g.networkWords)("confined")}; with services: ${(0,g.networkWords)("confined",{services:!0})}; egress-allowlist: ${(0,g.networkWords)("egress-allowlist")}; unsafe: ${(0,g.networkWords)("unsafe")}; host: ${(0,g.networkWords)("host")}.\n${g.HOST_BROWSER_WORDS}`}catch{return"Host capabilities: unknown (the action server did not answer); assume Docker may be unavailable and verify before proposing a sealed environment."}}(),d=(0,h.presetsLine)(a.map(e=>e?.config?.image).filter(Boolean))
7068
7080
  let p=""
7069
7081
  if(!a.some(e=>e?.config?.image)){const e=await(0,f.defaultBoxFor)(r.mainBasePath)
7070
7082
  e.language&&(p=`No sealed environment is set up, and none is needed to run this project's tests: \`run\` with no box starts one that fits it (${"python"===e.language?"Python":"Node"}, ${e.image}, with its dependencies installed). Set up an environment only when that box is not enough.`)}const m=(p?`\n\n${p}`:"")+(d?`\n\n${d}`:"")
7071
7083
  return 0===a.length?{content:[{type:"text",text:`${u}\n\nNo environments configured yet. Use \`environment_create\` to define one (the project's confined runtime: image, install, services, connection).`+m}]}:{content:[{type:"text",text:`${u}\n\n## Environments (${a.length})\n\n${a.map(s).join("\n\n")}${m}`}]}}catch(e){return l({success:!1,error:"api_error"}),{content:[{type:"text",text:`Failed to list environments: ${(0,d.getErrorMessage)(e)}`}],isError:!0}}},exports.handleEnvironmentGet=async function(e,t,r,n,i,a){const c=i.startTimer("tool_execution_time")
7072
7084
  try{const n=await r.proxyGet(`/api/projects/${t.projectId}/environments/${e.environmentId}`),i=n?.data||n
7073
- return c({success:!0}),i?(o(await(0,b.callerSessionKey)(a),[i.id]),{content:[{type:"text",text:`${s(i)}\n\nconfig:\n\`\`\`json\n${JSON.stringify(i.config||{},null,2)}\n\`\`\``}]}):{content:[{type:"text",text:`Environment not found: ${e.environmentId}`}],isError:!0}}catch(e){return c({success:!1,error:"api_error"}),{content:[{type:"text",text:`Failed to get environment: ${(0,d.getErrorMessage)(e)}`}],isError:!0}}},exports.handleEnvironmentCreate=async function(e,r,n,l,u,p){const f=u.startTimer("tool_execution_time"),m={...e,description:e.description??(e.preset?h.ENVIRONMENT_PRESETS[e.preset].description:void 0),config:(0,h.expandEnvironmentPreset)(e.preset,e.config)},g=c(m.config)
7085
+ return c({success:!0}),i?(o(await(0,v.callerSessionKey)(a),[i.id]),{content:[{type:"text",text:`${s(i)}\n\nconfig:\n\`\`\`json\n${JSON.stringify(i.config||{},null,2)}\n\`\`\``}]}):{content:[{type:"text",text:`Environment not found: ${e.environmentId}`}],isError:!0}}catch(e){return c({success:!1,error:"api_error"}),{content:[{type:"text",text:`Failed to get environment: ${(0,d.getErrorMessage)(e)}`}],isError:!0}}},exports.handleEnvironmentCreate=async function(e,r,n,l,u,p){const f=u.startTimer("tool_execution_time"),m={...e,description:e.description??(e.preset?h.ENVIRONMENT_PRESETS[e.preset].description:void 0),config:(0,h.expandEnvironmentPreset)(e.preset,e.config)},g=c(m.config)
7074
7086
  if(g)return f({success:!1,error:"localhost_conn"}),{content:[{type:"text",text:g}],isError:!0}
7075
7087
  try{const e=await i("environment_create",m,l,p)
7076
7088
  if(!e.ok)return f({success:!1,error:"verify_required"}),{content:[{type:"text",text:e.error}],isError:!0}
7077
- const c=await(0,b.callerSessionKey)(p)
7089
+ const c=await(0,v.callerSessionKey)(p)
7078
7090
  let u=null
7079
7091
  try{const e=await n.proxyGet(`/api/projects/${r.projectId}/environments`),t=e?.data||e||[]
7080
- u=Array.isArray(t)?t:null}catch{}const d=u?function(e,t){const r=I.get(e)
7092
+ u=Array.isArray(t)?t:null}catch{}const d=u?function(e,t){const r=E.get(e)
7081
7093
  return t.filter(e=>e?.id&&!0!==e?.config?.builtin&&!r?.has(e.id))}(c,u):[]
7082
7094
  if(d.length)return o(c,d.map(e=>e.id)),f({success:!1,error:"unseen_environments"}),{content:[{type:"text",text:`Not saved yet: this project already has ${1===d.length?"an environment":"environments"} you have not looked at (another setup may have saved ${1===d.length?"it":"them"} while you worked):\n\n${d.map(s).join("\n\n")}\n\nOne project, one runtime where it can be: if ${1===d.length?"it runs":"one of them runs"} what you need, use it (pass its id as environmentId, e.g. to testing_process_create) instead of saving another. If none fits, call environment_create again with the same arguments and it is saved.`}],isError:!0}
7083
7095
  const h=await n.proxyPost(`/api/projects/${r.projectId}/environments`,{name:m.name,description:m.description,scope:m.scope,config:m.config||{},isPrimary:m.isPrimary,createdBy:"setup-architect"}),g=h?.data||h
7084
7096
  o(c,[g?.id])
7085
7097
  try{const{sessionIdentity:e}=await Promise.resolve().then(()=>t(6688)),i=e.getIdentity()
7086
- i?.sessionId&&g?.id&&await n.proxyPost(`/api/projects/${r.projectId}/session-environment-associations`,{opencodeSessionId:i.sessionId,environmentId:g.id})}catch{}const A=g?.id?await(0,y.noteRunWrite)(n,r.projectId,p?.sessionId??r.currentSessionId,{kind:"environment",id:String(g.id),name:g.name},l):null
7087
- return(0,b.releaseSaveFirst)(c),f({success:!0}),{content:[{type:"text",text:`\u2705 Created environment **${g.name}** (${g.id})${g.isPrimary?": now the project's primary environment":""}.\n\n${s(g)}${e.note||""}${a(g?.config??m.config)}${A?b.SAVED_UNDER_RUN_NOTE:""}`}]}}catch(e){return f({success:!1,error:"api_error"}),{content:[{type:"text",text:`Failed to create environment: ${(0,d.getErrorMessage)(e)}`}],isError:!0}}},exports.handleEnvironmentUpdate=async function(e,t,r,n,o,l){const u=o.startTimer("tool_execution_time")
7098
+ i?.sessionId&&g?.id&&await n.proxyPost(`/api/projects/${r.projectId}/session-environment-associations`,{opencodeSessionId:i.sessionId,environmentId:g.id})}catch{}const A=g?.id?await(0,w.noteRunWrite)(n,r.projectId,p?.sessionId??r.currentSessionId,{kind:"environment",id:String(g.id),name:g.name},l):null
7099
+ return(0,v.releaseSaveFirst)(c),f({success:!0}),{content:[{type:"text",text:`\u2705 Created environment **${g.name}** (${g.id})${g.isPrimary?": now the project's primary environment":""}.\n\n${s(g)}${e.note||""}${a(g?.config??m.config)}${A?v.SAVED_UNDER_RUN_NOTE:""}`}]}}catch(e){return f({success:!1,error:"api_error"}),{content:[{type:"text",text:`Failed to create environment: ${(0,d.getErrorMessage)(e)}`}],isError:!0}}},exports.handleEnvironmentUpdate=async function(e,t,r,n,o,l){const u=o.startTimer("tool_execution_time")
7088
7100
  try{let o=""
7089
7101
  if(void 0!==e.config){const t=c(e.config)
7090
7102
  if(t)return u({success:!1,error:"localhost_conn"}),{content:[{type:"text",text:t}],isError:!0}
7091
7103
  const r=await i("environment_update",e,n,l)
7092
7104
  if(!r.ok)return u({success:!1,error:"verify_required"}),{content:[{type:"text",text:r.error}],isError:!0}
7093
- o=r.note||""}const{environmentId:d,skipVerify:p,skipVerifyReason:h,...f}=e,m=await r.proxyPatch(`/api/projects/${t.projectId}/environments/${d}`,f),g=m?.data||m,A=g?.id?await(0,y.noteRunWrite)(r,t.projectId,l?.sessionId??t.currentSessionId,{kind:"environment",id:String(g.id),name:g.name},n):null
7094
- return void 0!==e.config&&(0,b.releaseSaveFirst)(await(0,b.callerSessionKey)(l)),u({success:!0}),{content:[{type:"text",text:`\u2705 Updated environment **${g.name}** (${g.id}).\n\n${s(g)}${o}${a(g?.config)}${A&&void 0!==e.config?b.SAVED_UNDER_RUN_NOTE:""}`}]}}catch(e){return u({success:!1,error:"api_error"}),{content:[{type:"text",text:`Failed to update environment: ${(0,d.getErrorMessage)(e)}`}],isError:!0}}},exports.handleEnvironmentDelete=async function(e,t,r,n,i){const o=i.startTimer("tool_execution_time")
7105
+ o=r.note||""}const{environmentId:d,skipVerify:p,skipVerifyReason:h,...f}=e,m=await r.proxyPatch(`/api/projects/${t.projectId}/environments/${d}`,f),g=m?.data||m,A=g?.id?await(0,w.noteRunWrite)(r,t.projectId,l?.sessionId??t.currentSessionId,{kind:"environment",id:String(g.id),name:g.name},n):null
7106
+ return void 0!==e.config&&(0,v.releaseSaveFirst)(await(0,v.callerSessionKey)(l)),u({success:!0}),{content:[{type:"text",text:`\u2705 Updated environment **${g.name}** (${g.id}).\n\n${s(g)}${o}${a(g?.config)}${A&&void 0!==e.config?v.SAVED_UNDER_RUN_NOTE:""}`}]}}catch(e){return u({success:!1,error:"api_error"}),{content:[{type:"text",text:`Failed to update environment: ${(0,d.getErrorMessage)(e)}`}],isError:!0}}},exports.handleEnvironmentDelete=async function(e,t,r,n,i){const o=i.startTimer("tool_execution_time")
7095
7107
  try{return await r.proxyDelete(`/api/projects/${t.projectId}/environments/${e.environmentId}`),o({success:!0}),{content:[{type:"text",text:`\u{1f5d1}\ufe0f Deleted environment ${e.environmentId}. Test processes that referenced it fall back to the LLM path until reassigned.`}]}}catch(e){return o({success:!1,error:"api_error"}),{content:[{type:"text",text:`Failed to delete environment: ${(0,d.getErrorMessage)(e)}`}],isError:!0}}},exports.handleEnvironmentVerify=async function(e,r,i,o,s,a){const u=s.startTimer("tool_execution_time"),p={config:(0,h.expandEnvironmentPreset)(e.preset,e.config),smoke:e.smoke??(e.preset?h.ENVIRONMENT_PRESETS[e.preset].smoke:void 0)},f=c(p.config)
7096
7108
  if(f)return u({success:!1,error:"localhost_conn"}),{content:[{type:"text",text:f}],isError:!0}
7097
- const m=await(0,b.callerSessionKey)(a),y=(0,b.saveFirstRefusal)(m,n(p.config))
7098
- if(y)return u({success:!1,error:"save_first"}),{content:[{type:"text",text:y}],isError:!0}
7109
+ const m=p.config?.confinement
7110
+ if("host"===m||"pure-host"===m)return u({success:!1,error:"host_level"}),{content:[{type:"text",text:`Not verified: a ${m} environment runs on this computer, outside Docker, and environment_verify only stands up sealed Docker boxes, so it cannot try this one. On the host level nothing is installed for you (install and artifacts are not run there): its commands use what this computer already has. Save it with skipVerify: true and skipVerifyReason "runs on this computer", then open it with experiment_start and run the smoke command there.`}],isError:!0}
7111
+ const g=await(0,v.callerSessionKey)(a),w=(0,v.saveFirstRefusal)(g,n(p.config))
7112
+ if(w)return u({success:!1,error:"save_first"}),{content:[{type:"text",text:w}],isError:!0}
7099
7113
  try{const{actionServerFetch:e}=await Promise.resolve().then(()=>t(79414)),s=await e("/api/environments/verify",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({config:p.config,smoke:p.smoke,...await l(r,i,o)}),signal:AbortSignal.timeout(9e5)}),c=await s.json().catch(()=>({}))
7100
7114
  if(u({success:!0}),!s.ok||!c.success)return{content:[{type:"text",text:`Verify could not run: ${c.error||"HTTP "+s.status}`}],isError:!0}
7101
7115
  const d=c.data||{}
7102
- if(d.ok)return function(e,t){let r=v.get(e)
7103
- r||(r=new Set,v.set(e,r)),r.add(n(t))}(m,p.config),(0,b.notePassedVerify)(m,n(p.config)),{content:[{type:"text",text:"\u2705 Environment verified: it stands up and the smoke command passed. environment_create / environment_update now accept this exact config (edits to it need a fresh verify)."}]}
7104
- const h=d.commands&&d.commands[0],f=d.error?d.error:h?`${h.name}: exit ${h.exit}${h.timedOut?" (timed out)":""}\n${(h.stderr||h.stdout||"").slice(-1500)}`:"unknown failure",y=d.error?(0,g.admissionNumbers)(String(d.error)):null
7105
- if(y){const e={...y,holders:await(0,g.memoryHolders)()}
7106
- await(0,A.recordCommandRun)(i,o,r,a,{notStarted:!0,verify:!0,...p.smoke?{command:String(p.smoke).slice(0,2e3)}:{},environmentName:null,error:String(d.error).slice(0,600),room:(0,A.roomForRecord)(e,r.projectId)})
7107
- const t=(0,A.holdersLine)(e,r.projectId)
7108
- return{content:[{type:"text",text:`Verify could not start its box: ${d.error}${t?`\n${t}`:""}`}],isError:!0}}return{content:[{type:"text",text:`\u274c Environment did NOT verify (status: ${d.status||"failed"}). Fix the config and re-verify before saving.\n\n${f}${(0,b.noteFailedVerify)(m)}`}]}}catch(e){return u({success:!1,error:"verify_error"}),{content:[{type:"text",text:`Failed to verify environment: ${(0,d.getErrorMessage)(e)}`}],isError:!0}}},exports.handleEnvironmentSetup=async function(e,t,r,n,i,o){const s=i.startTimer("tool_execution_time")
7109
- try{const i=o?.sessionId,a=i?await w.sessionIdentity.getSessionRequestId(r,n,i):void 0,c=t?.projectId
7116
+ if(d.ok)return function(e,t){let r=I.get(e)
7117
+ r||(r=new Set,I.set(e,r)),r.add(n(t))}(g,p.config),(0,v.notePassedVerify)(g,n(p.config)),{content:[{type:"text",text:"\u2705 Environment verified: it stands up and the smoke command passed. environment_create / environment_update now accept this exact config (edits to it need a fresh verify)."}]}
7118
+ const h=d.commands&&d.commands[0],f=d.error?d.error:h?`${h.name}: exit ${h.exit}${h.timedOut?" (timed out)":""}\n${(h.stderr||h.stdout||"").slice(-1500)}`:"unknown failure",m=d.error?(0,A.admissionNumbers)(String(d.error)):null
7119
+ if(m){const e={...m,holders:await(0,A.memoryHolders)()}
7120
+ await(0,y.recordCommandRun)(i,o,r,a,{notStarted:!0,verify:!0,...p.smoke?{command:String(p.smoke).slice(0,2e3)}:{},environmentName:null,error:String(d.error).slice(0,600),room:(0,y.roomForRecord)(e,r.projectId)})
7121
+ const t=(0,y.holdersLine)(e,r.projectId)
7122
+ return{content:[{type:"text",text:`Verify could not start its box: ${d.error}${t?`\n${t}`:""}`}],isError:!0}}return{content:[{type:"text",text:`\u274c Environment did NOT verify (status: ${d.status||"failed"}). Fix the config and re-verify before saving.\n\n${f}${(0,v.noteFailedVerify)(g)}`}]}}catch(e){return u({success:!1,error:"verify_error"}),{content:[{type:"text",text:`Failed to verify environment: ${(0,d.getErrorMessage)(e)}`}],isError:!0}}},exports.handleEnvironmentSetup=async function(e,t,r,n,i,o){const s=i.startTimer("tool_execution_time")
7123
+ try{const i=o?.sessionId,a=i?await b.sessionIdentity.getSessionRequestId(r,n,i):void 0,c=t?.projectId
7110
7124
  if(!c)return s({success:!1,error:"no_project"}),{content:[{type:"text",text:"environment setup failed: no project in this session."}],isError:!0}
7111
7125
  if(a){const e=await r.getRequestClient().getRequest(a).catch(()=>null),t=e?(0,p.environmentSetupRefusal)(e.type):null
7112
7126
  if(t)return s({success:!1,error:"kind_refused"}),{content:[{type:"text",text:t}],isError:!0}}const l=[String(e.name??"").replace(/\s+/g," ").trim(),String(e.context??"").trim()].filter(Boolean).join(": ")||"A verified environment for the project."
7113
7127
  let u=null
7114
- try{u=i?await w.sessionIdentity.getAgentTypeFor(i):null}catch{u=null}const d=await(0,y.askSetupRun)(r,c,{part:"environment",need:l,askedBy:a?{by:"request",ref:a,...i?{sessionId:i}:{}}:{by:"alchemist"===u?"alchemist":"user",...i?{sessionId:i}:{}},...a?{waiter:a}:{},...i?{parentSessionId:i}:{}})
7115
- return s({success:!0}),{content:[{type:"text",text:`Environment setup asked. ${(0,y.askBriefing)(d,a??void 0)}`}]}}catch(e){return s({success:!1,error:"ask_failed"}),{content:[{type:"text",text:`environment setup failed: ${(0,d.getErrorMessage)(e)}. The ask was not recorded; you may try once more.`}],isError:!0}}},exports.handleEnvironmentProvision=async function(e,r,n,i,o,s){const a=o.startTimer("tool_execution_time")
7128
+ try{u=i?await b.sessionIdentity.getAgentTypeFor(i):null}catch{u=null}const d=await(0,w.askSetupRun)(r,c,{part:"environment",need:l,askedBy:a?{by:"request",ref:a,...i?{sessionId:i}:{}}:{by:"alchemist"===u?"alchemist":"user",...i?{sessionId:i}:{}},...a?{waiter:a}:{},...i?{parentSessionId:i}:{}})
7129
+ return s({success:!0}),{content:[{type:"text",text:`Environment setup asked. ${(0,w.askBriefing)(d,a??void 0)}`}]}}catch(e){return s({success:!1,error:"ask_failed"}),{content:[{type:"text",text:`environment setup failed: ${(0,d.getErrorMessage)(e)}. The ask was not recorded; you may try once more.`}],isError:!0}}},exports.handleEnvironmentProvision=async function(e,r,n,i,o,s){const a=o.startTimer("tool_execution_time")
7116
7130
  try{const{actionServerFetch:n}=await Promise.resolve().then(()=>t(79414)),i=await n(`/api/projects/${r.projectId}/environments/${e.environmentId}/provision`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sessionId:s?.sessionId,why:e.why}),signal:AbortSignal.timeout(72e4)}),o=await i.json().catch(()=>({}))
7117
7131
  if(a({success:!0}),!i.ok||!o.success)return{content:[{type:"text",text:`Provision could not run: ${o.error||"HTTP "+i.status}`}],isError:!0}
7118
7132
  const c=o.data||{}
@@ -7133,7 +7147,7 @@ try{const{actionServerFetch:n}=await Promise.resolve().then(()=>t(79414)),i=awai
7133
7147
  if(s({success:!0}),!i.ok||!o.success)return{content:[{type:"text",text:`Verify unavailable: ${o.error||"HTTP "+i.status}`}],isError:!0}
7134
7148
  const a=o.data||{},c=(a.checks||[]).map(e=>`${e.pass?"\u2713":"\u2717"} ${e.name}${e.pass?"":" -- "+(e.output||"").slice(0,140)}`).join("; ")||"(no checks declared)",l=!0===a.healthy?"yes":!1===a.healthy?"no":"n/a (no probe)"
7135
7149
  return{content:[{type:"text",text:`Verify ${a.verified?"PASSED":"FAILED"} | healthy: ${l} | checks: ${c}`}]}}catch(e){return s({success:!1,error:"verify_stack_error"}),{content:[{type:"text",text:`Failed to verify stack: ${(0,d.getErrorMessage)(e)}`}],isError:!0}}}
7136
- const u=t(76982),d=t(58824),p=t(87780),h=t(44853),f=t(44876),m=t(29603),g=t(22604),A=t(30288),y=t(75307),w=t(6688),b=t(64545),v=new Map,I=new Map},64545(e,exports,t){"use strict"
7150
+ const u=t(76982),d=t(58824),p=t(87780),h=t(44853),f=t(44876),m=t(29603),g=t(77580),A=t(22604),y=t(30288),w=t(75307),b=t(6688),v=t(64545),I=new Map,E=new Map},64545(e,exports,t){"use strict"
7137
7151
  exports.SAVED_UNDER_RUN_NOTE=exports.Mk=void 0,exports.callerSessionKey=async function(e){if(e?.sessionId)return e.sessionId
7138
7152
  try{const{sessionIdentity:e}=await Promise.resolve().then(()=>t(6688)),r=e.getIdentity()?.sessionId
7139
7153
  if(r)return r}catch{}return"(unknown-session)"},exports.saveFirstRefusal=function(e,t){const n=r.get(e)
@@ -7186,31 +7200,29 @@ Object.defineProperty(exports,"__esModule",{value:!0}),exports.experimentDefs=vo
7186
7200
  const s=o(t(76183)),a=t(82567),c=e=>(t,r)=>e(t,r.context,r.apiClient,r.logger,r.metrics,r.cliOrigin)
7187
7201
  exports.experimentDefs=[{name:"experiment_start",description:"Hold a box, where commands run (run({command, box})). Returns a handle; the box stays warm across runs (servers, scratch and dependencies persist) and shows a live view of your branch at /work. In a request, files a run changes are committed at once as that run's commit. Prefer the most confined environment that fits (by name, from environment_list): a sealed box runs anything without approval. Looser only with a reason: egress-allowlist for specific hosts, privileged/unsafe with the user's sign-off, env \"workspace\" (the live host, no sandbox) last. No suitable environment: environment_setup. The box stops when the request merges or sits idle; experiment_stop frees it sooner.",schema:s.ExperimentStartSchema,annotations:{title:"Start Experiment",readOnlyHint:!1},handler:c(a.handleExperimentStart)},{name:"experiment_stop",description:"Tear down a held experiment and its environment. Call when done (it also auto-expires when idle).",schema:s.ExperimentStopSchema,annotations:{title:"Stop Experiment",readOnlyHint:!1},handler:c(a.handleExperimentStop)},{name:"experiment_list",description:"List this project's saved experiment recipes and the experiments currently running.",schema:s.ExperimentListSchema,annotations:{title:"List Experiments",readOnlyHint:!0},handler:c(a.handleExperimentList)}]},82567(e,exports,t){"use strict"
7188
7202
  async function r(e,r,n,i){const{actionServerFetch:o}=await Promise.resolve().then(()=>t(79414)),s=await o(e,{method:r,headers:{"Content-Type":"application/json"},body:"POST"===r?JSON.stringify(n):void 0,signal:AbortSignal.timeout(i)}),a=await s.json().catch(()=>({}))
7189
- return{ok:s.ok,status:s.status,data:a}}exports.handleExperimentStart=async function(e,o,s,a,c,l){const u=c.startTimer("tool_execution_time")
7190
- try{const c=l?.sessionId
7191
- if(!c)return u({success:!1,error:"no_session"}),{content:[{type:"text",text:"Cannot start an experiment without a session context."}],isError:!0}
7192
- let d=e.environmentId
7193
- if(!d&&e.env?.trim()&&o.projectId){const t=e.env.trim().toLowerCase()
7194
- try{const r=await s.proxyGet(`/api/projects/${o.projectId}/environments`),n=r?.data||r||[],i=n.filter(e=>(e.name||"").toLowerCase()===t),a=n.filter(e=>(e.name||"").toLowerCase().includes(t)),c=i[0]||(1===a.length?a[0]:void 0)
7203
+ return{ok:s.ok,status:s.status,data:a}}exports.handleExperimentStart=async function(e,s,a,c,l,u){const d=l.startTimer("tool_execution_time")
7204
+ try{const l=u?.sessionId
7205
+ if(!l)return d({success:!1,error:"no_session"}),{content:[{type:"text",text:"Cannot start an experiment without a session context."}],isError:!0}
7206
+ let p=e.environmentId
7207
+ if(!p&&e.env?.trim()&&s.projectId){const t=e.env.trim().toLowerCase()
7208
+ try{const r=await a.proxyGet(`/api/projects/${s.projectId}/environments`),n=r?.data||r||[],i=n.filter(e=>(e.name||"").toLowerCase()===t),o=n.filter(e=>(e.name||"").toLowerCase().includes(t)),c=i[0]||(1===o.length?o[0]:void 0)
7195
7209
  if(!c){const t=n.map(e=>`"${e.name}"`).join(", ")||"(none)"
7196
- return u({success:!1,error:"env_name"}),{content:[{type:"text",text:`No${a.length>1?" unambiguous":""} environment matches "${e.env}". Available: ${t}. Use environment_setup to create one.`}],isError:!0}}d=c.id}catch(t){return u({success:!1,error:"env_lookup"}),{content:[{type:"text",text:`Could not resolve environment "${e.env}": ${(0,n.getErrorMessage)(t)}`}],isError:!0}}}if(e.confirmWorkspace&&o.projectId)try{const{sessionIdentity:e}=await Promise.resolve().then(()=>t(6688)),r=await e.getSessionRequestId(s,a,c).catch(()=>{})
7197
- if(r){const e=await s.proxyGet(`/api/projects/${o.projectId}/autonomy`),t=(e?.data??e)?.strictness
7198
- if("minimal"===t)return u({success:!1,error:"minimal_no_host_box"}),{content:[{type:"text",text:`Not started: this project's strictness is MINIMAL, so no live-host box is opened for ${r}'s checks. Run your one quick check in a sealed box instead: experiment_start with no environment opens one that fits this project (python with pytest for a Python project, node with its dependencies for a Node one). Then complete with your account per criterion.`}],isError:!0}}}catch{}if(!e.confirmWorkspace&&o.projectId)try{const e=d,t=await s.proxyGet(`/api/projects/${o.projectId}/environments`),r=t?.data||t||[],n=e=>e?.config?.confinement??"confined",i=e=>"pure-host"===n(e)||"workspace"===n(e)||"live"===e?.config?.worktreeTarget,a=e?r.find(t=>t.id===e):r.find(e=>e.isPrimary),c=r.filter(e=>!i(e)).map(e=>`"${e.name}"`)
7199
- if(a&&i(a))return u({success:!1,error:"workspace_unconfirmed"}),{content:[{type:"text",text:[`\u26a0\ufe0f Environment "${a.name}" is the LIVE HOST: no sandbox, commands run raw on the user's machine.`,c.length>0?`Use a confined environment instead: ${c.join(", ")}.`:"No sealed environment is set up yet, and you do not need one for checks: experiment_start with no environment opens a sealed box that fits this project (python with pytest for a Python project, node with its dependencies for a Node one). Call environment_setup only when that is not enough (services, a specific toolchain).","Only if the task genuinely requires the real machine (inspecting real host files, host-only tooling): re-call experiment_start with confirmWorkspace: true."].join("\n")}]}}catch{}const{sessionIdentity:p}=await Promise.resolve().then(()=>t(6688)),h=await p.getSessionRequestId(s,a,c).catch(()=>{}),f=o.workspaceManager?.getCurrentContext(o.currentSessionId)?.basePath||o.basePath,m=f&&o.mainBasePath&&t(16928).resolve(f)===t(16928).resolve(o.mainBasePath)?void 0:f,g=(t,r)=>(0,i.recordCommandRun)(s,a,o,l,{notStarted:!0,...e.forCommand?{command:String(e.forCommand).slice(0,2e3)}:{},environmentName:e.env?.trim()||null,environmentId:d??null,...e.size?{size:e.size}:{},...e.why?{why:String(e.why).slice(0,300)}:{},error:t.slice(0,600),...(0,i.roomForRecord)(r,o.projectId)?{room:(0,i.roomForRecord)(r,o.projectId)}:{}})
7200
- let A
7201
- try{A=await r("/api/experiments/start","POST",{sessionId:c,environmentId:d,requestId:h,liveWorktreePath:m,...e.size?{size:e.size}:{}},9e5)}catch(e){throw await g((0,n.getErrorMessage)(e)),e}const{ok:y,data:w}=A
7202
- if(!y||!w.success){u({success:!1,error:"start_failed"})
7203
- const e=String(w.error||"HTTP "+(w.status||""))
7204
- await g(e,w.room)
7205
- const t=(0,i.holdersLine)(w.room,o.projectId)
7206
- return{content:[{type:"text",text:`Could not start experiment: ${e}${t?`\n${t}`:""}`}],isError:!0}}const b=w.data||{}
7207
- u({success:!0})
7208
- const v=b.environmentName?`environment **${b.environmentName}** (${b.image}${b.hasServices?" + services":""})`:`a bare box (${b.image})`
7209
- let I=""
7210
- try{const e=b.environmentId||d
7211
- if(o.projectId){const t=await s.proxyGet(`/api/projects/${o.projectId}/check-commands`),r=e?(t?.data||t||[]).filter(e=>e.isActive??e.is_active??!0).filter(t=>(t.environmentId??t.environment_id)===e).map(e=>`\`${e.name}\``):[]
7212
- r.length>0&&(I=`\nRegistered commands here: ${r.join(", ")} (run them via \`run\`).`)}}catch{}const E="write large output to /scratch (on disk) or /tmp (256 MB, in memory)",C=b.sourceWritable?` -- your live branch source. A command may change it (a formatter, a --fix, codegen): whatever a run changes is committed as that run's commit and listed in its result, so the tree stays clean; ${E}`:` -- a copy of your branch made for this box: commands may write anywhere in it (build output, caches), and it goes when the box stops; ${E}`,k=b.workdir?`Commands run in \`${b.workdir}\`${b.hostTier?" (a disposable writable worktree on the host)":C}. `:`The source is your branch${C}. `,x=function(e){return e.hostTier?"":e.hasGit&&!1===e.gitBinary?'This image has no git binary: a git command fails here ("git: not found"), so read the files themselves (grep, cat, or the project\'s own runtime); merge_preview and query_history show main and the history. ':e.hasGit?"git works here on your branch and its history, read-only (ls-files, log, diff, rev-parse); commits and other git writes fail. ":"git has no repository in this box: a command that needs one fails or reports nothing, so do not record such a run as proof. "}(b)
7213
- return{content:[{type:"text",text:`${b.reused?`\u{1f9ea} You already hold a box on ${v}; it is returned as it is (its files, servers and state are still there). To start clean, experiment_stop it and start again.`:`\u{1f9ea} Experiment started: ${v}.`}\nHandle: \`${b.handle}\`\n\n${k}${x}State persists across exec calls, including background processes (a server you start with \`&\` keeps running -- wait ~2s before its first request on a fresh box, and don't start it twice).${b.sourceWritable?" Edits through the usual tools flow into the source live.":""} It stops by itself when the request's work merges or it sits idle; experiment_stop frees it sooner.${I}`}]}}catch(e){return u({success:!1,error:"start_error"}),{content:[{type:"text",text:`Could not start experiment: ${(0,n.getErrorMessage)(e)}`}],isError:!0}}},exports.handleExperimentStop=async function(e,t,i,o,s,a){const c=s.startTimer("tool_execution_time")
7210
+ return d({success:!1,error:"env_name"}),{content:[{type:"text",text:`No${o.length>1?" unambiguous":""} environment matches "${e.env}". Available: ${t}. Use environment_setup to create one.`}],isError:!0}}p=c.id}catch(t){return d({success:!1,error:"env_lookup"}),{content:[{type:"text",text:`Could not resolve environment "${e.env}": ${(0,n.getErrorMessage)(t)}`}],isError:!0}}}if(!e.confirmWorkspace&&s.projectId)try{const e=p,t=await a.proxyGet(`/api/projects/${s.projectId}/environments`),r=t?.data||t||[],n=e=>e?.config?.confinement??"confined",i=e=>"pure-host"===n(e)||"workspace"===n(e)||"live"===e?.config?.worktreeTarget,o=e?r.find(t=>t.id===e):r.find(e=>e.isPrimary),c=r.filter(e=>!i(e)).map(e=>`"${e.name}"`)
7211
+ if(o&&i(o))return d({success:!1,error:"workspace_unconfirmed"}),{content:[{type:"text",text:[`\u26a0\ufe0f Environment "${o.name}" is the LIVE HOST: no sandbox, commands run raw on the user's machine.`,c.length>0?`Use a confined environment instead: ${c.join(", ")}.`:"No sealed environment is set up yet, and you do not need one for checks: experiment_start with no environment opens a sealed box that fits this project (python with pytest for a Python project, node with its dependencies for a Node one). Call environment_setup only when that is not enough (services, a specific toolchain).","Only if the task genuinely requires the real machine (inspecting real host files, host-only tooling): re-call experiment_start with confirmWorkspace: true."].join("\n")}]}}catch{}const{sessionIdentity:h}=await Promise.resolve().then(()=>t(6688)),f=await h.getSessionRequestId(a,c,l).catch(()=>{}),m=s.workspaceManager?.getCurrentContext(s.currentSessionId)?.basePath||s.basePath,g=m&&s.mainBasePath&&t(16928).resolve(m)===t(16928).resolve(s.mainBasePath)?void 0:m,A=(t,r)=>(0,o.recordCommandRun)(a,c,s,u,{notStarted:!0,...e.forCommand?{command:String(e.forCommand).slice(0,2e3)}:{},environmentName:e.env?.trim()||null,environmentId:p??null,...e.size?{size:e.size}:{},...e.why?{why:String(e.why).slice(0,300)}:{},error:t.slice(0,600),...(0,o.roomForRecord)(r,s.projectId)?{room:(0,o.roomForRecord)(r,s.projectId)}:{}})
7212
+ let y
7213
+ try{y=await r("/api/experiments/start","POST",{sessionId:l,environmentId:p,requestId:f,liveWorktreePath:g,...e.size?{size:e.size}:{}},9e5)}catch(e){throw await A((0,n.getErrorMessage)(e)),e}const{ok:w,data:b}=y
7214
+ if(!w||!b.success){d({success:!1,error:"start_failed"})
7215
+ const e=String(b.error||"HTTP "+(b.status||""))
7216
+ await A(e,b.room)
7217
+ const t=(0,o.holdersLine)(b.room,s.projectId)
7218
+ return{content:[{type:"text",text:`Could not start experiment: ${e}${t?`\n${t}`:""}`}],isError:!0}}const v=b.data||{}
7219
+ d({success:!0})
7220
+ const I=v.environmentName?`environment **${v.environmentName}** (${v.image}${v.hasServices?" + services":""})`:`a bare box (${v.image})${v.noLanguage?". Nothing in the project says its language yet: write its manifest (requirements.txt, pyproject.toml or package.json) and start the box again, and the next one fits it, its dependencies installed":""}`
7221
+ let E=""
7222
+ try{const e=v.environmentId||p
7223
+ if(s.projectId){const t=await a.proxyGet(`/api/projects/${s.projectId}/check-commands`),r=e?(t?.data||t||[]).filter(e=>e.isActive??e.is_active??!0).filter(t=>(t.environmentId??t.environment_id)===e).map(e=>`\`${e.name}\``):[]
7224
+ r.length>0&&(E=`\nRegistered commands here: ${r.join(", ")} (run them via \`run\`).`)}}catch{}const C="write large output to /scratch (on disk) or /tmp (256 MB, in memory)",k=v.sourceWritable?` -- your live branch source. A command may change it (a formatter, a --fix, codegen): whatever a run changes is committed as that run's commit and listed in its result, so the tree stays clean; ${C}`:` -- a copy of your branch made for this box: commands may write anywhere in it (build output, caches), and it goes when the box stops; ${C}`,x=v.workdir?`Commands run in \`${v.workdir}\`${v.hostTier?" (a disposable writable worktree on the host)":k}. `:`The source is your branch${k}. `,S=function(e){return e.hostTier?"":e.hasGit&&!1===e.gitBinary?'This image has no git binary: a git command fails here ("git: not found"), so read the files themselves (grep, cat, or the project\'s own runtime); merge_preview and query_history show main and the history. ':e.hasGit?"git works here on your branch and its history, read-only (ls-files, log, diff, rev-parse); commits and other git writes fail. ":"git has no repository in this box: a command that needs one fails or reports nothing, so do not record such a run as proof. "}(v),_=v.network?`Network: ${v.network}. ${i.HOST_BROWSER_WORDS}\n`:""
7225
+ return{content:[{type:"text",text:`${v.reused?`\u{1f9ea} You already hold a box on ${I}; it is returned as it is (its files, servers and state are still there). To start clean, experiment_stop it and start again.`:`\u{1f9ea} Experiment started: ${I}.`}\nHandle: \`${v.handle}\`\n\n${_}${x}${S}State persists across exec calls, including background processes (a server you start with \`&\` keeps running -- wait ~2s before its first request on a fresh box, and don't start it twice).${v.sourceWritable?" Edits through the usual tools flow into the source live.":""} It stops by itself when the request's work merges or it sits idle; experiment_stop frees it sooner.${E}`}]}}catch(e){return d({success:!1,error:"start_error"}),{content:[{type:"text",text:`Could not start experiment: ${(0,n.getErrorMessage)(e)}`}],isError:!0}}},exports.handleExperimentStop=async function(e,t,i,o,s,a){const c=s.startTimer("tool_execution_time")
7214
7226
  try{if(!e.handle)return c({success:!1,error:"bad_args"}),{content:[{type:"text",text:"experiment_stop needs a handle."}],isError:!0}
7215
7227
  const{ok:t,data:n}=await r("/api/experiments/stop","POST",{handle:e.handle,...a?.sessionId?{sessionId:a.sessionId}:{}},3e4)
7216
7228
  return c({success:t}),t&&n.success?{content:[{type:"text",text:n.data?.stopped?"\u{1f9ea} Experiment stopped and its environment torn down.":"That experiment was already gone."}]}:{content:[{type:"text",text:`experiment_stop failed: ${n.error||"unknown"}`}],isError:!0}}catch(e){return c({success:!1,error:"stop_error"}),{content:[{type:"text",text:`experiment_stop failed: ${(0,n.getErrorMessage)(e)}`}],isError:!0}}},exports.handleExperimentList=async function(e,t,i,o,s,a){const c=s.startTimer("tool_execution_time")
@@ -7218,7 +7230,7 @@ try{const{data:e}=await r("/api/experiments","GET",void 0,3e4),t=e?.data||[]
7218
7230
  c({success:!0})
7219
7231
  const n=[]
7220
7232
  return n.push(0===t.length?"No experiments running.":""),t.length&&n.push(`## Running now (${t.length})\n`+t.map(e=>`- \`${e.handle}\`${e.environmentId?` (env ${e.environmentId})`:""}: idle ${Math.round((Date.now()-e.lastActivity)/1e3)}s${a?.sessionId&&e.sessionId?e.sessionId===a.sessionId?" (yours)":" (another agent's)":""}`).join("\n")),{content:[{type:"text",text:n.join("\n\n")}]}}catch(e){return c({success:!1,error:"list_error"}),{content:[{type:"text",text:`Could not list experiments: ${(0,n.getErrorMessage)(e)}`}],isError:!0}}}
7221
- const n=t(58824),i=t(30288)},39196(e,exports,t){"use strict"
7233
+ const n=t(58824),i=t(77580),o=t(30288)},39196(e,exports,t){"use strict"
7222
7234
  exports.def=void 0
7223
7235
  const r=t(76183),n=t(28309)
7224
7236
  exports.def={name:"create_directory",description:"Create a new directory or ensure a directory exists. Can create multiple nested directories in one operation. If the directory already exists, this operation will succeed silently. Perfect for setting up directory structures for projects or ensuring required paths exist.",schema:r.CreateDirectorySchema,annotations:{title:"Create Directory",readOnlyHint:!1,destructiveHint:!1,idempotentHint:!0},hidden:!0,handler:(e,t)=>(0,n.handleCreateDirectory)(e,t.context,t.apiClient.getTaskClient(),t.apiClient.getToolRecordClient(),t.apiClient.getRequestClient(),t.logger,t.metrics,t.mcpServer)}},28309(e,exports,t){"use strict"
@@ -8977,7 +8989,7 @@ else if(e&&"object"==typeof e)for(const t of Object.keys(e)){const n=e[t]
8977
8989
  null===n?delete e[t]:"object"==typeof n&&r(n)}}exports.preNormalize=function(e,t){r(t)
8978
8990
  const n=o[e]
8979
8991
  n&&n(t)}
8980
- const n=["change","replacesSummary","notChecked","show","guidance","skipTestsReason","skipTests","criteriaComments","afterRun","couldNotCheck"],i=["title","description","type","acceptanceCriteria","removesCriteria","arcName","estimate"],o={query_history:function(e){const t={"file-history":"fileHistory","task-file-history":"taskHistory","request-file-history":"requestHistory",recover:"recover",blame:"blame"},r=t[e?.queryType]
8992
+ const n=["change","replacesSummary","notChecked","method","methodReplaces","show","guidance","skipTestsReason","skipTests","criteriaComments","afterRun","couldNotCheck"],i=["title","description","type","acceptanceCriteria","removesCriteria","arcName","estimate"],o={query_history:function(e){const t={"file-history":"fileHistory","task-file-history":"taskHistory","request-file-history":"requestHistory",recover:"recover",blame:"blame"},r=t[e?.queryType]
8981
8993
  if(r)for(const n of Object.values(t))n!==r&&delete e[n]},replace_in_files:function(e){if(t(e,"operations"),e&&Array.isArray(e.operations))for(const t of e.operations)t.old_str&&t.new_str&&!t.replacements&&(t.replacements=[{old_str:t.old_str,new_str:t.new_str,match_mode:t.match_mode}],delete t.old_str,delete t.new_str,delete t.match_mode)},request_operations:function(e){for(const r of["complete","verifyCriteria","merge","revert","search","list"])t(e,r)
8982
8994
  if("complete"===e?.operation&&e?.summary&&!e?.complete&&(e.complete={summary:e.summary},delete e.summary),"complete"===e?.operation&&"string"==typeof e?.change&&e.change&&!e?.complete&&(e.complete={change:e.change},delete e.change),"complete"===e?.operation&&e?.worthKnowing&&e?.complete&&!e.complete.worthKnowing&&(e.complete.worthKnowing=Array.isArray(e.worthKnowing)?e.worthKnowing:[String(e.worthKnowing)],delete e.worthKnowing),"complete"===e?.operation&&e?.complete&&"object"==typeof e.complete)for(const t of n)null!=e[t]&&null==e.complete[t]&&(e.complete[t]=e[t]),delete e[t]
8983
8995
  if("update"===e?.operation){t(e,"update")
@@ -8989,7 +9001,7 @@ e.prompt=e.prompt||t.prompt,delete e.continue}if(e?.readMessages&&"object"==type
8989
9001
  e.limit=e.limit??t.limit,delete e.readMessages}if(e?.listSessions&&"object"==typeof e.listSessions){const t=e.listSessions
8990
9002
  e.activeOnly=e.activeOnly??t.activeOnly,e.requestId=e.requestId||t.requestId,e.limit=e.limit??t.limit,e.minTokens=e.minTokens??t.minTokens,e.maxTokens=e.maxTokens??t.maxTokens,e.maxAgeHours=e.maxAgeHours??t.maxAgeHours,delete e.listSessions}!e?.operation&&e&&(e.prompt&&!e.sessionId?e.operation="spawn":e.prompt&&e.sessionId?e.operation="continue":e.sessionId&&(e.operation="check-status")),e?.operation&&("spawn"===e.operation?(delete e.sessionId,delete e.activeOnly,delete e.minTokens,delete e.maxTokens,delete e.maxAgeHours):"continue"===e.operation?(delete e.agent,delete e.description,delete e.allowParallel,delete e.useExistingSession,delete e.activeOnly,delete e.minTokens,delete e.maxTokens,delete e.maxAgeHours,delete e.limit):"check-status"===e.operation||"abort"===e.operation?(delete e.agent,delete e.prompt,delete e.description,delete e.requestId,delete e.allowParallel,delete e.useExistingSession,delete e.limit,delete e.activeOnly,delete e.minTokens,delete e.maxTokens,delete e.maxAgeHours):"read-messages"===e.operation?(delete e.agent,delete e.prompt,delete e.description,delete e.requestId,delete e.allowParallel,delete e.useExistingSession,delete e.activeOnly,delete e.minTokens,delete e.maxTokens,delete e.maxAgeHours):"list-sessions"===e.operation&&(delete e.sessionId,delete e.agent,delete e.prompt,delete e.description,delete e.allowParallel,delete e.useExistingSession))},planner_ops:function(e){if(e?.request&&"object"==typeof e.request){const t=e.request
8991
9003
  e.prompt=e.prompt||t.prompt,e.description=e.description||t.description,delete e.request}if(e?.list&&"object"==typeof e.list){const t=e.list
8992
- e.activeOnly=e.activeOnly??t.activeOnly,e.limit=e.limit??t.limit,delete e.list}!e?.operation&&e&&(e.prompt?e.operation="request":e.sessionId&&(e.operation="check-status")),e?.operation&&("request"===e.operation?(delete e.sessionId,delete e.activeOnly,delete e.limit):"check-status"===e.operation?(delete e.prompt,delete e.description,delete e.userRequested,delete e.userStarts,delete e.activeOnly,delete e.limit):"list"===e.operation&&(delete e.sessionId,delete e.prompt,delete e.description,delete e.userRequested,delete e.userStarts))},document_operations:function(e){const t=["create","search","createSnapshot","getHistory","getVersion","restoreVersion","listAnnotations","createAnnotation","replyToAnnotation","resolveAnnotation","createArtifactAnnotation"]
9004
+ e.activeOnly=e.activeOnly??t.activeOnly,e.limit=e.limit??t.limit,delete e.list}!e?.operation&&e&&(e.prompt?e.operation="request":e.sessionId&&(e.operation="check-status")),e?.operation&&("request"===e.operation?(delete e.activeOnly,delete e.limit):"check-status"===e.operation?(delete e.prompt,delete e.description,delete e.userRequested,delete e.userStarts,delete e.activeOnly,delete e.limit):"list"===e.operation&&(delete e.sessionId,delete e.prompt,delete e.description,delete e.userRequested,delete e.userStarts))},document_operations:function(e){const t=["create","search","createSnapshot","getHistory","getVersion","restoreVersion","listAnnotations","createAnnotation","replyToAnnotation","resolveAnnotation","createArtifactAnnotation"]
8993
9005
  for(const r of t)e?.[r]&&"object"==typeof e[r]&&(Object.assign(e,e[r]),delete e[r])},task_inspect:function(e){e?.list?.filters&&"object"==typeof e.list.filters?(Object.assign(e,e.list.filters),delete e.list):e?.list&&"object"==typeof e.list&&(Object.assign(e,e.list),delete e.list)},plan_write:function(e){const t=["create","update","append","replace","createSnapshot","restore","setActive"]
8994
9006
  for(const r of t)e?.[r]&&"object"==typeof e[r]&&(Object.assign(e,e[r]),delete e[r])},plan_read:function(e){e?.get?.planId&&!e.planId&&(e.planId=e.get.planId,delete e.get),e?.getHistory?.planId&&!e.planId&&(e.planId=e.getHistory.planId,delete e.getHistory),e?.getRequests?.planId&&!e.planId&&(e.planId=e.getRequests.planId,delete e.getRequests),e?.list&&!e.searchQuery&&(Object.assign(e,e.list),delete e.list)},create_request:function(e){t(e,"acceptanceCriteria"),t(e,"areas"),e&&"string"==typeof e.follows&&e.follows.startsWith("round:")&&void 0===e.target&&(e.target=[])},verify_acceptance_criteria:function(e){t(e,"verifications")}}},56946(e,exports,t){"use strict"
8995
9007
  exports.def=void 0
@@ -9132,160 +9144,161 @@ if("completed"!==u.status)return{text:`The target of the thread of ${i} was not
9132
9144
  const d=await e.proxyGet(`/api/projects/${n}/threads/${encodeURIComponent(i)}`),p=(d?.data??d)?.target
9133
9145
  return p?.state?{text:t(i,p.state)}:{text:`The thread of ${i} has no target.`}}
9134
9146
  const r=new Set(["completed","failed","canceled","cancelled","timeout","expired"])},15715(e,exports,t){"use strict"
9135
- function r(e,t){return`Not completed yet: ${e} was completed before, and the summary you pass now REPLACES the one the user reads when it lands. The earlier one:\n "${t.length>600?`${t.slice(0,597)}...`:t}"\nWrite a summary that describes everything ${e} delivers, with this round's fixes folded in (not only the fixes), and call complete again with replacesSummary: true.`}exports.handleCompleteRequest=async function(e,S,_,B,R,$,Q){const T=B.child({tool:"complete_request"}),D=R.startTimer("tool_execution_time")
9136
- try{const{requestId:o,summary:s,change:O,worthKnowing:N,skipTestsReason:M,skipTests:P,criteriaComments:j,couldNotCheck:F,guidance:q,replacesSummary:L,notChecked:U,show:z}=e
9137
- if(!o)throw(0,i.createValidationError)("Request ID is required")
9138
- if(!s&&!O)throw(0,i.createValidationError)("Summary is required")
9139
- let H=null
9140
- try{H=await _.getRequestClient().getRequest(o)}catch(e){T.debug("the request could not be read before completing; completion continues",{requestId:o,error:(0,p.getErrorMessage)(e)})}const V="string"==typeof H?.roundOf&&!!H.roundOf
9141
- if(!s&&!H)return{content:[{type:"text",text:`Not completed: ${o} could not be read just now, so nothing was recorded. Call complete again with the same arguments.`}],isError:!0}
9142
- if(!s&&!V)throw(0,i.createValidationError)(`Summary is required: ${o} is not a round, and \`change\` is one change on a round.`)
9143
- let G=s||O||""
9144
- if(M&&P&&P.length>0)throw(0,i.createValidationError)("Provide either skipTestsReason (skip all) or skipTests (per-command), not both.")
9145
- if(!0!==L){const e=String(H?.completionSummary??"").trim()
9146
- if(e)return T.info("re-completion refused until the summary is confirmed to cover the whole request",{requestId:o}),{content:[{type:"text",text:r(o,e)}],isError:!0}}T.info("Starting request completion",{requestId:o,skipAllRequested:!!M,perCommandSkipCount:P?.length||0})
9147
- const Z=Q?.sessionId??S.currentSessionId,W=Q??(Z?{providerId:"opencode",sessionId:Z,createdAt:(new Date).toISOString()}:void 0)
9148
- try{const e=await fetch(`${(0,f.getActionServerBaseUrl)()}/api/commit-worktree-leftovers`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({requestId:o}),signal:AbortSignal.timeout(2e4)})
9147
+ function r(e,t,r){return[`Not completed yet; nothing has changed. Your \`method\` tip will lead for the next worker on similar work${r.length?` in ${r.join(", ")}`:""}, and ${1===t.length?"this tip drops":"these tips drop"} out of what that worker sees:`,...t.map(e=>`- ${e.localId}${e.title?` "${e.title}"`:""}: ${e.note}`),`Call complete on ${e} again with the same arguments plus \`methodReplaces: true\`, and a \`method\` that keeps whatever in ${1===t.length?"it":"them"} is still true (your own text as it is, when nothing is).`].join("\n")}function n(e,t){return`Not completed yet: ${e} was completed before, and the summary you pass now REPLACES the one the user reads when it lands. The earlier one:\n "${t.length>600?`${t.slice(0,597)}...`:t}"\nWrite a summary that describes everything ${e} delivers, with this round's fixes folded in (not only the fixes), and call complete again with replacesSummary: true.`}exports.handleCompleteRequest=async function(e,_,B,R,$,Q,T){const D=R.child({tool:"complete_request"}),O=$.startTimer("tool_execution_time")
9148
+ try{const{requestId:s,summary:a,change:N,worthKnowing:M,skipTestsReason:P,skipTests:j,criteriaComments:F,couldNotCheck:q,guidance:L,replacesSummary:U,notChecked:z,method:H,methodReplaces:V,show:G}=e
9149
+ if(!s)throw(0,o.createValidationError)("Request ID is required")
9150
+ if(!a&&!N)throw(0,o.createValidationError)("Summary is required")
9151
+ let Z=null
9152
+ try{Z=await B.getRequestClient().getRequest(s)}catch(e){D.debug("the request could not be read before completing; completion continues",{requestId:s,error:(0,h.getErrorMessage)(e)})}const W="string"==typeof Z?.roundOf&&!!Z.roundOf
9153
+ if(!a&&!Z)return{content:[{type:"text",text:`Not completed: ${s} could not be read just now, so nothing was recorded. Call complete again with the same arguments.`}],isError:!0}
9154
+ if(!a&&!W)throw(0,o.createValidationError)(`Summary is required: ${s} is not a round, and \`change\` is one change on a round.`)
9155
+ let Y=a||N||""
9156
+ if(P&&j&&j.length>0)throw(0,o.createValidationError)("Provide either skipTestsReason (skip all) or skipTests (per-command), not both.")
9157
+ if(!0!==U){const e=String(Z?.completionSummary??"").trim()
9158
+ if(e)return D.info("re-completion refused until the summary is confirmed to cover the whole request",{requestId:s}),{content:[{type:"text",text:n(s,e)}],isError:!0}}if("string"==typeof H&&H.trim()&&!0!==V){const e=await B.proxyGet(`/api/projects/${_.projectId}/requests/${encodeURIComponent(s)}/tips-displaced`).catch(()=>null),t=(e?.data??e)?.displaced
9159
+ if(Array.isArray(t)&&t.length>0)return{content:[{type:"text",text:r(s,t,(e?.data??e)?.areas??[])}],isError:!0}}D.info("Starting request completion",{requestId:s,skipAllRequested:!!P,perCommandSkipCount:j?.length||0})
9160
+ const J=T?.sessionId??_.currentSessionId,K=T??(J?{providerId:"opencode",sessionId:J,createdAt:(new Date).toISOString()}:void 0)
9161
+ try{const e=await fetch(`${(0,m.getActionServerBaseUrl)()}/api/commit-worktree-leftovers`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({requestId:s}),signal:AbortSignal.timeout(2e4)})
9149
9162
  if(e.ok){const t=await e.json()
9150
- t?.data?.committed&&T.info("Committed uncommitted worktree changes before completing",{requestId:o,sha:t.data.sha?.slice(0,8),files:t.data.files?.length??0})}}catch(e){T.debug("commit-worktree-leftovers unavailable; completing as before",{requestId:o,error:(0,p.getErrorMessage)(e)})}S.mainBasePath&&await(0,c.foldAtDoor)({repoDir:S.mainBasePath,branch:`requests/${o}`,defaultBranch:await(0,w.resolveDefaultBranch)(S.mainBasePath,S.gitBinary).catch(()=>"main"),requestId:o,door:"complete",gitBinary:S.gitBinary,postRecord:e=>_.proxyPost(`/api/projects/${S.projectId}/tool-records`,e),log:T})
9151
- let Y=null,J=null
9152
- try{const e=await fetch(`${(0,f.getActionServerBaseUrl)()}/api/branch-head?requestId=${encodeURIComponent(o)}`,{signal:AbortSignal.timeout(5e3)})
9163
+ t?.data?.committed&&D.info("Committed uncommitted worktree changes before completing",{requestId:s,sha:t.data.sha?.slice(0,8),files:t.data.files?.length??0})}}catch(e){D.debug("commit-worktree-leftovers unavailable; completing as before",{requestId:s,error:(0,h.getErrorMessage)(e)})}_.mainBasePath&&await(0,l.foldAtDoor)({repoDir:_.mainBasePath,branch:`requests/${s}`,defaultBranch:await(0,b.resolveDefaultBranch)(_.mainBasePath,_.gitBinary).catch(()=>"main"),requestId:s,door:"complete",gitBinary:_.gitBinary,postRecord:e=>B.proxyPost(`/api/projects/${_.projectId}/tool-records`,e),log:D})
9164
+ let X=null,ee=null
9165
+ try{const e=await fetch(`${(0,m.getActionServerBaseUrl)()}/api/branch-head?requestId=${encodeURIComponent(s)}`,{signal:AbortSignal.timeout(5e3)})
9153
9166
  if(e.ok){const t=await e.json()
9154
- Y=t?.data?.sha??null,J=t?.data?.tree??null}}catch(e){T.debug("branch-head fetch failed; server will fall back",{error:(0,p.getErrorMessage)(e)})}const K=Y?{headSha:Y,...J?{headTree:J}:{}}:{}
9155
- let X=null
9156
- try{const e=await fetch(`${(0,f.getActionServerBaseUrl)()}/api/request-diff-stats?requestId=${encodeURIComponent(o)}`,{signal:AbortSignal.timeout(5e3)})
9167
+ X=t?.data?.sha??null,ee=t?.data?.tree??null}}catch(e){D.debug("branch-head fetch failed; server will fall back",{error:(0,h.getErrorMessage)(e)})}const te=X?{headSha:X,...ee?{headTree:ee}:{}}:{}
9168
+ let re=null
9169
+ try{const e=await fetch(`${(0,m.getActionServerBaseUrl)()}/api/request-diff-stats?requestId=${encodeURIComponent(s)}`,{signal:AbortSignal.timeout(5e3)})
9157
9170
  if(e.ok){const t=await e.json()
9158
9171
  if(t?.data&&"number"==typeof t.data.additions&&"number"==typeof t.data.deletions&&"number"==typeof t.data.filesChanged){const e=Array.isArray(t.data.files)?t.data.files.filter(e=>"string"==typeof e):void 0,r=Array.isArray(t.data.deleted)?t.data.deleted.filter(e=>"string"==typeof e):void 0
9159
- if(X={additions:t.data.additions,deletions:t.data.deletions,filesChanged:t.data.filesChanged,...e?{files:e}:{},docsOnly:!0===t.data.docsOnly,...r?{deleted:r}:{},..."string"==typeof t.data.branch&&t.data.branch?{branch:t.data.branch}:{}},r&&r.length>0)try{const e=await _.proxyGet(`/api/projects/${S.projectId}/test-processes`),t=Array.isArray(e)?e:Array.isArray(e?.data)?e.data:[],n=await _.getRequestClient().getRequest(o).catch(()=>null),i="string"==typeof n?.exchangeMessageCode&&n.exchangeMessageCode?await(_.getSessionMessageClient()?.getMessage(n.exchangeMessageCode).catch(()=>null)):null
9160
- X.checkLosses=await(0,l.signoffCheckLosses)(r,t,n?{...n,askedAt:i?.createdAt??null}:null)}catch(e){T.warn("checks could not be read; the sign-off card will not name deleted files a check covered",{requestId:o,error:(0,p.getErrorMessage)(e)})}}}}catch(e){T.debug("request-diff-stats fetch failed; review scaling falls back to the full panel",{error:(0,p.getErrorMessage)(e)})}const ee=X?{diffStats:X}:{}
9161
- let te,re=null
9162
- try{const e=await _.proxyGet(`/api/projects/${S.projectId}/requests/${o}/kind`)
9163
- re=(e?.data??e)?.kind??null}catch(e){T.debug("request kind unavailable; kind checks skipped",{error:(0,p.getErrorMessage)(e)})}try{if("writing"===re){const e=await fetch(`${(0,f.getActionServerBaseUrl)()}/api/sibling-overlap?requestId=${encodeURIComponent(o)}`,{signal:AbortSignal.timeout(1e4)}),t=(e.ok?(await e.json())?.data??[]:[]).filter(e=>!g.DOC_PATH_RE.test(e.path))
9172
+ if(re={additions:t.data.additions,deletions:t.data.deletions,filesChanged:t.data.filesChanged,...e?{files:e}:{},docsOnly:!0===t.data.docsOnly,...r?{deleted:r}:{},..."string"==typeof t.data.branch&&t.data.branch?{branch:t.data.branch}:{}},r&&r.length>0)try{const e=await B.proxyGet(`/api/projects/${_.projectId}/test-processes`),t=Array.isArray(e)?e:Array.isArray(e?.data)?e.data:[],n=await B.getRequestClient().getRequest(s).catch(()=>null),i="string"==typeof n?.exchangeMessageCode&&n.exchangeMessageCode?await(B.getSessionMessageClient()?.getMessage(n.exchangeMessageCode).catch(()=>null)):null
9173
+ re.checkLosses=await(0,u.signoffCheckLosses)(r,t,n?{...n,askedAt:i?.createdAt??null}:null)}catch(e){D.warn("checks could not be read; the sign-off card will not name deleted files a check covered",{requestId:s,error:(0,h.getErrorMessage)(e)})}}}}catch(e){D.debug("request-diff-stats fetch failed; review scaling falls back to the full panel",{error:(0,h.getErrorMessage)(e)})}const ne=re?{diffStats:re}:{}
9174
+ let ie,oe=null
9175
+ try{const e=await B.proxyGet(`/api/projects/${_.projectId}/requests/${s}/kind`)
9176
+ oe=(e?.data??e)?.kind??null}catch(e){D.debug("request kind unavailable; kind checks skipped",{error:(0,h.getErrorMessage)(e)})}try{if("writing"===oe){const e=await fetch(`${(0,m.getActionServerBaseUrl)()}/api/sibling-overlap?requestId=${encodeURIComponent(s)}`,{signal:AbortSignal.timeout(1e4)}),t=(e.ok?(await e.json())?.data??[]:[]).filter(e=>!A.DOC_PATH_RE.test(e.path))
9164
9177
  if(t.length>0){const e=t.slice(0,5).map(e=>{const t=e.mine.map(e=>e[0]===e[1]?`line ${e[0]}`:`lines ${e[0]}-${e[1]}`).join(", ")
9165
9178
  return`- ${e.path} (${t}) is also changed by ${e.sibling}${e.siblingTitle?` ("${e.siblingTitle}"`:""}${e.siblingStatus?`, ${e.siblingStatus.replace(/_/g," ")})`:e.siblingTitle?")":""}`}),r=[...new Set(t.map(e=>e.sibling))]
9166
- return{content:[{type:"text",text:`Completion NOT recorded: this is a writing request, and it changes code that a sibling request is changing.\n\n${e.join("\n")}\n\nA writing request documents the product as it is. Revert those changes on your branch. If your text depends on that work, wait on it: request_operations wait-on with waitsOn: ${JSON.stringify(r)}. Do not implement it and do not delete the text that describes it.`}],isError:!0}}}}catch(e){T.debug("sibling-overlap check could not run; completion continues",{error:(0,p.getErrorMessage)(e)})}try{if("drive"===re){const e=function(e,t){const r=Array.isArray(e?.acceptanceCriteria)?e.acceptanceCriteria:[]
9179
+ return{content:[{type:"text",text:`Completion NOT recorded: this is a writing request, and it changes code that a sibling request is changing.\n\n${e.join("\n")}\n\nA writing request documents the product as it is. Revert those changes on your branch. If your text depends on that work, wait on it: request_operations wait-on with waitsOn: ${JSON.stringify(r)}. Do not implement it and do not delete the text that describes it.`}],isError:!0}}}}catch(e){D.debug("sibling-overlap check could not run; completion continues",{error:(0,h.getErrorMessage)(e)})}try{if("drive"===oe){const e=function(e,t){const r=Array.isArray(e?.acceptanceCriteria)?e.acceptanceCriteria:[]
9167
9180
  if(0===r.length)return null
9168
- const i=e=>String(e??"").trim().toLowerCase(),o=new Set((Array.isArray(t?.couldNotCheck)?t.couldNotCheck:[]).map(i).filter(Boolean)),s=new Set((t?.commentedKeys??[]).map(i).filter(Boolean)),a=(e,t)=>e?.id?(0,n.criterionShortKey)(String(e.id),t):"",c=r.filter((e,t)=>{if((0,n.isUserOwnCheck)(e))return!1
9169
- const r=(0,n.latestRow)(e,["driver"])?.verdict
9181
+ const n=e=>String(e??"").trim().toLowerCase(),o=new Set((Array.isArray(t?.couldNotCheck)?t.couldNotCheck:[]).map(n).filter(Boolean)),s=new Set((t?.commentedKeys??[]).map(n).filter(Boolean)),a=(e,t)=>e?.id?(0,i.criterionShortKey)(String(e.id),t):"",c=r.filter((e,t)=>{if((0,i.isUserOwnCheck)(e))return!1
9182
+ const r=(0,i.latestRow)(e,["driver"])?.verdict
9170
9183
  if("met"===r||"unmet"===r||"not-checked"===r)return!1
9171
- const c=i(a(e,t))
9184
+ const c=n(a(e,t))
9172
9185
  return!(c&&o.has(c)&&s.has(c))})
9173
9186
  if(0===c.length)return null
9174
9187
  const l=c.slice(0,8).map(e=>{const t=r.indexOf(e),n=a(e,t),i=String(e?.description??"").trim()
9175
9188
  return` - ${n?`[${n}] `:""}${i.length>120?`${i.slice(0,117)}...`:i}`}).join("\n")
9176
- return`Completion NOT recorded: ${c.length} of this drive's ${r.length} claims ${1===c.length?"has":"have"} no verdict:\n${l}\n\nThe claims are the drive's result, and the card counts them from the stamps. Call record_criteria_verification with a verdict for each claim you checked (VERIFIED stamps met, DIVERGED stamps unmet, with the evidence), then complete again. A claim you could NOT check goes in couldNotCheck on this call, by its key, with the reason in that criterion's criteriaComments entry: it is not a verdict, and it reads as unchecked.`}(await _.getRequestClient().getRequest(o),{couldNotCheck:F,commentedKeys:(j??[]).map(e=>e.key)})
9189
+ return`Completion NOT recorded: ${c.length} of this drive's ${r.length} claims ${1===c.length?"has":"have"} no verdict:\n${l}\n\nThe claims are the drive's result, and the card counts them from the stamps. Call record_criteria_verification with a verdict for each claim you checked (VERIFIED stamps met, DIVERGED stamps unmet, with the evidence), then complete again. A claim you could NOT check goes in couldNotCheck on this call, by its key, with the reason in that criterion's criteriaComments entry: it is not a verdict, and it reads as unchecked.`}(await B.getRequestClient().getRequest(s),{couldNotCheck:q,commentedKeys:(F??[]).map(e=>e.key)})
9177
9190
  if(e)return{content:[{type:"text",text:e}],isError:!0}
9178
- const t=await async function(e,t,r,i){const o=(Array.isArray(r)?r:[]).map(e=>String(e??"").trim().toLowerCase()).filter(Boolean)
9191
+ const t=await async function(e,t,r,n){const o=(Array.isArray(r)?r:[]).map(e=>String(e??"").trim().toLowerCase()).filter(Boolean)
9179
9192
  if(0===o.length)return[]
9180
9193
  const s=await e.getRequest(t),a=Array.isArray(s?.acceptanceCriteria)?s.acceptanceCriteria:[],c=[]
9181
9194
  return a.forEach((e,t)=>{if(!e?.id)return
9182
- const r=(0,n.criterionShortKey)(String(e.id),t).toLowerCase()
9195
+ const r=(0,i.criterionShortKey)(String(e.id),t).toLowerCase()
9183
9196
  if(!o.includes(r))return
9184
- const s=(i??[]).find(e=>String(e?.key??"").trim().toLowerCase()===r)
9185
- c.push({criterionId:String(e.id),reason:s?.comment??null})}),c}(_.getRequestClient(),o,F,j)
9186
- t.length>0&&await _.proxyPost(`/api/projects/${S.projectId}/requests/${o}/drive-not-checked`,{items:t})}}catch(e){T.debug("drive-claim stamp check could not run; completion continues",{error:(0,p.getErrorMessage)(e)})}const ne=[]
9187
- if(j&&j.length>0)try{const e=_.getRequestClient(),t=await e.getRequest(o),r=t?.acceptanceCriteria||[]
9188
- if(T.info("Recording coder criteria comments",{requestId:o,comments:j.length,criteriaFetched:r.length}),0===r.length)return T.error("Criteria comments provided but the request came back with no criteria",{requestId:o}),{content:[{type:"text",text:`Completion NOT recorded: your criteria comments could not be attached because ${o} came back with no acceptance criteria. This is a read failure on our side, not a mistake in your call. Retry complete once; if it repeats, report it to your parent as blocked and say the criteria read is failing.`}],isError:!0}
9189
- {const e=(0,h.buildCriteriaKeyMap)(r),n=[],i=[]
9190
- for(const t of j){const r=e.get(t.key),o=Array.isArray(t.verify)?t.verify.filter(e=>"string"==typeof e&&e.trim()):[]
9197
+ const s=(n??[]).find(e=>String(e?.key??"").trim().toLowerCase()===r)
9198
+ c.push({criterionId:String(e.id),reason:s?.comment??null})}),c}(B.getRequestClient(),s,q,F)
9199
+ t.length>0&&await B.proxyPost(`/api/projects/${_.projectId}/requests/${s}/drive-not-checked`,{items:t})}}catch(e){D.debug("drive-claim stamp check could not run; completion continues",{error:(0,h.getErrorMessage)(e)})}const se=[]
9200
+ if(F&&F.length>0)try{const e=B.getRequestClient(),t=await e.getRequest(s),r=t?.acceptanceCriteria||[]
9201
+ if(D.info("Recording coder criteria comments",{requestId:s,comments:F.length,criteriaFetched:r.length}),0===r.length)return D.error("Criteria comments provided but the request came back with no criteria",{requestId:s}),{content:[{type:"text",text:`Completion NOT recorded: your criteria comments could not be attached because ${s} came back with no acceptance criteria. This is a read failure on our side, not a mistake in your call. Retry complete once; if it repeats, report it to your parent as blocked and say the criteria read is failing.`}],isError:!0}
9202
+ {const e=(0,f.buildCriteriaKeyMap)(r),n=[],i=[]
9203
+ for(const t of F){const r=e.get(t.key),o=Array.isArray(t.verify)?t.verify.filter(e=>"string"==typeof e&&e.trim()):[]
9191
9204
  if(r&&o.length>0&&"subjective"===r.kind){n.push({criterionId:r.id,evidence:`${t.comment}\n\nWhat I ran (a judgment decides this criterion, not these): ${o.join(" ; ")}`})
9192
9205
  continue}const s=o.length&&Array.isArray(t.testFiles)?t.testFiles.filter(e=>"string"==typeof e&&e.trim()).map(e=>e.trim().replace(/^\.\//,"")):[]
9193
- r?n.push({criterionId:r.id,evidence:t.comment,...o.length?{verify:o}:{},...s.length?{testFiles:s}:{}}):i.push(t.key)}if(i.length>0)return T.warn("criteriaComments keys did not match any criterion",{requestId:o,unmatched:i}),{content:[{type:"text",text:`Completion NOT recorded: ${i.length} criteriaComments key(s) match no criterion on ${o}: ${i.map(e=>JSON.stringify(String(e).slice(0,60))).join(", ")}.\n\nA key is the short code before each criterion, such as "1p": just those characters. The keys on ${o}:\n${(0,h.criteriaKeyChoices)(r)}\n\nRun complete again with these keys. Nothing was recorded.`}],isError:!0}
9194
- const s=[]
9195
- for(const e of n.flatMap(e=>e.verify??[])){const t=(0,m.gitVerifyProblem)(e)??(S.git?await(0,m.nodeCheckProblem)(S.git,`requests/${o}`,e):null)
9196
- t&&s.push(` ${e}\n -> ${t}`)}if(S.git)for(const e of[...new Set(n.flatMap(e=>e.testFiles??[]))])await S.git.raw(["cat-file","-e",`requests/${o}:${e}`]).then(()=>!0,()=>!1)||s.push(` testFiles: ${e}\n -> no such file on ${o}'s branch; name the test files your verify commands run, as repo paths`)
9197
- if(s.length>0)return T.warn("verify commands that cannot prove anything; completion refused",{requestId:o,count:s.length}),{content:[{type:"text",text:`Completion NOT recorded: ${s.length} verify command(s) or test file(s) can never prove their criterion.\n\n${s.join("\n")}\n\nReplace them and complete again. Nothing was recorded.`}],isError:!0}
9206
+ r?n.push({criterionId:r.id,evidence:t.comment,...o.length?{verify:o}:{},...s.length?{testFiles:s}:{}}):i.push(t.key)}if(i.length>0)return D.warn("criteriaComments keys did not match any criterion",{requestId:s,unmatched:i}),{content:[{type:"text",text:`Completion NOT recorded: ${i.length} criteriaComments key(s) match no criterion on ${s}: ${i.map(e=>JSON.stringify(String(e).slice(0,60))).join(", ")}.\n\nA key is the short code before each criterion, such as "1p": just those characters. The keys on ${s}:\n${(0,f.criteriaKeyChoices)(r)}\n\nRun complete again with these keys. Nothing was recorded.`}],isError:!0}
9207
+ const o=[]
9208
+ for(const e of n.flatMap(e=>e.verify??[])){const t=(0,g.gitVerifyProblem)(e)??(_.git?await(0,g.nodeCheckProblem)(_.git,`requests/${s}`,e):null)
9209
+ t&&o.push(` ${e}\n -> ${t}`)}if(_.git)for(const e of[...new Set(n.flatMap(e=>e.testFiles??[]))])await _.git.raw(["cat-file","-e",`requests/${s}:${e}`]).then(()=>!0,()=>!1)||o.push(` testFiles: ${e}\n -> no such file on ${s}'s branch; name the test files your verify commands run, as repo paths`)
9210
+ if(o.length>0)return D.warn("verify commands that cannot prove anything; completion refused",{requestId:s,count:o.length}),{content:[{type:"text",text:`Completion NOT recorded: ${o.length} verify command(s) or test file(s) can never prove their criterion.\n\n${o.join("\n")}\n\nReplace them and complete again. Nothing was recorded.`}],isError:!0}
9198
9211
  const a=e=>{const t=r.find(t=>t?.id===e.criterionId)
9199
- if((0,E.hasBaseline)(t))return[]
9212
+ if((0,C.hasBaseline)(t))return[]
9200
9213
  const n="string"==typeof t?.command&&t.command.trim()?[t.command.trim()]:[],i=t?.standing&&Array.isArray(t.verify)?t.verify.filter(e=>"string"==typeof e&&!!e.trim()):n
9201
9214
  return e.verify?.length?[...i,...e.verify.filter(e=>!i.includes(e))]:[]},c=[...new Set(n.flatMap(a))]
9202
9215
  let l=null
9203
- const u=function(e,t,r){return!(!t||0===r.length)&&r.every(r=>(Array.isArray(e)?e:[]).some(e=>(Array.isArray(e?.ledger)?e.ledger:[]).some(e=>"verify"===e?.source&&"met"===e?.verdict&&e?.ran===r&&"string"==typeof e?.commit&&!!e.commit&&(e.commit.startsWith(t)||t.startsWith(e.commit)))))}(r,Y,c)
9204
- u&&T.info("verify preflight: every proof already passed on this head; not run again",{requestId:o,sha:Y})
9205
- const d=(0,k.checksOff)(t)
9206
- if(d&&c.length>0&&T.info("verify preflight: test gate off, the worker's proofs stand as its claims; not replayed",{requestId:o,commands:c.length}),c.length>0&&!u&&!d)try{const e=await fetch(`${(0,f.getActionServerBaseUrl)()}/api/verify-preflight`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({requestId:o,commands:c,background:!0,proofs:n.flatMap(e=>a(e).map(t=>({criterionId:e.criterionId,command:t})))}),signal:AbortSignal.timeout(18e4)}),t=e.ok?await e.json():null,r=t?.data
9207
- r?.pending&&T.info("verify preflight started beside the sign-off check; the merge waits for it",{requestId:o,sha:r.sha,commands:c.length}),r?.available&&Array.isArray(r.results)&&(l=r),r?.available&&(r.dropped??0)>0&&T.warn("verify preflight dropped commands over the ceiling",{requestId:o,dropped:r.dropped})
9208
- const i=new Set(n.flatMap(e=>e.verify??[])),s=r?.available&&Array.isArray(r.results)?r.results.filter(e=>!e.ok&&!0===e.unparsed&&i.has(e.command)):[]
9209
- if(s.length>0)return{content:[{type:"text",text:`Completion NOT recorded: ${s.length} of your own verify command(s) do not parse in the shell, so they never ran and prove nothing:\n\n`+s.map(e=>` ${e.command}\n -> ${e.output?e.output.split("\n")[0]:"syntax error"}`).join("\n")+"\n\nFix the command (quoting, backquotes, parentheses), then complete again. Nothing was recorded."}],isError:!0}
9216
+ const u=function(e,t,r){return!(!t||0===r.length)&&r.every(r=>(Array.isArray(e)?e:[]).some(e=>(Array.isArray(e?.ledger)?e.ledger:[]).some(e=>"verify"===e?.source&&"met"===e?.verdict&&e?.ran===r&&"string"==typeof e?.commit&&!!e.commit&&(e.commit.startsWith(t)||t.startsWith(e.commit)))))}(r,X,c)
9217
+ u&&D.info("verify preflight: every proof already passed on this head; not run again",{requestId:s,sha:X})
9218
+ const d=(0,x.checksOff)(t)
9219
+ if(d&&c.length>0&&D.info("verify preflight: test gate off, the worker's proofs stand as its claims; not replayed",{requestId:s,commands:c.length}),c.length>0&&!u&&!d)try{const e=await fetch(`${(0,m.getActionServerBaseUrl)()}/api/verify-preflight`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({requestId:s,commands:c,background:!0,proofs:n.flatMap(e=>a(e).map(t=>({criterionId:e.criterionId,command:t})))}),signal:AbortSignal.timeout(18e4)}),t=e.ok?await e.json():null,r=t?.data
9220
+ r?.pending&&D.info("verify preflight started beside the sign-off check; the merge waits for it",{requestId:s,sha:r.sha,commands:c.length}),r?.available&&Array.isArray(r.results)&&(l=r),r?.available&&(r.dropped??0)>0&&D.warn("verify preflight dropped commands over the ceiling",{requestId:s,dropped:r.dropped})
9221
+ const i=new Set(n.flatMap(e=>e.verify??[])),o=r?.available&&Array.isArray(r.results)?r.results.filter(e=>!e.ok&&!0===e.unparsed&&i.has(e.command)):[]
9222
+ if(o.length>0)return{content:[{type:"text",text:`Completion NOT recorded: ${o.length} of your own verify command(s) do not parse in the shell, so they never ran and prove nothing:\n\n`+o.map(e=>` ${e.command}\n -> ${e.output?e.output.split("\n")[0]:"syntax error"}`).join("\n")+"\n\nFix the command (quoting, backquotes, parentheses), then complete again. Nothing was recorded."}],isError:!0}
9210
9223
  const u=r?.available&&Array.isArray(r.results)?r.results.filter(e=>!e.ok&&(e.timedOut||e.couldNotRun)):[]
9211
- u.length>0&&T.info("verify preflight: some proofs did not finish or could not run here; the gate decides them",{requestId:o,count:u.length,sha:r?.sha})
9224
+ u.length>0&&D.info("verify preflight: some proofs did not finish or could not run here; the gate decides them",{requestId:s,count:u.length,sha:r?.sha})
9212
9225
  const d=r?.available&&Array.isArray(r.results)?r.results.filter(e=>!e.ok&&!e.timedOut&&!e.couldNotRun):[],p=d.filter(e=>!1!==(e.authoritative??r?.authoritative))
9213
- if(d.length>0&&0===p.length&&T.warn("verify preflight failed but the gate environment is ambiguous; completing anyway",{requestId:o,failed:d.length,sha:r?.sha}),p.length>0){T.warn("verify preflight failed; completion refused",{requestId:o,failed:p.length,sha:r?.sha})
9226
+ if(d.length>0&&0===p.length&&D.warn("verify preflight failed but the gate environment is ambiguous; completing anyway",{requestId:s,failed:d.length,sha:r?.sha}),p.length>0){D.warn("verify preflight failed; completion refused",{requestId:s,failed:p.length,sha:r?.sha})
9214
9227
  const e=p.map(e=>` ${e.command}\n -> exit ${e.exit}${e.timedOut?" (timed out)":""}${e.output?`: ${e.output.split("\n")[0]}`:""}`).join("\n")
9215
- return{content:[{type:"text",text:`Completion NOT recorded: ${p.length} of the criteria's verify command(s) (a criterion's own command, any a guidance check put there, and those you attached) FAIL on the tree you are completing (${String(r?.sha??"").slice(0,8)}).\n\n`+e+((r?.dropped??0)>0?`\n\n(${r.dropped} further command(s) were over the 20-command ceiling and were not run.)`:"")+"\n\nThis is the box your checks are judged in, so this is exactly what the sign-off card would show. Two things cause it: the check is wrong (it pins something you changed after writing it, such as a renamed local, or it matches by accident, such as a word found inside a longer word), or the code genuinely does not do what the criterion promises.\n\nFix whichever is wrong -- adjust a command you attached to grep the observable thing (an id, a name, an exported symbol), or fix the code -- then complete again. A criterion's own command that cannot pass where it runs: replace it by its key with request_operations update, with a line that checks what the criterion says, never less (the replacement is recorded with the line it replaced). The user's own command is not yours to replace. Nothing was recorded."}],isError:!0}}}catch(e){T.debug("verify preflight unavailable; completing without it",{requestId:o,error:(0,p.getErrorMessage)(e)})}if(n.length>0&&(await _.proxyPost(`/api/projects/${S.projectId}/requests/${o}/verify-criteria`,{verifications:n,dimension:"coder"}),ne.push(...n.map(e=>e.criterionId))),l?.sha&&l.results?.length){const e=n.flatMap(e=>a(e).flatMap(t=>{const r=l.results.find(e=>e.command===t)
9228
+ return{content:[{type:"text",text:`Completion NOT recorded: ${p.length} of the criteria's verify command(s) (a criterion's own command, any a guidance check put there, and those you attached) FAIL on the tree you are completing (${String(r?.sha??"").slice(0,8)}).\n\n`+e+((r?.dropped??0)>0?`\n\n(${r.dropped} further command(s) were over the 20-command ceiling and were not run.)`:"")+"\n\nThis is the box your checks are judged in, so this is exactly what the sign-off card would show. Two things cause it: the check is wrong (it pins something you changed after writing it, such as a renamed local, or it matches by accident, such as a word found inside a longer word), or the code genuinely does not do what the criterion promises.\n\nFix whichever is wrong -- adjust a command you attached to grep the observable thing (an id, a name, an exported symbol), or fix the code -- then complete again. A criterion's own command that cannot pass where it runs: replace it by its key with request_operations update, with a line that checks what the criterion says, never less (the replacement is recorded with the line it replaced). The user's own command is not yours to replace. Nothing was recorded."}],isError:!0}}}catch(e){D.debug("verify preflight unavailable; completing without it",{requestId:s,error:(0,h.getErrorMessage)(e)})}if(n.length>0&&(await B.proxyPost(`/api/projects/${_.projectId}/requests/${s}/verify-criteria`,{verifications:n,dimension:"coder"}),se.push(...n.map(e=>e.criterionId))),l?.sha&&l.results?.length){const e=n.flatMap(e=>a(e).flatMap(t=>{const r=l.results.find(e=>e.command===t)
9216
9229
  return r?[{criterionId:e.criterionId,command:t,ok:r.ok,..."number"==typeof r.exit?{exit:r.exit}:{},...r.checkedNothing?{checkedNothing:r.checkedNothing}:{},...r.timedOut?{timedOut:!0}:{},...r.couldNotRun?{couldNotRun:!0}:{},output:String(r.output??"").split("\n")[0].slice(0,200)}]:[]}))
9217
- e.length>0&&await _.proxyPost(`/api/projects/${S.projectId}/requests/${o}/proof-evidence`,{commitSha:l.sha,tree:l.tree??null,proofs:e}).catch(e=>T.warn("the preflight's results were not recorded as evidence (the gate runs the proofs again)",{requestId:o,error:(0,p.getErrorMessage)(e)}))}}}catch(e){return T.error("Failed to persist coder criteria comments",{requestId:o,error:(0,p.getErrorMessage)(e)}),{content:[{type:"text",text:`Completion NOT recorded: your criteria comments could not be saved (${(0,p.getErrorMessage)(e)}). Retry complete -- without them, objective criteria stay unproven and completion is refused.`}],isError:!0}}if(S.git){try{const e=await(0,a.findCommittedMarkers)(S.git,`requests/${o}`)
9218
- if(e.determined&&e.files.length>0)return T.warn("completion refused: committed conflict markers on the branch",{requestId:o,files:e.files}),D({success:!1,reason:"unresolved_markers"}),{content:[{type:"text",text:`Completion NOT recorded: ${o}'s branch still contains committed conflict markers in ${e.files.join(", ")}.\n\nThe system merged the default branch into yours and committed the conflicted state so you could see it; those <<<<<<< / ======= / >>>>>>> lines are plain file content now. Resolve every block with your file tools, re-run your checks, then complete again.\n\nStopping here on purpose: a reviewer would only tell you the same thing a minute later.`}],isError:!0}}catch(e){T.warn("conflict-marker precheck could not run; leaving it to the merge gate",{requestId:o,error:(0,p.getErrorMessage)(e)})}if(!x.has(o))try{const e=await(0,u.scanStaleProse)(S.git,`requests/${o}`),t=(0,u.renderStaleProse)(e)
9219
- if(t)return x.add(o),T.info("completion paused once: prose the branch made stale",{requestId:o,gone:e.goneReferences.length,previewUrls:e.previewUrls.length}),D({success:!1,reason:"stale_prose_shown"}),{content:[{type:"text",text:`Completion paused for one look (${o}): your change may have made prose in the repository untrue.\n\n${t}\n\nFix what is wrong (READMEs and entry points are part of the work), then complete again. If a line is fine as it is, completing again goes straight through; this notice is shown once.`}],isError:!0}}catch(e){T.warn("stale-prose scan could not run; completing without it",{requestId:o,error:(0,p.getErrorMessage)(e)})}}let ie,oe,se=null
9220
- try{const e=await _.getRequestClient().getRequest(o),t=Array.isArray(e?.acceptanceCriteria)?e.acceptanceCriteria:[],r=t.map(e=>(0,n.latestRow)(e,["reviewer"])).filter(e=>!(!e||"met"!==e.verdict&&"unmet"!==e.verdict||"string"!=typeof e.commit||!e.commit)).sort((e,t)=>String(t.at??"").localeCompare(String(e.at??"")))[0],i=S.mainBasePath?await(0,w.resolveDefaultBranch)(S.mainBasePath):null,s=`refs/heads/requests/${o}`
9221
- if(r&&S.mainBasePath&&i&&(se=await(0,A.measureChangesSinceReview)(S.mainBasePath,{branchRef:s,reviewedSha:r.commit,defaultBranch:i,acceptedSha:"string"==typeof e?.diffSummary?.reviewAcceptedSha?e.diffSummary.reviewAcceptedSha:null}),se&&(ie=se.authoredCommits)),Y&&S.mainBasePath&&i){const e=new Set
9222
- for(const r of t){const t=(0,n.latestRow)(r,[(0,n.isUserOwnCheck)(r)?"you":"reviewer"])
9223
- "met"===t?.verdict&&"string"==typeof t.commit&&t.commit&&t.commit!==Y&&e.add(t.commit)}oe=[]
9224
- for(const t of[...e].slice(0,6)){const e=await(0,A.measureChangesSinceReview)(S.mainBasePath,{branchRef:s,reviewedSha:t,defaultBranch:i}).catch(()=>null)
9225
- oe.push({sha:t,since:e?e.newLines+e.droppedLines>0?"changed":"same":"unknown"})}}}catch(e){T.debug("changes-since-review measure unavailable; the server keeps the review as it is",{error:(0,p.getErrorMessage)(e)})}if(X&&X.filesChanged>0)try{const e=await _.getRequestClient().getRequest(o)
9226
- if("merge"===(0,b.kindProfile)(e?.type).finalize){const t=await(0,v.closeOneTask)(_.getTaskClient(),{projectId:S.projectId,requestId:o,title:String(e?.title||o),summary:G})
9227
- t&&((0,I.clearActiveTaskCache)(),T.info("work in one piece: the request's one task closed",{requestId:o,taskId:t}))}}catch(e){T.warn("could not record the one task; completion continues as before",{requestId:o,error:(0,p.getErrorMessage)(e)})}const ae=X?.files??[]
9228
- let ce,le=null
9229
- const ue=async(e,r)=>(await Promise.resolve().then(()=>t(79414))).actionServerFetch(e,r)
9230
- if(V){const e="string"==typeof H?.sitting?.head?H.sitting.head:""
9231
- let t=e?null:X
9232
- if(e)try{const r=await fetch(`${(0,f.getActionServerBaseUrl)()}/api/request-diff-stats?requestId=${encodeURIComponent(o)}&since=${encodeURIComponent(e)}`,{signal:AbortSignal.timeout(5e3)}),n=r.ok?await r.json():null
9233
- n?.data&&(t={files:Array.isArray(n.data.files)?n.data.files.filter(e=>"string"==typeof e):[],additions:n.data.additions,deletions:n.data.deletions})}catch(e){T.debug("the change's own diff could not be read; it is recorded without its files",{requestId:o,error:(0,p.getErrorMessage)(e)})}le={...O?{account:O}:{},files:t?.files??[],..."number"==typeof t?.additions?{additions:t.additions}:{},..."number"==typeof t?.deletions?{deletions:t.deletions}:{}},ce=await(0,C.lookNow)(o,ae,ue).catch(()=>{})}const de=V&&("settle"===(0,b.kindProfile)(re??H?.type).finalize||!!X&&0===X.filesChanged&&0===X.additions&&0===X.deletions),pe=async e=>{const t=await _.proxyPost(`/api/projects/${S.projectId}/requests/${o}/validate-completion`,(0,y.validateCompletionBody)("tool",{sessionId:Z,skipTestsReason:M,skipTests:P,headSha:Y,...de?{diffNone:!0}:{},...le?{change:le}:{},...e?{look:e}:{},...void 0!==ie?{authoredSinceReview:ie}:{},...se?{reviewDelta:se}:{},...oe?{intentMoves:oe}:{},...X?.files?{files:X.files}:{}}))
9230
+ e.length>0&&await B.proxyPost(`/api/projects/${_.projectId}/requests/${s}/proof-evidence`,{commitSha:l.sha,tree:l.tree??null,proofs:e}).catch(e=>D.warn("the preflight's results were not recorded as evidence (the gate runs the proofs again)",{requestId:s,error:(0,h.getErrorMessage)(e)}))}}}catch(e){return D.error("Failed to persist coder criteria comments",{requestId:s,error:(0,h.getErrorMessage)(e)}),{content:[{type:"text",text:`Completion NOT recorded: your criteria comments could not be saved (${(0,h.getErrorMessage)(e)}). Retry complete -- without them, objective criteria stay unproven and completion is refused.`}],isError:!0}}if(_.git){try{const e=await(0,c.findCommittedMarkers)(_.git,`requests/${s}`)
9231
+ if(e.determined&&e.files.length>0)return D.warn("completion refused: committed conflict markers on the branch",{requestId:s,files:e.files}),O({success:!1,reason:"unresolved_markers"}),{content:[{type:"text",text:`Completion NOT recorded: ${s}'s branch still contains committed conflict markers in ${e.files.join(", ")}.\n\nThe system merged the default branch into yours and committed the conflicted state so you could see it; those <<<<<<< / ======= / >>>>>>> lines are plain file content now. Resolve every block with your file tools, re-run your checks, then complete again.\n\nStopping here on purpose: a reviewer would only tell you the same thing a minute later.`}],isError:!0}}catch(e){D.warn("conflict-marker precheck could not run; leaving it to the merge gate",{requestId:s,error:(0,h.getErrorMessage)(e)})}if(!S.has(s))try{const e=await(0,d.scanStaleProse)(_.git,`requests/${s}`),t=(0,d.renderStaleProse)(e)
9232
+ if(t)return S.add(s),D.info("completion paused once: prose the branch made stale",{requestId:s,gone:e.goneReferences.length,previewUrls:e.previewUrls.length}),O({success:!1,reason:"stale_prose_shown"}),{content:[{type:"text",text:`Completion paused for one look (${s}): your change may have made prose in the repository untrue.\n\n${t}\n\nFix what is wrong (READMEs and entry points are part of the work), then complete again. If a line is fine as it is, completing again goes straight through; this notice is shown once.`}],isError:!0}}catch(e){D.warn("stale-prose scan could not run; completing without it",{requestId:s,error:(0,h.getErrorMessage)(e)})}}let ae,ce,le=null
9233
+ try{const e=await B.getRequestClient().getRequest(s),t=Array.isArray(e?.acceptanceCriteria)?e.acceptanceCriteria:[],r=t.map(e=>(0,i.latestRow)(e,["reviewer"])).filter(e=>!(!e||"met"!==e.verdict&&"unmet"!==e.verdict||"string"!=typeof e.commit||!e.commit)).sort((e,t)=>String(t.at??"").localeCompare(String(e.at??"")))[0],n=_.mainBasePath?await(0,b.resolveDefaultBranch)(_.mainBasePath):null,o=`refs/heads/requests/${s}`
9234
+ if(r&&_.mainBasePath&&n&&(le=await(0,y.measureChangesSinceReview)(_.mainBasePath,{branchRef:o,reviewedSha:r.commit,defaultBranch:n,acceptedSha:"string"==typeof e?.diffSummary?.reviewAcceptedSha?e.diffSummary.reviewAcceptedSha:null}),le&&(ae=le.authoredCommits)),X&&_.mainBasePath&&n){const e=new Set
9235
+ for(const r of t){const t=(0,i.latestRow)(r,[(0,i.isUserOwnCheck)(r)?"you":"reviewer"])
9236
+ "met"===t?.verdict&&"string"==typeof t.commit&&t.commit&&t.commit!==X&&e.add(t.commit)}ce=[]
9237
+ for(const t of[...e].slice(0,6)){const e=await(0,y.measureChangesSinceReview)(_.mainBasePath,{branchRef:o,reviewedSha:t,defaultBranch:n}).catch(()=>null)
9238
+ ce.push({sha:t,since:e?e.newLines+e.droppedLines>0?"changed":"same":"unknown"})}}}catch(e){D.debug("changes-since-review measure unavailable; the server keeps the review as it is",{error:(0,h.getErrorMessage)(e)})}if(re&&re.filesChanged>0)try{const e=await B.getRequestClient().getRequest(s)
9239
+ if("merge"===(0,v.kindProfile)(e?.type).finalize){const t=await(0,I.closeOneTask)(B.getTaskClient(),{projectId:_.projectId,requestId:s,title:String(e?.title||s),summary:Y})
9240
+ t&&((0,E.clearActiveTaskCache)(),D.info("work in one piece: the request's one task closed",{requestId:s,taskId:t}))}}catch(e){D.warn("could not record the one task; completion continues as before",{requestId:s,error:(0,h.getErrorMessage)(e)})}const ue=re?.files??[]
9241
+ let de,pe=null
9242
+ const he=async(e,r)=>(await Promise.resolve().then(()=>t(79414))).actionServerFetch(e,r)
9243
+ if(W){const e="string"==typeof Z?.sitting?.head?Z.sitting.head:""
9244
+ let t=e?null:re
9245
+ if(e)try{const r=await fetch(`${(0,m.getActionServerBaseUrl)()}/api/request-diff-stats?requestId=${encodeURIComponent(s)}&since=${encodeURIComponent(e)}`,{signal:AbortSignal.timeout(5e3)}),n=r.ok?await r.json():null
9246
+ n?.data&&(t={files:Array.isArray(n.data.files)?n.data.files.filter(e=>"string"==typeof e):[],additions:n.data.additions,deletions:n.data.deletions})}catch(e){D.debug("the change's own diff could not be read; it is recorded without its files",{requestId:s,error:(0,h.getErrorMessage)(e)})}pe={...N?{account:N}:{},files:t?.files??[],..."number"==typeof t?.additions?{additions:t.additions}:{},..."number"==typeof t?.deletions?{deletions:t.deletions}:{}},de=await(0,k.lookNow)(s,ue,he).catch(()=>{})}const fe=W&&("settle"===(0,v.kindProfile)(oe??Z?.type).finalize||!!re&&0===re.filesChanged&&0===re.additions&&0===re.deletions),me=async e=>{const t=await B.proxyPost(`/api/projects/${_.projectId}/requests/${s}/validate-completion`,(0,w.validateCompletionBody)("tool",{sessionId:J,skipTestsReason:P,skipTests:j,headSha:X,...fe?{diffNone:!0}:{},...pe?{change:pe}:{},...e?{look:e}:{},...void 0!==ae?{authoredSinceReview:ae}:{},...le?{reviewDelta:le}:{},...ce?{intentMoves:ce}:{},...re?.files?{files:re.files}:{}}))
9234
9247
  return t?.data||t}
9235
- let he=null
9236
- try{if(te=await pe(ce),te.canComplete&&"sitting-look"===te.plan?.action){const e=await(0,C.settleLook)(o,ae,ue)
9237
- he=e.served,te=await pe(e.look)}}catch(e){const t=(0,p.getErrorMessage)(e)
9238
- return T.error("Server validation failed",{requestId:o,error:t}),D({success:!1,error:"validation_request_failed"}),{content:[{type:"text",text:`Failed to validate completion: ${t}`}],isError:!0}}if(!te.canComplete){let e=`Cannot complete request ${o}: ${te.reason}\n`
9239
- if(te.blockedByTasks&&te.blockedByTasks.length>0&&(e+="\n**Tasks that must be completed or canceled first:**\n",te.blockedByTasks.forEach((t,r)=>{e+=`${r+1}. **${t.taskId}** - ${t.title} (Status: ${t.status})\n`})),te.lifoViolation&&(e+="\n**Requests that must be re-completed first (LIFO order):**\n",te.lifoViolation.blockingRequests.forEach((t,r)=>{e+=`${r+1}. **${t.requestId}** - ${t.title} (Status: ${t.status})\n`})),ne.length>0)try{const t=await _.getRequestClient().getRequest(o)
9240
- e+=`\n${function(e,t){const r=(Array.isArray(e)?e:[]).map((e,t)=>({c:e,i:t})).filter(({c:e})=>e?.id&&t.includes(String(e.id))).map(({c:e,i:t})=>{const r=Array.isArray(e.verify)?e.verify.filter(e=>"string"==typeof e):[],i=(0,n.latestRow)(e,["verify","check"]),o=i?"met"===i.verdict?"passed":"unmet"===i.verdict?"failed":i.verdict:"not run yet"
9241
- return`- [${(0,n.criterionShortKey)(String(e.id),t)}] ${r.length?`check now: ${r.map(e=>`\`${e.slice(0,120)}\``).join("; ")} (newest result: ${o})`:"no check of its own"}`})
9242
- return 0===r.length?"":`Recorded all the same, as the request reads now: your account on these criteria, and their checks as they stand:\n${r.join("\n")}\nOnly the completion was refused; say so if you tell the user.`}(t?.acceptanceCriteria,ne)}`}catch{}return D({success:!1,reason:"server_blocked"}),{content:[{type:"text",text:e}],isError:!0}}if("sitting-held"===te.plan?.action)return T.info("completion held: the round stays open while the user is on its page",{requestId:o,until:te.plan.detail,look:ce,preview:he}),await(0,v.reopenOneTask)(_.getTaskClient(),o).then(()=>(0,I.clearActiveTaskCache)(),e=>T.warn("the round's one task could not be opened again; its worker opens one for its next edit",{requestId:o,error:(0,p.getErrorMessage)(e)})),D({success:!0}),{content:[{type:"text",text:[`The change is recorded on ${o}, and the round stays open: the user is on its page and may ask for more. Nothing is checked or completed yet. That happens once, for everything this round changed, when they go quiet, leave the page or press Complete, and you are told then.`,"Between changes they look at what this round made: a preview of your working copy when it serves a page, and the pictures and documents it changed, as committed. What they see must be current, so after every change run the build or the script that makes it.","When they ask for another change on this round, make it and call complete with `change`: one line saying what that change did. That records it under their words and loads what they are looking at again. Do not call merge. End your turn with that one line."].join("\n")}]}
9243
- if(V&&!s)try{const e=await _.proxyGet(`/api/projects/${S.projectId}/requests/${encodeURIComponent(o)}/rounds`)
9244
- G=function(e,t){const r=[]
9248
+ let ge=null
9249
+ try{if(ie=await me(de),ie.canComplete&&"sitting-look"===ie.plan?.action){const e=await(0,k.settleLook)(s,ue,he)
9250
+ ge=e.served,ie=await me(e.look)}}catch(e){const t=(0,h.getErrorMessage)(e)
9251
+ return D.error("Server validation failed",{requestId:s,error:t}),O({success:!1,error:"validation_request_failed"}),{content:[{type:"text",text:`Failed to validate completion: ${t}`}],isError:!0}}if(!ie.canComplete){let e=`Cannot complete request ${s}: ${ie.reason}\n`
9252
+ if(ie.blockedByTasks&&ie.blockedByTasks.length>0&&(e+="\n**Tasks that must be completed or canceled first:**\n",ie.blockedByTasks.forEach((t,r)=>{e+=`${r+1}. **${t.taskId}** - ${t.title} (Status: ${t.status})\n`})),ie.lifoViolation&&(e+="\n**Requests that must be re-completed first (LIFO order):**\n",ie.lifoViolation.blockingRequests.forEach((t,r)=>{e+=`${r+1}. **${t.requestId}** - ${t.title} (Status: ${t.status})\n`})),se.length>0)try{const t=await B.getRequestClient().getRequest(s)
9253
+ e+=`\n${function(e,t){const r=(Array.isArray(e)?e:[]).map((e,t)=>({c:e,i:t})).filter(({c:e})=>e?.id&&t.includes(String(e.id))).map(({c:e,i:t})=>{const r=Array.isArray(e.verify)?e.verify.filter(e=>"string"==typeof e):[],n=(0,i.latestRow)(e,["verify","check"]),o=n?"met"===n.verdict?"passed":"unmet"===n.verdict?"failed":n.verdict:"not run yet"
9254
+ return`- [${(0,i.criterionShortKey)(String(e.id),t)}] ${r.length?`check now: ${r.map(e=>`\`${e.slice(0,120)}\``).join("; ")} (newest result: ${o})`:"no check of its own"}`})
9255
+ return 0===r.length?"":`Recorded all the same, as the request reads now: your account on these criteria, and their checks as they stand:\n${r.join("\n")}\nOnly the completion was refused; say so if you tell the user.`}(t?.acceptanceCriteria,se)}`}catch{}return O({success:!1,reason:"server_blocked"}),{content:[{type:"text",text:e}],isError:!0}}if("sitting-held"===ie.plan?.action)return D.info("completion held: the round stays open while the user is on its page",{requestId:s,until:ie.plan.detail,look:de,preview:ge}),await(0,I.reopenOneTask)(B.getTaskClient(),s).then(()=>(0,E.clearActiveTaskCache)(),e=>D.warn("the round's one task could not be opened again; its worker opens one for its next edit",{requestId:s,error:(0,h.getErrorMessage)(e)})),O({success:!0}),{content:[{type:"text",text:[`The change is recorded on ${s}, and the round stays open: the user is on its page and may ask for more. Nothing is checked or completed yet. That happens once, for everything this round changed, when they go quiet, leave the page or press Complete, and you are told then.`,"Between changes they look at what this round made: a preview of your working copy when it serves a page, and the pictures and documents it changed, as committed. What they see must be current, so after every change run the build or the script that makes it.","When they ask for another change on this round, make it and call complete with `change`: one line saying what that change did. That records it under their words and loads what they are looking at again. Do not call merge. End your turn with that one line."].join("\n")}]}
9256
+ if(W&&!a)try{const e=await B.proxyGet(`/api/projects/${_.projectId}/requests/${encodeURIComponent(s)}/rounds`)
9257
+ Y=function(e,t){const r=[]
9245
9258
  for(const t of Array.isArray(e)?e:[]){const e="string"==typeof t?.changed?t.changed.trim():""
9246
9259
  e&&!r.includes(e)&&r.push(e)}const n=(t??"").replace(/\s+/g," ").trim()
9247
- return n&&!r.includes(n)&&r.push(n),r.join("\n")}(((e?.data??e)?.rounds??[]).find(e=>e?.id===o)?.changes,O)||G}catch(e){T.debug("the round's changes could not be read for its summary; this change's line stands for it",{requestId:o,error:(0,p.getErrorMessage)(e)})}const fe=(0,C.showGiven)(z),me=te.plan,ge="answer"===me?.action
9248
- ge||await async function(e,t,r,n){try{const i=await e.getRequestClient().getRequest(t),o=function(e,t){return(Array.isArray(e)?e:[]).flatMap(e=>{if(!e?.id||!(0,E.hasBaseline)(e)||e.retired||e.standing&&e.waived)return[]
9260
+ return n&&!r.includes(n)&&r.push(n),r.join("\n")}(((e?.data??e)?.rounds??[]).find(e=>e?.id===s)?.changes,N)||Y}catch(e){D.debug("the round's changes could not be read for its summary; this change's line stands for it",{requestId:s,error:(0,h.getErrorMessage)(e)})}const Ae=(0,k.showGiven)(G),ye=ie.plan,we="answer"===ye?.action
9261
+ we||await async function(e,t,r,n){try{const i=await e.getRequestClient().getRequest(t),o=function(e,t){return(Array.isArray(e)?e:[]).flatMap(e=>{if(!e?.id||!(0,C.hasBaseline)(e)||e.retired||e.standing&&e.waived)return[]
9249
9262
  const r="string"==typeof e.command?e.command.trim():""
9250
9263
  if(!r)return[]
9251
9264
  const n=Array.isArray(e.ledger)?e.ledger:[]
9252
9265
  return t&&n.some(e=>"verify"===e?.source&&"string"==typeof e?.instrument&&e.instrument.startsWith("baseline")&&("met"===e.verdict||"unmet"===e.verdict)&&"string"==typeof e.commit&&!!e.commit&&(e.commit.startsWith(t)||t.startsWith(e.commit)))?[]:[{criterionId:String(e.id),command:r,baseline:e.baseline}]})}(i?.acceptanceCriteria??[],r)
9253
9266
  if(0===o.length)return
9254
- const s=await fetch(`${(0,f.getActionServerBaseUrl)()}/api/baseline-measure`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({requestId:t,baselines:o}),signal:AbortSignal.timeout(2e4)})
9255
- n.info("baseline measurement started beside the sign-off check; the merge waits for it",{requestId:t,baselines:o.length,ok:s.ok})}catch(e){n.debug("baseline measurement could not start; the card says it was not measured",{requestId:t,error:(0,p.getErrorMessage)(e)})}}(_,o,Y,T)
9256
- const Ae=me&&fe.artifact?await _.getRequestClient().getRequest(o).then(e=>String(e?.title??"")).catch(()=>""):"",ye=me&&!ge?await(0,C.startShowing)({projectId:S.projectId,post:(e,t)=>_.proxyPost(e,t),log:T,sessionExternalId:Z??void 0,assetThere:e=>(0,C.assetIsThere)(S.basePath,e),resultsOf:e=>_.proxyGet(`/api/projects/${S.projectId}/results?requestId=${encodeURIComponent(e)}`).then(e=>Array.isArray(e?.data)?e.data:Array.isArray(e)?e:[])},o,fe,Ae).catch(()=>""):""
9257
- if(ye&&T.info("showing started",{requestId:o,said:ye}),"review-submit"===me?.action&&Y&&X&&(X.reviewAcceptedSha=Y),me?.reviewChange&&se&&X&&(X.afterReview={...se,outcome:me.reviewChange},"restarted"===me.reviewChange&&(X.sinceReview=se)),!me)return D({success:!1,error:"plan_missing"}),{content:[{type:"text",text:`Server validation returned no completion plan for ${o} -- the remote is likely mid-deploy. Retry complete.`}],isError:!0}
9258
- const we="testing"===me.action||"human-review"===me.action||"merge-cleared"===me.action
9259
- if(we&&te.testsSkipped&&(M||P&&P.length>0))try{const e=_.getToolRecordClient()
9260
- e&&await e.createToolRecord({projectId:S.projectId,requestId:o,type:"tests-skipped",metadata:{...M?{reason:M}:{},...P&&P.length>0?{skipTests:P}:{},testGate:te.gates.test,timestamp:(new Date).toISOString()}})}catch(e){T.warn("Failed to persist tests-skipped tool_record",{error:(0,p.getErrorMessage)(e)})}if(we&&N&&N.length>0&&Z){const e=_.getSessionMessageClient()
9267
+ const s=await fetch(`${(0,m.getActionServerBaseUrl)()}/api/baseline-measure`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({requestId:t,baselines:o}),signal:AbortSignal.timeout(2e4)})
9268
+ n.info("baseline measurement started beside the sign-off check; the merge waits for it",{requestId:t,baselines:o.length,ok:s.ok})}catch(e){n.debug("baseline measurement could not start; the card says it was not measured",{requestId:t,error:(0,h.getErrorMessage)(e)})}}(B,s,X,D)
9269
+ const be=ye&&Ae.artifact?await B.getRequestClient().getRequest(s).then(e=>String(e?.title??"")).catch(()=>""):"",ve=ye&&!we?await(0,k.startShowing)({projectId:_.projectId,post:(e,t)=>B.proxyPost(e,t),log:D,sessionExternalId:J??void 0,assetThere:e=>(0,k.assetIsThere)(_.basePath,e),resultsOf:e=>B.proxyGet(`/api/projects/${_.projectId}/results?requestId=${encodeURIComponent(e)}`).then(e=>Array.isArray(e?.data)?e.data:Array.isArray(e)?e:[])},s,Ae,be).catch(()=>""):""
9270
+ if(ve&&D.info("showing started",{requestId:s,said:ve}),"review-submit"===ye?.action&&X&&re&&(re.reviewAcceptedSha=X),ye?.reviewChange&&le&&re&&(re.afterReview={...le,outcome:ye.reviewChange},"restarted"===ye.reviewChange&&(re.sinceReview=le)),!ye)return O({success:!1,error:"plan_missing"}),{content:[{type:"text",text:`Server validation returned no completion plan for ${s} -- the remote is likely mid-deploy. Retry complete.`}],isError:!0}
9271
+ const Ie="testing"===ye.action||"human-review"===ye.action||"merge-cleared"===ye.action
9272
+ if(Ie&&ie.testsSkipped&&(P||j&&j.length>0))try{const e=B.getToolRecordClient()
9273
+ e&&await e.createToolRecord({projectId:_.projectId,requestId:s,type:"tests-skipped",metadata:{...P?{reason:P}:{},...j&&j.length>0?{skipTests:j}:{},testGate:ie.gates.test,timestamp:(new Date).toISOString()}})}catch(e){D.warn("Failed to persist tests-skipped tool_record",{error:(0,h.getErrorMessage)(e)})}if(Ie&&M&&M.length>0&&J){const e=B.getSessionMessageClient()
9261
9274
  if(e){let t=new Set
9262
- try{const e=await _.proxyGet(`/api/projects/${S.projectId}/session-messages`,{requestId:o,messageType:"report",limit:200}),r=Array.isArray(e)?e:e?.data??[]
9263
- t=new Set(r.filter(e=>(e?.content?.requestId??e?.content?.anchor?.id)===o).map(e=>String(e?.content?.summary??"")))}catch{}for(const r of N.slice(0,5)){const n=r.length>140?`${r.slice(0,137)}\u2026`:r
9264
- if(!t.has(n))try{await e.createReport({fromSessionId:Z,status:"success",summary:n,...r.length>140?{details:r}:{},attention:"fyi",requestId:o})}catch(e){T.warn("worthKnowing note failed (completion continues)",{error:(0,p.getErrorMessage)(e)})}}T.info("worthKnowing notes processed",{count:Math.min(N.length,5),requestId:o})}}if(q&&q.length>0&&Z)for(const e of q.slice(0,2))try{const t=Array.isArray(e.files)&&e.files.length>0
9265
- await _.proxyPost(`/api/projects/${S.projectId}/guidance/propose`,{body:e.body,scope:t?"files":"project",...t?{pathAnchors:e.files}:{},...e.evidence?{evidence:e.evidence}:{},...e.userSaid?{userSaid:e.userSaid}:{},requestId:o,sessionExternalId:Z}),T.info("guidance proposed at completion",{requestId:o})}catch(e){T.info("guidance at completion not proposed (completion continues)",{requestId:o,reason:(0,p.getErrorMessage)(e)})}const be=_.getRequestClient()
9266
- if("answer"===me.action){try{const t=await be.updateRequest(o,{completionSummary:G,completionFormat:"markdown",completionNotChecked:"string"==typeof U&&U.trim()?U.trim():null,cliOrigin:W,...K,...ee})
9267
- e.refsSeen&&t?.namedRefs&&(e.refsSeen.namedRefs=t.namedRefs),await _.proxyPost(`/api/projects/${S.projectId}/requests/${o}/finalize-completion`,{summary:G,acceptanceEvidence:[{evidence:"This changed nothing: its branch holds no change and no record writes into the project."}],mergeResult:{mergeCommitHash:"unknown",cleanedUp:!1,method:"answer-only",diffStats:{additions:0,deletions:0,filesChanged:0}}})}catch(e){return D({success:!1,error:"answer_finish_failed"}),{content:[{type:"text",text:`Your reply on ${o} could not be recorded: ${(0,p.getErrorMessage)(e)}. Call complete again.`}],isError:!0}}return D({success:!0}),{content:[{type:"text",text:`Your summary is the reply to the user on ${o}, and it is with them now. Nothing in the project changed, so nothing is checked and nothing waits for a sign-off: it is finished. Stop here.`}]}}for(const t of me.patches)try{const r=await be.updateRequest(o,{...t,completionSummary:G,completionFormat:"markdown",completionNotChecked:"string"==typeof U&&U.trim()?U.trim():null,cliOrigin:W,...K,...ee})
9268
- e.refsSeen&&r?.namedRefs&&(e.refsSeen.namedRefs=r.namedRefs)}catch(e){return D({success:!1,error:"plan_patch_failed"}),{content:[{type:"text",text:`Failed to apply completion transition (${Object.keys(t).join(", ")}): ${(0,p.getErrorMessage)(e)}`}],isError:!0}}if(D({success:!0}),"review-escalated"===me.action)return{content:[{type:"text",text:`Request ${o}: review stopped the work and it went to the user (out of review tries, or completed again unchanged). No further action from you; a human finalizes or sends back must_fix items.`}]}
9269
- if("review-start"===me.action)return{content:[{type:"text",text:`Request ${o}: review started. Read-only reviewers are reading your diff. STOP here and wait: do NOT poll, sleep, or call tools to pass the time; each reviewer's report is delivered to you automatically and wakes you, so just end your turn between reports. When their reports arrive (a detailed handoff follows with the specifics), act on the findings, finish your tasks, and call complete again. If no reviewer finds anything, you are told the review was clean and the request went on with this completion: then do not complete again. After review passes, the server runs any remaining gates and messages you the exact next step (re-run tests if your fixes changed code, await human sign-off, or merge) -- wait for that and don't merge until it tells you to.`}]}
9270
- if("review-submit"===me.action)return{content:[{type:"text",text:`Request ${o}: review submitted. The server is evaluating it and will message you the exact next step (re-run tests if your fixes changed code, await human sign-off, or merge) -- wait for that before acting. If a criterion is still unmet, the criteria reviewer takes another pass and reports back; address any must_fix and call complete again.`}]}
9271
- if("signoff-park"===me.action)return{content:[{type:"text",text:`Request ${o}: review is complete. This request now awaits the user's sign-off in the UI -- stop here and do NOT call merge.`}]}
9272
- if("promise-hold"===me.action){const e=me.detail?` (${me.detail})`:""
9273
- return{content:[{type:"text",text:`Request ${o}: your change touches the test behind a promise an earlier request made${e}. Only the user changes a promise's test: on the promise card they accept your test, send the work back to put the test back, or retire the promise. Stop here and end your turn; do not call complete or merge again. You are told their answer.`}]}}if("merge-cleared-after-review"===me.action)return{content:[{type:"text",text:`Request ${o}: review is complete and all gates are clear. Call merge to finalize (do not call complete again).`}]}
9274
- if("testing"===me.action){const e="human"===me.testResolver?"A notification has been created asking the user to choose how to handle testing (auto-test, interactive-test, or skip). Stop here; you will receive a report when testing finishes.":"Tests are running automatically in the background. Stop here; each active test process will run and post its own report back to you."
9275
- return{content:[{type:"text",text:`Request ${o}: tests are now running. ${e}\n\n## When reports arrive\nEach test process reports separately: an individual passed report does NOT mean you are cleared.\n- When every process has passed, the server advances the remaining gates automatically: agent review may start (reviewers report their findings to you), the request may wait for sign-off from the user, or the request merges. With your account of every criterion on the record (criteriaComments in this call), the server merges it and tells you so; you call nothing. Only if you are told explicitly that you are "cleared to merge" do you call \`merge\`, with evidence for the criteria you had not accounted for.\n- **Failed**: fix the issues and call \`complete\` again.\n\nDo NOT call merge, finalize, or any other tool before that explicit instruction: stop here and wait.`}]}}if("delivery-review"===me.action){const e=te.testsSkipped&&M?`\n\n_Tests skipped: ${M}_`:""
9276
- return{content:[{type:"text",text:`Request ${o} is handed to the DELIVERY review, which is the last gate on this project and resolves the sign-off.${e}\n\nStop here and do NOT call merge: the review finalizes the request, or comes back to you with what it found. End your turn with one short line; your completion summary is what the user reads, so do not write it out again.`}]}}if("human-review"===me.action){const e=te.testsSkipped&&M?`\n\n_Tests skipped: ${M}_`:""
9277
- return{content:[{type:"text",text:`Request ${o} submitted for human review.${e}\n\nReview is human-only for this project: the user approves the merge in the UI. Do NOT call merge yourself.`}]}}let ve,Ie
9278
- try{ve=await be.getRequest(o)}catch(e){Ie=e}const Ee=(0,b.kindProfile)(re??ve?.type)
9279
- if("settle"===Ee.finalize)return{content:[{type:"text",text:`Request ${o} is complete. A ${Ee.label.toLowerCase()} request has no branch, so nothing merges: it closes on its own record now. Stop here and do not call merge.`}]}
9280
- if(void 0!==Ie)return{content:[{type:"text",text:`Request ${o} is cleared to merge, but fetching its criteria failed: ${(0,p.getErrorMessage)(Ie)}. Call merge with verification evidence for each acceptance criterion.`}]}
9281
- const Ce=ve?.acceptanceCriteria||[]
9282
- if(!1===te?.gates?.review?.enabled&&(0,k.checksOff)(te)&&!1===te?.gates?.signoff?.enabled&&"merge"===Ee.finalize&&(0===Ce.length||Array.isArray(j)&&j.length>0)){T.info("gates off and account recorded: merging in the same call",{requestId:o})
9283
- const e=await(0,d.handleMergeRequest)({requestId:o,verifications:[]},S,_,B,R,$)
9284
- return e?.isError?e:{...e,content:[{type:"text",text:`Request ${o}: nothing gates this project (review, tests and sign-off are off) and your account per criterion is on the record, so it merged in this call.\n\n${e.content?.[0]?.text??""}`},...e.content?.slice(1)??[]]}}const ke=me.skippedReason?`\n\n_Skipped re-running tests: ${me.skippedReason}._\n`:"",xe=me.noTestsAutoMerge?"\n\n_No test processes are configured for this project, so the test gate has nothing to run; this completion merges without tests._\n":""
9285
- return 0===Ce.length?{content:[{type:"text",text:`Request ${o} is ready. ${ke}${xe}No acceptance criteria defined: call merge directly.`}]}:{content:[{type:"text",text:`Request ${o}: all gates are clear.${ke}${xe}\n\n## Next Step: Verify & Merge\n\n${(0,h.formatVerifyInstructions)(Ce)}\n\nUse the merge operation with your verification evidence to merge to main.`}]}}catch(t){let r="An unexpected error occurred",n=""
9286
- t instanceof o.ZodError?(r="Validation error",n=t.errors.map(e=>`${e.path.join(".")}: ${e.message}`).join(", ")):t instanceof Error?(r=t.message,n=t.stack||""):n=String(t),T.error("Complete request failed",{error:r,details:n})
9287
- try{await(0,s.createFailureToolRecord)("complete-request",t,{error:r,details:n,requestId:e?.requestId},_.getToolRecordClient(),T,R,{requestId:e?.requestId,projectId:S.projectId,note:"Failed to complete request"})}catch{}return R.startTimer("tool_execution_time")({success:!1,error:t instanceof Error?t.name:"unknown"}),{content:[{type:"text",text:`Error: ${r}`}],isError:!0}}}
9288
- const n=t(65454),i=t(59860),o=t(7552),s=t(18616),a=t(35510),c=t(82860),l=t(95122),u=t(7161),d=t(34306),p=t(58824),h=t(65454),f=t(79414),m=t(67071),g=t(92456),A=t(80331),y=t(63580),w=t(52232),b=t(87780),v=t(45391),I=t(43451),E=t(70715),C=t(42692),k=t(64721),x=new Set},32283(e,exports,t){"use strict"
9275
+ try{const e=await B.proxyGet(`/api/projects/${_.projectId}/session-messages`,{requestId:s,messageType:"report",limit:200}),r=Array.isArray(e)?e:e?.data??[]
9276
+ t=new Set(r.filter(e=>(e?.content?.requestId??e?.content?.anchor?.id)===s).map(e=>String(e?.content?.summary??"")))}catch{}for(const r of M.slice(0,5)){const n=r.length>140?`${r.slice(0,137)}\u2026`:r
9277
+ if(!t.has(n))try{await e.createReport({fromSessionId:J,status:"success",summary:n,...r.length>140?{details:r}:{},attention:"fyi",requestId:s})}catch(e){D.warn("worthKnowing note failed (completion continues)",{error:(0,h.getErrorMessage)(e)})}}D.info("worthKnowing notes processed",{count:Math.min(M.length,5),requestId:s})}}if(L&&L.length>0&&J)for(const e of L.slice(0,2))try{const t=Array.isArray(e.files)&&e.files.length>0
9278
+ await B.proxyPost(`/api/projects/${_.projectId}/guidance/propose`,{body:e.body,scope:t?"files":"project",...t?{pathAnchors:e.files}:{},...e.evidence?{evidence:e.evidence}:{},...e.userSaid?{userSaid:e.userSaid}:{},requestId:s,sessionExternalId:J}),D.info("guidance proposed at completion",{requestId:s})}catch(e){D.info("guidance at completion not proposed (completion continues)",{requestId:s,reason:(0,h.getErrorMessage)(e)})}const Ee=B.getRequestClient()
9279
+ if("answer"===ye.action){try{const t=await Ee.updateRequest(s,{completionSummary:Y,completionFormat:"markdown",completionNotChecked:"string"==typeof z&&z.trim()?z.trim():null,cliOrigin:K,...te,...ne})
9280
+ e.refsSeen&&t?.namedRefs&&(e.refsSeen.namedRefs=t.namedRefs),await B.proxyPost(`/api/projects/${_.projectId}/requests/${s}/finalize-completion`,{summary:Y,acceptanceEvidence:[{evidence:"This changed nothing: its branch holds no change and no record writes into the project."}],mergeResult:{mergeCommitHash:"unknown",cleanedUp:!1,method:"answer-only",diffStats:{additions:0,deletions:0,filesChanged:0}}})}catch(e){return O({success:!1,error:"answer_finish_failed"}),{content:[{type:"text",text:`Your reply on ${s} could not be recorded: ${(0,h.getErrorMessage)(e)}. Call complete again.`}],isError:!0}}return O({success:!0}),{content:[{type:"text",text:`Your summary is the reply to the user on ${s}, and it is with them now. Nothing in the project changed, so nothing is checked and nothing waits for a sign-off: it is finished. Stop here.`}]}}for(const t of ye.patches)try{const r=await Ee.updateRequest(s,{...t,completionSummary:Y,completionFormat:"markdown",completionNotChecked:"string"==typeof z&&z.trim()?z.trim():null,methodNote:"string"==typeof H&&H.trim()?H.trim():null,cliOrigin:K,...te,...ne})
9281
+ e.refsSeen&&r?.namedRefs&&(e.refsSeen.namedRefs=r.namedRefs)}catch(e){return O({success:!1,error:"plan_patch_failed"}),{content:[{type:"text",text:`Failed to apply completion transition (${Object.keys(t).join(", ")}): ${(0,h.getErrorMessage)(e)}`}],isError:!0}}if(O({success:!0}),"review-escalated"===ye.action)return{content:[{type:"text",text:`Request ${s}: review stopped the work and it went to the user (out of review tries, or completed again unchanged). No further action from you; a human finalizes or sends back must_fix items.`}]}
9282
+ if("review-start"===ye.action)return{content:[{type:"text",text:`Request ${s}: review started. Read-only reviewers are reading your diff. STOP here and wait: do NOT poll, sleep, or call tools to pass the time; each reviewer's report is delivered to you automatically and wakes you, so just end your turn between reports. When their reports arrive (a detailed handoff follows with the specifics), act on the findings, finish your tasks, and call complete again. If no reviewer finds anything, you are told the review was clean and the request went on with this completion: then do not complete again. After review passes, the server runs any remaining gates and messages you the exact next step (re-run tests if your fixes changed code, await human sign-off, or merge) -- wait for that and don't merge until it tells you to.`}]}
9283
+ if("review-submit"===ye.action)return{content:[{type:"text",text:`Request ${s}: review submitted. The server is evaluating it and will message you the exact next step (re-run tests if your fixes changed code, await human sign-off, or merge) -- wait for that before acting. If a criterion is still unmet, the criteria reviewer takes another pass and reports back; address any must_fix and call complete again.`}]}
9284
+ if("signoff-park"===ye.action)return{content:[{type:"text",text:`Request ${s}: review is complete. This request now awaits the user's sign-off in the UI -- stop here and do NOT call merge.`}]}
9285
+ if("promise-hold"===ye.action){const e=ye.detail?` (${ye.detail})`:""
9286
+ return{content:[{type:"text",text:`Request ${s}: your change touches the test behind a promise an earlier request made${e}. Only the user changes a promise's test: on the promise card they accept your test, send the work back to put the test back, or retire the promise. Stop here and end your turn; do not call complete or merge again. You are told their answer.`}]}}if("merge-cleared-after-review"===ye.action)return{content:[{type:"text",text:`Request ${s}: review is complete and all gates are clear. Call merge to finalize (do not call complete again).`}]}
9287
+ if("testing"===ye.action){const e="human"===ye.testResolver?"A notification has been created asking the user to choose how to handle testing (auto-test, interactive-test, or skip). Stop here; you will receive a report when testing finishes.":"Tests are running automatically in the background. Stop here; each active test process will run and post its own report back to you."
9288
+ return{content:[{type:"text",text:`Request ${s}: tests are now running. ${e}\n\n## When reports arrive\nEach test process reports separately: an individual passed report does NOT mean you are cleared.\n- When every process has passed, the server advances the remaining gates automatically: agent review may start (reviewers report their findings to you), the request may wait for sign-off from the user, or the request merges. With your account of every criterion on the record (criteriaComments in this call), the server merges it and tells you so; you call nothing. Only if you are told explicitly that you are "cleared to merge" do you call \`merge\`, with evidence for the criteria you had not accounted for.\n- **Failed**: fix the issues and call \`complete\` again.\n\nDo NOT call merge, finalize, or any other tool before that explicit instruction: stop here and wait.`}]}}if("delivery-review"===ye.action){const e=ie.testsSkipped&&P?`\n\n_Tests skipped: ${P}_`:""
9289
+ return{content:[{type:"text",text:`Request ${s} is handed to the DELIVERY review, which is the last gate on this project and resolves the sign-off.${e}\n\nStop here and do NOT call merge: the review finalizes the request, or comes back to you with what it found. End your turn with one short line; your completion summary is what the user reads, so do not write it out again.`}]}}if("human-review"===ye.action){const e=ie.testsSkipped&&P?`\n\n_Tests skipped: ${P}_`:""
9290
+ return{content:[{type:"text",text:`Request ${s} submitted for human review.${e}\n\nReview is human-only for this project: the user approves the merge in the UI. Do NOT call merge yourself.`}]}}let Ce,ke
9291
+ try{Ce=await Ee.getRequest(s)}catch(e){ke=e}const xe=(0,v.kindProfile)(oe??Ce?.type)
9292
+ if("settle"===xe.finalize)return{content:[{type:"text",text:`Request ${s} is complete. A ${xe.label.toLowerCase()} request has no branch, so nothing merges: it closes on its own record now. Stop here and do not call merge.`}]}
9293
+ if(void 0!==ke)return{content:[{type:"text",text:`Request ${s} is cleared to merge, but fetching its criteria failed: ${(0,h.getErrorMessage)(ke)}. Call merge with verification evidence for each acceptance criterion.`}]}
9294
+ const Se=Ce?.acceptanceCriteria||[]
9295
+ if(!1===ie?.gates?.review?.enabled&&(0,x.checksOff)(ie)&&!1===ie?.gates?.signoff?.enabled&&"merge"===xe.finalize&&(0===Se.length||Array.isArray(F)&&F.length>0)){D.info("gates off and account recorded: merging in the same call",{requestId:s})
9296
+ const e=await(0,p.handleMergeRequest)({requestId:s,verifications:[]},_,B,R,$,Q)
9297
+ return e?.isError?e:{...e,content:[{type:"text",text:`Request ${s}: nothing gates this project (review, tests and sign-off are off) and your account per criterion is on the record, so it merged in this call.\n\n${e.content?.[0]?.text??""}`},...e.content?.slice(1)??[]]}}const _e=ye.skippedReason?`\n\n_Skipped re-running tests: ${ye.skippedReason}._\n`:"",Be=ye.noTestsAutoMerge?"\n\n_No test processes are configured for this project, so the test gate has nothing to run; this completion merges without tests._\n":""
9298
+ return 0===Se.length?{content:[{type:"text",text:`Request ${s} is ready. ${_e}${Be}No acceptance criteria defined: call merge directly.`}]}:{content:[{type:"text",text:`Request ${s}: all gates are clear.${_e}${Be}\n\n## Next Step: Verify & Merge\n\n${(0,f.formatVerifyInstructions)(Se)}\n\nUse the merge operation with your verification evidence to merge to main.`}]}}catch(t){let r="An unexpected error occurred",n=""
9299
+ t instanceof s.ZodError?(r="Validation error",n=t.errors.map(e=>`${e.path.join(".")}: ${e.message}`).join(", ")):t instanceof Error?(r=t.message,n=t.stack||""):n=String(t),D.error("Complete request failed",{error:r,details:n})
9300
+ try{await(0,a.createFailureToolRecord)("complete-request",t,{error:r,details:n,requestId:e?.requestId},B.getToolRecordClient(),D,$,{requestId:e?.requestId,projectId:_.projectId,note:"Failed to complete request"})}catch{}return $.startTimer("tool_execution_time")({success:!1,error:t instanceof Error?t.name:"unknown"}),{content:[{type:"text",text:`Error: ${r}`}],isError:!0}}}
9301
+ const i=t(65454),o=t(59860),s=t(7552),a=t(18616),c=t(35510),l=t(82860),u=t(95122),d=t(7161),p=t(34306),h=t(58824),f=t(65454),m=t(79414),g=t(67071),A=t(92456),y=t(80331),w=t(63580),b=t(52232),v=t(87780),I=t(45391),E=t(43451),C=t(70715),k=t(42692),x=t(64721),S=new Set},32283(e,exports,t){"use strict"
9289
9302
  exports.def=void 0
9290
9303
  const r=t(76183),n=t(47334)
9291
9304
  exports.def={name:"create_request",description:"Create a request. delegate: true with draft has a background planner write its description and criteria from your title and this session. When it comes out of an existing record (a drive's finding, a report, a decision, another request), name that record's id in the description (\"Found by drive req000042\"): ids are the only followable trail.",schema:r.CreateRequestSchema,fieldTags:{budget:["budget"],estimate:["estimate"],scheduleDocumentId:["schedule"],questDocumentId:["schedule"],delegate:["delegate"],replaces:["refile"],asked:["asked"],process:["process"]},annotations:{title:"Create Request",readOnlyHint:!1,destructiveHint:!1},handler:(e,t)=>(0,n.handleCreateRequest)(e,t.context,t.apiClient,t.logger,t.metrics,t.mcpServer,t.cliOrigin)}},47334(e,exports,t){"use strict"
@@ -10188,14 +10201,13 @@ const t=e.trim().toLowerCase()
10188
10201
  if("decision"===t)return{kind:"decision"}
10189
10202
  if(/^q-[a-z0-9]+-\d{3,}$/.test(t))return{kind:"decision",card:t.toUpperCase()}
10190
10203
  const r=t.match(/^(req\d{6})$/)
10191
- return r?{kind:"request",requestId:r[1]}:null},exports.disproveBlock=function(e,t){const r=[]
10192
- return t.owns?(i.test(e)&&0===t.markerFiles.length&&null!==t.mainAheadBy&&r.push(`A moved ${t.defaultBranch}${null!=t.mainAheadBy?` (${t.mainAheadBy} commit(s) ahead)`:""} does not block you and your branch carries no conflict markers. Finish your change and complete the request; if the merge conflicts, the markers come back to you in the files. Nothing to report.`),s.test(e)&&r.push(`You ARE the live session on ${t.requestId}; the record says so. Edit, complete, and ignore any earlier message that said otherwise.`),o.test(e)&&!t.questionPending&&r.push(`No approval is pending for you on ${t.requestId}, and the file edit tools (write, replace, edit) are never gated: editing a file by hand is not reproducing a denied command. Make the change with the edit tools and complete. If a specific COMMAND needs approval, request that command; do not wait for anyone to clear a path.`),r):(r.push(`This session holds no live association on ${t.requestId}: the request is not yours to work. Stop on it; whoever owns it will finish it.`),r)},exports.renderBlockFacts=function(e){return[`Record for ${e.requestId}:`,"- you hold a live association: "+(e.owns?"yes":"NO"),`- ${e.defaultBranch} ahead of your branch: ${null==e.mainAheadBy?"unknown":`${e.mainAheadBy} commit(s)`}`,`- conflict markers on your branch: ${e.markerFiles.length?e.markerFiles.join(", "):"none"}`,"- test run in flight: "+(e.testRunning?"yes":"no"),"- question of yours pending: "+(e.questionPending?"yes":"no")].join("\n")},exports.gitBlockFacts=async function(e,t,i){const o=(0,r.simpleGit)({baseDir:e,binary:i}),s=await(0,n.defaultBranchOf)(o)
10204
+ return r?{kind:"request",requestId:r[1]}:null},exports.disproveBlock=function(e){return e.owns?[]:[`This session holds no live association on ${e.requestId}: the request is not yours to work. Stop on it; whoever owns it will finish it.`]},exports.renderBlockFacts=function(e){return[`Record for ${e.requestId}:`,"- you hold a live association: "+(e.owns?"yes":"NO"),`- ${e.defaultBranch} ahead of your branch: ${null==e.mainAheadBy?"unknown":`${e.mainAheadBy} commit(s)`}`,`- conflict markers on your branch: ${e.markerFiles.length?e.markerFiles.join(", "):"none"}`,"- test run in flight: "+(e.testRunning?"yes":"no"),"- question of yours pending: "+(e.questionPending?"yes":"no")].join("\n")},exports.gitBlockFacts=async function(e,t,i){const o=(0,r.simpleGit)({baseDir:e,binary:i}),s=await(0,n.defaultBranchOf)(o)
10193
10205
  let a=null
10194
10206
  try{a=(await o.raw(["for-each-ref","--format=%(refname:short)",`refs/heads/requests/${t}*`])).split("\n").map(e=>e.trim()).find(Boolean)??null}catch{}if(!a)return{defaultBranch:s,mainAheadBy:null,markerFiles:[]}
10195
10207
  let c=null
10196
10208
  try{c=Number((await o.raw(["rev-list","--count",`${a}..${s}`])).trim())||0}catch{}let l=[]
10197
10209
  try{l=(await o.raw(["grep","-l","-e","^<<<<<<< ",a,"--"])).split("\n").map(e=>e.trim()).filter(Boolean).map(e=>e.replace(/^[^:]*:/,""))}catch{}return{defaultBranch:s,mainAheadBy:c,markerFiles:l}},exports.cardsToWaitOn=function(e,t){return"decision"!==e.kind?{cards:[]}:e.card?t.includes(e.card)?{cards:[e.card]}:{refused:`${e.card} is not an open card of the user's on this request${t.length?` (open: ${t.join(", ")})`:""}.`}:{cards:[...t]}},exports.stillWaits=function(e,t){return!e||!0===e.blocksEverySession||["waitingOnSessions","pendingQuestionSessions","monitorArmedSessions"].some(r=>Array.isArray(e[r])&&e[r].includes(t))},exports.noWaitLeftLine=function(e){return`That was your last declared wait on ${e}: nothing on the record says you are waiting now, so if you end your turn here the silence watch treats ${e} as stopped work and reminds you to go on. If the user's answer leaves nothing for you to build, complete ${e} as it is: its checks run again, a failure that only the project's own failing tests cause is counted as the project's, and the sign-off card puts the rest to the user. If you still need the user to do something first, keep a question open for it instead (it is your declared wait, and their answer resumes you).`}
10198
- const r=t(94662),n=t(52232),i=/\b(conflict|merge|main (has )?moved|behind main|ahead of|rebase|diverg)/i,o=/\b(approval|approve[ds]?|denied|gated|authoriz|permission|clear(ed)? (the|a|this) (path|command|edit))\b/i,s=/\b(associat|reattach|attached to no|not attached|no request association|task association|session association)/i
10210
+ const r=t(94662),n=t(52232)
10199
10211
  exports.BLOCKED_ON_HELP='A blocked report on your own request must say what it needs, in blockedOn: "decision" (when the user has a card open on your request you wait on it; otherwise a question goes to your parent and escalates to the user), an open card\'s code like "Q-AB12CD34-001" (you wait on that card) or "req000123" (a wait is declared on that request and you are told when it settles). A block that names nothing is not filed. There is no hand check: the user checks their own [user-input] criteria on the card at the end of the work, so complete the request when it is built.'},68456(e,exports,t){"use strict"
10200
10212
  exports.def=void 0
10201
10213
  const r=t(76183),n=t(82113)
@@ -10242,7 +10254,7 @@ case"settles":return"\n\n\u{1fab5} Recorded on your setup request. Whoever waits
10242
10254
  case"explicit":case"parent-waits":return t?"\n\n\u{1f4ec} The receiving session has been woken with this report.":"\n\n\u26a0\ufe0f The receiving session could NOT be woken (delivery failed). The report is recorded, but nobody is acting on it: say so, or send it again."}},exports.ackWakesReporter=function(e){return"reviewer"!==e},exports.HUB_AGENT_TYPES=new Set(["alchemist","hub"])},17613(e,exports,t){"use strict"
10243
10255
  exports.def=void 0
10244
10256
  const r=t(76183),n=t(9072),i=t(31375)
10245
- exports.def={name:"session_message_operations",description:"Notes, questions, reports and decisions between sessions and the user (codes: Q-{id}-{seq} questions, R-{id}-{seq} reports).\n\n\u2022 note - One or two plain sentences on what you are doing (text), with percent while you build. Lands on your request, wakes nobody.\n\u2022 send - A question or a report (type). A report is for what the user must act on or would regret not seeing; narration is a note.\n\u2022 reply - Answer a question or acknowledge a report (messageCode).\n\u2022 record-decision - A call you already made without asking (decision, why), such as an alternative you dropped; or the user's own call, from a message of theirs that settles what is built (userSaid: their words). A question, a test or chat is no decision. A call the user could still flip is a question. Going against guidance is neither: that is the guidance tool (exception, conflict).\n\u2022 brief - The one opening brief for a request (requestId, sections); the result says whether to wait.\n\u2022 list / get - Messages, or one by messageCode.\n\u2022 offer-handoff - Offer the user to start building or keep planning.\n\u2022 withdraw - Settle your question that no longer needs an answer (messageCode, withdrawReason).",schema:r.SessionMessageOperationsSchema,fieldTags:{milestone:["note"],percent:["note"],withdrawReason:["withdraw"],type:["send"],toSessionId:["send"],questionType:["question"],questionContent:["question"],priority:["question"],expiresInHours:["question"],userSaid:["question","record-decision"],replaces:["question"],keepsOpen:["question"],forceAsk:["question"],criteria:["question"],status:["send"],blockedOn:["send"],attention:["send"],reportKind:["send"],summary:["send"],details:["send"],ruleVerdicts:["rule-verdicts"],silent:["silent"],relatesTo:["send","record-decision"],decision:["record-decision"],why:["record-decision"],context:["record-decision"],area:["record-decision"],sections:["brief"],sectionAnswers:["brief"],messageCode:["reply","get","withdraw"],amount:["plan-budget"],quote:["user-answers"],text:["reply","note"],selectedOptions:["reply"],notes:["reply"],escalate:["escalate"],list:["list"],offerHandoff:["offer-handoff"]},annotations:{title:"Session Message Operations",readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1},handler:(e,t)=>function(e,t){return(0,i.withHeartbeat)({progressToken:e.progressToken,sendNotification:e.sendNotification,intervalMs:void 0,message:()=>"waiting for the user's answer"},t)}(t,()=>(0,n.handleSessionMessageOperations)(e,t.context,t.logger,t.metrics,t.apiClient??void 0,t.cliOrigin))}},9072(e,exports,t){"use strict"
10257
+ exports.def={name:"session_message_operations",description:"Notes, questions, reports and decisions between sessions and the user (codes: Q-{id}-{seq} questions, R-{id}-{seq} reports).\n\n\u2022 note - One or two plain sentences on what you are doing (text), with percent while you build. Lands on your request, wakes nobody.\n\u2022 send - A question or a report (type). A report is for what the user must act on or would regret not seeing; narration is a note.\n\u2022 reply - Answer a question or acknowledge a report (messageCode).\n\u2022 record-decision - A call you already made without asking (decision, why), such as an alternative you dropped; or the user's own call, from a message of theirs that settles what is built (userSaid: their words). A question, a test or chat is no decision. A call the user could still flip is a question. Going against guidance is neither: that is the guidance tool (exception, conflict).\n\u2022 brief - The one opening brief for a request (requestId, sections); the result says whether to wait.\n\u2022 list / get - Messages, or one by messageCode.\n\u2022 offer-handoff - Offer the user to start building or keep planning.\n\u2022 withdraw - Settle your question that no longer needs an answer (messageCode, withdrawReason).",schema:r.SessionMessageOperationsSchema,fieldTags:{milestone:["note"],percent:["note"],withdrawReason:["withdraw"],type:["send"],toSessionId:["send"],questionType:["question"],questionContent:["question"],priority:["question"],expiresInHours:["question"],userSaid:["question","record-decision"],replaces:["question"],keepsOpen:["question"],forceAsk:["question"],criteria:["question"],status:["send"],blockedOn:["send"],attention:["send"],reportKind:["send"],summary:["send"],details:["send"],ruleVerdicts:["rule-verdicts"],review:["rule-verdicts"],silent:["silent"],relatesTo:["send","record-decision"],decision:["record-decision"],why:["record-decision"],context:["record-decision"],area:["record-decision"],sections:["brief"],sectionAnswers:["brief"],messageCode:["reply","get","withdraw"],amount:["plan-budget"],quote:["user-answers"],text:["reply","note"],selectedOptions:["reply"],notes:["reply"],escalate:["escalate"],sameAs:["escalate"],list:["list"],offerHandoff:["offer-handoff"]},annotations:{title:"Session Message Operations",readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1},handler:(e,t)=>function(e,t){return(0,i.withHeartbeat)({progressToken:e.progressToken,sendNotification:e.sendNotification,intervalMs:void 0,message:()=>"waiting for the user's answer"},t)}(t,()=>(0,n.handleSessionMessageOperations)(e,t.context,t.logger,t.metrics,t.apiClient??void 0,t.cliOrigin))}},9072(e,exports,t){"use strict"
10246
10258
  function r(e,t){if("send"===e)return"report"===t?.type?"send-report":"question"===t?.type?"send-question":"send-message"
10247
10259
  if("reply"===e){const e=t?.messageCode
10248
10260
  return"string"==typeof e&&e.startsWith("R-")?"acknowledge-report":"string"==typeof e&&e.startsWith("Q-")?"answer-question":"reply-message"}return"note"===e?!0===t?.milestone?"send-milestone":"send-note":"list"===e?"list-messages":"get"===e?"get-message":"offer-handoff"===e?"offer-handoff":"record-decision"===e?"record-decision":"brief"===e?"opening-brief":"session-message-operations"}async function n(e){return _.sessionIdentity.getAgentTypeFor(e)}async function i(e,t,r,n,i){const o=i?.alchemy,s=await(0,Q.deliverToSession)(e,t,{kind:o?.kind??"inserted-prompt",...o?.payload?{partPayload:o.payload}:{}})
@@ -10259,9 +10271,9 @@ case"withdrawn":return"nobody: it was withdrawn or closed by the system"
10259
10271
  case"agent":return`an agent (session ${e.respondedBySessionId??"unknown"})`
10260
10272
  default:return e.respondedBySessionId?`session ${e.respondedBySessionId}`:"not recorded"}}(e)}`),t.push(`**Answered at:** ${e.respondedAt}`),e.response.text&&t.push(`**Answer:** ${e.response.text}`),e.response.selectedOptions&&t.push(`**Selected:** ${e.response.selectedOptions.join(", ")}`),e.response.quote&&t.push(`**The user's words:** "${e.response.quote}"`),e.response.notes&&t.push(`**Notes:** ${e.response.notes}`)),c(r,t),t.join("\n")}function c(e,t){e?.relatesTo&&(t.push(""),t.push(`**Relates to:** ${e.relatesTo}`))}function l(e){const t=[],r=e.content||{}
10261
10273
  return t.push(`**Status:** ${r.reportStatus||"unknown"}`),t.push(`**Summary:** ${r.summary||""}`),r.details&&(t.push(""),t.push("**Details:**"),t.push(r.details)),c(r,t),t.join("\n")}function u(e){return e.ruleCard?e.guidance:`\u{1f4da} **Already decided: no question needed.**\n\n${e.guidance}`}function d(e){return"question"===e.messageType?a(e):"instruction"===e.messageType?function(e){const t=[],r=e.content||{}
10262
- return t.push(`**Message Code:** ${e.messageCode}`),t.push("**Type:** instruction"),t.push(`**Status:** ${e.status}`),t.push(`**From:** ${e.fromSessionId}`),t.push(`**To:** ${e.toSessionId}`),t.push(`**Created:** ${e.createdAt}`),r.requestId&&t.push(`**Request:** ${r.requestId}`),t.push(""),t.push("**Body:**"),t.push(r.body||""),t.join("\n")}(e):l(e)}function p(e){const t=e.content||{},r=["\u{1f4e8} **You have a new question!**","",`**Question Code:** ${e.messageCode}`,`**From:** ${e.fromSessionId}`,`**Priority:** ${e.priority}`,"",`**Question:** ${t.question}`]
10263
- if(t.context&&r.push(`**Context:** ${t.context}`),"alternatives"===t.questionType&&t.options){r.push(""),r.push("**Options:**")
10264
- for(const e of t.options)r.push(` - [${e.id}] ${e.label}${e.description?` - ${e.description}`:""}`)}return r.push(""),r.push(`Use \`session_message_operations\` with \`reply\` operation and message code \`${e.messageCode}\` to respond.`),r.join("\n")}function h(e){const t=String(e??"").trim().replace(/^plan\s+(?:is\s+)?(?:ready|complete|completed|done|finished)\s*[:.\-]\s*/i,"").trim()
10274
+ return t.push(`**Message Code:** ${e.messageCode}`),t.push("**Type:** instruction"),t.push(`**Status:** ${e.status}`),t.push(`**From:** ${e.fromSessionId}`),t.push(`**To:** ${e.toSessionId}`),t.push(`**Created:** ${e.createdAt}`),r.requestId&&t.push(`**Request:** ${r.requestId}`),t.push(""),t.push("**Body:**"),t.push(r.body||""),t.join("\n")}(e):l(e)}function p(e){const t=e.content||{},r=[`A question${t.requestId?` on ${t.requestId}`:""} (${e.messageCode}): ${t.question}`]
10275
+ if(t.context&&r.push(`Context: ${t.context}`),"alternatives"===t.questionType&&Array.isArray(t.options)&&t.options.length){r.push("Options:")
10276
+ for(const e of t.options)r.push(`- [${e.id}] ${e.label}${e.description?`: ${e.description}`:""}`)}return r.push("",`Answer it with session_message_operations reply, messageCode ${e.messageCode}.`),r.join("\n")}function h(e){const t=String(e??"").trim().replace(/^plan\s+(?:is\s+)?(?:ready|complete|completed|done|finished)\s*[:.\-]\s*/i,"").trim()
10265
10277
  return t?`Plan ready: ${t}`:"Plan ready"}function f(e,t){const r=e?.content??{},n=String(e?.response?.selectedOptions?.[0]??r.defaultOption??t.defaultOption??""),i=(Array.isArray(r.options)?r.options:t.options||[]).find(e=>e?.id===n),o=i?`${i.label??i.id}${i.description?`: ${i.description}`:""}`:n
10266
10278
  return Array.isArray(r.criteria)&&r.criteria.length>0?`\u{1f916} **Answered "not yet"** (Self-driving): **${o}**\n\nBuild with a clearly marked placeholder and go on: do not wait for the user. Nothing was recorded as their check; the criterion stays open and the card at the end of the work asks them for it. It is on the record as question **${e.messageCode}**; if they answer it, the answer reaches you like any answer.`:`\u{1f916} **Taken on its default** (Self-driving): **${o}**\n\nProceed on it now: do not wait for the user. It is on the record as question **${e.messageCode}**, closed on its default; the user can still change it from its card, and a change reaches you like any answer. Do NOT also call record-decision for it.`}async function m(e,t,r,c,l,d,h){const{toSessionId:m,questionType:g,questionContent:A,priority:y,expiresInHours:w,relatesTo:I}=e
10267
10279
  let{forceAsk:E}=e
@@ -10305,11 +10317,11 @@ let n
10305
10317
  return n="expired"===t.kind?`\u23f3 **The question expired unanswered** (**${e}**). Decide yourself now: pick the best option, record it with the record-decision operation, and proceed. No automatic resume will come for this question.`:"collaborative"===M?`\u23f3 **No answer yet.** The interaction posture is collaborative: this decision stays with the user: do NOT proceed or assume a default. Wrap up your turn cleanly now; the question remains open as **${e}** and you will be automatically resumed with the answer (or a dismissal) whenever it arrives.`:A.defaultOption?`\u23f3 **No answer within the hold window.** The question stays open as **${e}** (inbox).\n\n**Proceed on your default now: ${String(A.defaultOption)}**\n\nIf the eventual answer differs, you will be resumed with a correction: fold it in then.`:`\u23f3 **No answer within the hold window** and no default was stated. **Decide yourself now**: pick the best option, record it with the record-decision operation (so the user can review or correct it later), and proceed. The question stays open as **${e}**; if the user answers later you will be resumed: treat it as confirmation or a correction.`,{content:[{type:"text",text:n+(0,N.namedRefsNote)(q?.content?.namedRefs)}]}}if(P)return{content:[{type:"text",text:`\u2705 Question posted as **${q.messageCode}**. It goes ahead on its default, so nothing waits for it.\n\n**Proceed on your default now: ${String(A.defaultOption)}**\n\nIf the user answers differently, you will be resumed with a correction: fold it in then.${Q}${(0,N.namedRefsNote)(q?.content?.namedRefs)}`}]}
10306
10318
  let L=""
10307
10319
  if(B){const e=await n(_)??"agent",o={alchemy:{kind:"question-received",payload:{messageCode:q.messageCode,question:A.question,questionType:g,options:A.options,context:A.context,allowMultiple:A.allowMultiple,...Array.isArray(A.fields)&&A.fields.length>0?{fields:A.fields}:{},fromAgent:e,fromSessionId:_,priority:q.priority,expiresAt:q.expiresAt,...I?{relatesTo:I}:{}}}},s=R.HUB_AGENT_TYPES.has(String(await n(B)??""))?`${p(q)}\n\n${exports.zk}${await async function(e,t,r){if(!t)return""
10308
- try{const n=await e.proxyGet(`/api/projects/${t}/hub-digest/${encodeURIComponent(r)}`),i=String((n?.data??n)?.text??"")
10309
- return i?`\n${i}`:""}catch{return""}}(c,t.projectId,B)}`:p(q)
10320
+ try{const n=Intl.DateTimeFormat().resolvedOptions().timeZone,i=await e.proxyGet(`/api/projects/${t}/hub-digest/${encodeURIComponent(r)}?for=worker${n?`&tz=${encodeURIComponent(n)}`:""}`),o=String((i?.data??i)?.text??"")
10321
+ return o?`\n${o}`:""}catch{return""}}(c,t.projectId,B)}`:p(q)
10310
10322
  L=await i(B,s,0,r,o)?"\n\n\u{1f4ec} The target session has been notified.":"\n\n\u26a0\ufe0f Could not notify the target session (may be offline)."}return{content:[{type:"text",text:`\u2705 Question created!${L}${Q}${(0,N.namedRefsNote)(q?.content?.namedRefs)}${$?"\n\n\u21aa Sent to the user, not the Alchemist: a planner plans with the user, and design questions are theirs to answer.":""}\n\n**Message Code: ${q.messageCode}**\n\nUse this code to check for answers or share with other sessions.\n\n${a(q)}`}]}}function g(e,t){if(t?.briefChange){const r=t.briefChange
10311
10323
  return`Recorded on the brief ${e} (${(r.recorded??[]).join(", ")}). ${r.message??""}`.trim()}if(t?.points){const r=t.points
10312
- return`Recorded on the brief ${e}: ${(r.answered??[]).join(", ")}. Still open on its card, not answered and not taken for the user: ${(r.open??[]).join(", ")}.`}return"string"==typeof t?.lateAnswer?.message&&t.lateAnswer.message.trim()?`Recorded on ${e}: ${t.lateAnswer.message.trim()}`:`\u2705 Question answered! The asking session is resumed by the server with your answer.\n\n${a(t)}`}exports.zk=void 0,exports.handleSendQuestion=m,exports.handleSessionMessageOperations=async function(e,a,c,u,p,f){const Q=c.child({component:"session_message_operations"})
10324
+ return`Recorded on the brief ${e}: ${(r.answered??[]).join(", ")}. Still open on its card, not answered and not taken for the user: ${(r.open??[]).join(", ")}.`}return"string"==typeof t?.lateAnswer?.message&&t.lateAnswer.message.trim()?`Recorded on ${e}: ${t.lateAnswer.message.trim()}`:`\u2705 Question answered! The asking session is resumed by the server with your answer.\n\n${a(t)}`}exports.cL=exports.zk=void 0,exports.handleSendQuestion=m,exports.handleSessionMessageOperations=async function(e,a,c,u,p,f){const Q=c.child({component:"session_message_operations"})
10313
10325
  if(!p)throw new b.ValidationError("API client not available")
10314
10326
  const{operation:O}=e
10315
10327
  Q.info("Session message operation",{operation:O}),u.incrementCounter("session_message_operations",1,{operation:O})
@@ -10323,6 +10335,10 @@ return t.isError||(0,y.releaseSaveFirst)(await(0,y.callerSessionKey)(u)),t}if("r
10323
10335
  if(!p)throw new b.ValidationError("Report requires a summary")
10324
10336
  const x=s(0,0,"session_message_operations",0,d),S=await _.sessionIdentity.getSessionRequestId(c,a,x).catch(()=>null),Q=(0,w.requestNeedingAnchor)({summary:p,details:h},e,S)
10325
10337
  if(Q)throw new b.ValidationError((0,w.misfiledReportMessage)(Q))
10338
+ const D=await n(x),O=e.review&&"object"==typeof e.review&&"string"==typeof e.review.focus&&e.review.focus.trim()&&Array.isArray(e.review.mustFix)
10339
+ let M="reviewer"===D||"criteria-reviewer"===D
10340
+ if(!M&&!O&&D&&S&&r.projectId&&!["worker","alchemist","driver","planner","research"].includes(D))try{const e=await c.proxyGet(`/api/projects/${r.projectId}/requests/${S}/kind`),t=e?.data??e
10341
+ M="string"==typeof t?.checker&&t.checker.trim()===D}catch{}if(M&&!O)return{content:[{type:"text",text:`\u26d4 Not filed. ${exports.cL}`}],isError:!0}
10326
10342
  if("blocked"!==k&&"act"!==y)try{const e=await(0,I.getServerUrl)(r,a)
10327
10343
  if(e){const t=await fetch(`${e.replace(/\/$/,"")}/session/${x}/message?limit=12`,{signal:AbortSignal.timeout(5e3)})
10328
10344
  if(t.ok){const e=(0,$.answerEchoOf)(await t.json())
@@ -10336,30 +10352,30 @@ const u=r.workspaceManager?.getCurrentContext(r.currentSessionId)?.basePath||r.b
10336
10352
  let d=[],p=[]
10337
10353
  const h={requestId:c,owns:l,defaultBranch:"main",mainAheadBy:null,markerFiles:[],testRunning:!1,questionPending:!1}
10338
10354
  try{Object.assign(h,await(0,B.gitBlockFacts)(u,c,r.gitBinary))}catch(e){n.warn("blocked report: git facts unavailable",{error:(0,v.getErrorMessage)(e)})}try{const e=await i.proxyGet(`/api/projects/${a}/requests/${c}/wait-state`),r=e?.data??e??{}
10339
- h.testRunning=!0===r.runInFlight,h.questionPending=Array.isArray(r.pendingQuestionSessions)&&r.pendingQuestionSessions.includes(t),d=(Array.isArray(r.openCardCodes)?r.openCardCodes:Array.isArray(r.userQuestionCodes)?r.userQuestionCodes:[]).filter(e=>"string"==typeof e),Array.isArray(r.userParkCodes)&&(p=r.userParkCodes.filter(e=>"string"==typeof e))}catch{}const f=`${e.summary??""}\n${e.details??""}`,g=(0,B.disproveBlock)(f,h)
10340
- if(g.length>0)return n.info("blocked report disproved from the record; not filed",{sessionId:t,requestId:c,disproofs:g.length}),{content:[{type:"text",text:`\u26d4 Not filed: the record says this does not block you.\n\n${g.map(e=>`- ${e}`).join("\n")}\n\n${(0,B.renderBlockFacts)(h)}`}]}
10341
- const A=(0,B.parseBlockedOn)(e.blockedOn)
10342
- if(!A)return{content:[{type:"text",text:`\u26d4 Not filed. ${B.BLOCKED_ON_HELP}\n\n${(0,B.renderBlockFacts)(h)}`}],isError:!0}
10343
- if("request"===A.kind)return A.requestId===c?{content:[{type:"text",text:`\u26d4 Not filed: ${c} cannot wait on itself. ${B.BLOCKED_ON_HELP}`}],isError:!0}:(await i.proxyPost(`/api/projects/${a}/requests/${c}/waits`,{waitsOn:A.requestId,reason:String(e.summary).slice(0,300),sessionId:t}),{content:[{type:"text",text:`\u23f3 Wait declared: ${c} waits on ${A.requestId} (${String(e.summary).slice(0,120)}). This is a registered wait: you are idle by design until ${A.requestId} settles, and you will be told the moment it does. End your turn now; do nothing else on ${c}.`}]})
10344
- const y=(0,B.cardsToWaitOn)(A,d)
10345
- if("refused"in y)return{content:[{type:"text",text:`\u26d4 Not filed: ${y.refused} ${B.BLOCKED_ON_HELP}`}],isError:!0}
10346
- if(y.cards.length>0){await o.createReport({fromSessionId:t,status:"blocked",attention:"act",summary:String(e.summary??"").slice(0,2e3),...e.details?{details:String(e.details)}:{},requestId:c})
10355
+ h.testRunning=!0===r.runInFlight,h.questionPending=Array.isArray(r.pendingQuestionSessions)&&r.pendingQuestionSessions.includes(t),d=(Array.isArray(r.openCardCodes)?r.openCardCodes:Array.isArray(r.userQuestionCodes)?r.userQuestionCodes:[]).filter(e=>"string"==typeof e),Array.isArray(r.userParkCodes)&&(p=r.userParkCodes.filter(e=>"string"==typeof e))}catch{}const f=(0,B.disproveBlock)(h)
10356
+ if(f.length>0)return n.info("blocked report disproved from the record; not filed",{sessionId:t,requestId:c,disproofs:f.length}),{content:[{type:"text",text:`\u26d4 Not filed: the record says this does not block you.\n\n${f.map(e=>`- ${e}`).join("\n")}\n\n${(0,B.renderBlockFacts)(h)}`}]}
10357
+ const g=(0,B.parseBlockedOn)(e.blockedOn)
10358
+ if(!g)return{content:[{type:"text",text:`\u26d4 Not filed. ${B.BLOCKED_ON_HELP}\n\n${(0,B.renderBlockFacts)(h)}`}],isError:!0}
10359
+ if("request"===g.kind)return g.requestId===c?{content:[{type:"text",text:`\u26d4 Not filed: ${c} cannot wait on itself. ${B.BLOCKED_ON_HELP}`}],isError:!0}:(await i.proxyPost(`/api/projects/${a}/requests/${c}/waits`,{waitsOn:g.requestId,reason:String(e.summary).slice(0,300),sessionId:t}),{content:[{type:"text",text:`\u23f3 Wait declared: ${c} waits on ${g.requestId} (${String(e.summary).slice(0,120)}). This is a registered wait: you are idle by design until ${g.requestId} settles, and you will be told the moment it does. End your turn now; do nothing else on ${c}.`}]})
10360
+ const A=(0,B.cardsToWaitOn)(g,d)
10361
+ if("refused"in A)return{content:[{type:"text",text:`\u26d4 Not filed: ${A.refused} ${B.BLOCKED_ON_HELP}`}],isError:!0}
10362
+ if(A.cards.length>0){await o.createReport({fromSessionId:t,status:"blocked",attention:"act",summary:String(e.summary??"").slice(0,2e3),...e.details?{details:String(e.details)}:{},requestId:c})
10347
10363
  let r=!0
10348
- try{await i.proxyPatch(`/api/projects/${a}/cli-sessions/${encodeURIComponent(t)}`,{waitingOn:{kind:"card",messageCodes:y.cards,requestId:c,since:(new Date).toISOString()}})}catch(e){r=!1,n.warn("blocked on a card: the wait was not declared",{sessionId:t,requestId:c,cards:y.cards,error:(0,v.getErrorMessage)(e)})}return{content:[{type:"text",text:r?`\u23f3 Wait declared on the user's open card ${y.cards.join(", ")} on ${c}; your words were posted beside it. No second question was filed. You are resumed with the answer when it is settled. End your turn now.`:`\u{1f4ec} Posted to the user on ${c}, beside their open card ${y.cards.join(", ")}; no second question was filed. The wait could not be recorded. End your turn.`}]}}const w={toSessionId:"parent",questionType:"free-form",priority:"high",expiresInHours:1,questionContent:{question:`${c} is blocked on a decision: ${String(e.summary).slice(0,400)}`,context:`[blocked on a decision, from the worker of ${c}] ${String(e.details??"").slice(0,1500)}\n\nIf you cannot decide this, do not acknowledge it: let it escalate to the user.`,onExpiry:"escalate"}},b=await m({...w,requestId:c,forceAsk:!0,keepsOpen:!0},r,n,i,o,s)
10349
- return{...b,content:[...b.content,{type:"text",text:"\n\n\u23f8 Filed as a QUESTION to your parent (a registered wait), not a report: it must be answered, and it escalates to the user if the parent cannot. You will get the answer as a message. Meanwhile work on anything not blocked, then end your turn."}]}}(e,x,r,a,c,u,d)
10350
- if(t)return t}}const D=await(async()=>{if(e.toSessionId&&"parent"!==e.toSessionId)return a.info("Using explicit toSessionId for report",{parentSessionId:e.toSessionId}),(0,T.asExternal)(e.toSessionId)
10364
+ try{await i.proxyPatch(`/api/projects/${a}/cli-sessions/${encodeURIComponent(t)}`,{waitingOn:{kind:"card",messageCodes:A.cards,requestId:c,since:(new Date).toISOString()}})}catch(e){r=!1,n.warn("blocked on a card: the wait was not declared",{sessionId:t,requestId:c,cards:A.cards,error:(0,v.getErrorMessage)(e)})}return{content:[{type:"text",text:r?`\u23f3 Wait declared on the user's open card ${A.cards.join(", ")} on ${c}; your words were posted beside it. No second question was filed. You are resumed with the answer when it is settled. End your turn now.`:`\u{1f4ec} Posted to the user on ${c}, beside their open card ${A.cards.join(", ")}; no second question was filed. The wait could not be recorded. End your turn.`}]}}const y={toSessionId:"parent",questionType:"free-form",priority:"high",expiresInHours:1,questionContent:{question:`${c} is blocked on a decision: ${String(e.summary).slice(0,400)}`,context:`[blocked on a decision, from the worker of ${c}] ${String(e.details??"").slice(0,1500)}\n\nIf you cannot decide this, do not acknowledge it: let it escalate to the user.`,onExpiry:"escalate"}},w=await m({...y,requestId:c,forceAsk:!0,keepsOpen:!0},r,n,i,o,s)
10365
+ return{...w,content:[...w.content,{type:"text",text:"\n\n\u23f8 Filed as a QUESTION to your parent (a registered wait), not a report: it must be answered, and it escalates to the user if the parent cannot. You will get the answer as a message. Meanwhile work on anything not blocked, then end your turn."}]}}(e,x,r,a,c,u,d)
10366
+ if(t)return t}}const P=await(async()=>{if(e.toSessionId&&"parent"!==e.toSessionId)return a.info("Using explicit toSessionId for report",{parentSessionId:e.toSessionId}),(0,T.asExternal)(e.toSessionId)
10351
10367
  if("parent"===e.toSessionId)return await o(x,c,a)||void a.warn('toSessionId "parent" but no parent is recorded; filing target-less',{sessionId:x})
10352
10368
  const t=await o(x,c,a)
10353
10369
  if(t)return a.info("Auto-resolved parent session",{parentSessionId:t}),t
10354
10370
  a.info("No parent session; filing report as broadcast",{sessionId:x})})()
10355
- a.info("Submitting report",{sessionId:x,parentSessionId:D,status:k})
10356
- const O=await u.createReport({fromSessionId:x,toSessionId:D,status:k,summary:p,details:h,...f?{relatesTo:f}:{},...y?{attention:y}:{},...E?{reportKind:E}:{},..."string"==typeof e.requestId&&e.requestId.trim()?{requestId:e.requestId.trim()}:"string"==typeof e.area&&e.area.trim()?{area:e.area.trim()}:{},...Array.isArray(e.ruleVerdicts)&&e.ruleVerdicts.length?{extraContent:{ruleVerdicts:e.ruleVerdicts}}:{}}),M=await n(x)??"agent",P=O.content,j="string"==typeof P?.requestId?P.requestId:"request"===P?.anchor?.type&&"string"==typeof P.anchor.id?P.anchor.id:void 0,F={alchemy:{kind:"worker-report",payload:{status:k,summary:p,details:h,fromAgent:M,fromSessionId:x,...f?{relatesTo:f}:{},...j&&/^req\d+$/.test(j)?{requestId:j}:{}}}},q=!(!e.toSessionId||"parent"===e.toSessionId),L=D??(await o(x,c,a).catch(()=>null)||void 0),U=L?await n(L):null
10357
- let z=!1
10371
+ a.info("Submitting report",{sessionId:x,parentSessionId:P,status:k})
10372
+ const j=await u.createReport({fromSessionId:x,toSessionId:P,status:k,summary:p,details:h,...f?{relatesTo:f}:{},...y?{attention:y}:{},...E?{reportKind:E}:{},..."string"==typeof e.requestId&&e.requestId.trim()?{requestId:e.requestId.trim()}:"string"==typeof e.area&&e.area.trim()?{area:e.area.trim()}:{},...Array.isArray(e.ruleVerdicts)&&e.ruleVerdicts.length||e.review&&"object"==typeof e.review?{extraContent:{...Array.isArray(e.ruleVerdicts)&&e.ruleVerdicts.length?{ruleVerdicts:e.ruleVerdicts}:{},...e.review&&"object"==typeof e.review?{review:e.review}:{}}}:{}}),F=D??"agent",q=j.content,L="string"==typeof q?.requestId?q.requestId:"request"===q?.anchor?.type&&"string"==typeof q.anchor.id?q.anchor.id:void 0,U={alchemy:{kind:"worker-report",payload:{status:k,summary:p,details:h,fromAgent:F,fromSessionId:x,...f?{relatesTo:f}:{},...L&&/^req\d+$/.test(L)?{requestId:L}:{}}}},z=!(!e.toSessionId||"parent"===e.toSessionId),H=P??(await o(x,c,a).catch(()=>null)||void 0),V=H?await n(H):null
10373
+ let G=!1
10358
10374
  try{const e=await _.sessionIdentity.getSessionRequestId(c,a,x),n=r.projectId
10359
10375
  if(e&&n){const r=await c.proxyGet(`/api/projects/${n}/requests/${e}`).catch(()=>null),i=(r?.data??r)?.type,{kindProfile:o}=await Promise.resolve().then(()=>t(87780))
10360
- z=!!i&&"settle"===o(i).finalize&&"setup"===o(i).kind}}catch{}const H=(0,R.decideParentWake)({attention:y,explicitTarget:q,silent:g,hasParent:!!L,recipientAgentType:U,senderSettles:z}),V=!(!H.notify||!L)&&await i(L,function(e,t="agent"){const r=e.content||{}
10361
- return(0,C.formatReportNotification)({messageCode:e.messageCode,status:r.reportStatus,summary:r.summary,details:r.details,fromSessionId:e.fromSessionId,senderAgent:t})}(O,M),0,a,F)
10362
- return a.info("report routing",{messageCode:O.messageCode,attention:y,recipientAgentType:U,reason:H.reason,notified:V}),{content:[{type:"text",text:`\u2705 Report submitted.${(0,R.describeParentWake)(H,V)}${"blocked"===String(O.reportStatus??O.status??"")?"\n\n\u23f8 A blocked report registers no wait: the request is still yours and still watched. An acknowledgement from your parent is not a decision. If the block is a merge conflict, complete the request and the markers come to you. If you need a decision, ask a question (it registers a wait); otherwise keep working.":""}${(0,N.namedRefsNote)(O?.content?.namedRefs)}\n\n${l(O)}`}]}}(e,r,a,c,p,u)
10376
+ G=!!i&&"settle"===o(i).finalize&&"setup"===o(i).kind}}catch{}const Z=(0,R.decideParentWake)({attention:y,explicitTarget:z,silent:g,hasParent:!!H,recipientAgentType:V,senderSettles:G}),W=!(!Z.notify||!H)&&await i(H,function(e,t="agent"){const r=e.content||{}
10377
+ return(0,C.formatReportNotification)({messageCode:e.messageCode,status:r.reportStatus,summary:r.summary,details:r.details,fromSessionId:e.fromSessionId,senderAgent:t})}(j,F),0,a,U)
10378
+ return a.info("report routing",{messageCode:j.messageCode,attention:y,recipientAgentType:V,reason:Z.reason,notified:W}),{content:[{type:"text",text:`\u2705 Report submitted.${(0,R.describeParentWake)(Z,W)}${"blocked"===String(j.reportStatus??j.status??"")?"\n\n\u23f8 A blocked report registers no wait: the request is still yours and still watched. An acknowledgement from your parent is not a decision. If the block is a merge conflict, complete the request and the markers come to you. If you need a decision, ask a question (it registers a wait); otherwise keep working.":""}${(0,N.namedRefsNote)(j?.content?.namedRefs)}\n\n${l(j)}`}]}}(e,r,a,c,p,u)
10363
10379
  throw new b.ValidationError(`Unknown message type: ${d}. Use "question" or "report".`)}(e,a,Q,p,f),r("send",e))
10364
10380
  case"note":return(0,E.withAlchemyKind)(await async function(e,t,r,n,i){const o=n.getSessionMessageClient()
10365
10381
  if(!o)throw new b.ValidationError("Session message client not available")
@@ -10379,7 +10395,7 @@ const c=o.getSessionMessageClient()
10379
10395
  if(!c)throw new b.ValidationError("Session message client not available")
10380
10396
  if(a.startsWith("Q-")){const{text:t,selectedOptions:n,notes:i,quote:o,sectionAnswers:l}=e,u=s?.sessionId
10381
10397
  if(!u)throw new b.ValidationError("Cannot answer: this call carries no session identity.")
10382
- if(!0===e.escalate){const t=await c.escalateQuestion(a,{escalatedBySessionId:u,notes:i,...Array.isArray(n)&&"string"==typeof n[0]?{recommended:n[0]}:{},...Array.isArray(e.questionContent?.options)?{options:e.questionContent.options}:{}}),{escalatedTo:r}=t
10398
+ if(!0===e.escalate){const t=await c.escalateQuestion(a,{escalatedBySessionId:u,notes:i,...Array.isArray(n)&&"string"==typeof n[0]?{recommended:n[0]}:{},...Array.isArray(e.questionContent?.options)?{options:e.questionContent.options}:{},..."string"==typeof e.sameAs&&e.sameAs.trim()?{sameAs:e.sameAs.trim()}:{}}),{escalatedTo:r}=t
10383
10399
  return t.takenOnDefault?{content:[{type:"text",text:`\u2705 Question ${a} is answered with your recommendation, "${t.taken}": the request is Self-driving, so the user is not asked. The worker goes on with it, and the user can still change it from its card (${r}). Tell the user in one sentence what was decided, if they are talking to you.`}]}:{content:[{type:"text",text:`\u2934\ufe0f Question ${a} escalated to the user as **${r}**.\n\nYour recommendation${i?" was attached":" was not attached (none given)"}. The original asker keeps working and will receive the user's answer directly: you are done with this question.`}]}}return r.info("Answering question",{messageCode:a,respondedBySessionId:u,quoted:!!o}),{content:[{type:"text",text:g(a,await c.answerQuestion(a,{respondedBySessionId:u,text:t,selectedOptions:n,notes:i,...o?{quote:o}:{},...l&&"object"==typeof l?{sectionAnswers:l}:{},..."number"==typeof e.amount?{amount:e.amount}:{}}))}]}}if(a.startsWith("R-")){r.info("Acknowledging report",{messageCode:a})
10384
10400
  const t="string"==typeof e.text&&e.text.trim()?e.text.trim():void 0,o=await c.acknowledgeReport(a,t)
10385
10401
  let u=""
@@ -10458,7 +10474,7 @@ const o=await i.getMessage(n)
10458
10474
  return o?{content:[{type:"text",text:d(o)}]}:{content:[{type:"text",text:`\u274c Message with code ${n} not found.`}],isError:!0}}(e,Q,p),"get-message")
10459
10475
  default:throw new b.ValidationError(`Unknown operation: ${O}. Valid operations: send, reply, list, get, offer-handoff, record-decision, brief`)}}catch(e){return Q.error("Session message operation failed",{operation:O,error:(0,v.getErrorMessage)(e)}),{content:[{type:"text",text:`\u274c Error: ${(0,v.getErrorMessage)(e)}`}],isError:!0}}}
10460
10476
  const A=t(87780),y=t(64545),w=t(70508),b=t(59860),v=t(58824),I=t(47617),E=t(82272),C=t(67904),k=t(22316),x=t(22746),S=t(4871),_=t(6688),B=t(69883),R=t(69048),$=t(34221),Q=t(39844),T=t(43545),D=t(87280),O=t(29729),N=t(19670),M=t(53847),P=t(62032)
10461
- exports.zk="A worker asks you this while it builds. Answer from what the user said and what the record holds: reply with your answer (quote the user's words when they settle it). If the answer changes what the request asks, update its criteria first (request_operations update). How the work gets done (tools, environments, installs, where a command runs) is yours to answer. If only the user can decide (what is built, its scope, something hard to undo), reply with escalate: true, selectedOptions set to your recommendation and why in notes; it must be safe to happen without them. When the question has no options, give the ways forward in questionContent.options."},22746(e,exports,t){"use strict"
10477
+ exports.zk="A worker asks you this while it builds. Answer from what the user said and what the record holds (quote the user's words when they settle it). If the answer changes what the request asks, update its criteria first (request_operations update). How the work gets done (tools, environments, installs, where a command runs) is yours to answer. If only the user can decide (what is built, its scope, something hard to undo), reply with escalate: true, selectedOptions set to your recommendation and why in notes; it must be safe to happen without them. When the question has no options, give the ways forward in questionContent.options.",exports.cL='A reviewer\'s report carries its findings in the review field: { focus: "<your focus as your brief names it>", mustFix: ["<file:line> -- <what> -- <how to fix>", ...], niceToHave: [...], outOfScope: [...] }, each finding one line, [] when there is none. The delivery review adds escalate: "<one line to the user: why you cannot pass it, then what is theirs to decide>" only when it takes the request to them. Keep the markdown summary as the readable report, and send it again with the field.'},22746(e,exports,t){"use strict"
10462
10478
  exports.runSyncHold=async function(e){const{client:t,logger:n,messageCode:i,holdMs:o}=e,s=Date.now()+o,a=async e=>{try{await t.stampHoldOutcome(i,{holdDelivered:!0,clearHold:!0})}catch(e){n.warn("Hold claim failed; the arbiter may also inject this answer",{messageCode:i,error:(0,r.getErrorMessage)(e)})}return{kind:"answered",message:e}}
10463
10479
  let c=!1
10464
10480
  for(;Date.now()<s;){await new Promise(e=>setTimeout(e,3e3))
@@ -10497,77 +10513,82 @@ return{content:[{type:"text",text:`${s(o(h))} ended: ${e.outcome}. ${n} Your wor
10497
10513
  const n=t(58824),i=e=>e&&"object"==typeof e&&"data"in e?e.data:e,o=e=>e.name||"the setup",s=e=>e.charAt(0).toUpperCase()+e.slice(1)},44973(e,exports,t){"use strict"
10498
10514
  exports.def=void 0
10499
10515
  const r=t(76183),n=t(4592)
10500
- exports.def={name:"planner_ops",description:"Planning sessions. When asked to plan, design or architect, use request here; the planner files the plan's requests, not you.\n\n\u2022 request: prompt; optional description, userRequested, userStarts. To refine an existing plan pass its documentId (never describe the plan only in prose): its planner resumes, or a new one starts on it.\n\u2022 end: sessionId or documentId. Call it when the user leaves planning; never say planning is closed without it.\n\u2022 check-status: sessionId.\n\u2022 list: optional activeOnly, limit.\n\nuserRequested: the planner starts at once. Unset for planning you initiate: the project may route the choice to the user, and nothing runs until they choose, so never say planning has started. userStarts: the Plan ready card waits for their Start Building; the result says who starts it.",schema:r.PlannerOpsSchema,annotations:{title:"Planner Operations",readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1},handler:(e,t)=>(0,n.handlePlannerOps)(e,t.context,t.logger,t.metrics,t.apiClient??void 0,t.cliOrigin)}},4592(e,exports,t){"use strict"
10501
- exports.handlePlannerOps=async function(e,t,h,f,m,g){const A=h.child({tool:"planner_ops"}),y=f.startTimer("tool_execution_time")
10502
- try{const h=e,{operation:w,sessionId:b}=h
10503
- let v
10504
- switch(A.info("Processing planner_ops",{operation:w,sessionId:b}),w){case"request":{const e=!0===h.userRequested
10505
- if(h.prompt){const e=await(0,u.userWordsOfCall)(t.mainBasePath||t.basePath,g).catch(()=>[]),r=(0,u.userWordsBlock)(e)
10506
- r&&!h.prompt.includes("THE USER'S OWN WORDS")&&(h.prompt=`${h.prompt}${r}`)}const p=await async function(e,t,r){if(!e)return"ask"
10507
- try{const i=r?.sessionId||a.sessionIdentity.getIdentity()?.sessionId
10516
+ exports.def={name:"planner_ops",description:"Planning sessions. When asked to plan, design or architect, use request here; the planner files the plan's requests, not you.\n\n\u2022 request: prompt; optional description, userRequested, userStarts. To refine an existing plan pass its documentId (never describe the plan only in prose): its planner resumes, or a new one starts on it. To change what a planner still running is doing (the user now starts the plan, a changed ask), pass its sessionId: that planner gets the pass, never a second planner.\n\u2022 end: sessionId or documentId. Call it when the user leaves planning; never say planning is closed without it.\n\u2022 check-status: sessionId.\n\u2022 list: optional activeOnly, limit.\n\nuserRequested: the planner starts at once. Unset for planning you initiate: the project may route the choice to the user, and nothing runs until they choose, so never say planning has started. userStarts: the Plan ready card waits for their Start Building; the result says who starts it.",schema:r.PlannerOpsSchema,annotations:{title:"Planner Operations",readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1},handler:(e,t)=>(0,n.handlePlannerOps)(e,t.context,t.logger,t.metrics,t.apiClient??void 0,t.cliOrigin)}},4592(e,exports,t){"use strict"
10517
+ async function r(e,t,r){const n=await e.proxyGet(`/api/projects/${t}/cli-sessions/external/opencode/${encodeURIComponent(r)}`).catch(()=>null),i=n?.data??n
10518
+ return"planner"===i?.agentType&&"active"===i?.status}function n(e,t,r){return(0,s.deliverToSession)(e,`${r?d.USER_STARTS_RESUME_LINE:""}${t}`,{kind:"inserted-prompt"})}exports.handlePlannerOps=async function(e,t,s,m,g,A){const y=s.child({tool:"planner_ops"}),w=m.startTimer("tool_execution_time")
10519
+ try{const s=e,{operation:b,sessionId:v}=s
10520
+ let I
10521
+ switch(y.info("Processing planner_ops",{operation:b,sessionId:v}),b){case"request":{const e=!0===s.userRequested
10522
+ if(s.prompt){const e=await(0,p.userWordsOfCall)(t.mainBasePath||t.basePath,A).catch(()=>[]),r=(0,p.userWordsBlock)(e)
10523
+ r&&!s.prompt.includes("THE USER'S OWN WORDS")&&(s.prompt=`${s.prompt}${r}`)}const f=await async function(e,t,r){if(!e)return"ask"
10524
+ try{const n=r?.sessionId||l.sessionIdentity.getIdentity()?.sessionId
10525
+ if(!n)return"ask"
10526
+ const i=e.getCLISessionClient()
10508
10527
  if(!i)return"ask"
10509
- const o=e.getCLISessionClient()
10510
- if(!o)return"ask"
10511
- const s=await o.getByExternalId(i)
10528
+ const s=await i.getByExternalId(n)
10512
10529
  if(s?.workflowId){const t=await e.proxyGet(`/api/workflows/${s.workflowId}`),r=t?.data||t,n=r?.planningMode
10513
10530
  if("auto"===n)return"auto"
10514
10531
  if("ask"===n)return"ask"
10515
10532
  if("interactive"===n)return"interactive"}if(s?.projectId)try{const t=await e.proxyGet(`/api/projects/${s.projectId}`),r=t?.data||t,n=r?.metadata?.defaultPlanningMode
10516
10533
  if("auto"===n)return"auto"
10517
10534
  if("ask"===n)return"ask"
10518
- if("interactive"===n)return"interactive"}catch(e){t.warn("Failed to read project metadata for planningMode fallback",{error:(0,n.getErrorMessage)(e)})}return"ask"}catch(e){return t.warn("Failed to read planningMode from workflow, defaulting to ask",{error:(0,n.getErrorMessage)(e)}),"ask"}}(m,A,g),f=e?"auto":p,y=!0===h.userStarts,w="auto"===p&&!y
10519
- if("ask"===f||"interactive"===f){A.info("Returning planner choice pending",{planningMode:f})
10520
- try{const e=g?.sessionId||a.sessionIdentity.getIdentity()?.sessionId,t=m?.getSessionMessageClient()
10521
- if(t&&e){const r=await a.sessionIdentity.getParentSessionId(e,m,A),n=h.description||h.prompt
10522
- await t.createQuestion({fromSessionId:e,toSessionId:r||void 0,questionType:"alternatives",question:`Planning requested: ${n}`,options:[{id:"auto",label:"Auto Plan",description:"A planner agent writes the plan, and its Plan ready card asks you to start it (under Self-driving it starts on its own)"},{id:"manual",label:"Manual Plan",description:"You write the plan yourself in the plan editor"},{id:"cancel",label:"Skip",description:"No plan is made"}],context:{type:"planner-request",prompt:h.prompt,description:h.description},priority:"high"}),A.info("planner-request: notification created",{fromSessionId:e})}else A.warn("planner-request: skipping notification",{hasClient:!!t,hasCallerSessionId:!!e})}catch(e){A.warn("planner-request: failed to create notification",{error:(0,n.getErrorMessage)(e)})}v={content:[{type:"text",text:"Planning request submitted: nothing is running yet. The user must pick the planning approach (Auto Plan / Manual Plan / Skip) from a card in Steer before any planner starts. If you reply to the user, tell them the planning choice is waiting on them; do NOT say planning has started. (If the user themselves explicitly asked for this plan, pass userRequested: true to start it immediately.) This is non-blocking: continue with your other work."}],...(0,d.alchemyMeta)({pendingAction:{kind:"planner-choice",payload:{prompt:h.prompt,description:h.description}}})}
10523
- break}const b="planner"
10524
- let I=null
10525
- if(h.documentId){const e=await m.getDocumentClient().getDocument(h.documentId)
10526
- if(!e)throw new r.ValidationError(`Document not found: ${h.documentId}`)
10527
- I=e}let E=""
10528
- if(I)try{const e=I.localId||I.id
10529
- E=(0,s.linkedRequestsContext)(e,await(0,s.planLinkedRequests)(m.getRequestClient(),e))}catch(e){A.warn("Plan-linked requests unreadable for the planner context",{error:(0,n.getErrorMessage)(e)}),E="\n\nThe requests this plan already guides could not be read just now: read them with plan_read get-requests before filing anything."}const C=I?`\n\nYOU ARE PLANNING AN EXISTING DOCUMENT: ${I.localId||I.id} (${I.filename}). Read it FIRST with plan_read get, then evolve it in place with plan_write update/append/replace. Do NOT create a new plan document.${E}`:""
10530
- let k=""
10531
- if(I)try{const e=I.localId||I.id,r=await m.proxyGet(`/api/projects/${t.projectId}/session-plan-associations/by-plan/${encodeURIComponent(e)}`),n=Array.isArray(r)?r:r?.data??[]
10532
- let o=null
10533
- for(const e of n){const r=e.opencodeSessionId??e.opencode_session_id
10534
- if(!r)continue
10535
+ if("interactive"===n)return"interactive"}catch(e){t.warn("Failed to read project metadata for planningMode fallback",{error:(0,o.getErrorMessage)(e)})}return"ask"}catch(e){return t.warn("Failed to read planningMode from workflow, defaulting to ask",{error:(0,o.getErrorMessage)(e)}),"ask"}}(g,y,A),m=e?"auto":f,w=!0===s.userStarts,b="auto"===f&&!w
10536
+ if(s.sessionId){const e=s.sessionId
10537
+ if(!await r(g,t.projectId,e)){I={content:[{type:"text",text:`Not delivered: ${e} is not a planner that is still running. Leave sessionId out to start a planner.`}],isError:!0}
10538
+ break}const i=await n(e,s.prompt,w)
10539
+ if(!i.ok){I={content:[{type:"text",text:`Not delivered to planner ${e}: ${i.error??"the delivery failed"}. Nothing changed for it.`}],isError:!0}
10540
+ break}y.info("Planner pass delivered to the running planner",{sessionId:e,userStarts:w}),I={content:[{type:"text",text:`Delivered to the running planner ${e}; no new planner was started. ${w?"When the plan is ready, its Plan ready card waits for the user's Start Building; nothing starts before.":"It goes on with this pass in the background."}`}]}
10541
+ break}if("ask"===m||"interactive"===m){y.info("Returning planner choice pending",{planningMode:m})
10542
+ try{const e=A?.sessionId||l.sessionIdentity.getIdentity()?.sessionId,t=g?.getSessionMessageClient()
10543
+ if(t&&e){const r=await l.sessionIdentity.getParentSessionId(e,g,y),n=s.description||s.prompt
10544
+ await t.createQuestion({fromSessionId:e,toSessionId:r||void 0,questionType:"alternatives",question:`Planning requested: ${n}`,options:[{id:"auto",label:"Auto Plan",description:"A planner agent writes the plan, and its Plan ready card asks you to start it (under Self-driving it starts on its own)"},{id:"manual",label:"Manual Plan",description:"You write the plan yourself in the plan editor"},{id:"cancel",label:"Skip",description:"No plan is made"}],context:{type:"planner-request",prompt:s.prompt,description:s.description},priority:"high"}),y.info("planner-request: notification created",{fromSessionId:e})}else y.warn("planner-request: skipping notification",{hasClient:!!t,hasCallerSessionId:!!e})}catch(e){y.warn("planner-request: failed to create notification",{error:(0,o.getErrorMessage)(e)})}I={content:[{type:"text",text:"Planning request submitted: nothing is running yet. The user must pick the planning approach (Auto Plan / Manual Plan / Skip) from a card in Steer before any planner starts. If you reply to the user, tell them the planning choice is waiting on them; do NOT say planning has started. (If the user themselves explicitly asked for this plan, pass userRequested: true to start it immediately.) This is non-blocking: continue with your other work."}],...(0,h.alchemyMeta)({pendingAction:{kind:"planner-choice",payload:{prompt:s.prompt,description:s.description}}})}
10545
+ break}const v="planner"
10546
+ let E=null
10547
+ if(s.documentId){const e=await g.getDocumentClient().getDocument(s.documentId)
10548
+ if(!e)throw new i.ValidationError(`Document not found: ${s.documentId}`)
10549
+ E=e}let C=""
10550
+ if(E)try{const e=E.localId||E.id
10551
+ C=(0,c.linkedRequestsContext)(e,await(0,c.planLinkedRequests)(g.getRequestClient(),e))}catch(e){y.warn("Plan-linked requests unreadable for the planner context",{error:(0,o.getErrorMessage)(e)}),C="\n\nThe requests this plan already guides could not be read just now: read them with plan_read get-requests before filing anything."}const k=E?`\n\nYOU ARE PLANNING AN EXISTING DOCUMENT: ${E.localId||E.id} (${E.filename}). Read it FIRST with plan_read get, then evolve it in place with plan_write update/append/replace. Do NOT create a new plan document.${C}`:""
10552
+ let x=""
10553
+ if(E)try{const e=E.localId||E.id,i=await g.proxyGet(`/api/projects/${t.projectId}/session-plan-associations/by-plan/${encodeURIComponent(e)}`),o=Array.isArray(i)?i:i?.data??[]
10554
+ let a=null
10555
+ for(const e of o){const i=e.opencodeSessionId??e.opencode_session_id
10556
+ if(!i)continue
10535
10557
  if(e.endedAt??e.ended_at)continue
10536
- const n=await m.proxyGet(`/api/projects/${t.projectId}/cli-sessions/external/opencode/${encodeURIComponent(r)}`).catch(()=>null),s=n?.data??n
10537
- if("planner"!==s?.agentType||"active"!==s?.status)continue
10538
- const a=await(0,i.deliverToSession)(r,`${y?l.USER_STARTS_RESUME_LINE:""}${h.prompt}${E}`,{kind:"inserted-prompt"})
10539
- if(a.ok){o=r
10540
- break}A.warn("Planner resume not delivered",{sessionId:r,error:a.error,detail:a.detail})}if(o){A.info("Planner reuse-first: resumed existing planner",{sessionId:o,documentId:e}),v={content:[{type:"text",text:`Planning resumed in the EXISTING planner session ${o} on ${e} (reuse-first: no new planner spawned). It is working in the background -- continue with your other work; check with planner_ops check-status.${y?" Its Plan ready card waits for the user's Start Building.":""}`}]}
10541
- break}}catch(e){A.warn("Planner reuse check failed; falling through to spawn",{error:(0,n.getErrorMessage)(e)})}if(I)try{const e=I.localId||I.id,r=[]
10542
- for(const n of["planDocumentId","scheduleDocumentId"])for(let i=0;i<1e3;i+=100){const o=await m.proxyGet(`/api/projects/${t.projectId}/requests`,{[n]:e,limit:100,offset:i}),s=Array.isArray(o)?o:o?.data??[]
10558
+ if(!await r(g,t.projectId,i))continue
10559
+ const o=await n(i,`${s.prompt}${C}`,w)
10560
+ if(o.ok){a=i
10561
+ break}y.warn("Planner resume not delivered",{sessionId:i,error:o.error,detail:o.detail})}if(a){y.info("Planner reuse-first: resumed existing planner",{sessionId:a,documentId:e}),I={content:[{type:"text",text:`Planning resumed in the EXISTING planner session ${a} on ${e} (reuse-first: no new planner spawned). It is working in the background -- continue with your other work; check with planner_ops check-status.${w?" Its Plan ready card waits for the user's Start Building.":""}`}]}
10562
+ break}}catch(e){y.warn("Planner reuse check failed; falling through to spawn",{error:(0,o.getErrorMessage)(e)})}if(E)try{const e=E.localId||E.id,r=[]
10563
+ for(const n of["planDocumentId","scheduleDocumentId"])for(let i=0;i<1e3;i+=100){const o=await g.proxyGet(`/api/projects/${t.projectId}/requests`,{[n]:e,limit:100,offset:i}),s=Array.isArray(o)?o:o?.data??[]
10543
10564
  if(!Array.isArray(s))break
10544
10565
  if(r.push(...s),s.length<100)break}const n=Array.from(new Set(r.map(e=>e.localId||e.id).filter(e=>"string"==typeof e&&e))),i=[]
10545
10566
  for(let e=0;e<n.length;e+=24)i.push(n.slice(e,e+24))
10546
- const o=await Promise.all(i.map(e=>m.proxyGet(`/api/projects/${t.projectId}/decisions/for-requests`,{reqIds:e.join(",")}).catch(()=>null))),s=e=>String(e?.decidedBy??"").startsWith("user")?0:1,a=o.flatMap(e=>e?.decisions??e?.data?.decisions??[]).sort((e,t)=>s(e)-s(t)||(Date.parse(t?.decidedAt??"")||0)-(Date.parse(e?.decidedAt??"")||0))
10567
+ const o=await Promise.all(i.map(e=>g.proxyGet(`/api/projects/${t.projectId}/decisions/for-requests`,{reqIds:e.join(",")}).catch(()=>null))),s=e=>String(e?.decidedBy??"").startsWith("user")?0:1,a=o.flatMap(e=>e?.decisions??e?.data?.decisions??[]).sort((e,t)=>s(e)-s(t)||(Date.parse(t?.decidedAt??"")||0)-(Date.parse(e?.decidedAt??"")||0))
10547
10568
  if(a.length>0){const e=a.slice(0,15),t=a.length-e.length
10548
- k="\n\nSETTLED DECISIONS under this goal (human-authority first; do NOT re-litigate these -- if your plan contradicts one, follow the decision or surface the conflict explicitly):\n"+e.map(e=>`- [${e.decidedBy}] ${e.question} -> ${e.answer} (${e.requestId})`).join("\n")+(t>0?`\n- ...and ${t} more settled under this goal's ${n.length} requests (decisions search-decisions finds one before you decide against it).`:"")}}catch(e){A.warn("Settled-decisions context skipped (planner still spawns)",{error:(0,n.getErrorMessage)(e)})}A.info("Spawning planner",{planningMode:f,agent:b,flavor:w?"autonomous":"autonomous-plan/human-build",userRequested:e,userStarts:y,existingDoc:I?.localId}),v=await(0,c.handleSpawn)({agent:b,prompt:`${(0,l.buildAutonomousPlannerContext)({autoBuild:w})}${C}${k}${await(0,o.spendFactsContext)(m,t.projectId)}\n\n${h.prompt}`,title:h.description},t,A,m,g)
10549
- const x=v?._meta?.alchemy?.spawnedSessionId
10550
- if(!v.isError&&"string"==typeof x&&x&&(v={...v,_meta:{...v._meta,alchemy:{...v._meta?.alchemy,threadRef:`ses:${x}`}}}),I&&!v.isError){const e=v?._meta?.alchemy?.spawnedSessionId
10551
- if(e)try{await m.proxyPost(`/api/projects/${t.projectId}/session-plan-associations`,{opencodeSessionId:e,documentId:I.localId||I.id}),A.info("Planner bound to existing document",{spawnedSessionId:e,documentId:I.localId})}catch(e){A.warn("Planner-document binding failed (planner still runs; role derives once it edits/links)",{error:(0,n.getErrorMessage)(e)})}}if(!v.isError&&v.content.length>0){const e=v.content[0].text,t=w?"When the plan is ready its steps start on their own: this project builds plans without waiting for Start Building.":"When the plan is ready, its Plan ready card waits for the user's Start Building; nothing starts before."
10552
- v.content[0].text=`Planning is running in the background: continue with your other work. ${t}\n${e}`}break}case"end":{const e=[]
10553
- if(h.sessionId){const r=await m.proxyGet(`/api/projects/${t.projectId}/session-plan-associations/by-session/${encodeURIComponent(h.sessionId)}`).catch(()=>null)
10554
- for(const t of r?.data??r??[])t?.documentId&&e.push({sessionId:h.sessionId,documentId:t.documentId})
10555
- 0===e.length&&e.push({sessionId:h.sessionId,documentId:""})}else{if(!h.documentId)throw new r.ValidationError("'end' requires sessionId or documentId")
10556
- {const r=await m.proxyGet(`/api/projects/${t.projectId}/session-plan-associations/by-plan/${encodeURIComponent(h.documentId)}`).catch(()=>null)
10569
+ x="\n\nSETTLED DECISIONS under this goal (human-authority first; do NOT re-litigate these -- if your plan contradicts one, follow the decision or surface the conflict explicitly):\n"+e.map(e=>`- [${e.decidedBy}] ${e.question} -> ${e.answer} (${e.requestId})`).join("\n")+(t>0?`\n- ...and ${t} more settled under this goal's ${n.length} requests (decisions search-decisions finds one before you decide against it).`:"")}}catch(e){y.warn("Settled-decisions context skipped (planner still spawns)",{error:(0,o.getErrorMessage)(e)})}y.info("Spawning planner",{planningMode:m,agent:v,flavor:b?"autonomous":"autonomous-plan/human-build",userRequested:e,userStarts:w,existingDoc:E?.localId}),I=await(0,u.handleSpawn)({agent:v,prompt:`${(0,d.buildAutonomousPlannerContext)({autoBuild:b})}${k}${x}${await(0,a.spendFactsContext)(g,t.projectId)}\n\n${s.prompt}`,title:s.description},t,y,g,A)
10570
+ const S=I?._meta?.alchemy?.spawnedSessionId
10571
+ if(!I.isError&&"string"==typeof S&&S&&(I={...I,_meta:{...I._meta,alchemy:{...I._meta?.alchemy,threadRef:`ses:${S}`}}}),E&&!I.isError){const e=I?._meta?.alchemy?.spawnedSessionId
10572
+ if(e)try{await g.proxyPost(`/api/projects/${t.projectId}/session-plan-associations`,{opencodeSessionId:e,documentId:E.localId||E.id}),y.info("Planner bound to existing document",{spawnedSessionId:e,documentId:E.localId})}catch(e){y.warn("Planner-document binding failed (planner still runs; role derives once it edits/links)",{error:(0,o.getErrorMessage)(e)})}}if(!I.isError&&I.content.length>0){const e=I.content[0].text,t=b?"When the plan is ready its steps start on their own: this project builds plans without waiting for Start Building.":"When the plan is ready, its Plan ready card waits for the user's Start Building; nothing starts before."
10573
+ I.content[0].text=`Planning is running in the background: continue with your other work. ${t}\n${e}`}break}case"end":{const e=[]
10574
+ if(s.sessionId){const r=await g.proxyGet(`/api/projects/${t.projectId}/session-plan-associations/by-session/${encodeURIComponent(s.sessionId)}`).catch(()=>null)
10575
+ for(const t of r?.data??r??[])t?.documentId&&e.push({sessionId:s.sessionId,documentId:t.documentId})
10576
+ 0===e.length&&e.push({sessionId:s.sessionId,documentId:""})}else{if(!s.documentId)throw new i.ValidationError("'end' requires sessionId or documentId")
10577
+ {const r=await g.proxyGet(`/api/projects/${t.projectId}/session-plan-associations/by-plan/${encodeURIComponent(s.documentId)}`).catch(()=>null)
10557
10578
  for(const t of r?.data??r??[]){const r=t?.opencodeSessionId??t?.opencode_session_id
10558
- r&&e.push({sessionId:r,documentId:h.documentId})}}}let i=0
10559
- for(const r of e)r.documentId&&await m.proxyPost(`/api/projects/${t.projectId}/session-plan-associations/end`,{opencodeSessionId:r.sessionId,documentId:r.documentId}).catch(e=>A.warn("end association failed",{error:(0,n.getErrorMessage)(e)})),await m.proxyPatch(`/api/projects/${t.projectId}/cli-sessions/${encodeURIComponent(r.sessionId)}`,{status:"completed"}).catch(e=>A.warn("end session-status failed",{error:(0,n.getErrorMessage)(e)})),i++
10560
- v={content:[{type:"text",text:i>0?`Planning ended: ${i} planner session${1===i?"":"s"} closed${h.documentId?` on ${h.documentId}`:""}. Tell the user planning mode is closed only AFTER this call succeeds.`:"No active planning sessions matched; nothing to end."}]}
10561
- break}case"check-status":v=await(0,c.handleCheckStatus)(b,t,A,m)
10562
- break
10563
- case"list":const e=g?.sessionId||a.sessionIdentity.getIdentity()?.sessionId,[p,f]=await Promise.all([(0,c.handleListSessions)(t,A,m,{activeOnly:h.activeOnly,limit:h.limit,agentType:"auto-planner",parentSessionId:e}),(0,c.handleListSessions)(t,A,m,{activeOnly:h.activeOnly,limit:h.limit,agentType:"planner",parentSessionId:e})]),y=p.content[0]?.text??"",I=f.content[0]?.text??""
10564
- if(y.trim()||I.trim()){const e=[]
10565
- y.trim()&&e.push(`## Auto-planner sessions (legacy)\n${y}`),I.trim()&&e.push(`## Interactive plan sessions\n${I}`),v={content:[{type:"text",text:e.join("\n\n")}]}}else v={content:[{type:"text",text:"No planning sessions found."}]}
10566
- break
10567
- default:throw new r.ValidationError(`Unknown operation: ${w}`)}y({success:!0}),f.incrementCounter(`planner_ops_${w}_success`,1)
10568
- const I=p[w]??"planner-request"
10569
- return(0,d.withAlchemyKind)(v,I)}catch(e){return A.error("Planner ops failed",{error:e instanceof Error?e.stack:String(e)}),y({success:!1}),f.incrementCounter("planner_ops_errors",1),{content:[{type:"text",text:`Planner operation failed: ${(0,n.getErrorMessage)(e)}`}],isError:!0}}}
10570
- const r=t(59860),n=t(58824),i=t(39844),o=t(68320),s=t(53847),a=t(6688),c=t(51725),l=t(4871),u=t(62032),d=t(82272),p={request:"planner-request","check-status":"planner-check-status",list:"planner-list"}},48155(e,exports,t){"use strict"
10579
+ r&&e.push({sessionId:r,documentId:s.documentId})}}}let r=0
10580
+ for(const n of e)n.documentId&&await g.proxyPost(`/api/projects/${t.projectId}/session-plan-associations/end`,{opencodeSessionId:n.sessionId,documentId:n.documentId}).catch(e=>y.warn("end association failed",{error:(0,o.getErrorMessage)(e)})),await g.proxyPatch(`/api/projects/${t.projectId}/cli-sessions/${encodeURIComponent(n.sessionId)}`,{status:"completed"}).catch(e=>y.warn("end session-status failed",{error:(0,o.getErrorMessage)(e)})),await(0,u.handleAbort)(n.sessionId,t,y).catch(e=>y.warn("end abort failed",{sessionId:n.sessionId,error:(0,o.getErrorMessage)(e)})),r++
10581
+ I={content:[{type:"text",text:r>0?`Planning ended: ${r} planner session${1===r?"":"s"} closed${s.documentId?` on ${s.documentId}`:""}. Tell the user planning mode is closed only AFTER this call succeeds.`:"No active planning sessions matched; nothing to end."}]}
10582
+ break}case"check-status":I=await(0,u.handleCheckStatus)(v,t,y,g)
10583
+ break
10584
+ case"list":const e=A?.sessionId||l.sessionIdentity.getIdentity()?.sessionId,[f,m]=await Promise.all([(0,u.handleListSessions)(t,y,g,{activeOnly:s.activeOnly,limit:s.limit,agentType:"auto-planner",parentSessionId:e}),(0,u.handleListSessions)(t,y,g,{activeOnly:s.activeOnly,limit:s.limit,agentType:"planner",parentSessionId:e})]),w=f.content[0]?.text??"",E=m.content[0]?.text??""
10585
+ if(w.trim()||E.trim()){const e=[]
10586
+ w.trim()&&e.push(`## Auto-planner sessions (legacy)\n${w}`),E.trim()&&e.push(`## Interactive plan sessions\n${E}`),I={content:[{type:"text",text:e.join("\n\n")}]}}else I={content:[{type:"text",text:"No planning sessions found."}]}
10587
+ break
10588
+ default:throw new i.ValidationError(`Unknown operation: ${b}`)}w({success:!0}),m.incrementCounter(`planner_ops_${b}_success`,1)
10589
+ const E=f[b]??"planner-request"
10590
+ return(0,h.withAlchemyKind)(I,E)}catch(e){return y.error("Planner ops failed",{error:e instanceof Error?e.stack:String(e)}),w({success:!1}),m.incrementCounter("planner_ops_errors",1),{content:[{type:"text",text:`Planner operation failed: ${(0,o.getErrorMessage)(e)}`}],isError:!0}}}
10591
+ const i=t(59860),o=t(58824),s=t(39844),a=t(68320),c=t(53847),l=t(6688),u=t(51725),d=t(4871),p=t(62032),h=t(82272),f={request:"planner-request","check-status":"planner-check-status",list:"planner-list"}},48155(e,exports,t){"use strict"
10571
10592
  exports.def=void 0
10572
10593
  const r=t(76183),n=t(72966)
10573
10594
  exports.def={name:"research_fanout",description:'Kick off an advanced, multi-angle research pass. Fans out read-only workers in parallel across angles, gap-loops for what is missing, then adversarially verifies the findings and saves a "Research Report" document. Use it for broad or complex questions BEFORE drafting/implementing; skip it for small one-file work.\n\nParams (flat):\n\u2022 goal: what to research (required to START)\n\u2022 angles: optional research angles (omit for the default set)\n\u2022 planId: optional plan to reference the report from\n\u2022 checkRunId: optional: pass a prior runId to check that run live progress instead of starting\n\nNon-blocking: starting returns a runId immediately; you get a completion nudge with the findings when it finishes, or call again with checkRunId to poll progress.',schema:r.ResearchFanoutSchema,annotations:{title:"Research Fan-out",readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1},handler:(e,t)=>(0,n.handleResearchFanout)(e,t.context,t.logger,t.metrics,t.apiClient??void 0,t.cliOrigin)}},72966(e,exports,t){"use strict"
@@ -10588,30 +10609,30 @@ const r=t(58824),n=t(6688)},1540(e,exports,t){"use strict"
10588
10609
  exports.def=void 0
10589
10610
  const r=t(76183),n=t(51725),i=t(8955)
10590
10611
  exports.def={name:"subagent_operations",description:`WHEN TO USE spawn (agent: coder): any change touching 2+ files, feature work, bug fixes, or anything needing acceptance-criteria verification.\n\nManage background subagents that work independently.\n\nOperations:\n\u2022 spawn - Create a new detached subagent to work in the background. Include a short 'description' for display in the UI.\n\u2022 continue - Send new instructions/feedback to an existing subagent session\n\u2022 check-status - Check if a subagent is idle or busy\n\u2022 abort - Stop a running subagent\n\u2022 read-messages - Read a subagent's messages. Default: the latest message in full (its text AND tool calls), skipping trailing empty turns so you always get the substantive result. Add compact:true for a one-line index of EVERY message with tool-call markers (its trajectory: what it actually did), then select:N to pull one message in full ({tool, input, status, output} per step).\n\nThe spawned subagent runs as a separate OpenCode process.\n\nAvailable agents: ${(0,i.formatAgentsForDescription)()}`,schema:r.SubagentOperationsSchema,annotations:{title:"Subagent Operations",readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1},handler:(e,t)=>(0,n.handleSubagentOperations)(e,t.context,t.logger,t.metrics,t.apiClient??void 0,t.cliOrigin)}},51725(e,exports,t){"use strict"
10591
- function r(e,t,r){if(x)try{const n=`[${(new Date).toISOString()}] ${t}${r?" "+JSON.stringify(r):""}\n`,i=(0,I.join)(e.basePath||process.cwd(),".alchemy_lab","spawn-debug.log");(0,v.appendFileSync)(i,n)}catch{}}async function n(e,r,n,i,o,s){if(!(e&&r&&n&&i))return null
10612
+ function r(e,t,r){if(S)try{const n=`[${(new Date).toISOString()}] ${t}${r?" "+JSON.stringify(r):""}\n`,i=(0,E.join)(e.basePath||process.cwd(),".alchemy_lab","spawn-debug.log");(0,I.appendFileSync)(i,n)}catch{}}async function n(e,r,n,i,o,s){if(!(e&&r&&n&&i))return null
10592
10613
  try{const o=await e.proxyPost(`/api/projects/${r}/requests/${n}/pickup-compose`,{sessionId:i}),a=o?.data?.text??o?.text,{withInFlightFiles:c}=await t.e(912).then(()=>t(47912)),l=await c(s||process.cwd(),"string"==typeof a&&a.trim()?a.trim():null,o?.data??o)
10593
10614
  if(l&&/Project map/.test(l))return l
10594
10615
  let u=null
10595
10616
  try{const{generateProjectMap:e}=await t.e(356).then(()=>t.t(69356,23))
10596
- u=e(s||process.cwd())}catch{u=null}return l||u?[l,u].filter(Boolean).join("\n\n"):null}catch(e){return o.warn("pickup-compose failed; the association hook will deliver guidance as an inject",{requestId:n,error:(0,b.getErrorMessage)(e)}),null}}async function i(e){const{apiClient:t,fromSessionId:r,toSessionId:n,body:i,requestId:o,logger:s}=e
10617
+ u=e(s||process.cwd())}catch{u=null}return l||u?[l,u].filter(Boolean).join("\n\n"):null}catch(e){return o.warn("pickup-compose failed; the association hook will deliver guidance as an inject",{requestId:n,error:(0,v.getErrorMessage)(e)}),null}}async function i(e){const{apiClient:t,fromSessionId:r,toSessionId:n,body:i,requestId:o,logger:s}=e
10597
10618
  if(!t||!r)return
10598
10619
  const a=t.getSessionMessageClient()
10599
10620
  if(a)try{const e=await a.createInstruction({fromSessionId:r,toSessionId:n,body:i,...o?{requestId:o}:{}})
10600
- return e?.messageCode}catch(e){return void s.warn("Failed to mint instruction code",{error:(0,b.getErrorMessage)(e),fromSessionId:r,toSessionId:n})}}async function o(e,t,r,n,i){if(e&&t&&r)try{await e.proxyPost(`/api/projects/${t}/session-messages/${encodeURIComponent(r)}/delivered`,{ok:n.ok,error:n.detail||n.error,messageId:n.messageId})}catch(e){i.warn("instruction delivery receipt not written",{instructionCode:r,error:(0,b.getErrorMessage)(e)})}}async function s(e,t,s,a,c){const{agent:l="work",prompt:u,requestId:d,allowParallel:p=!1,useExistingSession:h}=e
10601
- if(r(t,"handleSpawn called",{agent:l,promptLength:u.length,requestId:d,useExistingSession:h,cliOriginSessionId:c?.sessionId||null}),!(0,E.isValidAgent)(l))return{content:[{type:"text",text:`\u274c Unknown agent '${l}'.\n\n**Available agents:** ${(0,E.getAgentNames)().join(", ")}\n\nChoose one of the available agents listed above to see detailed descriptions of each agent.`}],isError:!0}
10602
- const v=c?.sessionId,I=v?y.sessionIdentity.getAgentTypeFor(v).then(e=>e||void 0):Promise.resolve(void 0),k=async function(e){try{const t=`http://localhost:${B}/api/agents/by-name/${encodeURIComponent(e)}`,r=await fetch(t,{signal:AbortSignal.timeout(8e3)})
10621
+ return e?.messageCode}catch(e){return void s.warn("Failed to mint instruction code",{error:(0,v.getErrorMessage)(e),fromSessionId:r,toSessionId:n})}}async function o(e,t,r,n,i){if(e&&t&&r)try{await e.proxyPost(`/api/projects/${t}/session-messages/${encodeURIComponent(r)}/delivered`,{ok:n.ok,error:n.detail||n.error,messageId:n.messageId})}catch(e){i.warn("instruction delivery receipt not written",{instructionCode:r,error:(0,v.getErrorMessage)(e)})}}async function s(e,t,s,a,c){const{agent:l="work",prompt:u,requestId:d,allowParallel:p=!1,useExistingSession:h}=e
10622
+ if(r(t,"handleSpawn called",{agent:l,promptLength:u.length,requestId:d,useExistingSession:h,cliOriginSessionId:c?.sessionId||null}),!(0,C.isValidAgent)(l))return{content:[{type:"text",text:`\u274c Unknown agent '${l}'.\n\n**Available agents:** ${(0,C.getAgentNames)().join(", ")}\n\nChoose one of the available agents listed above to see detailed descriptions of each agent.`}],isError:!0}
10623
+ const f=c?.sessionId,I=f?w.sessionIdentity.getAgentTypeFor(f).then(e=>e||void 0):Promise.resolve(void 0),E=async function(e){try{const t=`http://localhost:${R}/api/agents/by-name/${encodeURIComponent(e)}`,r=await fetch(t,{signal:AbortSignal.timeout(8e3)})
10603
10624
  if(!r.ok)return null
10604
10625
  const n=await r.json()
10605
- return n.success?n.data??null:null}catch{return null}}(l).catch(()=>null),S=(0,g.getServerUrl)(t,s),_=(async()=>{if(!a||!d)return null
10626
+ return n.success?n.data??null:null}catch{return null}}(l).catch(()=>null),x=(0,A.getServerUrl)(t,s),_=(async()=>{if(!a||!d)return null
10606
10627
  const e=a.getSessionRequestAssociationClient()
10607
- return e?await e.getSessionsForRequest(d,!0):null})().catch(e=>(s.debug("Failed to check for existing workers",{error:(0,b.getErrorMessage)(e)}),null)),[R,$,Q,T]=await Promise.all([I,k,S,_])
10608
- try{if(r(t,"Spawn agent type check",{callerSessionId:v||null,callerAgentType:R||null,pid:process.pid}),R){const e=(0,E.getAllowedSubagents)(R)
10609
- if(e&&!e.includes(l))return{content:[{type:"text",text:`\u274c Agent type '${R}' cannot spawn '${l}'.\n\n**Allowed subagents for ${R}:** ${e.join(", ")}\n\nUse one of the allowed agent types instead.`}],isError:!0}}}catch(e){r(t,"allowedSubagents check failed",{error:(0,b.getErrorMessage)(e)})}let D=$?.requestContextMode
10610
- if(!D){const e=(0,E.getBuiltInAgentConfig)(l)
10628
+ return e?await e.getSessionsForRequest(d,!0):null})().catch(e=>(s.debug("Failed to check for existing workers",{error:(0,v.getErrorMessage)(e)}),null)),[B,$,Q,T]=await Promise.all([I,E,x,_])
10629
+ try{if(r(t,"Spawn agent type check",{callerSessionId:f||null,callerAgentType:B||null,pid:process.pid}),B){const e=(0,C.getAllowedSubagents)(B)
10630
+ if(e&&!e.includes(l))return{content:[{type:"text",text:`\u274c Agent type '${B}' cannot spawn '${l}'.\n\n**Allowed subagents for ${B}:** ${e.join(", ")}\n\nUse one of the allowed agent types instead.`}],isError:!0}}}catch(e){r(t,"allowedSubagents check failed",{error:(0,v.getErrorMessage)(e)})}let D=$?.requestContextMode
10631
+ if(!D){const e=(0,C.getBuiltInAgentConfig)(l)
10611
10632
  D=e?.requestContextMode}if(D&&"none"!==D&&!d)return{content:[{type:"text",text:`\u274c Agent '${l}' requires a requestId to be provided.\n\nThis agent has requestContextMode: '${D}' which means it needs to work in the context of a specific request.\n\nPlease provide a requestId when spawning this agent.`}],isError:!0}
10612
10633
  if(d&&"required"===D&&a)try{const e=await a.getRequestClient().getRequest(d)
10613
10634
  if("draft"===e?.status){const t=e?.intake,r="rejected"===t?.status?`the intake reviewer rejected it: ${String(t?.objection||"").slice(0,240)||"(no objection text)"}`:"pending"===t?.status?"it is held at intake awaiting a verdict":"it has not been registered"
10614
- return{content:[{type:"text",text:`\u274c ${d} is a DRAFT: ${r}.\n\nWork cannot start on drafts. Wait for the request to be registered (the user resolves the intake question from the timeline), or address the objection with a revised proposal. Do NOT spawn workers for draft requests.`}],isError:!0}}}catch(e){r(t,"draft-spawn guard read failed (continuing)",{error:(0,b.getErrorMessage)(e)})}if(r(t,"serverUrl resolved",{serverUrl:Q}),!t.workspaceManager?.getCurrentContext(t.currentSessionId)?.basePath&&!t.basePath)throw new f.ValidationError("Working directory not available in context")
10635
+ return{content:[{type:"text",text:`\u274c ${d} is a DRAFT: ${r}.\n\nWork cannot start on drafts. Wait for the request to be registered (the user resolves the intake question from the timeline), or address the objection with a revised proposal. Do NOT spawn workers for draft requests.`}],isError:!0}}}catch(e){r(t,"draft-spawn guard read failed (continuing)",{error:(0,v.getErrorMessage)(e)})}if(r(t,"serverUrl resolved",{serverUrl:Q}),!t.workspaceManager?.getCurrentContext(t.currentSessionId)?.basePath&&!t.basePath)throw new m.ValidationError("Working directory not available in context")
10615
10636
  const O=c?.sessionId
10616
10637
  let N=""
10617
10638
  if(T){const e=(O?T.filter(e=>e.opencodeSessionId!==O):T).filter(e=>!1!==e.working)
@@ -10620,35 +10641,35 @@ if(!p)return s.warn("Blocked spawn: request already has active workers",{request
10620
10641
  N=`\u26a0\ufe0f Warning: Request ${d} already has ${e.length} active worker(s): ${t}. Proceeding anyway (allowParallel=true).\n\n`,s.warn("Spawning parallel subagent for request with existing active workers",{requestId:d,existingWorkerCount:e.length,existingSessionIds:e.map(e=>e.opencodeSessionId),allowParallel:!0})}}let M,P,j=u
10621
10642
  d&&(M=`requests/${d}`,r(t,"Setting initialBranch for agent",{agent:l,requestId:d,initialBranch:M,agentRequestContextMode:D||"undefined"}),j="readonly"===D?`Working on request ${d} (READONLY mode - you cannot modify files, only explore and report).\n\n${u}`:`Working on request ${d}.\n\n${u}`)
10622
10643
  let F,q=!1
10623
- if(h){if(P=h,s.info("Using existing session for spawn",{sessionId:P,requestId:d}),!Q)throw new f.ValidationError("Could not determine OpenCode server URL")
10644
+ if(h){if(P=h,s.info("Using existing session for spawn",{sessionId:P,requestId:d}),!Q)throw new m.ValidationError("Could not determine OpenCode server URL")
10624
10645
  try{let e
10625
- try{const t=await fetch(`http://localhost:${B}/api/project-settings`,{signal:AbortSignal.timeout(8e3)})
10646
+ try{const t=await fetch(`http://localhost:${R}/api/project-settings`,{signal:AbortSignal.timeout(8e3)})
10626
10647
  if(t.ok){const r=await t.json()
10627
10648
  if(r.data?.defaultModel){const[t,...n]=r.data.defaultModel.split("/")
10628
10649
  e={providerID:t,modelID:n.join("/")}}}}catch{}F=await i({apiClient:a,fromSessionId:c?.sessionId,toSessionId:P,body:j,requestId:d,logger:s})
10629
10650
  let r=j
10630
10651
  const u=await n(a,t.projectId,d,P,s,t.mainBasePath||t.basePath)
10631
10652
  u&&(r=`${u}\n\n---\n\n${r}`)
10632
- const p={body:j,fromSessionId:c?.sessionId,fromAgent:R,...d?{requestId:d}:{},...F?{instructionCode:F}:{}},h=await(0,A.deliverToSession)(P,r,{kind:"agent-delegation",partPayload:p})
10633
- if(await o(a,t.projectId,F,h,s),!h.ok)throw new Error((0,A.deliveryFailureText)(P,h))
10634
- s.info("Prompt delivered to existing session",{sessionId:P,agent:l,messageId:h.messageId})}catch(e){throw new Error(`Failed to use existing session: ${(0,b.getErrorMessage)(e)}`)}}else{q=!0,r(t,"Creating session via spawn-session endpoint")
10653
+ const p={body:j,fromSessionId:c?.sessionId,fromAgent:B,...d?{requestId:d}:{},...F?{instructionCode:F}:{}},h=await(0,y.deliverToSession)(P,r,{kind:"agent-delegation",partPayload:p})
10654
+ if(await o(a,t.projectId,F,h,s),!h.ok)throw new Error((0,y.deliveryFailureText)(P,h))
10655
+ s.info("Prompt delivered to existing session",{sessionId:P,agent:l,messageId:h.messageId})}catch(e){throw new Error(`Failed to use existing session: ${(0,v.getErrorMessage)(e)}`)}}else{q=!0,r(t,"Creating session via spawn-session endpoint")
10635
10656
  const o=O
10636
- x&&(0,w.logSessionIdentity)("subagent_operations.spawn","_meta",{parentSessionId:o})
10637
- try{const h=`http://localhost:${B}/api/spawn-session`,f=await fetch(h,{method:"POST",signal:AbortSignal.timeout(15e3),headers:{"Content-Type":"application/json"},body:JSON.stringify({agent:l,prompt:u,requestId:d,parentSessionId:o,title:e.title,allowParallel:p,callerAgentType:R})})
10657
+ S&&(0,b.logSessionIdentity)("subagent_operations.spawn","_meta",{parentSessionId:o})
10658
+ try{const h=`http://localhost:${R}/api/spawn-session`,f=await fetch(h,{method:"POST",signal:AbortSignal.timeout(15e3),headers:{"Content-Type":"application/json"},body:JSON.stringify({agent:l,prompt:u,requestId:d,parentSessionId:o,title:e.title,allowParallel:p,callerAgentType:B})})
10638
10659
  if(!f.ok){const e=await f.text().catch(()=>"Unknown error")
10639
- throw new Error(`Spawn session endpoint failed: ${f.status} ${e}`)}const g=await f.json()
10640
- if(!g.success||!g.data)throw new Error(g.error||"Spawn session returned unsuccessful")
10641
- const{sessionId:A,model:y,serverUrl:w,initialBranch:b}=g.data
10660
+ throw new Error(`Spawn session endpoint failed: ${f.status} ${e}`)}const m=await f.json()
10661
+ if(!m.success||!m.data)throw new Error(m.error||"Spawn session returned unsuccessful")
10662
+ const{sessionId:A,model:y,serverUrl:w,initialBranch:b}=m.data
10642
10663
  M=b,F=await i({apiClient:a,fromSessionId:c?.sessionId,toSessionId:A,body:j,requestId:d,logger:s})
10643
10664
  let v=j
10644
- const I="pickupGuidance"in(g?.data??{})?g.data.pickupGuidance??null:await n(a,t.projectId,d,A,s,t.mainBasePath||t.basePath)
10665
+ const I="pickupGuidance"in(m?.data??{})?m.data.pickupGuidance??null:await n(a,t.projectId,d,A,s,t.mainBasePath||t.basePath)
10645
10666
  I&&(v=`${I}\n\n---\n\n${v}`)
10646
- const E=await(0,m.deliverInitialPrompt)({serverUrl:w,sessionId:A,agent:l,prompt:v,model:y,directory:t.mainBasePath,metadata:{alchemy:{kind:"agent-delegation",payload:{body:j,fromSessionId:c?.sessionId,fromAgent:R,...d?{requestId:d}:{},...F?{instructionCode:F}:{}}}}})
10667
+ const E=await(0,g.deliverInitialPrompt)({serverUrl:w,sessionId:A,agent:l,prompt:v,model:y,directory:t.mainBasePath,metadata:{alchemy:{kind:"agent-delegation",payload:{body:j,fromSessionId:c?.sessionId,fromAgent:B,...d?{requestId:d}:{},...F?{instructionCode:F}:{}}}}})
10647
10668
  if(!E.success)throw new Error(`Failed to spawn subagent: ${E.error}`)
10648
- P=A,r(t,"Spawn successful via unified endpoint",{sessionId:A,parentSessionId:o,model:y,instructionCode:F}),s.info("Background subagent spawned via unified endpoint",{agent:l,requestId:d,sessionId:A,parentSessionId:o,instructionCode:F})}catch(e){throw new Error(`Failed to spawn subagent: ${(0,b.getErrorMessage)(e)}`)}}let L=N
10669
+ P=A,r(t,"Spawn successful via unified endpoint",{sessionId:A,parentSessionId:o,model:y,instructionCode:F}),s.info("Background subagent spawned via unified endpoint",{agent:l,requestId:d,sessionId:A,parentSessionId:o,instructionCode:F})}catch(e){throw new Error(`Failed to spawn subagent: ${(0,v.getErrorMessage)(e)}`)}}let L=N
10649
10670
  L+=h?`Prompt sent to existing session ${P}.`:`Background ${l} subagent spawned successfully.`,d&&(L+=`\nWorking on: ${d}`),F&&(L+=`\nInstruction code: ${F} (the agent will cite this on its reports/questions via relatesTo)`),L+="\n\nThe subagent is now working independently. You can continue with other tasks."
10650
10671
  const U=!!P&&"unknown"!==P&&"spawning"!==P
10651
- return{content:[{type:"text",text:L}],...(0,C.alchemyMeta)({kind:"spawn-subagent",spawnedSessionId:U?P:void 0,agentType:l,requestId:d||void 0,description:e.title||u.slice(0,50)||l,...F?{instructionCode:F}:{}}),...F?{instructionCode:F}:{}}}function a(e){const t=Math.floor(e/1e3)
10672
+ return{content:[{type:"text",text:L}],...(0,k.alchemyMeta)({kind:"spawn-subagent",spawnedSessionId:U?P:void 0,agentType:l,requestId:d||void 0,description:e.title||u.slice(0,50)||l,...F?{instructionCode:F}:{}}),...F?{instructionCode:F}:{}}}function a(e){const t=Math.floor(e/1e3)
10652
10673
  if(t<60)return`${t}s`
10653
10674
  const r=Math.floor(t/60)
10654
10675
  return r<60?`${r}m ${t%60}s`:`${Math.floor(r/60)}h ${r%60}m`}function c(e){const t=[]
@@ -10663,8 +10684,8 @@ const i=l(n).replace(/ ago$/,"")
10663
10684
  return n>3e5&&e?{status:"stalled",detail:`no activity for ${i}`,isReady:!0}:e?{status:"idle",detail:`no activity for ${i}`,isReady:!0}:{status:"idle",detail:"no tool activity",isReady:!0}}function d(e){let t=0,r=0,n=0
10664
10685
  for(const i of e.trim().split("\n")){if(!i)continue
10665
10686
  const[e,o]=i.split("\t")
10666
- "-"!==e&&(t+=parseInt(e,10)||0),"-"!==o&&(r+=parseInt(o,10)||0),n++}return{additions:t,deletions:r,files:n}}async function p(e,r,n,i){const o=await(0,g.getServerUrl)(r,n)
10667
- if(!o)throw new f.ValidationError("Could not determine OpenCode server URL")
10687
+ "-"!==e&&(t+=parseInt(e,10)||0),"-"!==o&&(r+=parseInt(o,10)||0),n++}return{additions:t,deletions:r,files:n}}async function p(e,r,n,i){const o=await(0,A.getServerUrl)(r,n)
10688
+ if(!o)throw new m.ValidationError("Could not determine OpenCode server URL")
10668
10689
  n.info("Checking status of subagent session",{sessionId:e})
10669
10690
  try{const s=`${o.replace(/\/$/,"")}/session/${e}`,p=await fetch(s,{signal:AbortSignal.timeout(8e3)})
10670
10691
  if(!p.ok)return{content:[{type:"text",text:`Session ${e} not found or has exited.`}],isError:!0}
@@ -10684,16 +10705,21 @@ const a=[]
10684
10705
  t.length>0&&a.push(`Active: ${t.map(e=>e.requestLocalId).join(", ")}`),i.length>0&&a.push(`Previous: ${i.map(e=>e.requestLocalId).join(", ")}`),a.length>0&&(k=`\nRequests: ${a.join(" | ")}`),n.debug("Found request associations",{sessionId:e,activeCount:t.length,historicalCount:i.length})}}}catch(e){n.debug("Failed to get request associations, falling back to title extraction",{error:e instanceof Error?e.message:String(e)})}if(x||(x=function(e){const t=e.match(/\[(req\d{6})\]/)
10685
10706
  return t?t[1]:null}(h.title||"")),x&&r.basePath){const e=await async function(e,r){try{const{execFileSync:n}=await Promise.resolve().then(()=>t.t(35317,23)),i=`requests/${e}`
10686
10707
  let o=!1
10687
- try{n("git",["rev-parse","--verify",i],{cwd:r,stdio:"pipe"}),o=!0}catch{o=!1}const s=await(0,_.resolveDefaultBranch)(r)
10708
+ try{n("git",["rev-parse","--verify",i],{cwd:r,stdio:"pipe"}),o=!0}catch{o=!1}const s=await(0,B.resolveDefaultBranch)(r)
10688
10709
  if(o){const e=n("git",["diff",`${s}...${i}`,"--numstat"],{cwd:r,encoding:"utf8"})
10689
10710
  if(e.trim())return d(e)}try{const t=n("git",["log",s,"--merges","--oneline","-n","50",`--grep=${e}`],{cwd:r,encoding:"utf8"}).trim()
10690
10711
  if(t){const e=t.split("\n")[0].split(" ")[0],i=n("git",["diff",`${e}^1...${e}`,"--numstat"],{cwd:r,encoding:"utf8"})
10691
10712
  if(i.trim())return{...d(i),merged:!0}}}catch{}return null}catch{return null}}(x,r.basePath)
10692
10713
  if(e){const t=e.additions+e.deletions,r=e.merged?" (merged)":""
10693
10714
  C=`\nDiff vs main (${x}): +${e.additions}/-${e.deletions} lines (${t} total) in ${e.files} file${1!==e.files?"s":""}${r}`}}const S=function(e,t,r,n){const i=e.get(t)
10694
- return e.set(t,{at:n,fingerprint:r}),i&&i.fingerprint===r?`Nothing changed since your last check at ${new Date(i.at).toISOString().slice(11,19)} UTC (${l(n-i.at)}): same activity, same messages, same tokens.`:null}(R,`${r.currentSessionId??""}>${e}`,`${h.time.updated}:${w}:${E}`,Date.now())
10695
- return{content:[{type:"text",text:`${S?`${S}\n\n`:""}Session ${e}\nTitle: ${h.title||"Untitled"}\nStatus: ${v} (${y.detail})\nReady for input: ${I?"Yes":"No"}\nAge: ${a(b)}\nTokens: ${E.toLocaleString()} (in: ${g.input.toLocaleString()}, out: ${g.output.toLocaleString()}${g.reasoning>0?`, reasoning: ${g.reasoning.toLocaleString()}`:""})${A}${k}${C}`}]}}catch(e){throw new Error(`Failed to check session status: ${(0,b.getErrorMessage)(e)}`)}}async function h(e,t,r,n={}){const{activeOnly:i=!1,requestId:o,limit:s,minTokens:l,maxTokens:d,maxAgeHours:p}=n,h=await(0,g.getServerUrl)(e,t)
10696
- if(!h)throw new f.ValidationError("Could not determine OpenCode server URL")
10715
+ return e.set(t,{at:n,fingerprint:r}),i&&i.fingerprint===r?`Nothing changed since your last check at ${new Date(i.at).toISOString().slice(11,19)} UTC (${l(n-i.at)}): same activity, same messages, same tokens.`:null}($,`${r.currentSessionId??""}>${e}`,`${h.time.updated}:${w}:${E}`,Date.now())
10716
+ return{content:[{type:"text",text:`${S?`${S}\n\n`:""}Session ${e}\nTitle: ${h.title||"Untitled"}\nStatus: ${v} (${y.detail})\nReady for input: ${I?"Yes":"No"}\nAge: ${a(b)}\nTokens: ${E.toLocaleString()} (in: ${g.input.toLocaleString()}, out: ${g.output.toLocaleString()}${g.reasoning>0?`, reasoning: ${g.reasoning.toLocaleString()}`:""})${A}${k}${C}`}]}}catch(e){throw new Error(`Failed to check session status: ${(0,v.getErrorMessage)(e)}`)}}async function h(e,t,r){const n=await(0,A.getServerUrl)(t,r)
10717
+ if(!n)throw new m.ValidationError("Could not determine OpenCode server URL")
10718
+ r.info("Aborting subagent session",{sessionId:e})
10719
+ try{const t=`${n.replace(/\/$/,"")}/session/${e}/abort`,i=await fetch(t,{signal:AbortSignal.timeout(8e3),method:"POST"})
10720
+ if(!i.ok){const e=await i.text().catch(()=>"Unknown error")
10721
+ throw new Error(`Failed to abort session: ${i.status} ${e}`)}return r.info("Session aborted",{sessionId:e}),{content:[{type:"text",text:`Session ${e} has been aborted.`}]}}catch(e){throw new Error(`Failed to abort session: ${(0,v.getErrorMessage)(e)}`)}}async function f(e,t,r,n={}){const{activeOnly:i=!1,requestId:o,limit:s,minTokens:l,maxTokens:d,maxAgeHours:p}=n,h=await(0,A.getServerUrl)(e,t)
10722
+ if(!h)throw new m.ValidationError("Could not determine OpenCode server URL")
10697
10723
  t.info("Listing sessions",{filters:n})
10698
10724
  try{const e=`${h.replace(/\/$/,"")}/session?limit=1000`,t=await fetch(e,{signal:AbortSignal.timeout(8e3)})
10699
10725
  if(!t.ok)throw new Error(`Failed to fetch sessions: ${t.status}`)
@@ -10732,39 +10758,34 @@ const C=t.title||"Untitled",k=C.length>50?C.slice(0,47)+"...":C
10732
10758
  E.push({id:t.id,title:k,status:m,age:r,totalTokens:i,requestsInfo:A,allRequestIds:b,statusDetail:p.detail,currentAction:s,recentActions:a})}if(0===E.length)return{content:[{type:"text",text:"No sessions match the specified filters."}]}
10733
10759
  const C=s?E.slice(0,s):E,k=C.map(e=>{const t=e.currentAction?`\n Current: ${e.currentAction}`:"",r=e.recentActions.length>0?`\n Recent: ${e.recentActions.join(", ")}`:""
10734
10760
  return`\u2022 ${e.id}\n ${e.title}\n Status: ${e.status} (${e.statusDetail}) | Age: ${a(e.age)} | ${e.totalTokens.toLocaleString()} tokens${e.requestsInfo}${t}${r}`}),x=s&&E.length>s?` (showing ${s} of ${E.length})`:""
10735
- return{content:[{type:"text",text:`Found ${C.length} session${1!==C.length?"s":""}${x}:\n\n${k.join("\n\n")}`}]}}catch(e){throw new Error(`Failed to list sessions: ${(0,b.getErrorMessage)(e)}`)}}exports.handleSpawn=s,exports.extractRecentActivity=c,exports.formatTimeSince=l,exports.deriveActivityStatus=u,exports.handleCheckStatus=p,exports.handleListSessions=h,exports.handleSubagentOperations=async function(e,t,r,n,a,c){const l=r.child({tool:"subagent_operations"}),u=n.startTimer("tool_execution_time")
10736
- try{const r=e,{sessionId:d,operation:m}=r
10737
- let w
10738
- switch(l.info("Processing subagent_operations",{operation:m,sessionId:d}),m){case"spawn":w=await s({agent:r.agent,prompt:r.prompt,requestId:r.requestId,allowParallel:r.allowParallel,useExistingSession:r.useExistingSession},t,l,a,c)
10761
+ return{content:[{type:"text",text:`Found ${C.length} session${1!==C.length?"s":""}${x}:\n\n${k.join("\n\n")}`}]}}catch(e){throw new Error(`Failed to list sessions: ${(0,v.getErrorMessage)(e)}`)}}exports.handleSpawn=s,exports.extractRecentActivity=c,exports.formatTimeSince=l,exports.deriveActivityStatus=u,exports.handleCheckStatus=p,exports.handleAbort=h,exports.handleListSessions=f,exports.handleSubagentOperations=async function(e,t,r,n,a,c){const l=r.child({tool:"subagent_operations"}),u=n.startTimer("tool_execution_time")
10762
+ try{const r=e,{sessionId:d,operation:g}=r
10763
+ let b
10764
+ switch(l.info("Processing subagent_operations",{operation:g,sessionId:d}),g){case"spawn":b=await s({agent:r.agent,prompt:r.prompt,requestId:r.requestId,allowParallel:r.allowParallel,useExistingSession:r.useExistingSession},t,l,a,c)
10739
10765
  break
10740
- case"continue":w=await async function(e,t,r,n,s,a,c){if(!await(0,g.getServerUrl)(r,n))throw new f.ValidationError("Could not determine OpenCode server URL")
10766
+ case"continue":b=await async function(e,t,r,n,s,a,c){if(!await(0,A.getServerUrl)(r,n))throw new m.ValidationError("Could not determine OpenCode server URL")
10741
10767
  if(n.info("Sending message to subagent session",{sessionId:e,promptLength:t.length}),s)try{const t=c?.sessionId
10742
10768
  if(t){const r=s.getCLISessionClient()
10743
10769
  if(r){let i=await r.getByExternalId(t)
10744
10770
  if(i||(i=await r.createOrUpdateSession({externalSessionId:t,providerId:"opencode",status:"active"}),n.info("Created caller session in DB for re-parenting",{callerSessionId:t,dbId:i?.id})),i){const o=await r.getByExternalId(e).catch(()=>null),s=o?.parentSessionId??null
10745
- s===i.id?n.debug("Parent unchanged for continued agent; no re-parent write",{sessionId:e}):(await r.createOrUpdateSession({externalSessionId:e,providerId:"opencode",parentSessionId:i.id,status:"active"}),n.info("RE-PARENTED continued agent: reports now flow to the current caller",{sessionId:e,fromParentDbId:s,toParentDbId:i.id,toCallerSessionId:t}))}}}}catch(e){n.warn("Failed to update parent session - reports may go to wrong parent",{error:(0,b.getErrorMessage)(e)})}let l
10746
- if(!l)try{const t=`http://localhost:${B}/api/sessions/${encodeURIComponent(e)}/agent-type`,r=await fetch(t,{signal:AbortSignal.timeout(8e3)})
10771
+ s===i.id?n.debug("Parent unchanged for continued agent; no re-parent write",{sessionId:e}):(await r.createOrUpdateSession({externalSessionId:e,providerId:"opencode",parentSessionId:i.id,status:"active"}),n.info("RE-PARENTED continued agent: reports now flow to the current caller",{sessionId:e,fromParentDbId:s,toParentDbId:i.id,toCallerSessionId:t}))}}}}catch(e){n.warn("Failed to update parent session - reports may go to wrong parent",{error:(0,v.getErrorMessage)(e)})}let l
10772
+ if(!l)try{const t=`http://localhost:${R}/api/sessions/${encodeURIComponent(e)}/agent-type`,r=await fetch(t,{signal:AbortSignal.timeout(8e3)})
10747
10773
  if(r.ok){const e=await r.json()
10748
- e.success&&e.agentType&&(l=e.agentType)}}catch{n.warn("Agent type lookup failed for session",{sessionId:e})}if(!l)throw new f.ValidationError(`Cannot continue session ${e}: unable to determine agent type. The session's agent type could not be looked up from the action server.`)
10774
+ e.success&&e.agentType&&(l=e.agentType)}}catch{n.warn("Agent type lookup failed for session",{sessionId:e})}if(!l)throw new m.ValidationError(`Cannot continue session ${e}: unable to determine agent type. The session's agent type could not be looked up from the action server.`)
10749
10775
  try{let a
10750
- try{const e=await fetch(`http://localhost:${B}/api/project-settings`,{signal:AbortSignal.timeout(8e3)})
10776
+ try{const e=await fetch(`http://localhost:${R}/api/project-settings`,{signal:AbortSignal.timeout(8e3)})
10751
10777
  if(e.ok){const t=await e.json()
10752
10778
  if(t.data?.defaultModel){const[e,...r]=t.data.defaultModel.split("/")
10753
- a={providerID:e,modelID:r.join("/")}}}}catch{}const u=await i({apiClient:s,fromSessionId:c?.sessionId,toSessionId:e,body:t,logger:n}),d=t,p={body:t,fromSessionId:c?.sessionId,fromAgent:await y.sessionIdentity.getAgentTypeFor(c?.sessionId)||void 0,...u?{instructionCode:u}:{}},h=await(0,A.deliverToSession)(e,d,{kind:"agent-followup",partPayload:p})
10754
- return await o(s,r.projectId,u,h,n),h.ok?(n.info("Message delivered to subagent session",{sessionId:e,agent:l,instructionCode:u,messageId:h.messageId}),{content:[{type:"text",text:[u?`Message delivered to session ${e} (${u}); it is in that session's transcript.`:`Message delivered to session ${e}; it is in that session's transcript.`,l?`That session is a ${l}. It will process this in the background -- it is NOT proof that work started; check for tasks before reporting progress.`:"Role unknown for that session. It will process this in the background -- that is not proof work started; check for tasks before reporting progress."].join("\n")}],...u?{_meta:{alchemy:{kind:"continue-subagent",instructionCode:u}},instructionCode:u}:{}}):{content:[{type:"text",text:(0,A.deliveryFailureText)(e,h)+(l?` That session is a ${l}.`:"")}],isError:!0,...u?{_meta:{alchemy:{kind:"continue-subagent",instructionCode:u,failed:!0}},instructionCode:u}:{}}}catch(e){throw new Error(`Failed to continue session: ${(0,b.getErrorMessage)(e)}`)}}(d,r.prompt,t,l,a,0,c)
10779
+ a={providerID:e,modelID:r.join("/")}}}}catch{}const u=await i({apiClient:s,fromSessionId:c?.sessionId,toSessionId:e,body:t,logger:n}),d=t,p={body:t,fromSessionId:c?.sessionId,fromAgent:await w.sessionIdentity.getAgentTypeFor(c?.sessionId)||void 0,...u?{instructionCode:u}:{}},h=await(0,y.deliverToSession)(e,d,{kind:"agent-followup",partPayload:p})
10780
+ return await o(s,r.projectId,u,h,n),h.ok?(n.info("Message delivered to subagent session",{sessionId:e,agent:l,instructionCode:u,messageId:h.messageId}),{content:[{type:"text",text:[u?`Message delivered to session ${e} (${u}); it is in that session's transcript.`:`Message delivered to session ${e}; it is in that session's transcript.`,l?`That session is a ${l}. It will process this in the background -- it is NOT proof that work started; check for tasks before reporting progress.`:"Role unknown for that session. It will process this in the background -- that is not proof work started; check for tasks before reporting progress."].join("\n")}],...u?{_meta:{alchemy:{kind:"continue-subagent",instructionCode:u}},instructionCode:u}:{}}):{content:[{type:"text",text:(0,y.deliveryFailureText)(e,h)+(l?` That session is a ${l}.`:"")}],isError:!0,...u?{_meta:{alchemy:{kind:"continue-subagent",instructionCode:u,failed:!0}},instructionCode:u}:{}}}catch(e){throw new Error(`Failed to continue session: ${(0,v.getErrorMessage)(e)}`)}}(d,r.prompt,t,l,a,0,c)
10755
10781
  break
10756
- case"check-status":w=await p(d,t,l,a)
10782
+ case"check-status":b=await p(d,t,l,a)
10757
10783
  break
10758
- case"abort":w=await async function(e,t,r){const n=await(0,g.getServerUrl)(t,r)
10759
- if(!n)throw new f.ValidationError("Could not determine OpenCode server URL")
10760
- r.info("Aborting subagent session",{sessionId:e})
10761
- try{const t=`${n.replace(/\/$/,"")}/session/${e}/abort`,i=await fetch(t,{signal:AbortSignal.timeout(8e3),method:"POST"})
10762
- if(!i.ok){const e=await i.text().catch(()=>"Unknown error")
10763
- throw new Error(`Failed to abort session: ${i.status} ${e}`)}return r.info("Session aborted",{sessionId:e}),{content:[{type:"text",text:`Session ${e} has been aborted.`}]}}catch(e){throw new Error(`Failed to abort session: ${(0,b.getErrorMessage)(e)}`)}}(d,t,l)
10784
+ case"abort":b=await h(d,t,l)
10764
10785
  break
10765
10786
  case"read-messages":{const e=!(!r.compact&&null==r.select),n=!e&&null==r.limit,i=r.limit??(e?50:8)
10766
- w=await async function(e,t,r,n,i){const o=await(0,g.getServerUrl)(r,n)
10767
- if(!o)throw new f.ValidationError("Could not determine OpenCode server URL")
10787
+ b=await async function(e,t,r,n,i){const o=await(0,A.getServerUrl)(r,n)
10788
+ if(!o)throw new m.ValidationError("Could not determine OpenCode server URL")
10768
10789
  n.info("Reading messages from subagent session",{sessionId:e,limit:t,compact:i?.compact,select:i?.select})
10769
10790
  try{const r=`${o.replace(/\/$/,"")}/session/${e}/message?limit=${t}`,n=await fetch(r,{signal:AbortSignal.timeout(8e3)})
10770
10791
  if(!n.ok){if(404===n.status)return{content:[{type:"text",text:`Session ${e} not found.`}],isError:!0}
@@ -10789,14 +10810,14 @@ let r=s.length-1
10789
10810
  for(;r>0&&!t(s[r]);)r--
10790
10811
  const n=s.length-1-r
10791
10812
  return{content:[{type:"text",text:`Session ${e}: latest message${n>0?` (skipped ${n} trailing empty message${1===n?"":"s"}; use compact:true for the full trajectory)`:""}:\n\n${p(s[r],r)}`}]}}const h=s.map((e,t)=>p(e,t)).join("\n\n---\n\n")
10792
- return{content:[{type:"text",text:`Session ${e}: last ${s.length} messages:\n\n${h}`}]}}catch(e){throw new Error(`Failed to read messages: ${(0,b.getErrorMessage)(e)}`)}}(d,i,t,l,{compact:r.compact,select:r.select,latest:n})
10813
+ return{content:[{type:"text",text:`Session ${e}: last ${s.length} messages:\n\n${h}`}]}}catch(e){throw new Error(`Failed to read messages: ${(0,v.getErrorMessage)(e)}`)}}(d,i,t,l,{compact:r.compact,select:r.select,latest:n})
10793
10814
  break}case"list-sessions":const e={activeOnly:r.activeOnly,requestId:r.requestId,limit:r.limit,minTokens:r.minTokens,maxTokens:r.maxTokens,maxAgeHours:r.maxAgeHours,parentSessionId:c?.sessionId}
10794
- w=await h(t,l,a,e)
10815
+ b=await f(t,l,a,e)
10795
10816
  break
10796
- default:throw new f.ValidationError(`Unknown operation: ${m}`)}u({success:!0}),n.incrementCounter(`subagent_operations_${m}_success`,1)
10797
- const v=k[m]
10798
- return v?(0,C.withAlchemyKind)(w,v):w}catch(e){return l.error("Subagent operations failed",{error:e instanceof Error?e.stack:String(e)}),u({success:!1}),n.incrementCounter("subagent_operations_errors",1),{content:[{type:"text",text:`Subagent operation failed: ${(0,b.getErrorMessage)(e)}`}],isError:!0}}}
10799
- const f=t(59860),m=t(15899),g=t(47617),A=t(39844),y=t(6688),w=t(87280),b=t(58824),v=t(79896),I=t(16928),E=t(8955),C=t(82272),k={spawn:"spawn-subagent",continue:"continue-subagent","check-status":"check-subagent-status",abort:"abort-subagent","read-messages":"read-subagent-messages","list-sessions":"list-subagent-sessions"},x="true"===process.env.ALCHEMY_DEBUG_LOGS,S=t(79414),_=t(52232),B=(0,S.getActionServerPort)(),R=new Map},3126(e,exports){"use strict"
10817
+ default:throw new m.ValidationError(`Unknown operation: ${g}`)}u({success:!0}),n.incrementCounter(`subagent_operations_${g}_success`,1)
10818
+ const I=x[g]
10819
+ return I?(0,k.withAlchemyKind)(b,I):b}catch(e){return l.error("Subagent operations failed",{error:e instanceof Error?e.stack:String(e)}),u({success:!1}),n.incrementCounter("subagent_operations_errors",1),{content:[{type:"text",text:`Subagent operation failed: ${(0,v.getErrorMessage)(e)}`}],isError:!0}}}
10820
+ const m=t(59860),g=t(15899),A=t(47617),y=t(39844),w=t(6688),b=t(87280),v=t(58824),I=t(79896),E=t(16928),C=t(8955),k=t(82272),x={spawn:"spawn-subagent",continue:"continue-subagent","check-status":"check-subagent-status",abort:"abort-subagent","read-messages":"read-subagent-messages","list-sessions":"list-subagent-sessions"},S="true"===process.env.ALCHEMY_DEBUG_LOGS,_=t(79414),B=t(52232),R=(0,_.getActionServerPort)(),$=new Map},3126(e,exports){"use strict"
10800
10821
  exports.allTasksOf=async function(e,t){const r=new Map
10801
10822
  for(let n=0;n<2e3;n+=100){const i=await e.listTasks({requestId:t,sortBy:"created",sortDirection:"asc",limit:100,offset:n}),o=Array.isArray(i)?i:[]
10802
10823
  for(const e of o)e?.id&&!r.has(e.id)&&r.set(e.id,e)
@@ -11260,7 +11281,7 @@ if(null!=e)for(var o=r(e),s=0;s<o.length;s++)"default"!==o[s]&&n(t,e,o[s])
11260
11281
  return i(t,e),t})
11261
11282
  Object.defineProperty(exports,"__esModule",{value:!0}),exports.visualDefs=void 0
11262
11283
  const s=o(t(76183)),a=t(55432),c=t(93978),l=t(70089),u=t(70901),d=t(20339),p=t(75010),h=t(36399)
11263
- exports.visualDefs=[{name:"request_visual_feedback",description:'Ask the USER to mark up a screenshot of the UI and get their annotations back. EASIEST: start a box on a screenshot-capable env (experiment_start), then call this with `box` + `route` (e.g. "/") -- I render + screenshot that route for you. For FULL control (navigate, click, fill, scroll, log in, multi-step), pass `box` + `script`: a Playwright snippet (body of async (page, baseUrl) => {...}) -- I serve the app, launch headless Chromium, run your script, then screenshot the final state. You write ONLY the interaction; no serve/launch/flags. (Also: `fullPage` for the whole page; `box`+`path` for a screenshot you already wrote; `assetFilename` for an uploaded asset.) The user paints on it in the app (the action-required bell) and their annotated image comes back so you can SEE the changes they want. Returns the annotated image (or notes the user skipped / did not respond). Advisory -- never gates a merge.',schema:s.RequestVisualFeedbackSchema,annotations:{title:"Request Visual Feedback",readOnlyHint:!1},handler:(e,t)=>(0,a.handleRequestVisualFeedback)(e,t.context,t.apiClient,t.logger,t.metrics,t.cliOrigin)},{name:"ask_user_visual",description:'Ask the USER a question ANCHORED TO A REGION OF AN IMAGE -- point at the thing instead of describing it. Give `imagePath` (a screenshot you produced, repo-relative) or `previewName` (a registered live preview; I freeze it into a snapshot), plus `box` ("x,y,w,h", normalized 0..1) marking the region, your `question`, and optionally `options` for multiple choice. The question is pinned to that region and waits in the user inbox: they open the marked image in one click and answer on the record. The answer returns like any question answer (inline if they respond during the hold window, otherwise you are resumed automatically -- do not poll). Unlike request_visual_feedback (which returns an image the user paints), this returns the user\'s WORDS about a region YOU marked.',schema:s.AskUserVisualSchema,annotations:{title:"Ask User (Visual)",readOnlyHint:!1},handler:(e,t)=>(0,c.handleAskUserVisual)(e,t.context,t.apiClient,t.logger,t.metrics,t.cliOrigin)},{name:"capture_site",description:"Screenshot an http(s) URL, public or locally served, with the platform's host browser (it reaches the internet when boxes cannot). Stores the image as a project asset and returns it with an asset:<filename> ref to cite in documents or criteria, which a judge can later view with view_asset offline.",schema:s.CaptureSiteSchema,annotations:{title:"Capture Site Screenshot",readOnlyHint:!1},handler:(e,t)=>(0,l.handleCaptureSite)(e,t.logger,t.metrics,t.context,t.apiClient,t.cliOrigin)},{name:"read_site",description:"Load an http(s) URL in the platform's host browser and return the page's rendered visible text (up to 60k chars); pages that bot-wall webfetch render here. The result carries a ready `Source:` line and its read time: a text that rests on the page ends with that line. A claim checked here is verified as of that time; one from search summaries alone stays inferred, and say so.",schema:s.ReadSiteSchema,annotations:{title:"Read Site (Rendered Text)",readOnlyHint:!0},handler:(e,t)=>(0,u.handleReadSite)(e,t.logger,t.metrics,t.context,t.apiClient,t.cliOrigin)},{name:"drive_site",description:"DRIVE a running page in the platform's headless host browser: click, type, press keys, scroll, then read what the page says and looks like. Up to 20 steps per call; each reports whether it found its target. clickat (x,y) for canvas or game surfaces with no DOM; eval runs a script in the page and reports what it returns (game state, a frame rate, stepping a game in slow motion), and shots: \"each\" makes a frame sequence. This browser draws with the computer's graphics card: for a 3D or WebGL page, its frame rate, frames and how it moves, use it, not a browser in a box, which draws in software at many times the CPU and a quarter of the frames. Pass `session` to keep the page open between calls (the only way to check accumulated state or elapsed time) and close:true on the last call. Persistent checks belong in a test process. The user never sees this browser: to hand a page to them, give the URL, the steps left for them in their own browser, and the final screenshot (ask_user_visual with imagePath, or cite it in a report). Never type a password, a key or an acceptance on the user's behalf.",schema:s.DriveSiteSchema,annotations:{title:"Drive Site (Interact)",readOnlyHint:!1},handler:(e,t)=>(0,d.handleDriveSite)(e,t.logger,t.metrics,t.context,t.apiClient,t.cliOrigin)},{name:"fetch_asset",description:"Download an image or font from an http(s) URL straight INTO the working tree (e.g. `sites/acme/assets/hero.jpg`). Use it to vendor the assets a page needs so the result is self-contained -- confined runs have no network, so a page that hotlinks an image renders broken in the test gate and to anyone offline. webfetch cannot do this: it returns a page as text, or an image into the conversation, and neither writes bytes to a file. Images and fonts only, 12MB cap, path confined to the working tree.",schema:s.FetchAssetSchema,annotations:{title:"Fetch Asset",readOnlyHint:!1},handler:(e,t)=>(0,p.handleFetchAsset)(e,t.context,t.logger,t.metrics)},{name:"preview_site",description:"Serve a built static directory (containing index.html) from this machine and return a link the user can open; it is also registered as a live preview in the artifact viewer. Use it when the deliverable is a page. With stop: true it stops a running preview (name it when several run). Pass requestId to serve an unmerged request's own working copy. Build into a folder git ignores (the project's build folder, e.g. dist/): a box run commits every file it writes outside ignored folders to your branch, and the preview serves the folder only while it exists. Never copy files into main to preview them.",schema:s.PreviewSiteSchema,annotations:{title:"Preview Site",readOnlyHint:!1},handler:(e,t)=>(0,h.handlePreviewSite)(e,t.logger,t.metrics,t.context)}]},20339(e,exports,t){"use strict"
11284
+ exports.visualDefs=[{name:"request_visual_feedback",description:'Ask the USER to mark up a screenshot of the UI and get their annotations back. EASIEST: start a box on a screenshot-capable env (experiment_start), then call this with `box` + `route` (e.g. "/") -- I render + screenshot that route for you. For FULL control (navigate, click, fill, scroll, log in, multi-step), pass `box` + `script`: a Playwright snippet (body of async (page, baseUrl) => {...}) -- I serve the app, launch headless Chromium, run your script, then screenshot the final state. You write ONLY the interaction; no serve/launch/flags. (Also: `fullPage` for the whole page; `box`+`path` for a screenshot you already wrote; `assetFilename` for an uploaded asset.) The user paints on it in the app (the action-required bell) and their annotated image comes back so you can SEE the changes they want. Returns the annotated image (or notes the user skipped / did not respond). Advisory -- never gates a merge.',schema:s.RequestVisualFeedbackSchema,annotations:{title:"Request Visual Feedback",readOnlyHint:!1},handler:(e,t)=>(0,a.handleRequestVisualFeedback)(e,t.context,t.apiClient,t.logger,t.metrics,t.cliOrigin)},{name:"ask_user_visual",description:'Ask the USER a question ANCHORED TO A REGION OF AN IMAGE -- point at the thing instead of describing it. Give `imagePath` (a screenshot you produced, repo-relative) or `previewName` (a registered live preview; I freeze it into a snapshot), plus `box` ("x,y,w,h", normalized 0..1) marking the region, your `question`, and optionally `options` for multiple choice. The question is pinned to that region and waits in the user inbox: they open the marked image in one click and answer on the record. The answer returns like any question answer (inline if they respond during the hold window, otherwise you are resumed automatically -- do not poll). Unlike request_visual_feedback (which returns an image the user paints), this returns the user\'s WORDS about a region YOU marked.',schema:s.AskUserVisualSchema,annotations:{title:"Ask User (Visual)",readOnlyHint:!1},handler:(e,t)=>(0,c.handleAskUserVisual)(e,t.context,t.apiClient,t.logger,t.metrics,t.cliOrigin)},{name:"capture_site",description:"Screenshot an http(s) URL, public or locally served, with the platform's host browser (it reaches the internet when boxes cannot). Stores the image as a project asset and returns it with an asset:<filename> ref to cite in documents or criteria, which a judge can later view with view_asset offline.",schema:s.CaptureSiteSchema,annotations:{title:"Capture Site Screenshot",readOnlyHint:!1},handler:(e,t)=>(0,l.handleCaptureSite)(e,t.logger,t.metrics,t.context,t.apiClient,t.cliOrigin)},{name:"read_site",description:"Load an http(s) URL in the platform's host browser and return the page's rendered visible text (up to 60k chars); pages that bot-wall webfetch render here. The result carries a ready `Source:` line and its read time: a text that rests on the page ends with that line. A claim checked here is verified as of that time; one from search summaries alone stays inferred, and say so.",schema:s.ReadSiteSchema,annotations:{title:"Read Site (Rendered Text)",readOnlyHint:!0},handler:(e,t)=>(0,u.handleReadSite)(e,t.logger,t.metrics,t.context,t.apiClient,t.cliOrigin)},{name:"drive_site",description:"DRIVE a running page in the platform's headless host browser: click, type, press keys, scroll, then read what the page says and looks like. Up to 20 steps per call; each reports whether it found its target. clickat (x,y) for canvas or game surfaces with no DOM; eval runs a script in the page and reports what it returns (game state, a frame rate, stepping a game in slow motion), and shots: \"each\" makes a frame sequence. This browser is a real Chrome on the user's computer, outside every box: it has the computer's graphics card and network, so it is the place for what a box cannot do (a box draws in software at many times the CPU, and has no network address): a 3D page's frame rate and motion, and pages that reach each other or the internet. Several sessions are several pages open at once. Pass `session` to keep the page open between calls (the only way to check accumulated state or elapsed time) and close:true on the last call. Persistent checks belong in a test process. The user never sees this browser: to hand a page to them, give the URL, the steps left for them in their own browser, and the final screenshot (ask_user_visual with imagePath, or cite it in a report). Never type a password, a key or an acceptance on the user's behalf.",schema:s.DriveSiteSchema,annotations:{title:"Drive Site (Interact)",readOnlyHint:!1},handler:(e,t)=>(0,d.handleDriveSite)(e,t.logger,t.metrics,t.context,t.apiClient,t.cliOrigin)},{name:"fetch_asset",description:"Download an image or font from an http(s) URL straight INTO the working tree (e.g. `sites/acme/assets/hero.jpg`). Use it to vendor the assets a page needs so the result is self-contained -- confined runs have no network, so a page that hotlinks an image renders broken in the test gate and to anyone offline. webfetch cannot do this: it returns a page as text, or an image into the conversation, and neither writes bytes to a file. Images and fonts only, 12MB cap, path confined to the working tree.",schema:s.FetchAssetSchema,annotations:{title:"Fetch Asset",readOnlyHint:!1},handler:(e,t)=>(0,p.handleFetchAsset)(e,t.context,t.logger,t.metrics)},{name:"preview_site",description:"Serve a built static directory (containing index.html) from this machine and return a link the user can open; it is also registered as a live preview in the artifact viewer. Use it when the deliverable is a page. With stop: true it stops a running preview (name it when several run). Pass requestId to serve an unmerged request's own working copy. Build into a folder git ignores (the project's build folder, e.g. dist/): a box run commits every file it writes outside ignored folders to your branch, and the preview serves the folder only while it exists. Never copy files into main to preview them.",schema:s.PreviewSiteSchema,annotations:{title:"Preview Site",readOnlyHint:!1},handler:(e,t)=>(0,h.handlePreviewSite)(e,t.logger,t.metrics,t.context)}]},20339(e,exports,t){"use strict"
11264
11285
  exports.handleDriveSite=async function(e,n,i,o,s,a){const c=i.startTimer("tool_execution_time"),l=e=>(c({success:!1,error:"bad"}),{content:[{type:"text",text:e}],isError:!0})
11265
11286
  try{const i=(e.url||"").trim()
11266
11287
  if(!/^https?:\/\//i.test(i))return l("`url` must be an http(s) URL.")
@@ -11402,17 +11423,17 @@ exports.SubagentOperationsSchema=i.z.object({sessionId:f.optional(),operation:i.
11402
11423
  "propose-standard"!==r||e.standardRules&&e.standardRules.length||t.addIssue({code:"custom",message:"standardRules is required for propose-standard: [{ text, section, checked, kinds? }]"}),["create-snapshot","get-history","get-version","restore-version","list-annotations","create-annotation","propose-standard"].includes(r)&&!e.documentId&&t.addIssue({code:"custom",message:`documentId is required for '${r}'. Example: { "operation": "${r}", "documentId": "doc000001" }`}),"create"===r&&(e.filename?/\.(md|html?)$/i.test(e.filename)||t.addIssue({code:"custom",message:"Filename must end in .md (prose) or .html (a rendered page)"}):t.addIssue({code:"custom",message:"filename is required for create (must end in .md or .html)"}),e.content||t.addIssue({code:"custom",message:"content is required for create"})),"search"!==r||e.query||t.addIssue({code:"custom",message:'query is required for search. Example: { "operation": "search", "query": "search term" }'}),"get-version"!==r&&"restore-version"!==r||e.versionNumber||t.addIssue({code:"custom",message:`versionNumber is required for '${r}'`}),"create-annotation"===r&&(e.selectedText||t.addIssue({code:"custom",message:"selectedText is required for create-annotation"}),e.body||t.addIssue({code:"custom",message:"body is required for create-annotation"})),"reply-to-annotation"===r&&(e.annotationId||t.addIssue({code:"custom",message:"annotationId is required for reply-to-annotation"}),e.body||t.addIssue({code:"custom",message:"body is required for reply-to-annotation"})),"resolve-annotation"!==r||e.annotationId||t.addIssue({code:"custom",message:"annotationId is required for resolve-annotation"}),"create-artifact-annotation"===r&&(e.ref||t.addIssue({code:"custom",message:'ref is required for create-artifact-annotation. Example: { "operation": "create-artifact-annotation", "ref": "file:src/main.cpp", "line": 42, "body": "..." }'}),e.body||t.addIssue({code:"custom",message:"body is required for create-artifact-annotation"}))})
11403
11424
  const m=d
11404
11425
  exports.PlanReadSchema=i.z.object({operation:i.z.enum(["get","get-active","list","get-history","get-requests"]),planId:m.optional(),searchQuery:i.z.string().optional(),limit:i.z.number().min(1).max(100).default(20),offset:i.z.number().min(0).default(0),sortBy:i.z.enum(["createdAt","updatedAt","filename"]).default("updatedAt"),sortDirection:i.z.enum(["ASC","DESC"]).default("DESC")}).superRefine((e,t)=>{["get","get-history","get-requests"].includes(e.operation)&&!e.planId&&t.addIssue({code:"custom",message:`planId is required for '${e.operation}'. Example: { "operation": "${e.operation}", "planId": "plan000001" }`})}),exports.PlanWriteSchema=i.z.object({operation:i.z.enum(["create","update","append","replace","create-snapshot","restore"]),planId:m.optional(),kind:i.z.enum(["plan"]).default("plan").describe("Document kind to create. Plans only."),content:i.z.string().optional(),name:i.z.string().optional().describe("Plan name (used in filename for create; optional new name for update)."),old_str:i.z.string().optional().describe("Text to find in the plan (for replace)"),new_str:i.z.string().optional().describe("Replacement text (for replace)"),versionLabel:i.z.string().optional(),changeSummary:i.z.string().optional(),versionNumber:i.z.number().int().min(1).optional()}).superRefine((e,t)=>{const r=e.operation
11405
- "create"===r?(e.name||t.addIssue({code:"custom",message:"name is required for 'create'. Letters, numbers, hyphens, underscores only."}),void 0===e.content&&t.addIssue({code:"custom",message:"content is required for 'create' (use empty string for an empty body)."})):e.planId||t.addIssue({code:"custom",message:`planId is required for '${r}'.`}),"append"!==r||e.content||t.addIssue({code:"custom",message:'content is required for \'append\'. Example: { "operation": "append", "planId": "plan000001", "content": "..." }'}),"update"===r&&(void 0!==e.content||e.name||t.addIssue({code:"custom",message:"update requires 'content' and/or 'name'."})),"replace"===r&&(e.old_str||t.addIssue({code:"custom",message:'old_str is required for replace. Example: { "operation": "replace", "planId": "plan000001", "old_str": "find this", "new_str": "replace with" }'}),void 0===e.new_str&&t.addIssue({code:"custom",message:"new_str is required for replace"})),"restore"!==r||e.versionNumber||t.addIssue({code:"custom",message:'versionNumber is required for restore. Example: { "operation": "restore", "planId": "plan000001", "versionNumber": 1 }'})}),exports.RevertRequestSchema=i.z.object({requestId:u,reason:i.z.string(),confirmDestructive:i.z.boolean().optional()}),exports.ModifyFileSchema=i.z.object({operations:i.z.array(i.z.object({path:i.z.string().optional(),documentId:d.optional(),operationType:i.z.enum(["replace-string","insert-lines","replace-lines","delete-string","delete-lines"]),replaceString:i.z.object({replacements:i.z.array(i.z.object({old_str:i.z.string(),new_str:i.z.string()})),mode:i.z.enum(["unique","all"]).optional()}).optional(),insertLines:i.z.object({insertions:i.z.array(i.z.object({type:i.z.enum(["before","after","between"]),line:i.z.number().optional(),afterLine:i.z.number().optional(),beforeLine:i.z.number().optional(),content:i.z.union([i.z.string(),i.z.array(i.z.string())])}).refine(e=>"between"===e.type?void 0!==e.afterLine&&void 0!==e.beforeLine&&void 0===e.line:void 0!==e.line&&void 0===e.afterLine&&void 0===e.beforeLine,{message:"Invalid line parameters"}))}).optional(),replaceLines:i.z.object({replacements:i.z.array(i.z.object({lines:i.z.string(),content:i.z.union([i.z.string(),i.z.array(i.z.string())])}))}).optional(),deleteString:i.z.object({patterns:i.z.array(i.z.string()),mode:i.z.enum(["all","unique"]).optional()}).optional(),deleteLines:i.z.object({lines:i.z.string()}).optional()}).transform(p).refine(h,{message:"path or documentId required"}).refine(e=>"replace-string"===e.operationType?e.replaceString&&!e.insertLines&&!e.replaceLines&&!e.deleteString&&!e.deleteLines:"insert-lines"===e.operationType?e.insertLines&&!e.replaceString&&!e.replaceLines&&!e.deleteString&&!e.deleteLines:"replace-lines"===e.operationType?e.replaceLines&&!e.replaceString&&!e.insertLines&&!e.deleteString&&!e.deleteLines:"delete-string"===e.operationType?e.deleteString&&!e.replaceString&&!e.insertLines&&!e.replaceLines&&!e.deleteLines:"delete-lines"===e.operationType&&e.deleteLines&&!e.replaceString&&!e.insertLines&&!e.replaceLines&&!e.deleteString,{message:"Invalid operation data"})),comment:i.z.string()}),exports.ManageFilesSchema=i.z.object({operations:i.z.array(i.z.object({operationType:i.z.enum(["create-directory","move-files","delete-files","get-file-info"]),createDirectory:i.z.object({path:i.z.string()}).optional(),moveFiles:i.z.object({moves:i.z.array(i.z.object({source:i.z.string(),destination:i.z.string()}))}).optional(),deleteFiles:i.z.object({targets:i.z.array(i.z.object({path:i.z.string().optional(),documentId:d.optional()}).transform(p).refine(h,{message:"path or documentId required"}))}).optional(),getFileInfo:i.z.object({path:i.z.string()}).optional()}).refine(e=>"create-directory"===e.operationType?e.createDirectory&&!e.moveFiles&&!e.deleteFiles&&!e.getFileInfo:"move-files"===e.operationType?e.moveFiles&&!e.createDirectory&&!e.deleteFiles&&!e.getFileInfo:"delete-files"===e.operationType?e.deleteFiles&&!e.createDirectory&&!e.moveFiles&&!e.getFileInfo:"get-file-info"===e.operationType&&e.getFileInfo&&!e.createDirectory&&!e.moveFiles&&!e.deleteFiles,{message:"Invalid operation data"})),comment:i.z.string()}),exports.RequestOperationsSchema=i.z.object({requestId:u.optional(),operation:i.z.enum(["update","activate","complete","merge","revert","reopen","get-info","list","search","status","cancel","hold","delete-empty","list-areas","update-areas","wait-on","clear-wait","premise-block","dispute-criterion","hand-over","bring-main-in","check-target","round-piece"]),planId:i.z.string().max(64).optional().describe("status / check-target: a plan's thread."),criterion:i.z.string().optional(),reason:i.z.string().optional().describe('cancel, revert, reopen, wait-on: why, to the user ("you"), in their language (a replacing request named). premise-block (required): what the request builds on that the project lacks; not what it adds, not unclear wording (ask instead).'),userSaid:i.z.string().min(8).max(600).optional().describe("The user's words asking for this, exact (checked). Required for activate, cancel of finished work waiting on them, and reopen with a question pending on them."),waitsOn:i.z.string().optional(),update:i.z.preprocess(e=>"string"==typeof e?(()=>{try{return JSON.parse(e)}catch{return e}})():e,i.z.object({userSaid:i.z.string().min(8).max(600).optional().describe("The user's words asking for this change, exact; a worker narrows or rewords its own request only with them."),budget:i.z.number().positive().max(1e5).nullable().optional().describe("The budget the user named for this request's thread (or plan), dollars at list price, their words in userSaid; null removes it."),target:exports.TargetConditionsSchema.optional(),estimate:i.z.number().positive().max(1e5).optional().describe("Expected cost, dollars at list price."),removesCriteria:i.z.boolean().optional().describe("true to delete the criteria your list leaves out; otherwise a shorter list is refused."),arcName:i.z.string().min(2).max(120).optional().describe("The thread's name, two to five plain words (\"Weekly menu app\"); set once, never over the user's."),title:i.z.string().optional(),description:i.z.string().optional(),process:i.z.string().nullable().optional().describe("A workflow process by name, or null; drafts only."),type:exports.SettableRequestTypeEnum.optional(),acceptanceCriteria:i.z.array(i.z.union([i.z.string(),i.z.object({description:i.z.string().min(1),beforeMerge:i.z.boolean().optional(),command:i.z.string().min(1).max(500).optional().describe("With the criterion's key: replaces its command, and the old line is kept on the record beside it. A worker replaces one that cannot pass where it runs with a line that checks the same thing, never less; the user's own command only with their answer in userSaid."),environment:i.z.string().min(1).max(120).optional().describe("As on create_request; set it instead of dropping a command the box cannot run."),kind:i.z.enum(["objective","subjective","gate"]),baseline:r(exports.BaselineSchema).optional(),key:i.z.string().optional().describe("The key of a criterion you reword (it keeps its verdicts); omit for a new one.")})])).optional().describe("Replaces the whole list; items as on create_request.")})).optional(),areas:i.z.array(i.z.string()).max(3).optional().describe("update-areas: up to 3 replacing the current ones; [] clears."),complete:i.z.preprocess(e=>"string"==typeof e?(()=>{try{return JSON.parse(e)}catch{return e}})():e,i.z.object({change:i.z.preprocess(a,i.z.string().min(3).max(200).optional()).describe('On a round (its description opens "Round N of req######"), instead of summary while the round is open: what this one change did, in one line. The user reads it under the message that asked for it.'),summary:i.z.preprocess(a,i.z.string().optional()).describe('Markdown the user reads when it lands. First line: one sentence with the answer (if the request asked a question) or what changed. Then, only if there is more, one short "- " line per thing; no paragraphs. Never "tested" or "verified" when that failed. It replaces earlier summaries (after rework it covers the whole request) and stays after sign-off, so it says what was delivered, not what waits for the user. On a round it is written once, when you are told the round is being completed; until then pass change.'),replacesSummary:i.z.boolean().optional().describe("Required on a re-completion: the summary covers everything, fixes included."),notChecked:i.z.preprocess(a,i.z.string().min(3).max(160).optional()).describe('What you did not check that the user could assume you did ("the page on a phone"); the card leads with it. Not what registered checks run or say they leave out (the card shows those).'),show:i.z.preprocess(e=>"string"==typeof e?(()=>{try{return JSON.parse(e)}catch{return e}})():e,i.z.object({commands:i.z.array(i.z.string().min(1).max(400)).max(5).optional().describe("Command lines that show the result working. AlchemyLab runs each in a box on the work as it was and on the completed work; the user sees both."),artifact:i.z.string().min(1).max(200).optional().describe("A picture, page or document on the record: a result's address, a document id (doc000012) or asset:<file>.")}).optional()).describe("Optional: something more to show with the finished result. What the request already delivered is shown by itself."),worthKnowing:i.z.array(i.z.string().min(10)).max(5).optional().describe("What the user would regret not knowing, one per entry (a discovery, caveat, side effect, follow-up); always one when what was asked was already true or turned out different. Not the summary again; subject first."),guidance:i.z.array(i.z.object({body:i.z.string().min(10).max(800).describe('One fact or instruction for the next agent ("calc.py is generated from ops.json: edit ops.json and regenerate.")'),files:i.z.array(i.z.string().min(1)).min(1).max(8).optional().describe("Files or folders (globs) it is pinned to; without them (userSaid only) the whole project."),evidence:i.z.string().min(1).max(1500).optional().describe("What shows it (command and output, file); not with userSaid."),userSaid:i.z.string().min(8).max(600).optional().describe("A correction of the user's that should hold later: their words, exact.")}).refine(e=>!!e.userSaid||!!e.files?.length&&!!e.evidence,{message:"a trap needs files and evidence; a correction of the user's needs userSaid"})).max(2).optional().describe("A trap the next agent in these files would hit too, or a user correction, proposed as guidance. Not taste, not request-specific, not already guidance. Usually none."),skipTestsReason:exports.skipTestsReasonSchema,skipTests:exports.skipTestsSchema,criteriaComments:i.z.array(i.z.object({key:i.z.string().min(1),comment:i.z.string().min(1),verify:i.z.array(i.z.string().min(1)).max(4).optional().describe("Commands that prove it in the sealed acceptance box (a grep, a targeted test, a screenshot into .visual-check/), judged on their output. Clean checkout of your commit, dependencies installed, nothing built: a check needing a build runs it in the same command. No git, no network, no branching on whether a tool exists. A proof you saw pass on this commit, written exactly as you ran it, is reused."),testFiles:i.z.array(i.z.string().min(1)).max(10).optional().describe("With verify, when it runs just these tests (not the whole suite): their repo paths. Only the user changes a test a promise names.")})).optional().describe("One entry per criterion, keyed as your pickup lists it: what you did and how you saw it hold."),afterRun:i.z.boolean().optional().describe("Call `run` with your final check, then this complete in the SAME response: it completes only if that run passes."),couldNotCheck:i.z.array(i.z.string().min(1)).max(20).optional().describe("Drives only: keys of claims you could not check (reason in criteriaComments); they read as unchecked. Stamp the rest with record_criteria_verification.")})).optional(),merge:i.z.preprocess(e=>"string"==typeof e?(()=>{try{return JSON.parse(e)}catch{return e}})():e,i.z.object({verifications:i.z.array(i.z.object({key:i.z.string().min(2).max(4),evidence:i.z.string().min(1)})).min(1)})).optional(),revert:i.z.object({reason:i.z.string(),confirmDestructive:i.z.boolean().optional()}).optional(),list:i.z.object({filters:i.z.object({type:exports.RequestTypeEnum.optional(),status:exports.RequestStatusFilterEnum.optional(),planDocumentId:i.z.string().optional(),scheduleDocumentId:i.z.string().optional(),questDocumentId:i.z.string().optional(),dateFrom:i.z.string().optional(),dateTo:i.z.string().optional(),limit:i.z.number().min(1).max(100).default(20),offset:i.z.number().min(0).default(0),sortBy:i.z.enum(["created","updated"]).default("updated"),sortDirection:i.z.enum(["asc","desc"]).default("desc")}).optional()}).optional(),search:i.z.object({query:i.z.string().min(1),searchIn:i.z.enum(["title","description","criteria","all"]).default("all"),filters:i.z.object({type:exports.RequestTypeEnum.optional(),status:exports.RequestStatusFilterEnum.optional(),planDocumentId:i.z.string().optional(),scheduleDocumentId:i.z.string().optional(),questDocumentId:i.z.string().optional(),dateFrom:i.z.string().optional(),dateTo:i.z.string().optional(),limit:i.z.number().min(1).max(100).default(20),offset:i.z.number().min(0).default(0)}).optional()}).optional()}).superRefine((e,t)=>{e.requestId
11426
+ "create"===r?(e.name||t.addIssue({code:"custom",message:"name is required for 'create'. Letters, numbers, hyphens, underscores only."}),void 0===e.content&&t.addIssue({code:"custom",message:"content is required for 'create' (use empty string for an empty body)."})):e.planId||t.addIssue({code:"custom",message:`planId is required for '${r}'.`}),"append"!==r||e.content||t.addIssue({code:"custom",message:'content is required for \'append\'. Example: { "operation": "append", "planId": "plan000001", "content": "..." }'}),"update"===r&&(void 0!==e.content||e.name||t.addIssue({code:"custom",message:"update requires 'content' and/or 'name'."})),"replace"===r&&(e.old_str||t.addIssue({code:"custom",message:'old_str is required for replace. Example: { "operation": "replace", "planId": "plan000001", "old_str": "find this", "new_str": "replace with" }'}),void 0===e.new_str&&t.addIssue({code:"custom",message:"new_str is required for replace"})),"restore"!==r||e.versionNumber||t.addIssue({code:"custom",message:'versionNumber is required for restore. Example: { "operation": "restore", "planId": "plan000001", "versionNumber": 1 }'})}),exports.RevertRequestSchema=i.z.object({requestId:u,reason:i.z.string(),confirmDestructive:i.z.boolean().optional()}),exports.ModifyFileSchema=i.z.object({operations:i.z.array(i.z.object({path:i.z.string().optional(),documentId:d.optional(),operationType:i.z.enum(["replace-string","insert-lines","replace-lines","delete-string","delete-lines"]),replaceString:i.z.object({replacements:i.z.array(i.z.object({old_str:i.z.string(),new_str:i.z.string()})),mode:i.z.enum(["unique","all"]).optional()}).optional(),insertLines:i.z.object({insertions:i.z.array(i.z.object({type:i.z.enum(["before","after","between"]),line:i.z.number().optional(),afterLine:i.z.number().optional(),beforeLine:i.z.number().optional(),content:i.z.union([i.z.string(),i.z.array(i.z.string())])}).refine(e=>"between"===e.type?void 0!==e.afterLine&&void 0!==e.beforeLine&&void 0===e.line:void 0!==e.line&&void 0===e.afterLine&&void 0===e.beforeLine,{message:"Invalid line parameters"}))}).optional(),replaceLines:i.z.object({replacements:i.z.array(i.z.object({lines:i.z.string(),content:i.z.union([i.z.string(),i.z.array(i.z.string())])}))}).optional(),deleteString:i.z.object({patterns:i.z.array(i.z.string()),mode:i.z.enum(["all","unique"]).optional()}).optional(),deleteLines:i.z.object({lines:i.z.string()}).optional()}).transform(p).refine(h,{message:"path or documentId required"}).refine(e=>"replace-string"===e.operationType?e.replaceString&&!e.insertLines&&!e.replaceLines&&!e.deleteString&&!e.deleteLines:"insert-lines"===e.operationType?e.insertLines&&!e.replaceString&&!e.replaceLines&&!e.deleteString&&!e.deleteLines:"replace-lines"===e.operationType?e.replaceLines&&!e.replaceString&&!e.insertLines&&!e.deleteString&&!e.deleteLines:"delete-string"===e.operationType?e.deleteString&&!e.replaceString&&!e.insertLines&&!e.replaceLines&&!e.deleteLines:"delete-lines"===e.operationType&&e.deleteLines&&!e.replaceString&&!e.insertLines&&!e.replaceLines&&!e.deleteString,{message:"Invalid operation data"})),comment:i.z.string()}),exports.ManageFilesSchema=i.z.object({operations:i.z.array(i.z.object({operationType:i.z.enum(["create-directory","move-files","delete-files","get-file-info"]),createDirectory:i.z.object({path:i.z.string()}).optional(),moveFiles:i.z.object({moves:i.z.array(i.z.object({source:i.z.string(),destination:i.z.string()}))}).optional(),deleteFiles:i.z.object({targets:i.z.array(i.z.object({path:i.z.string().optional(),documentId:d.optional()}).transform(p).refine(h,{message:"path or documentId required"}))}).optional(),getFileInfo:i.z.object({path:i.z.string()}).optional()}).refine(e=>"create-directory"===e.operationType?e.createDirectory&&!e.moveFiles&&!e.deleteFiles&&!e.getFileInfo:"move-files"===e.operationType?e.moveFiles&&!e.createDirectory&&!e.deleteFiles&&!e.getFileInfo:"delete-files"===e.operationType?e.deleteFiles&&!e.createDirectory&&!e.moveFiles&&!e.getFileInfo:"get-file-info"===e.operationType&&e.getFileInfo&&!e.createDirectory&&!e.moveFiles&&!e.deleteFiles,{message:"Invalid operation data"})),comment:i.z.string()}),exports.RequestOperationsSchema=i.z.object({requestId:u.optional(),operation:i.z.enum(["update","activate","complete","merge","revert","reopen","get-info","list","search","status","cancel","hold","delete-empty","list-areas","update-areas","wait-on","clear-wait","premise-block","dispute-criterion","hand-over","bring-main-in","check-target","round-piece"]),planId:i.z.string().max(64).optional().describe("status / check-target: a plan's thread."),criterion:i.z.string().optional(),reason:i.z.string().optional().describe('cancel, revert, reopen, wait-on: why, to the user ("you"), in their language (a replacing request named). premise-block (required): what the request builds on that the project lacks; not what it adds, not unclear wording (ask instead).'),userSaid:i.z.string().min(8).max(600).optional().describe("The user's words asking for this, exact (checked). Required for activate, cancel of finished work waiting on them, and reopen with a question pending on them."),waitsOn:i.z.string().optional(),update:i.z.preprocess(e=>"string"==typeof e?(()=>{try{return JSON.parse(e)}catch{return e}})():e,i.z.object({userSaid:i.z.string().min(8).max(600).optional().describe("The user's words asking for this change, exact; a worker narrows or rewords its own request only with them."),budget:i.z.number().positive().max(1e5).nullable().optional().describe("The budget the user named for this request's thread (or plan), dollars at list price, their words in userSaid; null removes it."),target:exports.TargetConditionsSchema.optional(),estimate:i.z.number().positive().max(1e5).optional().describe("Expected cost, dollars at list price."),removesCriteria:i.z.boolean().optional().describe("true to delete the criteria your list leaves out; otherwise a shorter list is refused."),arcName:i.z.string().min(2).max(120).optional().describe("The thread's name, two to five plain words (\"Weekly menu app\"); set once, never over the user's."),title:i.z.string().optional(),description:i.z.string().optional(),process:i.z.string().nullable().optional().describe("A workflow process by name, or null; drafts only."),type:exports.SettableRequestTypeEnum.optional(),acceptanceCriteria:i.z.array(i.z.union([i.z.string(),i.z.object({description:i.z.string().min(1),beforeMerge:i.z.boolean().optional(),command:i.z.string().min(1).max(500).optional().describe("With the criterion's key: replaces its command, and the old line is kept on the record beside it. A worker replaces one that cannot pass where it runs with a line that checks the same thing, never less; the user's own command only with their answer in userSaid."),environment:i.z.string().min(1).max(120).optional().describe("As on create_request; set it instead of dropping a command the box cannot run."),kind:i.z.enum(["objective","subjective","gate"]),baseline:r(exports.BaselineSchema).optional(),key:i.z.string().optional().describe("The key of a criterion you reword (it keeps its verdicts); omit for a new one.")})])).optional().describe("Replaces the whole list; items as on create_request.")})).optional(),areas:i.z.array(i.z.string()).max(3).optional().describe("update-areas: up to 3 replacing the current ones; [] clears."),complete:i.z.preprocess(e=>"string"==typeof e?(()=>{try{return JSON.parse(e)}catch{return e}})():e,i.z.object({change:i.z.preprocess(a,i.z.string().min(3).max(200).optional()).describe('On a round (its description opens "Round N of req######"), instead of summary while the round is open: what this one change did, in one line. The user reads it under the message that asked for it.'),summary:i.z.preprocess(a,i.z.string().optional()).describe('Markdown the user reads when it lands. First line: one sentence with the answer (if the request asked a question) or what changed. Then, only if there is more, one short "- " line per thing; no paragraphs. Never "tested" or "verified" when that failed. It replaces earlier summaries (after rework it covers the whole request) and stays after sign-off, so it says what was delivered, not what waits for the user. On a round it is written once, when you are told the round is being completed; until then pass change.'),replacesSummary:i.z.boolean().optional().describe("Required on a re-completion: the summary covers everything, fixes included."),notChecked:i.z.preprocess(a,i.z.string().min(3).max(160).optional()).describe('What you did not check that the user could assume you did ("the page on a phone"); the card leads with it. Not what registered checks run or say they leave out (the card shows those).'),method:i.z.preprocess(a,i.z.string().min(10).max(600).optional()).describe("A tip for the next worker on similar work here, read as precedent, never as a rule: how you checked or measured the result, naming by path any tool you made or used for it, what that cost (runs, time), and what you would do differently. Leave it out when checking was trivial."),methodReplaces:i.z.boolean().optional().describe("Required when the completion says your method pushes earlier tips out: it keeps what is still true in them."),show:i.z.preprocess(e=>"string"==typeof e?(()=>{try{return JSON.parse(e)}catch{return e}})():e,i.z.object({commands:i.z.array(i.z.string().min(1).max(400)).max(5).optional().describe("Command lines that show the result working. AlchemyLab runs each in a box on the work as it was and on the completed work; the user sees both."),artifact:i.z.string().min(1).max(200).optional().describe("A picture, page or document on the record: a result's address, a document id (doc000012) or asset:<file>.")}).optional()).describe("Optional: something more to show with the finished result. What the request already delivered is shown by itself."),worthKnowing:i.z.array(i.z.string().min(10)).max(5).optional().describe("What the user would regret not knowing, one per entry (a discovery, caveat, side effect, follow-up); always one when what was asked was already true or turned out different. Not the summary again; subject first."),guidance:i.z.array(i.z.object({body:i.z.string().min(10).max(800).describe('One fact or instruction for the next agent ("calc.py is generated from ops.json: edit ops.json and regenerate.")'),files:i.z.array(i.z.string().min(1)).min(1).max(8).optional().describe("Files or folders (globs) it is pinned to; without them (userSaid only) the whole project."),evidence:i.z.string().min(1).max(1500).optional().describe("What shows it (command and output, file); not with userSaid."),userSaid:i.z.string().min(8).max(600).optional().describe("A correction of the user's that should hold later: their words, exact.")}).refine(e=>!!e.userSaid||!!e.files?.length&&!!e.evidence,{message:"a trap needs files and evidence; a correction of the user's needs userSaid"})).max(2).optional().describe("A trap the next agent in these files would hit too, or a user correction, proposed as guidance. Not taste, not request-specific, not already guidance. Usually none."),skipTestsReason:exports.skipTestsReasonSchema,skipTests:exports.skipTestsSchema,criteriaComments:i.z.array(i.z.object({key:i.z.string().min(1),comment:i.z.string().min(1),verify:i.z.array(i.z.string().min(1)).max(4).optional().describe("Commands that prove it in the sealed acceptance box (a grep, a targeted test, a screenshot into .visual-check/), judged on their output. Clean checkout of your commit, dependencies installed, nothing built: a check needing a build runs it in the same command. No git, no network, no branching on whether a tool exists. A proof you saw pass on this commit, written exactly as you ran it, is reused."),testFiles:i.z.array(i.z.string().min(1)).max(10).optional().describe("With verify, when it runs just these tests (not the whole suite): their repo paths. Only the user changes a test a promise names.")})).optional().describe("One entry per criterion, keyed as your pickup lists it: what you did and how you saw it hold."),afterRun:i.z.boolean().optional().describe("Call `run` with your final check, then this complete in the SAME response: it completes only if that run passes."),couldNotCheck:i.z.array(i.z.string().min(1)).max(20).optional().describe("Drives only: keys of claims you could not check (reason in criteriaComments); they read as unchecked. Stamp the rest with record_criteria_verification.")})).optional(),merge:i.z.preprocess(e=>"string"==typeof e?(()=>{try{return JSON.parse(e)}catch{return e}})():e,i.z.object({verifications:i.z.array(i.z.object({key:i.z.string().min(2).max(4),evidence:i.z.string().min(1)})).min(1)})).optional(),revert:i.z.object({reason:i.z.string(),confirmDestructive:i.z.boolean().optional()}).optional(),list:i.z.object({filters:i.z.object({type:exports.RequestTypeEnum.optional(),status:exports.RequestStatusFilterEnum.optional(),planDocumentId:i.z.string().optional(),scheduleDocumentId:i.z.string().optional(),questDocumentId:i.z.string().optional(),dateFrom:i.z.string().optional(),dateTo:i.z.string().optional(),limit:i.z.number().min(1).max(100).default(20),offset:i.z.number().min(0).default(0),sortBy:i.z.enum(["created","updated"]).default("updated"),sortDirection:i.z.enum(["asc","desc"]).default("desc")}).optional()}).optional(),search:i.z.object({query:i.z.string().min(1),searchIn:i.z.enum(["title","description","criteria","all"]).default("all"),filters:i.z.object({type:exports.RequestTypeEnum.optional(),status:exports.RequestStatusFilterEnum.optional(),planDocumentId:i.z.string().optional(),scheduleDocumentId:i.z.string().optional(),questDocumentId:i.z.string().optional(),dateFrom:i.z.string().optional(),dateTo:i.z.string().optional(),limit:i.z.number().min(1).max(100).default(20),offset:i.z.number().min(0).default(0)}).optional()}).optional()}).superRefine((e,t)=>{e.requestId
11406
11427
  const r=e.operation,n={complete:{field:"complete",hint:'Provide: { complete: { summary: "..." } }'},merge:{field:"merge",hint:'Provide: { merge: { verifications: [{key: "1a", evidence: "..."}] } }'},revert:{field:"revert",hint:'Provide: { revert: { reason: "..." } }'},search:{field:"search",hint:'Provide: { search: { query: "..." } }'}}[r]
11407
11428
  n&&!e[n.field]&&t.addIssue({code:"custom",message:`'${r}' operation requires '${n.field}' data. ${n.hint}`})})
11408
11429
  const g=i.z.string({required_error:"Required. A quick edit either touches up an earlier request's result or stands on its own: pass that request id (req######) or the word 'new'."}).refine(e=>"new"===e||/^req\d{6}$/.test(e),{message:"Pass a request id like req000031, or 'new'."}).describe("The request whose result this edit adjusts (the user pointed at it), or 'new': the same rule as create_request's follows.")
11409
11430
  exports.QuickWriteSchema=i.z.object({title:i.z.string().min(1,"Title is required"),description:i.z.string().min(1,"Description is required"),follows:g,path:i.z.string().optional(),documentId:d.optional(),content:i.z.union([i.z.string(),i.z.array(i.z.string())]),format:i.z.enum(["text","json"]).optional(),append:i.z.boolean().optional()}).transform(p).refine(h,{message:"path or documentId required"}),exports.QuickReplaceSchema=i.z.object({title:i.z.string().min(1,"Title is required"),description:i.z.string().min(1,"Description is required"),follows:g,operations:i.z.array(i.z.object({path:i.z.string().optional(),documentId:d.optional(),replacements:i.z.array(i.z.object({old_str:i.z.string().min(1),new_str:i.z.string(),match_mode:i.z.enum(["unique","all"]).optional()})).min(1),fuzzy_match:i.z.boolean().optional()}).transform(p).refine(h,{message:"path or documentId required"})).min(1),fuzzy_match:i.z.boolean().optional(),comment:i.z.string().min(1,"Comment describing the change is required")}),exports.MetaWorkflowListSchema=i.z.object({}),exports.MetaWorkflowGetSchema=i.z.object({workflowId:i.z.string().describe("Workflow ID to retrieve")}),exports.MetaWorkflowCreateSchema=i.z.object({name:i.z.string().min(1).describe("Unique name for the workflow"),shortName:i.z.string().min(1).max(10).describe("Short display name (required, max 10 chars)"),description:i.z.string().optional().describe("Description of what the workflow does"),testGate:i.z.enum(["off","auto","ask"]).optional().describe("Test gate: off (no test gate), auto (checks run automatically; a blocking command must pass, an advisory one may be skipped with a reason), ask (the user picks auto/interactive/skip at each completion)"),reviewGate:i.z.enum(["off","on"]).optional().describe("Review gate: off, or on (one reviewer checks each acceptance criterion, with the reviewers the kind assigns: guidance, model on engineering, any the workflow added)"),signoffGate:i.z.enum(["off","agent","you"]).optional().describe('Sign-off gate, as the workflow page names it: off; agent ("Delivery review": an agent asks whether the delivery is what was asked for, then merges or brings it to the user); you ("You sign off": the user approves before merge)'),approvalMode:i.z.enum(["none","require-approval","agent-check"]).optional().describe("Pre-work approval: none (default), require-approval (the user approves each request), or agent-check (an independent intake reviewer rules on agent-created requests before work starts)"),interactionPosture:i.z.enum(["autonomous","balanced","collaborative"]).optional().describe("Interaction posture (question-gate dial): autonomous (defaulted questions are self-answered -- proceed + FYI report), balanced (default; questions post normally, non-blocking when defaulted), collaborative (always ask the human)")}),exports.MetaWorkflowUpdateSchema=i.z.object({workflowId:i.z.string().describe("Workflow ID to update"),name:i.z.string().min(1).optional().describe("New name"),shortName:i.z.string().max(10).optional().describe("New short name (max 10 chars)"),description:i.z.string().optional().describe("New description"),testGate:i.z.enum(["off","auto","ask"]).optional().describe("Test gate: off (no test gate), auto (checks run automatically; a blocking command must pass, an advisory one may be skipped with a reason), ask (the user picks auto/interactive/skip at each completion)"),reviewGate:i.z.enum(["off","on"]).optional().describe("Review gate: off, or on (one reviewer checks each acceptance criterion, with the reviewers the kind assigns: guidance, model on engineering, any the workflow added)"),signoffGate:i.z.enum(["off","agent","you"]).optional().describe('Sign-off gate, as the workflow page names it: off; agent ("Delivery review": an agent asks whether the delivery is what was asked for, then merges or brings it to the user); you ("You sign off": the user approves before merge)'),approvalMode:i.z.enum(["none","require-approval","agent-check"]).optional().describe("Pre-work approval: none (default), require-approval (the user approves each request), or agent-check (an independent intake reviewer rules on agent-created requests before work starts)"),interactionPosture:i.z.enum(["autonomous","balanced","collaborative"]).optional().describe("Interaction posture (question-gate dial): autonomous (defaulted questions are self-answered -- proceed + FYI report), balanced (default; questions post normally, non-blocking when defaulted), collaborative (always ask the human)")}),exports.MetaRecipeListSchema=i.z.object({}),exports.MetaRecipeGetSchema=i.z.object({recipeId:i.z.string().describe("Recipe ID to retrieve")}),exports.MetaRecipeCreateSchema=i.z.object({name:i.z.string().min(1).describe("Unique name for the recipe"),description:i.z.string().optional().describe("Description of what the recipe does"),documentation:i.z.string().optional().describe("Detailed documentation")}),exports.MetaRecipeAddHookSchema=i.z.object({recipeId:i.z.string().describe("Recipe ID to add the hook to"),hookId:i.z.string().describe("Hook template ID to add"),position:i.z.number().int().min(0).optional().describe("Position in the recipe (0-based, appended if omitted)")}),exports.MetaRecipeRemoveHookSchema=i.z.object({recipeId:i.z.string().describe("Recipe ID to remove the hook from"),hookId:i.z.string().describe("Hook template ID to remove")}),exports.MetaHookListSchema=i.z.object({}),exports.MetaHookCreateSchema=i.z.object({name:i.z.string().min(1).describe("Unique name for the hook"),description:i.z.string().optional().describe("Description of what the hook does"),eventType:i.z.string().describe("Event type that triggers this hook (e.g., session_idle, request_completed, custom:plan_ready)"),category:i.z.enum(["alchemy","session","message","editing","reading","file_management","documents","history","tool"]).default("session").describe("Hook category"),agent:i.z.string().describe("Agent template to execute when hook fires"),promptTemplate:i.z.string().describe("Prompt template with {{variable}} substitution"),condition:i.z.record(i.z.any()).optional().describe("Condition object for filtering when hook fires"),timing:i.z.enum(["before","after"]).default("after").describe("When to fire relative to the event"),action:i.z.enum(["spawn_agent","inject_prompt"]).optional().describe("What the hook does: spawn_agent (default) starts a new session of `agent` with the prompt; inject_prompt sends the prompt into an existing session"),target:i.z.enum(["independent_session","child_session","same_session","parent_session","ancestor_chain"]).optional().describe("Where the prompt goes. With spawn_agent: independent_session (default, no parent) or child_session (under the session that fired the event). With inject_prompt: same_session (default, the session that fired the event), parent_session, or ancestor_chain"),maxIterations:i.z.number().int().min(1).optional().describe("Maximum times the hook fires for one session, counted from its firing record. Set it on every session_idle hook that sends into the same session. A session a hook started never triggers that hook again, whatever this says"),emitEvent:i.z.string().optional().describe("Custom event to emit on completion"),emitEventOnCondition:i.z.union([i.z.object({condition:i.z.object({field:i.z.string(),operator:i.z.enum(["contains","not_contains","equals","not_equals","matches","not_matches"]),value:i.z.string()}),event:i.z.string()}),i.z.array(i.z.object({condition:i.z.object({field:i.z.string(),operator:i.z.enum(["contains","not_contains","equals","not_equals","matches","not_matches"]),value:i.z.string()}),event:i.z.string()}))]).optional().describe("Conditional event emission based on output")}),exports.MetaWorkflowAddRecipeSchema=i.z.object({workflowId:i.z.string().describe("Workflow ID to add the recipe to"),recipeId:i.z.string().describe("Recipe ID to add"),position:i.z.number().int().min(0).optional().describe("Position in the workflow (0-based, appended if omitted)")}),exports.MetaWorkflowRemoveRecipeSchema=i.z.object({workflowId:i.z.string().describe("Workflow ID to remove the recipe from"),recipeId:i.z.string().describe("Recipe ID to remove")}),exports.MetaAgentListSchema=i.z.object({}),exports.MetaOperationsSchema=i.z.object({operation:i.z.enum(["workflow-list","workflow-get","workflow-create","workflow-update","recipe-list","recipe-get","recipe-create","recipe-add-hook","recipe-remove-hook","hook-list","hook-create","workflow-add-recipe","workflow-remove-recipe","agent-list"]).describe("Which meta operation to perform; the other params apply per operation (see tool description)"),...exports.MetaWorkflowGetSchema.partial().shape,...exports.MetaWorkflowCreateSchema.partial().shape,...exports.MetaWorkflowUpdateSchema.partial().shape,...exports.MetaRecipeGetSchema.partial().shape,...exports.MetaRecipeCreateSchema.partial().shape,...exports.MetaRecipeAddHookSchema.partial().shape,...exports.MetaRecipeRemoveHookSchema.partial().shape,...exports.MetaHookCreateSchema.partial().shape,...exports.MetaWorkflowAddRecipeSchema.partial().shape,...exports.MetaWorkflowRemoveRecipeSchema.partial().shape}),exports.TestingProcessListSchema=i.z.object({}),exports.TestingProcessGetSchema=i.z.object({processId:i.z.string().describe("Test process ID to retrieve")})
11410
11431
  const A=i.z.object({name:i.z.string().min(1).describe('Command name (e.g., "Build", "Unit Tests")'),command:i.z.string().min(1).describe('Shell command to run (e.g., "npm test")'),required:i.z.boolean().default(!0).describe("If true, failure blocks the request (default: true)"),timeout:i.z.number().int().min(10).max(3600).default(300).describe("Max execution time in seconds (default: 300)")})
11411
11432
  exports.TestingProcessCreateSchema=i.z.object({name:i.z.string().min(1).describe("Name for the test process"),environmentId:i.z.string().optional().describe("ID of the environment (confined runtime) this process runs in. Get it from environment_list or environment_setup."),description:i.z.string().optional().describe("Description of what this process verifies"),scope:i.z.string().optional().describe('LABEL ONLY -- a monorepo path prefix recorded for display (e.g. "packages/api/"). It does NOT affect when the process runs; use pathGlobs for that.'),pathGlobs:i.z.array(i.z.string()).optional().describe('Which changes this process runs on: glob patterns matched against the changed files (e.g. ["sites/**"], ["src/**", "*.md"]). THIS is what limits when the process runs. Omit it and the process runs on EVERY change, which is the right choice only for a check that is cheap and universal.'),areas:i.z.array(i.z.string()).optional().describe('Work-area labels this process covers (e.g. ["graphics/opengl"]). An alternative to pathGlobs for work that is not file-shaped; a process applies if EITHER dimension matches.'),setup:i.z.array(i.z.string()).optional().describe("Commands that prepare the tree for the tests (install the package itself, generate files the tests import), e.g. [\"python -m pip install --no-deps -e .\"]. They run in its box before its commands on every run, and also before a request's criteria checks, which run in a clean checkout of the commit where `commands` do not run: a preparation step put in `commands` is missing there and those checks fail. If one fails, the run fails as the environment's fault and the commands do not run."),commands:i.z.array(A).min(1).describe("Test commands to execute in order"),verifyAcceptanceCriteria:i.z.boolean().optional().describe("When true, auto-tester verifies request acceptance criteria after running commands"),fingerprint:i.z.array(i.z.string()).optional().describe('Files whose content invalidates setup. Setup commands re-run only when these files change between runs (e.g. ["package-lock.json"], ["Cargo.lock"], ["uv.lock", "poetry.lock"]). Polyglot repos list multiple. Omit / empty list = always re-run setup (existing behavior).'),poolSize:i.z.number().int().min(1).max(8).optional().describe("Number of warm test worktrees in the pool. Default 2. Set to 1 for languages with very large build artifacts (Rust target/, C++ build/). Higher values allow more parallel test runs at the cost of disk."),runtimeKept:i.z.string().optional().describe('Only when the user chose to run the check on a runtime other than the lowest one the project declares: that runtime and version, e.g. "python 3.12". Without it, a check whose environment runs another version than the declared minimum is not registered.'),leavesOut:i.z.array(i.z.object({what:i.z.string().describe('The left-out part in plain words, e.g. "the Valkey backend tests"'),paths:i.z.array(i.z.string()).min(1).describe('Globs for the code that part checks, so a change there goes unchecked by this check: for a backend\'s tests, that backend (e.g. ["aiocache/backends/valkey.py"]); for browser or end-to-end tests, the app source they drive (e.g. ["src/**"]). Not only the left-out test files.'),command:i.z.string().min(1).max(300).optional().describe('The command that runs that part, e.g. "pnpm test:e2e": a request whose criterion runs it is not told this part was left out.')})).optional().describe('What this check deliberately does not run and no other check runs (the suites the card lists as "not run on every change"), each with the code it checks. A change to that code is reported to the user as not checked by this check, unless the request ran that part itself.')}),exports.TestingProcessDeleteSchema=i.z.object({processId:i.z.string().describe("Test process ID to delete")}),exports.EnvironmentListSchema=i.z.object({}),exports.ResultsSchema=i.z.object({operation:i.z.enum(["record","fetch","pin","list","read"]),path:i.z.string().optional().describe("record: the file, inside the project (not /tmp); your worktree first, then the main project. Bulk that must not reach a commit can be written by a command under `.alchemy_lab/scratch/` (gitignored)."),name:i.z.string().optional().describe("record: the name a person uses for it (defaults to the filename). list: show every version recorded under this name, newest first."),summary:i.z.record(i.z.any()).optional().describe('record (required): the small facts a reader wants instead of the bytes, e.g. {maxStress: 412, unit: "MPa", converged: true}.'),box:i.z.string().optional().describe("An experiment handle (exp_...). fetch: put the bytes inside that sealed box under its /scratch (a box cannot see the host scratch; omit for host work, which is faster). record: the result lives inside that box and is streamed out and stored in one step (a box cannot write your worktree)."),mediaType:i.z.string().optional().describe("record: MIME type, when you know it."),inputs:i.z.array(i.z.string()).optional().describe("record: addresses of the results this one was made from (provenance)."),note:i.z.string().optional().describe("record: one line on how it was produced."),address:i.z.string().optional().describe("pin / read / fetch: the full 64-character address the record returned (a unique prefix works too)."),at:i.z.string().optional().describe("pin: the pointer path in your worktree (.result.json is added). fetch: where the bytes land, under AlchemyLab's scratch folder for the project on the host (the answer gives the full path) or the box's `/scratch`. Defaults to results/<name>."),comment:i.z.string().optional().describe("pin: the commit comment for the pointer file."),offset:i.z.number().optional().describe("read: line to start at (default 0)."),limit:i.z.number().optional().describe("read: how many lines (default 200). list: how many results (default 25)."),find:i.z.string().optional().describe("read: return only lines containing this text, instead of a range.")}),exports.ResultInspectSchema=i.z.object({operation:i.z.enum(["list","read"]),name:i.z.string().optional().describe("list: show every version recorded under this name, newest first."),address:i.z.string().optional().describe("read: the full 64-character address of the result, a check run's id (tr_...) or a command run's record id (trec...), to read what its commands printed."),offset:i.z.number().optional().describe("read: line to start at (default 0)."),limit:i.z.number().optional().describe("read: how many lines (default 200). list: how many results (default 25)."),find:i.z.string().optional().describe("read: return only lines containing this text, instead of a range.")}),exports.ExperimentStartSchema=i.z.object({env:i.z.string().optional().describe("Environment by name (from environment_list), one whose image has the toolchain you need."),environmentId:i.z.string().optional().describe("Environment by id (alternative to `env`). The primary env is used if neither is given."),size:i.z.enum(["light","standard","heavy"]).optional().describe("light: 2 cores (lint, a typecheck, one test file). standard (default): a share of the free cores (a suite, a build). heavy: every free core but 2 and 6 GB of memory (2 GB otherwise, unless the environment sets its own), for work that uses them (a CFD solve, meshing, a large parallel build, a model that runs out of memory). A size you do not hold opens another box."),confirmWorkspace:i.z.boolean().optional().describe('Required for env "workspace" (the live host): the task genuinely needs the real machine. Building or trying out the app does not.')}),exports.ExperimentStopSchema=i.z.object({handle:i.z.string().describe("The experiment handle to tear down.")}),exports.ExperimentListSchema=i.z.object({}),exports.ReadSiteSchema=i.z.object({url:i.z.string()}),exports.DriveSiteSchema=i.z.object({url:i.z.string().describe("The running product, e.g. the environment's published URL or http://localhost:5173."),steps:i.z.array(i.z.object({action:i.z.enum(["click","clickat","type","press","move","scroll","wait","media","eval"]).describe('click (selector) \xb7 clickat (x,y) \xb7 type (selector + text) \xb7 press (key) \xb7 move (x,y) \xb7 scroll (dy) \xb7 wait (ms, max 5000) \xb7 media ("print" shows the printout, "screen" switches back) \xb7 eval (script, the body of an async function run in the page; what it returns is reported; ms is its time limit, default 10000, max 20000)'),selector:i.z.string().optional().describe('Playwright selector, e.g. "text=Build" or "#start".'),text:i.z.string().optional(),key:i.z.string().optional().describe('e.g. "Enter"'),x:i.z.number().optional(),y:i.z.number().optional(),dy:i.z.number().optional(),ms:i.z.number().optional(),media:i.z.enum(["print","screen"]).optional(),script:i.z.string().optional().describe('eval: e.g. "let n = 0; const t = performance.now(); await new Promise((r) => { const f = () => { n++; performance.now() - t < 3000 ? requestAnimationFrame(f) : r(); }; requestAnimationFrame(f); }); return n / 3;"')})).min(1).max(20),shots:i.z.enum(["each","final"]).optional().describe('"each" screenshots every step; default the final state only.'),session:i.z.string().optional().describe("Keeps the page open: the next call with this name continues where this one stopped. Idle sessions close after 10 minutes."),close:i.z.boolean().optional().describe("Close the named session after this call."),width:i.z.number().optional().describe("Viewport width for a new session (default 1280; 390 is a phone).")}),exports.CaptureSiteSchema=i.z.object({url:i.z.string(),fullPage:i.z.boolean().optional().describe("The whole scrollable page instead of the viewport."),width:i.z.number().optional().describe("Viewport width (default 1280; 390 is a phone).")}),exports.PreviewSiteSchema=i.z.object({dir:i.z.string().optional().describe('Repo-relative, e.g. "dist" or "." for the top of the repository; or a folder in AlchemyLab\'s scratch for this project, by the absolute path a tool gave you (a build you made there to check). Not needed with stop.'),requestId:i.z.string().regex(/^req\d{6}$/).optional().describe("Serve from this unmerged request's working copy instead of main."),name:i.z.string().optional().describe("Preview handle (default: a slug of dir); reusing one replaces that preview. With stop: the preview to stop."),stop:i.z.boolean().optional().describe("Stop a running preview instead of serving one: the one named, or the only one running."),label:i.z.string().optional(),page:i.z.string().optional().describe('The page in dir to open at, e.g. "cooking.html", when it is not dir\'s index.html. Left out, the preview opens at the page this request made or changed.')}),exports.FetchAssetSchema=i.z.object({url:i.z.string().describe("The http(s) URL of the image or font to download."),destPath:i.z.string().describe('Where to write it, repo-relative (e.g. "sites/acme/assets/hero.jpg"). Must stay inside the working tree; parent directories are created.')}),exports.RequestVisualFeedbackSchema=i.z.object({box:i.z.string().optional().describe("The experiment handle (from experiment_start, on a screenshot-capable env) to render in / read from. The box must stay held until this returns."),route:i.z.string().optional().describe('With `box`: the route I render + screenshot FOR YOU (e.g. "/" or "/settings"). I serve `serveDir` and screenshot it -- you do NOT hand-roll serve/screenshot. This is the easiest path.'),script:i.z.string().optional().describe('With `box`, for FULL control: a Playwright snippet (the body of `async (page, baseUrl) => { ... }`). I serve the app, launch headless Chromium, and run your script -- navigate (page.goto(baseUrl + "/x")), click, fill, scroll, waitForSelector, etc. -- then screenshot the final page state. Reliable: you write only the interaction, no serve/launch/flags. Use this to reach a state that needs interaction (open a menu, log in, submit a form).'),fullPage:i.z.boolean().optional().describe("Capture the entire scrollable page instead of just the viewport."),serveDir:i.z.string().optional().describe("With `box`+`route`: the static dir to serve (default /work). For a built app, build first via run(), then point this at the build output (e.g. /work/dist)."),path:i.z.string().optional().describe("Alternative to route: a screenshot you ALREADY wrote inside the box (e.g. /tmp/shot.png); I read it out."),assetFilename:i.z.string().optional().describe("Alternative to box: the filename of an already-uploaded screenshot asset to annotate."),prompt:i.z.string().optional().describe("What you want the human to look at or mark up (shown with the request)."),timeoutMs:i.z.number().optional().describe("How long to wait for the human, in ms (default ~10 min).")}),exports.AskUserVisualSchema=i.z.object({question:i.z.string().describe('The question for the USER about the marked region (e.g. "Should this button use the primary color?").'),imagePath:i.z.string().optional().describe("Repo-relative path to an image you already produced (png/jpg/gif/webp), e.g. a screenshot you wrote. Provide this OR previewName."),previewName:i.z.string().optional().describe("Name of a registered live preview (a `live:` ref); I freeze it into a snapshot image for you. Provide this OR imagePath."),box:i.z.string().optional().describe('The region the question is about: "x,y,w,h" in normalized 0..1 image coordinates, origin top-left (e.g. "0.62,0.08,0.3,0.12"). Omit to ask about the whole image.'),options:i.z.array(i.z.string()).optional().describe("Optional answer choices (labels). Omit for a free-text answer."),context:i.z.string().optional().describe("Extra context shown with the question in the inbox (why you are asking, what you will do with the answer)."),priority:i.z.enum(["low","normal","high"]).optional().describe('Question priority; "high" always reaches the user regardless of autonomy dial.')}),exports.EnvironmentGetSchema=i.z.object({environmentId:i.z.string().describe("Environment ID to retrieve")}),exports.EnvironmentProvisionSchema=i.z.object({environmentId:i.z.string(),why:i.z.string().optional().describe("Why provision now (shown on the approval).")}),exports.EnvironmentTeardownSchema=i.z.object({environmentId:i.z.string()}),exports.EnvironmentStatusSchema=i.z.object({environmentId:i.z.string()}),exports.EnvironmentVerifyStackSchema=i.z.object({environmentId:i.z.string()})
11412
- const y=i.z.object({image:i.z.string().optional().describe("Container image carrying the project toolchain/deps, e.g. node:20-alpine, python:3.12-slim, golang:1.22, ruby:3.3"),install:i.z.string().optional().describe("Deps install command -- Node-only sugar that populates a /work/node_modules cache volume, run with registry egress before the confined gate, e.g. npm ci. Non-Node runtimes use `artifacts` instead (a cache volume + prepare command: .venv/uv sync, cargo, go, vendor/bundle). Omit if the project has no deps."),composeFile:i.z.string().optional().describe("Path to the project docker-compose file for its services. Omit if no services."),shellInit:i.z.string().optional().describe("Shell preamble run before EVERY command in the box, e.g. '. /usr/lib/openfoam/openfoam2312/etc/bashrc'. REQUIRED for a toolchain that publishes itself through a script rather than the image PATH (OpenFOAM etc/bashrc, oneAPI setvars.sh, conda activate, ROS setup.bash): `docker exec` runs no ENTRYPOINT and reads no login profile, so without this every command gets a bare PATH. Put the preamble HERE, never inside the smoke command -- a smoke command carrying its own preamble proves the preamble and not the environment, and the next agent gets nothing."),connEnv:i.z.record(i.z.string()).optional().describe("Connection env injected into the gate (e.g. DATABASE_URL) using SERVICE NAMES + internal ports, never localhost/host ports."),seed:i.z.array(i.z.string()).optional().describe("Optional commands run after services are up (e.g. migrations)."),artifacts:i.z.array(i.z.object({path:i.z.string().describe("Mount target for the writable, populated volume: relative to /work for a dependency folder the project's toolchain looks for there (node_modules, .venv, vendor/bundle), or absolute (e.g. /cache/cargo). Tools you install (apt/apk packages, binaries) go at an absolute path outside /work, such as /tools: /work is the project's folder, and its test runners see anything in it."),prepare:i.z.string().optional().describe("Command that populates it -- the one phase with registry egress (e.g. npm ci, uv sync, cargo fetch). Omit for a writable empty volume."),manifests:i.z.array(i.z.string()).optional().describe("Lockfiles (relative to /work) whose content hash keys the cache; list them so the volume is reused across runs and only rebuilt when deps change (e.g. uv.lock, Cargo.lock). Omit => rebuilt every run."),env:i.z.record(i.z.string()).optional().describe('Env applied during prepare AND every run, e.g. { CARGO_HOME: "/cache/cargo" } for caches outside /work.')})).optional().describe("Per-runtime build/dependency artifacts: writable volumes populated by a prepare command, generalizing `install` beyond node_modules. Use for Python (.venv), Rust (target + $CARGO_HOME), Go, Ruby (vendor/bundle), etc. `install` is sugar for a single node_modules artifact; for any non-Node runtime use artifacts (install would write to node_modules, the wrong place)."),confinement:i.z.enum(["confined","egress-allowlist","privileged","unsafe","host","pure-host"]).optional().describe("Confinement level. Default 'confined' (fully sealed Docker; add egressAllowlist hosts to open exactly those -- stored as 'egress-allowlist'). privileged/unsafe relax the container. 'host' runs on the host machine (no Docker) inside an OS sandbox (Seatbelt/bubblewrap) for toolchains Docker can't run. 'pure-host' is the built-in workspace env (the live worktree) -- do not create new envs with it. Merge gating is independent (the Sign-off setting)."),commandGate:i.z.array(i.z.enum(["policy","agent","human"])).optional().describe("Gates an experiment command passes before it runs, in escalation order policy -> agent -> human. Empty = run freely; escalating past the last gate blocks (fail closed). policy = deterministic allowlist/deny-floor; agent = isolated LLM judge (allow or escalate only); human = ask. Registered commands are pre-approved. Defaults by level ([] for confined/egress-allowlist, ['human'] for privileged/unsafe)."),worktreeTarget:i.z.enum(["ephemeral","live"]).optional().describe("Worktree the env runs against: 'ephemeral' (a disposable copy at HEAD, default) or 'live' (the project's live working dir, read-write). The built-in workspace env is 'live'."),egressAllowlist:i.z.array(i.z.string()).optional().describe("Hosts the box may reach at the 'egress-allowlist' level (exact name or subdomain, e.g. registry.npmjs.org). Ignored at other levels."),addCaps:i.z.array(i.z.string()).optional().describe("Linux capabilities to add at the 'privileged' level (on top of cap-drop ALL), e.g. SYS_PTRACE for debuggers, IPC_LOCK for memlock. Ignored at other levels."),sysctls:i.z.record(i.z.string()).optional().describe("Namespaced sysctls to set at the 'privileged' level, e.g. net.ipv4.ip_local_port_range. Host-wide sysctls (vm.max_map_count) are NOT settable per-container."),runAsRoot:i.z.boolean().optional().describe("Run the box as root (privileged / unsafe levels). Needed for some caps + the docker socket."),dockerSocket:i.z.boolean().optional().describe("Mount the host docker socket for testcontainers-style tests. WARNING: full host control over the docker daemon. Only honored at the 'unsafe' level."),persistentVolumes:i.z.array(i.z.object({name:i.z.string(),target:i.z.string(),service:i.z.string().optional()})).optional().describe("Data volumes that survive between runs (keyed to this environment; off by default since experiments are ephemeral). Set service to mount into a compose service (e.g. a DB data dir), or omit it to mount into the box (e.g. a SQLite dir)."),resources:i.z.object({memory:i.z.string().optional().describe("Memory cap for this environment's boxes and its checks' boxes, e.g. '4g' (default 2g; 6g at size heavy). Set it when the test suite or build needs more."),cpus:i.z.number().optional().describe("A fixed CPU count. Usually leave it unset and set size instead."),size:i.z.enum(["light","standard","heavy"]).optional().describe("What this environment's boxes and checks are for, which sets their cores from what is free: light (2 cores: lint, small checks), standard (the default: a share of the free cores, for test suites and builds), heavy (every free core but 2: CFD or FEM solves, meshing, large parallel builds). An agent can ask for another size per box."),pidsLimit:i.z.number().optional().describe("Max processes (default 512)."),scratchMb:i.z.number().optional().describe("Size in MB of an in-memory /scratch, used only when the image cannot take the disk-backed one (default 512). /scratch is normally on disk and needs no size."),shmMb:i.z.number().optional().describe("/dev/shm size in MB -- bump for headless browsers (Chrome crashes on the 64m default).")}).optional().describe("Per-environment resource caps that override the confined defaults.")}),w=r(y),b='Start from a built-in preset; anything in config overrides it. "office": python-docx, openpyxl, python-pptx, pandoc, LibreOffice and segno/zbarimg (QR codes) in a sealed box, with `render-pdf FILE` to turn Word, Excel, PowerPoint, Markdown or HTML into PDF. Its image is built locally on first use (a few minutes). Pass the same preset (and config) to environment_verify and environment_create.',v="Save without a passing environment_verify, only for a config that cannot be verified from here (it needs credentials, external services or hardware the confined verifier lacks). Requires skipVerifyReason; the skip is logged."
11433
+ const y=i.z.object({image:i.z.string().optional().describe("Container image carrying the project toolchain/deps, e.g. node:20-alpine, python:3.12-slim, golang:1.22, ruby:3.3"),systemPackages:i.z.array(i.z.string().regex(/^[A-Za-z0-9][A-Za-z0-9+._-]*(=[A-Za-z0-9+.:~_-]+)?$/)).optional().describe('System packages the boxes need from the image\'s package manager (apt, apk or dnf), e.g. ["gettext"] for msgfmt, ["ffmpeg"], ["libgl1"]. AlchemyLab builds an image from `image` with them installed, once per machine; the box itself cannot install them (it does not run as root).'),install:i.z.string().optional().describe("Deps install command -- Node-only sugar that populates a /work/node_modules cache volume, run with registry egress before the confined gate, e.g. npm ci. Non-Node runtimes use `artifacts` instead (a cache volume + prepare command: .venv/uv sync, cargo, go, vendor/bundle). Omit if the project has no deps."),composeFile:i.z.string().optional().describe("Path to the project docker-compose file for its services. Omit if no services."),shellInit:i.z.string().optional().describe("Shell preamble run before EVERY command in the box, e.g. '. /usr/lib/openfoam/openfoam2312/etc/bashrc'. REQUIRED for a toolchain that publishes itself through a script rather than the image PATH (OpenFOAM etc/bashrc, oneAPI setvars.sh, conda activate, ROS setup.bash): `docker exec` runs no ENTRYPOINT and reads no login profile, so without this every command gets a bare PATH. Put the preamble HERE, never inside the smoke command -- a smoke command carrying its own preamble proves the preamble and not the environment, and the next agent gets nothing."),connEnv:i.z.record(i.z.string()).optional().describe("Connection env injected into the gate (e.g. DATABASE_URL) using SERVICE NAMES + internal ports, never localhost/host ports."),seed:i.z.array(i.z.string()).optional().describe("Optional commands run after services are up (e.g. migrations)."),artifacts:i.z.array(i.z.object({path:i.z.string().describe("Mount target for the writable, populated volume: relative to /work for a dependency folder the project's toolchain looks for there (node_modules, .venv, vendor/bundle), or absolute (e.g. /cache/cargo). Binaries you download go at an absolute path outside /work, such as /tools: /work is the project's folder, and its test runners see anything in it. apt/apk packages go in systemPackages, not here."),prepare:i.z.string().optional().describe("Command that populates it -- the one phase with registry egress (e.g. npm ci, uv sync, cargo fetch). Omit for a writable empty volume."),manifests:i.z.array(i.z.string()).optional().describe("Lockfiles (relative to /work) whose content hash keys the cache; list them so the volume is reused across runs and only rebuilt when deps change (e.g. uv.lock, Cargo.lock). Omit => rebuilt every run."),env:i.z.record(i.z.string()).optional().describe('Env applied during prepare AND every run, e.g. { CARGO_HOME: "/cache/cargo" } for caches outside /work.')})).optional().describe("Per-runtime build/dependency artifacts: writable volumes populated by a prepare command, generalizing `install` beyond node_modules. Use for Python (.venv), Rust (target + $CARGO_HOME), Go, Ruby (vendor/bundle), etc. `install` is sugar for a single node_modules artifact; for any non-Node runtime use artifacts (install would write to node_modules, the wrong place)."),confinement:i.z.enum(["confined","egress-allowlist","privileged","unsafe","host","pure-host"]).optional().describe("Confinement level. Default 'confined' (fully sealed Docker; add egressAllowlist hosts to open exactly those -- stored as 'egress-allowlist'). privileged/unsafe relax the container. 'host' runs on the host machine (no Docker) inside an OS sandbox (Seatbelt/bubblewrap) for toolchains Docker can't run. 'pure-host' is the built-in workspace env (the live worktree) -- do not create new envs with it. Merge gating is independent (the Sign-off setting)."),commandGate:i.z.array(i.z.enum(["policy","agent","human"])).optional().describe("Gates an experiment command passes before it runs, in escalation order policy -> agent -> human. Empty = run freely; escalating past the last gate blocks (fail closed). policy = deterministic allowlist/deny-floor; agent = isolated LLM judge (allow or escalate only); human = ask. Registered commands are pre-approved. Defaults by level ([] for confined/egress-allowlist, ['human'] for privileged/unsafe)."),worktreeTarget:i.z.enum(["ephemeral","live"]).optional().describe("Worktree the env runs against: 'ephemeral' (a disposable copy at HEAD, default) or 'live' (the project's live working dir, read-write). The built-in workspace env is 'live'."),egressAllowlist:i.z.array(i.z.string()).optional().describe("Hosts the box may reach at the 'egress-allowlist' level (exact name or subdomain, e.g. registry.npmjs.org). Ignored at other levels."),addCaps:i.z.array(i.z.string()).optional().describe("Linux capabilities to add at the 'privileged' level (on top of cap-drop ALL), e.g. SYS_PTRACE for debuggers, IPC_LOCK for memlock. Ignored at other levels."),sysctls:i.z.record(i.z.string()).optional().describe("Namespaced sysctls to set at the 'privileged' level, e.g. net.ipv4.ip_local_port_range. Host-wide sysctls (vm.max_map_count) are NOT settable per-container."),runAsRoot:i.z.boolean().optional().describe("Run the box as root (privileged / unsafe levels). Needed for some caps + the docker socket."),dockerSocket:i.z.boolean().optional().describe("Mount the host docker socket for testcontainers-style tests. WARNING: full host control over the docker daemon. Only honored at the 'unsafe' level."),persistentVolumes:i.z.array(i.z.object({name:i.z.string(),target:i.z.string(),service:i.z.string().optional()})).optional().describe("Data volumes that survive between runs (keyed to this environment; off by default since experiments are ephemeral). Set service to mount into a compose service (e.g. a DB data dir), or omit it to mount into the box (e.g. a SQLite dir)."),resources:i.z.object({memory:i.z.string().optional().describe("Memory cap for this environment's boxes and its checks' boxes, e.g. '4g' (default 2g; 6g at size heavy). Set it when the test suite or build needs more."),cpus:i.z.number().optional().describe("A fixed CPU count. Usually leave it unset and set size instead."),size:i.z.enum(["light","standard","heavy"]).optional().describe("What this environment's boxes and checks are for, which sets their cores from what is free: light (2 cores: lint, small checks), standard (the default: a share of the free cores, for test suites and builds), heavy (every free core but 2: CFD or FEM solves, meshing, large parallel builds). An agent can ask for another size per box."),pidsLimit:i.z.number().optional().describe("Max processes (default 512)."),scratchMb:i.z.number().optional().describe("Size in MB of an in-memory /scratch, used only when the image cannot take the disk-backed one (default 512). /scratch is normally on disk and needs no size."),shmMb:i.z.number().optional().describe("/dev/shm size in MB -- bump for headless browsers (Chrome crashes on the 64m default).")}).optional().describe("Per-environment resource caps that override the confined defaults.")}),w=r(y),b='Start from a built-in preset; anything in config overrides it. "office": python-docx, openpyxl, python-pptx, pandoc, LibreOffice and segno/zbarimg (QR codes) in a sealed box, with `render-pdf FILE` to turn Word, Excel, PowerPoint, Markdown or HTML into PDF. Its image is built locally on first use (a few minutes). Pass the same preset (and config) to environment_verify and environment_create.',v="Save without a passing environment_verify, only for a config that cannot be verified from here (it needs credentials, external services or hardware the confined verifier lacks). Requires skipVerifyReason; the skip is logged."
11413
11434
  exports.EnvironmentCreateSchema=i.z.object({name:i.z.string().min(1).describe("Name for the environment, e.g. node + postgres"),description:i.z.string().optional().describe("What this environment provides"),scope:i.z.string().optional().describe("Subproject path prefix for monorepos (e.g. packages/api/). Omit for the whole repo."),preset:i.z.enum(["office"]).optional().describe(b),config:w.optional().describe("The exact config (after the preset) that passed environment_verify in this session, unless skipVerify; its fields are described on environment_verify. Optional with a preset."),isPrimary:i.z.boolean().optional().describe("Make this the project primary environment (default for new test processes). The first environment is primary automatically."),skipVerify:i.z.boolean().optional().describe(v),skipVerifyReason:i.z.string().optional().describe("Why this config cannot be verified (required with skipVerify: true; recorded in the log).")}),exports.EnvironmentUpdateSchema=i.z.object({environmentId:i.z.string(),name:i.z.string().min(1).optional(),description:i.z.string().optional(),scope:i.z.string().optional(),config:w.optional().describe("Replaces the config wholesale: the exact config that passed environment_verify in this session, unless skipVerify (fields described on environment_verify). Metadata-only updates need no verify."),isPrimary:i.z.boolean().optional().describe("Make this the project primary environment"),skipVerify:i.z.boolean().optional().describe(v),skipVerifyReason:i.z.string().optional().describe("Why this config cannot be verified (required with skipVerify: true; recorded in the log).")}),exports.EnvironmentDeleteSchema=i.z.object({environmentId:i.z.string().describe("Environment ID to delete")}),exports.EnvironmentVerifySchema=i.z.object({preset:i.z.enum(["office"]).optional().describe(b),config:y.optional().describe("The proposed environment config to verify (image, install, services, connection). Optional with a preset."),smoke:i.z.string().optional().describe("Optional smoke command run confined after the environment is up (default: true). Use a connection probe to confirm services are reachable.")}),exports.EnvironmentSetupSchema=i.z.object({name:i.z.string().max(60).optional().describe("A few words naming what you need (e.g. 'Node 20 for syntax checks')."),context:i.z.string().optional().describe("What you need and why, for the setup architect (e.g. 'Node 20 to run node --check on standalone .js files; no deps').")}),exports.SetupFinishSchema=i.z.object({outcome:i.z.enum(["ready","declined","could_not_finish","no_code","not_yet"]).describe("How the run ended. 'ready' needs something saved in this run."),summary:i.z.string().min(1).describe("What was set up (names and ids), and an answer to every ask the run carries: done, or not done and why. Plain words; it reaches the waiting workers and the user."),startsAfter:i.z.string().regex(/^req\d{6}$/).optional().describe("With 'not_yet': the request still writing what this setup registers.")}),exports.TestingProcessUpdateSchema=i.z.object({processId:i.z.string(),environmentId:i.z.string().optional(),name:i.z.string().min(1).optional(),description:i.z.string().optional(),scope:i.z.string().optional().describe("A display label only; pathGlobs decides when the process runs."),pathGlobs:i.z.array(i.z.string()).optional().describe('Globs matched against changed files (e.g. ["src/**", "*.md"]): the changes this process runs on. Replaces the list; [] means every change.'),areas:i.z.array(i.z.string()).optional().describe("Work-area labels this process covers; it applies when pathGlobs or areas match."),isActive:i.z.boolean().optional(),setup:i.z.array(i.z.string()).optional().describe("Replaces the setup commands (run in the check's box before its commands, and before the criteria checks in their clean checkout)."),commands:i.z.array(A).optional().describe("Replaces the test commands."),verifyAcceptanceCriteria:i.z.boolean().optional().describe("The auto-tester also verifies the request's acceptance criteria."),fingerprint:i.z.array(i.z.string()).optional().describe("Files whose content invalidates setup."),leavesOut:i.z.array(i.z.object({what:i.z.string().describe('The left-out part in plain words, e.g. "the Valkey backend tests"'),paths:i.z.array(i.z.string()).min(1).describe("Globs for the code that part checks (for end-to-end tests, the app source they drive), not only its test files."),command:i.z.string().min(1).max(300).optional().describe('The command that runs that part, e.g. "pnpm test:e2e".')})).optional().describe("Replaces what this check deliberately does not run, with the code each part checks; [] clears it."),poolSize:i.z.number().int().min(1).max(8).optional().describe("Warm test worktrees in the pool."),runtimeKept:i.z.string().optional().describe('With environmentId: only when the user chose a runtime other than the lowest one the project declares, e.g. "python 3.12".')}),exports.TestsSchema=i.z.object({operation:i.z.enum(["list","setup","run","switch-off","switch-on"]),process:i.z.string().optional().describe("run, switch-off, switch-on: one test process by name or id (run: omit for all active ones)."),userSaid:i.z.string().max(600).optional().describe("switch-off / switch-on: the user's words asking for it, copied exactly."),name:i.z.string().optional().describe("run: one test command by name; omit for all required commands."),ask:i.z.string().max(1500).optional().describe("setup, required outside a request: what the user asked the check to run, in their words; the setup architect registers exactly that.")}),exports.ExecuteTestCommandSchema=i.z.object({processId:i.z.string().describe("Test process ID containing the command"),commandName:i.z.string().describe('Name of the test command to run, or "setup" to run all setup commands in sequence')}),exports.RunSchema=i.z.object({command:i.z.string().optional().describe("A shell command, run in the box (pass box or env)."),name:i.z.string().optional().describe("A saved command by name, instead of command."),box:i.z.string().optional().describe("An experiment handle from experiment_start."),env:i.z.string().optional().describe("Instead of box: an environment by name; its box is started (or your open one reused) and the result gives the handle."),size:i.z.enum(["light","standard","heavy"]).optional().describe("With env: the box size (see experiment_start)."),args:i.z.string().optional(),grep:i.z.string().optional().describe("Filter output through grep -i with this pattern."),timeout:i.z.number().optional().describe("Seconds."),why:i.z.string().optional().describe("One short sentence, shown on the approval card when the command needs the user's approval."),cwd:i.z.string().optional().describe('A subdirectory for an ad-hoc command (e.g. "apps/web"); a command starting with `cd` is refused in favour of this.')}),exports.CommandsSchema=i.z.object({operation:i.z.enum(["run","list","remove","register","setup"]).default("run").describe('list, setup, register or remove ("run" is the run tool\'s job).'),name:i.z.string().optional().describe("remove: the command name."),args:i.z.string().optional(),grep:i.z.string().optional(),command:i.z.string().optional(),timeout:i.z.number().optional(),cwd:i.z.string().optional(),context:i.z.string().optional().describe("setup: the command wanted, where it runs and what it is for."),commands:i.z.array(i.z.object({name:i.z.string().min(1).describe('e.g. "test:web"'),command:i.z.string().min(1),description:i.z.string().min(1).describe("What this command verifies."),cwd:i.z.string().optional().describe("Relative to the project root (monorepo subdirs); omit for the root."),timeout:i.z.number().optional().describe("Seconds (default 60)."),environment:i.z.string().optional().describe('The environment it runs in, by name (environment_list). Omitted, it goes where you ran it (a bare box with no saved environment is refused, so name one). Name "Workspace" only when it truly needs the host.'),target:i.z.string().optional().describe("The id your setup instructions give for a target this command prints the number for.")})).optional().describe("register: the commands to register.")}),exports.ReportTestResultsSchema=i.z.object({status:i.z.enum(["passed","failed","error"]).describe("Overall result"),testRunId:i.z.string().optional().describe("The test run this verdict belongs to (given in your instructions). Strongly preferred: without it the latest run is guessed, which misattributes results when several testers run concurrently."),results:i.z.object({commands:i.z.array(i.z.object({name:i.z.string().describe("Command name"),status:i.z.enum(["passed","failed","skipped","error"]).describe("Command result"),output:i.z.string().optional().describe("Relevant output snippet"),diagnosis:i.z.string().optional().describe("Why it failed, if applicable")})).describe("Per-command results"),summary:i.z.string().describe("Brief overall summary"),acceptanceCriteria:i.z.array(i.z.object({criterion:i.z.string().describe("The acceptance criterion text"),status:i.z.enum(["verified","unverified","unclear"]),evidence:i.z.string().describe("How verified, or why not")})).optional().describe("AC verification results, if applicable"),faultClass:i.z.enum(["fail_code","fail_evidence","fail_environment","fail_process"]).optional().describe("On a FAILED verdict, WHY it failed so the gate routes to the right actor: 'fail_code' (the change is wrong), 'fail_evidence' (the run did not prove a criterion the coder could have proven: attach/adjust verify commands), or 'fail_environment' (a required tool/runtime is MISSING from the confined box, e.g. 'node: not found': the coder cannot fix this; the environment must be extended). Use 'fail_environment' ONLY when the evidence shows a command/runtime absent from the box, never for a genuine defect or a fixable evidence gap. 'fail_process' is for a test process whose OWN command names a file the repository does not have (a deleted script): nobody's code is wrong and extending the box cannot help; the process itself must be fixed or turned off."),missingCapabilities:i.z.array(i.z.string()).optional().describe("With faultClass 'fail_environment', the binaries/runtimes the box lacks (e.g. ['node']) so the environment can be extended to provide them.")}).describe("Structured test results")}).superRefine((e,t)=>{"fail_environment"!==e.results?.faultClass||e.results.missingCapabilities&&e.results.missingCapabilities.length>0||t.addIssue({code:i.z.ZodIssueCode.custom,path:["results","missingCapabilities"],message:"faultClass 'fail_environment' requires results.missingCapabilities: name the absent binaries/runtimes (the deterministic capability probe in your evidence lists them, e.g. ABSENT from the box: node). Without the names, the environment cannot be extended and the request strands."})})
11414
11435
  const I=i.z.string().min(1,"every option needs its consequence (description)").max(200,"an option's description is its consequence in one line: 200 characters at most").describe("Its consequence in one line: what it gets and costs."),E=i.z.string().min(1).max(120,"because is one clause: 120 characters at most")
11415
- exports.OpeningBriefSectionSchema=i.z.object({id:i.z.string().min(1).describe('Unique within the brief ("date-format").'),question:i.z.string().min(1).describe("The choice, one plain question answerable without your session."),context:i.z.string().min(1).describe("What it shapes, citing the request and its sources as stated; where they answer it, recommend their answer."),options:i.z.array(i.z.object({id:i.z.string().min(1),label:i.z.string().min(1),description:I})).min(2).describe("At least two genuine alternatives."),default:i.z.string().min(1).describe("The option you proceed on now (shown recommended)."),because:E.describe("Why, in one clause."),criteria:i.z.array(i.z.string().min(1)).optional().describe("Keys of criteria whose wording this choice decides (worded for your default); another pick marks them for rewording.")}).superRefine((e,t)=>{e.options.some(t=>t.id===e.default)||t.addIssue({code:i.z.ZodIssueCode.custom,path:["default"],message:`default '${e.default}' names no option in this section (option ids: ${e.options.map(e=>e.id).join(", ")})`}),"testing-mode"===e.id&&t.addIssue({code:i.z.ZodIssueCode.custom,path:["id"],message:"section id 'testing-mode' is reserved: the server composes the testing section when the project's gate needs it -- remove it from your sections"})}),exports.SessionMessageOperationsSchema=i.z.object({operation:i.z.enum(["send","note","reply","list","get","offer-handoff","record-decision","brief","withdraw"]),milestone:i.z.boolean().optional().describe("note: a real boundary (it works, the approach changed, the goal is met); one quiet line in the user's stream, a few per request at most."),percent:i.z.number().min(0).max(100).optional().describe("note: how far along your work on this request is; it may go down."),withdrawReason:i.z.string().optional().describe("withdraw: why the answer is no longer needed."),type:i.z.enum(["question","report"]).optional().describe("Required for send."),toSessionId:i.z.string().optional().describe('A session id, or "parent". Omit for the user (a broadcast).'),questionType:i.z.enum(["free-form","alternatives"]).optional().describe("Required for a question."),questionContent:i.z.object({question:i.z.string().min(1).describe("As the user reads it; a criterion by its words, never its key."),context:i.z.preprocess(e=>null!==e&&"object"==typeof e?JSON.stringify(e):e,i.z.string().optional()).optional().describe("What the user needs to answer without your session: what you found and why it matters. No paths or identifiers; output they should judge goes in a fenced block, as printed."),options:i.z.array(i.z.object({id:i.z.string(),label:i.z.string(),description:I,recommended:i.z.boolean().optional().describe("Your advice, highlighted (what happens unanswered is defaultOption). Not with criteria. Never on an option that makes up what the user has not given (estimates, sample values, invented names): leave it visibly empty or marked unknown."),because:i.z.preprocess(a,E.optional()).describe("With recommended (required there): why, in one clause."),notePrompt:i.z.string().min(3).max(120).optional().describe('For an option only useful with the user\'s words ("I want changes"): the question its text field asks ("What should change?"); the pick arrives with what they typed.'),effect:i.z.enum(["choose","send-back","not-yet"]).optional().describe('"choose" (default): records the pick. "send-back": asks for a change; on a request awaiting sign-off the pick itself sends it back (no reopen). "not-yet": [user-input] criteria only; they stay owed.')})).optional().describe("Genuine alternatives, each complete in one tap; recommended first. When the user's way cannot work, include one reaching their goal another way."),allowMultiple:i.z.boolean().optional(),fields:i.z.array(i.z.object({id:i.z.string().min(1),label:i.z.string().min(1),placeholder:i.z.string().optional()})).min(1).optional().describe('free-form: one labelled input per value; answers arrive as "Label: value" lines. Never ask for passwords or keys: answers are on the record.'),defaultOption:i.z.string().optional().describe("What happens if nobody answers before expiry (option id, or text for free-form); the asker resumes. It happens without the user, so it is the SAFE option when options differ in risk (silent value changes, data loss, made-up data, anything hard to undo), even if you recommend another; the context says so."),onExpiry:i.z.enum(["assume-default","escalate"]).optional().describe('"assume-default" (default with a defaultOption) or "escalate": stays pending for the user (on Self-driving only priority "high" reaches them).'),steer:i.z.enum(["what","how"]).optional().describe('"what" shapes the product (spec, wording, UX, scope); "how" the process (testing, sequencing), mostly yours to decide, not ask.'),changeableLater:i.z.boolean().optional().describe("true when the answer can change later without loss (nothing sent, deleted or published on it).")}).superRefine((e,t)=>{(e.options??[]).forEach((e,r)=>{!0!==e.recommended||e.because||t.addIssue({code:i.z.ZodIssueCode.custom,path:["options",r,"because"],message:`option '${e.id}' is recommended: say why in one clause (because)`})})}).optional().describe("Required for a question. Before asking the user: when context answers it and a wrong guess is cheap to fix, proceed. Otherwise ask with a defaultOption unless only the user can answer. Under 'autonomous' the default returns at once; 'balanced' holds up to ten minutes, then continues; 'collaborative' holds briefly, then ends your turn to wait. One decision per question, related uncertainties bundled; several candidate next steps are one alternatives question. A question the user already decided returns their decision: proceed on it."),priority:i.z.enum(["low","normal","high"]).optional().describe('"high": blocked with real stakes; reaches the user at once. "low" with a defaultOption: under \'balanced\', batched to the next checkpoint without holding.'),expiresInHours:i.z.number().optional(),userSaid:i.z.string().min(8).max(600).optional().describe('question: when the user asked to be asked ("ask me first"), their words exactly (checked); it then reaches them at any Autonomy. record-decision: when the call is the user\'s own (their message settles what is built), their words exactly (checked); it is recorded as their decision.'),replaces:i.z.array(i.z.string().regex(/^Q-[A-Za-z0-9]+-\d+$/)).max(10).optional().describe("question: your open questions on the request this supersedes (they close). With one open, pass this or keepsOpen."),keepsOpen:i.z.boolean().optional().describe("question: it asks something else; your open ones stay open."),forceAsk:i.z.boolean().optional().describe("question: re-ask what the decision record answers, only because circumstances changed (say what in context)."),status:i.z.enum(["success","partial","failure","blocked"]).optional().describe("report: how it ended. Omitted reads success, or blocked with blockedOn."),blockedOn:i.z.string().optional().describe('Required with status "blocked" on your request: "decision" (the user\'s open card on it, else a question to your parent), a card code ("Q-AB12CD34-001"), or a request id (a wait; you are told when it settles).'),criteria:i.z.array(i.z.string().min(1)).max(10).optional().describe('question: keys of the [user-input] criteria you ask for; only this counts as asking them. No default, no recommended option; the answer is their proof unless it is "not-yet". With a "not-yet" option you build on a placeholder meanwhile (Self-driving answers it at once); without one you wait at every Autonomy. Their check of finished work is asked on the end card, not here.'),attention:i.z.enum(["fyi","glance","act"]).optional().describe("'fyi' (no badge), 'glance', 'act'. Default from status: failure/blocked act, partial glance, success fyi."),reportKind:i.z.enum(["milestone","reply"]).optional().describe("'reply': answers the user's inbox message."),summary:i.z.string().optional().describe("Required for a report: outcome first, subject named first (it shows alone, often hours later), paths in details."),details:i.z.string().optional().describe("With blockedOn \"decision\", the question's context (maybe the user's card): plain words, no tool names; explain a refusal, never paste it."),ruleVerdicts:i.z.array(i.z.object({rule:i.z.string().min(1).max(80).describe("The guidance id, no brackets."),verdict:i.z.enum(["held","broken","not_applicable"]),note:i.z.string().max(300).optional().describe("broken: what and where (file:line).")})).max(40).optional().describe("report, Guidance reviewer: a verdict on every item you were given."),silent:i.z.boolean().optional().describe("Record without waking the coordinator (fan-out research)."),relatesTo:i.z.string().regex(/^[IQ]-[A-Za-z0-9]+-\d+$/,"relatesTo must be an instruction code (I-...) or question code (Q-...)").optional().describe("An instruction code (I-...); for record-decision, the question (Q-...) it resolves."),decision:i.z.string().optional().describe("record-decision (required): the one-line call."),why:i.z.string().optional().describe("record-decision: the reasoning."),context:i.z.string().optional().describe("record-decision: what the choice was about."),requestId:i.z.string().optional().describe("The request it is about, so it shows there (note: defaults to yours; brief: required); without it a report or decision is filed at the project."),area:i.z.string().optional().describe("record-decision: a work area the call is about (without requestId)."),sections:i.z.array(exports.OpeningBriefSectionSchema).min(1).optional().describe("brief (required): your real design choices."),messageCode:i.z.string().optional().describe("Required for reply, get and withdraw."),amount:i.z.number().positive().max(1e5).optional().describe("Dollars the user named, their words in quote: 'build' on a Plan ready card sets the plan's budget; 'allow' on a budget card allows that much more."),sectionAnswers:i.z.record(i.z.string(),i.z.string().nullable()).optional().describe("reply to an opening brief: { sectionId: optionId } for sections the user answered, changed or confirmed, with quote; null when one no longer applies; 'your-words' when no option says what they asked. Unnamed sections stay open; a change sends finished work back to rework."),quote:i.z.string().optional().describe("reply: the user's words this answer records, exactly (refused if they never wrote them); omit for your own decision. Options that are the user's own statement (\"I checked, it works\") only they can pick."),text:i.z.string().optional().describe("reply: a free-form answer. note (required): the note."),selectedOptions:i.z.array(i.z.string()).optional().describe("reply: the picked option ids."),notes:i.z.string().optional(),escalate:i.z.boolean().optional().describe("reply to a question to you that needs the user: a copy reaches them with your recommendation (notes); the asker gets their answer. Not for broadcasts."),list:i.z.object({sessionId:i.z.string().optional().describe("Defaults to your session."),messageType:i.z.enum(["question","report","instruction","all"]).default("all"),direction:i.z.enum(["incoming","outgoing","all"]).default("all"),status:i.z.enum(["pending","answered","expired","success","partial","failure","blocked","all"]).default("all"),readStatus:i.z.enum(["unread","read","acknowledged","all"]).default("all"),requestId:i.z.string().optional().describe("Only messages about this request."),limit:i.z.number().min(1).max(100).default(50),offset:i.z.number().min(0).default(0)}).optional(),offerHandoff:i.z.object({summary:i.z.string().describe("One sentence: what the plan builds."),requestIds:i.z.array(i.z.string()).describe("Requests created from the plan.")}).optional()}).superRefine((e,t)=>{const r=n(e)
11436
+ exports.OpeningBriefSectionSchema=i.z.object({id:i.z.string().min(1).describe('Unique within the brief ("date-format").'),question:i.z.string().min(1).describe("The choice, one plain question answerable without your session."),context:i.z.string().min(1).describe("What it shapes, citing the request and its sources as stated; where they answer it, recommend their answer."),options:i.z.array(i.z.object({id:i.z.string().min(1),label:i.z.string().min(1),description:I})).min(2).describe("At least two genuine alternatives."),default:i.z.string().min(1).describe("The option you proceed on now (shown recommended)."),because:E.describe("Why, in one clause."),criteria:i.z.array(i.z.string().min(1)).optional().describe("Keys of criteria whose wording this choice decides (worded for your default); another pick marks them for rewording.")}).superRefine((e,t)=>{e.options.some(t=>t.id===e.default)||t.addIssue({code:i.z.ZodIssueCode.custom,path:["default"],message:`default '${e.default}' names no option in this section (option ids: ${e.options.map(e=>e.id).join(", ")})`}),"testing-mode"===e.id&&t.addIssue({code:i.z.ZodIssueCode.custom,path:["id"],message:"section id 'testing-mode' is reserved: the server composes the testing section when the project's gate needs it -- remove it from your sections"})}),exports.SessionMessageOperationsSchema=i.z.object({operation:i.z.enum(["send","note","reply","list","get","offer-handoff","record-decision","brief","withdraw"]),milestone:i.z.boolean().optional().describe("note: a real boundary (it works, the approach changed, the goal is met); one quiet line in the user's stream, a few per request at most."),percent:i.z.number().min(0).max(100).optional().describe("note: how far along your work on this request is; it may go down."),withdrawReason:i.z.string().optional().describe("withdraw: why the answer is no longer needed."),type:i.z.enum(["question","report"]).optional().describe("Required for send."),toSessionId:i.z.string().optional().describe('A session id, or "parent". Omit for the user (a broadcast).'),questionType:i.z.enum(["free-form","alternatives"]).optional().describe("Required for a question."),questionContent:i.z.object({question:i.z.string().min(1).describe("As the user reads it; a criterion by its words, never its key."),context:i.z.preprocess(e=>null!==e&&"object"==typeof e?JSON.stringify(e):e,i.z.string().optional()).optional().describe("What the user needs to answer without your session: what you found and why it matters. No paths or identifiers; output they should judge goes in a fenced block, as printed."),options:i.z.array(i.z.object({id:i.z.string(),label:i.z.string(),description:I,recommended:i.z.boolean().optional().describe("Your advice, highlighted (what happens unanswered is defaultOption). Not with criteria. Never on an option that makes up what the user has not given (estimates, sample values, invented names): leave it visibly empty or marked unknown."),because:i.z.preprocess(a,E.optional()).describe("With recommended (required there): why, in one clause."),notePrompt:i.z.string().min(3).max(120).optional().describe('For an option only useful with the user\'s words ("I want changes"): the question its text field asks ("What should change?"); the pick arrives with what they typed.'),effect:i.z.enum(["choose","send-back","not-yet"]).optional().describe('"choose" (default): records the pick. "send-back": asks for a change; on a request awaiting sign-off the pick itself sends it back (no reopen). "not-yet": [user-input] criteria only; they stay owed.')})).optional().describe("Genuine alternatives, each complete in one tap; recommended first. When the user's way cannot work, include one reaching their goal another way."),allowMultiple:i.z.boolean().optional(),fields:i.z.array(i.z.object({id:i.z.string().min(1),label:i.z.string().min(1),placeholder:i.z.string().optional()})).min(1).optional().describe('free-form: one labelled input per value; answers arrive as "Label: value" lines. Never ask for passwords or keys: answers are on the record.'),defaultOption:i.z.string().optional().describe("What happens if nobody answers before expiry (option id, or text for free-form); the asker resumes. It happens without the user, so it is the SAFE option when options differ in risk (silent value changes, data loss, made-up data, anything hard to undo), even if you recommend another; the context says so."),onExpiry:i.z.enum(["assume-default","escalate"]).optional().describe('"assume-default" (default with a defaultOption) or "escalate": stays pending for the user (on Self-driving only priority "high" reaches them).'),steer:i.z.enum(["what","how"]).optional().describe('"what" shapes the product (spec, wording, UX, scope); "how" the process (testing, sequencing), mostly yours to decide, not ask.'),changeableLater:i.z.boolean().optional().describe("true when the answer can change later without loss (nothing sent, deleted or published on it).")}).superRefine((e,t)=>{(e.options??[]).forEach((e,r)=>{!0!==e.recommended||e.because||t.addIssue({code:i.z.ZodIssueCode.custom,path:["options",r,"because"],message:`option '${e.id}' is recommended: say why in one clause (because)`})})}).optional().describe("Required for a question. Before asking the user: when context answers it and a wrong guess is cheap to fix, proceed. Otherwise ask with a defaultOption unless only the user can answer. Under 'autonomous' the default returns at once; 'balanced' holds up to ten minutes, then continues; 'collaborative' holds briefly, then ends your turn to wait. One decision per question, related uncertainties bundled; several candidate next steps are one alternatives question. A question the user already decided returns their decision: proceed on it."),priority:i.z.enum(["low","normal","high"]).optional().describe('"high": blocked with real stakes; reaches the user at once. "low" with a defaultOption: under \'balanced\', batched to the next checkpoint without holding.'),expiresInHours:i.z.number().optional(),userSaid:i.z.string().min(8).max(600).optional().describe('question: when the user asked to be asked ("ask me first"), their words exactly (checked); it then reaches them at any Autonomy. record-decision: when the call is the user\'s own (their message settles what is built), their words exactly (checked); it is recorded as their decision.'),replaces:i.z.array(i.z.string().regex(/^Q-[A-Za-z0-9]+-\d+$/)).max(10).optional().describe("question: your open questions on the request this supersedes (they close). With one open, pass this or keepsOpen."),keepsOpen:i.z.boolean().optional().describe("question: it asks something else; your open ones stay open."),forceAsk:i.z.boolean().optional().describe("question: re-ask what the decision record answers, only because circumstances changed (say what in context)."),status:i.z.enum(["success","partial","failure","blocked"]).optional().describe("report: how it ended. Omitted reads success, or blocked with blockedOn."),blockedOn:i.z.string().optional().describe('Required with status "blocked" on your request: "decision" (the user\'s open card on it, else a question to your parent), a card code ("Q-AB12CD34-001"), or a request id (a wait; you are told when it settles).'),criteria:i.z.array(i.z.string().min(1)).max(10).optional().describe('question: keys of the [user-input] criteria you ask for; only this counts as asking them. No default, no recommended option; the answer is their proof unless it is "not-yet". With a "not-yet" option you build on a placeholder meanwhile (Self-driving answers it at once); without one you wait at every Autonomy. Their check of finished work is asked on the end card, not here.'),attention:i.z.enum(["fyi","glance","act"]).optional().describe("'fyi' (no badge), 'glance', 'act'. Default from status: failure/blocked act, partial glance, success fyi."),reportKind:i.z.enum(["milestone","reply"]).optional().describe("'reply': answers the user's inbox message."),summary:i.z.string().optional().describe("Required for a report: outcome first, subject named first (it shows alone, often hours later), paths in details."),details:i.z.string().optional().describe("With blockedOn \"decision\", the question's context (maybe the user's card): plain words, no tool names; explain a refusal, never paste it."),ruleVerdicts:i.z.array(i.z.object({rule:i.z.string().min(1).max(80).describe("The guidance id, no brackets."),verdict:i.z.enum(["held","broken","not_applicable"]),note:i.z.string().max(300).optional().describe("broken: what and where (file:line).")})).max(40).optional().describe("report, Guidance reviewer: a verdict on every item you were given."),review:i.z.object({focus:i.z.string().min(1).max(80).describe("Your focus as your brief names it (QUALITY, CRITERIA + QUALITY, DELIVERY, Guidance, a custom reviewer's name)."),mustFix:i.z.array(i.z.string()).max(20).describe("Each must_fix finding, one line: <file:line> -- <what is wrong> -- <how to fix>. [] when there is none."),niceToHave:i.z.array(i.z.string()).max(20).optional(),outOfScope:i.z.array(i.z.string()).max(20).optional(),escalate:i.z.string().max(800).optional().describe("DELIVERY only, when the request goes to the user: one line to them, why you cannot pass it, then what is theirs to decide.")}).optional().describe("report, a reviewer (required): your findings as data. What happens next is decided from this field; your summary is what people read."),silent:i.z.boolean().optional().describe("Record without waking the coordinator (fan-out research)."),relatesTo:i.z.string().regex(/^[IQ]-[A-Za-z0-9]+-\d+$/,"relatesTo must be an instruction code (I-...) or question code (Q-...)").optional().describe("An instruction code (I-...); for record-decision, the question (Q-...) it resolves."),decision:i.z.string().optional().describe("record-decision (required): the one-line call."),why:i.z.string().optional().describe("record-decision: the reasoning."),context:i.z.string().optional().describe("record-decision: what the choice was about."),requestId:i.z.string().optional().describe("The request it is about, so it shows there (note: defaults to yours; brief: required); without it a report or decision is filed at the project."),area:i.z.string().optional().describe("record-decision: a work area the call is about (without requestId)."),sections:i.z.array(exports.OpeningBriefSectionSchema).min(1).optional().describe("brief (required): your real design choices."),messageCode:i.z.string().optional().describe("Required for reply, get and withdraw."),amount:i.z.number().positive().max(1e5).optional().describe("Dollars the user named, their words in quote: 'build' on a Plan ready card sets the plan's budget; 'allow' on a budget card allows that much more."),sectionAnswers:i.z.record(i.z.string(),i.z.string().nullable()).optional().describe("reply to an opening brief: { sectionId: optionId } for sections the user answered, changed or confirmed, with quote; null when one no longer applies; 'your-words' when no option says what they asked. Unnamed sections stay open; a change sends finished work back to rework."),quote:i.z.string().optional().describe("reply: the user's words this answer records, exactly (refused if they never wrote them); omit for your own decision. Options that are the user's own statement (\"I checked, it works\") only they can pick."),text:i.z.string().optional().describe("reply: a free-form answer. note (required): the note."),selectedOptions:i.z.array(i.z.string()).optional().describe("reply: the picked option ids."),notes:i.z.string().optional(),escalate:i.z.boolean().optional().describe("reply to a question to you that needs the user: a copy reaches them with your recommendation (notes); the asker gets their answer. Not for broadcasts."),sameAs:i.z.string().optional().describe("with escalate: the code of a card already open in front of the user that asks the same thing (Q-...). The user is asked once; the asker gets that card's answer."),list:i.z.object({sessionId:i.z.string().optional().describe("Defaults to your session."),messageType:i.z.enum(["question","report","instruction","all"]).default("all"),direction:i.z.enum(["incoming","outgoing","all"]).default("all"),status:i.z.enum(["pending","answered","expired","success","partial","failure","blocked","all"]).default("all"),readStatus:i.z.enum(["unread","read","acknowledged","all"]).default("all"),requestId:i.z.string().optional().describe("Only messages about this request."),limit:i.z.number().min(1).max(100).default(50),offset:i.z.number().min(0).default(0)}).optional(),offerHandoff:i.z.object({summary:i.z.string().describe("One sentence: what the plan builds."),requestIds:i.z.array(i.z.string()).describe("Requests created from the plan.")}).optional()}).superRefine((e,t)=>{const r=n(e)
11416
11437
  r&&t.addIssue({code:i.z.ZodIssueCode.custom,message:r})}),exports.CommitToViewedSessionSchema=i.z.object({message:i.z.coerce.string().min(1).describe("The cleaned-up user message to post to the viewed session. Fix obvious dictation errors, add punctuation, but preserve the user's intent exactly.")}),exports.DirectiveOperationsSchema=i.z.object({operation:i.z.enum(["set","list","search-decisions"]).describe("search-decisions = free-text search the project's decision + annotation record"),targetType:i.z.enum(["project","area","request","you"]).optional(),targetId:i.z.string().optional(),body:i.z.string().max(2e3).optional(),note:i.z.string().optional(),userSaid:i.z.string().optional(),revises:i.z.string().max(160).optional(),status:i.z.string().optional(),query:i.z.string().max(200).optional().describe("search-decisions: the search text, matched against recorded decisions (question + answer) and annotations (comment body + quoted text). Required for search-decisions."),limit:i.z.number().int().min(1).max(50).optional().describe("search-decisions: max matches per record type (default 10).")}),exports.McpConnectionsSchema=i.z.object({operation:i.z.enum(["list","propose","update","remove"]).describe("list = the connections this project has; propose = register one, switched off, for the user to switch on; update = correct one by name (only the fields you pass change; a connection that was on goes off for the user to switch on again); remove = take back one that is switched off."),name:i.z.string().optional().describe("propose: the connection name, lower case letters, digits and dashes. It becomes the tool prefix (name_tool). update / remove: the name of the connection to change."),transport:i.z.enum(["repo","boxed","remote"]).optional().describe("propose / update: repo = a server in THIS repository, built from its Dockerfile and run in a confined box (the repository is mounted read-only at /work, /scratch is writable memory, the image ENTRYPOINT is what runs, rebuilt when its files change on main); boxed = a published npm package at an exact version, installed once and run in a box; remote = a hosted server reached by url."),dockerfile:i.z.string().optional().describe("propose / update repo: path of the Dockerfile inside the repository, e.g. mcp/server.Dockerfile."),context:i.z.string().optional().describe("propose / update repo: the docker build context directory inside the repository (default: the repository root)."),package:i.z.string().optional().describe("propose / update boxed: npm package with an exact version, e.g. @scope/server@1.2.3."),bin:i.z.string().optional().describe("propose / update boxed: the bin to run, the name under node_modules/.bin."),url:i.z.string().optional().describe("propose / update remote: the http(s) url."),environment:i.z.record(i.z.string()).optional().describe("propose / update: plain KEY=value configuration (headers for remote). Never put a secret here; name it in secretKeys instead."),secretKeys:i.z.array(i.z.string()).optional().describe("propose / update: names of environment variables (or headers) whose VALUES are secret. The user sets the values on the machine that runs the work; they never reach the database.")}),exports.AnswerQuestionSchema=i.z.object({messageCode:i.z.string().min(1).describe("The code in square brackets from <waiting_on_you>, e.g. Q-51209THD-005."),optionId:i.z.string().optional().describe("The id of the option the user picked, when the question has options."),text:i.z.string().optional().describe("The user's answer in their own words, cleaned up, when the question has no options or they said something the options do not cover.")})},19567(e,exports,t){"use strict"
11417
11438
  exports.Server=exports.SSEServerTransport=exports.StdioServerTransport=void 0
11418
11439
  const r=t(27467)