@sma1lboy/rove 0.9.247 → 0.9.248

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.
Files changed (121) hide show
  1. package/dist/cli/chunk-043hdsx6.js +3 -0
  2. package/dist/cli/chunk-0gz3an4p.js +3 -0
  3. package/dist/cli/{chunk-60fy3ckp.js → chunk-0qh9w2sc.js} +1 -1
  4. package/dist/cli/{chunk-adkva3ep.js → chunk-0vpn32r6.js} +1 -1
  5. package/dist/cli/{chunk-21fm5tks.js → chunk-1rhham18.js} +1 -1
  6. package/dist/cli/{chunk-dqvy3kxk.js → chunk-2rve8y8t.js} +1 -1
  7. package/dist/cli/{chunk-dbs8p34d.js → chunk-30fygd49.js} +2 -2
  8. package/dist/cli/chunk-32zj8r86.js +3 -0
  9. package/dist/cli/{chunk-hkbdrwtg.js → chunk-3b52kybr.js} +1 -1
  10. package/dist/cli/{chunk-ntkfhrbw.js → chunk-3y2tv956.js} +3 -3
  11. package/dist/cli/chunk-55rpt311.js +9 -0
  12. package/dist/cli/{chunk-tnghx1kw.js → chunk-5ftfwgnj.js} +1 -1
  13. package/dist/cli/{chunk-5zw9xngg.js → chunk-5nfdpc9j.js} +1 -1
  14. package/dist/cli/{chunk-kme8knh8.js → chunk-5xh7ee2k.js} +1 -1
  15. package/dist/cli/chunk-62wgn4br.js +2 -0
  16. package/dist/cli/{chunk-4r3s1z14.js → chunk-761w6mbm.js} +1 -1
  17. package/dist/cli/{chunk-2md5j9ws.js → chunk-7mxd2n7q.js} +1 -1
  18. package/dist/cli/{chunk-rqyaf0dw.js → chunk-86tw1190.js} +1 -1
  19. package/dist/cli/{chunk-ctmbj2y3.js → chunk-89ag6mab.js} +1 -1
  20. package/dist/cli/chunk-89ze9jc2.js +3 -0
  21. package/dist/cli/{chunk-ffs26p85.js → chunk-8gqkrbyr.js} +1 -1
  22. package/dist/cli/{chunk-7axwr3a5.js → chunk-8shm2q5k.js} +1 -1
  23. package/dist/cli/{chunk-pmszj74t.js → chunk-91gkmv3b.js} +1 -1
  24. package/dist/cli/{chunk-qqz6nxfe.js → chunk-9d2p1h1b.js} +1 -1
  25. package/dist/cli/{chunk-t45c781p.js → chunk-9wfdzbnr.js} +2 -2
  26. package/dist/cli/chunk-b27fyc2m.js +3 -0
  27. package/dist/cli/chunk-b9t4vq5j.js +3 -0
  28. package/dist/cli/{chunk-sb2ardv3.js → chunk-bqthw6ce.js} +1 -1
  29. package/dist/cli/{chunk-wam8sr6a.js → chunk-cbhzyn31.js} +1 -1
  30. package/dist/cli/{chunk-9sk0w5be.js → chunk-dhnbgfhh.js} +1 -1
  31. package/dist/cli/chunk-dk3g9ynd.js +81 -0
  32. package/dist/cli/{chunk-c9js0wd7.js → chunk-dmecdp9c.js} +1 -1
  33. package/dist/cli/chunk-e6w94amk.js +2 -0
  34. package/dist/cli/chunk-ecdhqwsj.js +3 -0
  35. package/dist/cli/{chunk-cst86kt3.js → chunk-ehs0wh6y.js} +1 -1
  36. package/dist/cli/chunk-ekknb5ft.js +2 -0
  37. package/dist/cli/{chunk-e5k2j2x8.js → chunk-empeprq2.js} +2 -2
  38. package/dist/cli/{chunk-xt92ptzx.js → chunk-esz0pj68.js} +1 -1
  39. package/dist/cli/{chunk-zeb0sttx.js → chunk-fd260eye.js} +1 -1
  40. package/dist/cli/{chunk-pa6teq2c.js → chunk-frtdk8ha.js} +1 -1
  41. package/dist/cli/{chunk-96cjpssp.js → chunk-fs3p13ah.js} +1 -1
  42. package/dist/cli/{chunk-szx3wehf.js → chunk-ha09kd22.js} +1 -1
  43. package/dist/cli/chunk-hdbj5yaz.js +52 -0
  44. package/dist/cli/{chunk-vrzb7s42.js → chunk-hfjd4j5b.js} +1 -1
  45. package/dist/cli/{chunk-a5vhekme.js → chunk-hmm0z8x9.js} +1 -1
  46. package/dist/cli/{chunk-4a2veqfk.js → chunk-jj640vvr.js} +1 -1
  47. package/dist/cli/{chunk-4qytyfmt.js → chunk-jjwxx8yp.js} +1 -1
  48. package/dist/cli/{chunk-73x2hszy.js → chunk-kcb75khv.js} +1 -1
  49. package/dist/cli/chunk-kxzv662p.js +87 -0
  50. package/dist/cli/{chunk-atmaakwp.js → chunk-mptaj95j.js} +1 -1
  51. package/dist/cli/chunk-mra8t9tk.js +3 -0
  52. package/dist/cli/{chunk-hr2xky15.js → chunk-mxag13a6.js} +1 -1
  53. package/dist/cli/{chunk-f3gedc7r.js → chunk-n4f4xxhr.js} +1 -1
  54. package/dist/cli/chunk-ncxhtx5a.js +2 -0
  55. package/dist/cli/{chunk-1kprhx82.js → chunk-ncxkn8gy.js} +2 -2
  56. package/dist/cli/{chunk-kjhhwhs4.js → chunk-nra5y7r7.js} +1 -1
  57. package/dist/cli/{chunk-h29wy8y3.js → chunk-nz1436cx.js} +1 -1
  58. package/dist/cli/{chunk-ry54qsbg.js → chunk-ppj32x3a.js} +3 -3
  59. package/dist/cli/chunk-q2177zrc.js +2 -0
  60. package/dist/cli/{chunk-axsm0gn5.js → chunk-q8q22wq3.js} +1 -1
  61. package/dist/cli/chunk-qb1agm2c.js +3 -0
  62. package/dist/cli/chunk-r3s2t9af.js +3 -0
  63. package/dist/cli/{chunk-4hm0smvv.js → chunk-shdzs3kz.js} +1 -1
  64. package/dist/cli/{chunk-t64hwtzt.js → chunk-sxtxxd65.js} +1 -1
  65. package/dist/cli/{chunk-j8evwrp3.js → chunk-taa06w9e.js} +1 -1
  66. package/dist/cli/{chunk-4fgpzkaq.js → chunk-twa07m8w.js} +1 -1
  67. package/dist/cli/{chunk-gvrtrvnz.js → chunk-v1ban2hc.js} +1 -1
  68. package/dist/cli/{chunk-g655yb7d.js → chunk-v4fa5ycf.js} +3 -3
  69. package/dist/cli/{chunk-466djpa8.js → chunk-wy7qtcym.js} +1 -1
  70. package/dist/cli/{chunk-9w5g7frx.js → chunk-x6gpzj4t.js} +1 -1
  71. package/dist/cli/{chunk-0422g8ac.js → chunk-x8fd9y51.js} +1 -1
  72. package/dist/cli/{chunk-5c57gz48.js → chunk-xpt1rabn.js} +6 -6
  73. package/dist/cli/{chunk-s9d6vjmc.js → chunk-y79j580z.js} +1 -1
  74. package/dist/cli/{chunk-vq6q30tg.js → chunk-y7xzcxb3.js} +1 -1
  75. package/dist/cli/{chunk-0b5mec1c.js → chunk-ypjca5g3.js} +1 -1
  76. package/dist/cli/{chunk-wd2k83dv.js → chunk-ysfe09dd.js} +2 -2
  77. package/dist/cli/chunk-zkmsvjtd.js +2 -0
  78. package/dist/cli/{chunk-83e13qdq.js → chunk-ztcxkm24.js} +1 -1
  79. package/dist/cli/index.js +4 -4
  80. package/dist/cli/pty-host-node.mjs +1 -0
  81. package/dist/cli/rove.js +6 -6
  82. package/dist/completions/rove.bash +1 -1
  83. package/dist/completions/rove.fish +1 -1
  84. package/dist/completions/rove.zsh +1 -1
  85. package/dist/skills/rove/SKILL.md +1 -1
  86. package/dist/skills/rove/references/api-flags.md +4 -0
  87. package/dist/web/assets/index-BC558ZDT.js +109 -0
  88. package/dist/web/assets/index-BYv91-Cs.css +1 -0
  89. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  90. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  91. package/dist/web/assets/jetbrains-mono-cyrillic-700-normal-BWTpRfYl.woff2 +0 -0
  92. package/dist/web/assets/jetbrains-mono-cyrillic-700-normal-CEoEElIJ.woff +0 -0
  93. package/dist/web/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  94. package/dist/web/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  95. package/dist/web/assets/jetbrains-mono-greek-700-normal-C6CZE3T8.woff2 +0 -0
  96. package/dist/web/assets/jetbrains-mono-greek-700-normal-DEigVDxa.woff +0 -0
  97. package/dist/web/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  98. package/dist/web/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  99. package/dist/web/assets/jetbrains-mono-latin-700-normal-BYuf6tUa.woff2 +0 -0
  100. package/dist/web/assets/jetbrains-mono-latin-700-normal-D3wTyLJW.woff +0 -0
  101. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  102. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  103. package/dist/web/assets/jetbrains-mono-latin-ext-700-normal-CZipNAKV.woff2 +0 -0
  104. package/dist/web/assets/jetbrains-mono-latin-ext-700-normal-CxPITLHs.woff +0 -0
  105. package/dist/web/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  106. package/dist/web/assets/jetbrains-mono-vietnamese-700-normal-BDLVIk2r.woff +0 -0
  107. package/dist/web/assets/pulse-3XJkP4CJ.wav +0 -0
  108. package/dist/web/index.html +14 -0
  109. package/package.json +14 -4
  110. package/dist/cli/chunk-1s93zcnx.js +0 -3
  111. package/dist/cli/chunk-6eb945nc.js +0 -2
  112. package/dist/cli/chunk-7j6escfh.js +0 -165
  113. package/dist/cli/chunk-8p579jct.js +0 -3
  114. package/dist/cli/chunk-8vbcj8xx.js +0 -2
  115. package/dist/cli/chunk-a10bkevn.js +0 -3
  116. package/dist/cli/chunk-c1j22psa.js +0 -3
  117. package/dist/cli/chunk-ctkf2m9h.js +0 -9
  118. package/dist/cli/chunk-hv77tgmh.js +0 -2
  119. package/dist/cli/chunk-m9q1whep.js +0 -3
  120. package/dist/cli/chunk-wh734g12.js +0 -2
  121. package/dist/cli/chunk-yvjgwnb9.js +0 -2
@@ -1,3 +0,0 @@
1
- // @bun
2
- import{isDev}from"./chunk-prqeddy7.js";import{fileURLToPath}from"url";var package_default={$schema:"https://json.schemastore.org/package.json",name:"@sma1lboy/rove",version:"0.9.247",description:"Rove \u2014 the agent multiplexer for your terminal. Run coding agents on parallel tasks with isolated worktrees and persistent sessions.",keywords:["terminal","tui","cli","multiplexer","ai-agents","coding-agent","ai-coding-assistant","agentic-ai","parallel-agents","git-worktree","claude-code","codex","llm","developer-tools"],type:"module",packageManager:"bun@1.3.13",bin:{rove:"dist/cli/rove.js"},files:["dist/cli","dist/completions","dist/web-ui","dist/skills","dist/*.wav","README.md","LICENSE"],publishConfig:{access:"public"},repository:{type:"git",url:"git+https://github.com/Sma1lboy/rove.git"},homepage:"https://rove.run",bugs:{url:"https://github.com/Sma1lboy/rove/issues"},engines:{bun:">=1.3.11"},scripts:{dev:"ROVE_DEV=1 bun --conditions=browser ./src/cli/rove.ts","dev:mock":"ROVE_DEV=1 bun ./src/tui-react/mock/host.tsx","dev:sandbox":"bun run scripts/dev-sandbox.ts run","dev:sandbox:reset":"bun run scripts/dev-sandbox.ts reset",build:"bun run scripts/build.ts","build:rubric":"bun run scripts/build-tier-rubric.ts",compile:"bun run scripts/compile.ts",typecheck:"tsc --noEmit","check-i18n":"bun run scripts/check-i18n.ts",test:"bun run test:fast && bun run test:socket","perf:golden":"bun scripts/perf-golden.ts","pty:soak":"bun scripts/pty-soak.ts","test:fast":"vitest run --passWithNoTests --minWorkers=1 --maxWorkers=8","test:socket":"ROVE_INCLUDE_SOCKET=1 vitest run test/daemon --pool forks --minWorkers=1 --maxWorkers=4 --passWithNoTests","test:socket:coverage":"ROVE_INCLUDE_SOCKET=1 ROVE_COVERAGE_DAEMON=1 vitest run test/daemon --coverage --pool forks --minWorkers=1 --maxWorkers=4 --passWithNoTests","test:behavior":"ROVE_INCLUDE_BEHAVIOR=1 vitest run test/behavior --pool forks --minWorkers=1 --maxWorkers=1 --retry=2 --passWithNoTests","test:render":"bun scripts/render-track.mjs",coverage:"vitest run --coverage --passWithNoTests",bench:"vitest bench --run",lint:"biome check .",knip:"knip-bun",postinstall:"bun run scripts/check-preview-deps.ts || true",prepublishOnly:"bun run typecheck && bun run build","plugin-sandbox":"bun run scripts/plugin-sandbox.ts"},"//":"biome.json + bunfig.toml live at the monorepo root since they apply repo-wide; bun.lock also lives at root (workspace-shared).",dependencies:{"@ansi-tools/parser":"^1.0.15","@opentui/core":"0.4.3","@opentui/react":"0.4.3","@xterm/addon-serialize":"^0.14.0","@xterm/addon-unicode11":"^0.9.0","@xterm/headless":"^6.0.0","node-pty":"^1.1.0",react:"^19.2.8","smol-toml":"^1.7.1",ws:"^8.21.3"},devDependencies:{"@biomejs/biome":"1.9.4","@sma1lboy/rove-daemon":"0.7.20","@tsconfig/bun":"1.0.11","@types/bun":"1.3.14","@types/node":"25.6.2","@types/react":"^19.2.0","@vitest/coverage-v8":"2.1.9",knip:"^6.14.2","react-devtools-core":"^7.0.1","rove-weixin":"0.0.8",typescript:"5.8.2",vitest:"2.1.9"},trustedDependencies:[]};var CURRENT_VERSION=package_default.version,PACKAGE_NAME=package_default.name;function repoSlug(){let url=package_default.repository?.url;if(!url)return null;let m=url.match(/github\.com[:/]([^/]+)\/([^/.]+)/);if(!m||!m[1]||!m[2])return null;return`${m[1]}/${m[2]}`}var UPDATE_SCRIPT_URL="https://raw.githubusercontent.com/Sma1lboy/rove/main/scripts/update.sh",UPDATE_COMMAND=`curl -fsSL ${UPDATE_SCRIPT_URL} | sh`;function owningNpmPrefix(modulePath=fileURLToPath(import.meta.url)){let at=modulePath.indexOf("/lib/node_modules/");if(at<=0)return null;return modulePath.slice(0,at)}function recommendedGlobalInstallCommand(prefix=owningNpmPrefix()){let target=`${PACKAGE_NAME}@latest`;return prefix===null?`npm install -g ${target}`:`npm install -g --prefix ${prefix} ${target}`}var BREAKING_VERSIONS=[];function breakingVersionsCrossed(from,to,breaking=BREAKING_VERSIONS){let[lo,hi]=compareSemver(from,to)<=0?[from,to]:[to,from];return breaking.filter((b)=>compareSemver(b,lo)>0&&compareSemver(b,hi)<=0)}var FETCH_TIMEOUT_MS=3000,RELEASE_CHANNELS=["latest","nightly"],DEFAULT_RELEASE_CHANNEL="latest";function channelOf(version=CURRENT_VERSION){return prereleaseOf(version)?.split(".")[0]==="nightly"?"nightly":DEFAULT_RELEASE_CHANNEL}async function fetchLatestFromRegistry(packageName,channel){let ctrl=new AbortController,timer=setTimeout(()=>ctrl.abort(),FETCH_TIMEOUT_MS);try{let encoded=packageName.replace("/","%2F"),res=await fetch(`https://registry.npmjs.org/${encoded}/${channel}`,{signal:ctrl.signal,headers:{accept:"application/json"}});if(!res.ok)return null;let body=await res.json();if(typeof body.version!=="string")return null;return body.version}catch{return null}finally{clearTimeout(timer)}}function isNewerSemver(latest,current){let core=compareSemver(latest,current);if(core!==0)return core>0;return comparePrerelease(prereleaseOf(latest),prereleaseOf(current))>0}function prereleaseOf(version){let dash=version.indexOf("-");return dash===-1?void 0:version.slice(dash+1)||void 0}function comparePrerelease(a,b){if(a===b)return 0;if(a===void 0)return 1;if(b===void 0)return-1;let aParts=a.split("."),bParts=b.split(".");for(let i=0;i<Math.max(aParts.length,bParts.length);i++){let av=aParts[i],bv=bParts[i];if(av===void 0)return-1;if(bv===void 0)return 1;if(av===bv)continue;let an=/^\d+$/.test(av)?Number.parseInt(av,10):null,bn=/^\d+$/.test(bv)?Number.parseInt(bv,10):null;if(an!==null&&bn!==null)return an>bn?1:-1;if(an!==null)return-1;if(bn!==null)return 1;return av>bv?1:-1}return 0}function compareSemver(aVersion,bVersion){let norm=(v)=>v.split("-")[0]??v,a=norm(aVersion).split(".").map((s)=>Number.parseInt(s,10)),b=norm(bVersion).split(".").map((s)=>Number.parseInt(s,10));for(let i=0;i<3;i++){let av=a[i]??0,bv=b[i]??0;if(Number.isNaN(av)||Number.isNaN(bv))return 0;if(av>bv)return 1;if(av<bv)return-1}return 0}async function checkLatestVersion(opts={}){let channel=opts.channel??channelOf(),fake=process.env.ROVE_FAKE_UPDATE;if(fake)return{current:CURRENT_VERSION,latest:fake,hasUpdate:isNewerSemver(fake,CURRENT_VERSION),channel};if(isDev()&&!opts.force)return null;let latest=await fetchLatestFromRegistry(PACKAGE_NAME,channel);if(!latest)return null;return{current:CURRENT_VERSION,latest,hasUpdate:isNewerSemver(latest,CURRENT_VERSION),channel}}function versionFromTagName(tagName){if(typeof tagName!=="string")return null;return tagName.match(/^v(\d+\.\d+\.\d+)$/)?.[1]??null}async function fetchReleaseNotes(version){let slug=repoSlug();if(!slug)return null;let tag=`v${version}`,ctrl=new AbortController,timer=setTimeout(()=>ctrl.abort(),FETCH_TIMEOUT_MS);try{let res=await fetch(`https://api.github.com/repos/${slug}/releases/tags/${tag}`,{signal:ctrl.signal,headers:{accept:"application/vnd.github+json","x-github-api-version":"2022-11-28"}});if(!res.ok)return null;let body=await res.json();if(typeof body.body!=="string"||typeof body.html_url!=="string")return null;return{body:body.body,url:body.html_url,version}}catch{return null}finally{clearTimeout(timer)}}async function fetchReleaseNotesRange(args){let slug=repoSlug();if(!slug)return[];let limit=args.limit??100,ctrl=new AbortController,timer=setTimeout(()=>ctrl.abort(),FETCH_TIMEOUT_MS);try{let res=await fetch(`https://api.github.com/repos/${slug}/releases?per_page=${limit}`,{signal:ctrl.signal,headers:{accept:"application/vnd.github+json","x-github-api-version":"2022-11-28"}});if(!res.ok)return[];let body=await res.json();if(!Array.isArray(body))return[];return body.map((release)=>{let version=versionFromTagName(release.tag_name);if(!version||typeof release.html_url!=="string"||typeof release.body!=="string")return null;if(compareSemver(version,args.current)<=0)return null;if(compareSemver(version,args.latest)>0)return null;return{version,url:release.html_url,body:release.body}}).filter((release)=>release!==null)}catch{return[]}finally{clearTimeout(timer)}}async function fetchReleaseSummaries(limit=12){let slug=repoSlug();if(!slug)return[];let ctrl=new AbortController,timer=setTimeout(()=>ctrl.abort(),FETCH_TIMEOUT_MS);try{let res=await fetch(`https://api.github.com/repos/${slug}/releases?per_page=${limit}`,{signal:ctrl.signal,headers:{accept:"application/vnd.github+json","x-github-api-version":"2022-11-28"}});if(!res.ok)return[];let body=await res.json();if(!Array.isArray(body))return[];return body.map((release)=>{let version=versionFromTagName(release.tag_name);if(!version||typeof release.html_url!=="string")return null;return{version,url:release.html_url}}).filter((release)=>release!==null)}catch{return[]}finally{clearTimeout(timer)}}function releasePageUrl(version){let slug=repoSlug();if(!slug)return null;return`https://github.com/${slug}/releases/tag/v${version}`}
3
- export{package_default,CURRENT_VERSION,PACKAGE_NAME,repoSlug,UPDATE_SCRIPT_URL,UPDATE_COMMAND,owningNpmPrefix,recommendedGlobalInstallCommand,BREAKING_VERSIONS,breakingVersionsCrossed,RELEASE_CHANNELS,DEFAULT_RELEASE_CHANNEL,channelOf,isNewerSemver,compareSemver,checkLatestVersion,fetchReleaseNotes,fetchReleaseNotesRange,fetchReleaseSummaries,releasePageUrl};
@@ -1,2 +0,0 @@
1
- // @bun
2
- import{invokeVerb,runApiSubcommand,toApiError}from"./chunk-t45c781p.js";import{API_SCHEMA_VERSION,API_VERBS,VERBS,VERB_GROUPS,VerbArgs,apiUsage,buildCountPlan,defaultApiRuntime,deliverPrompt,findVerb,fullSchema,parseAgentsSpec,parseFlags,schemaIndex,validateAgainstSpec,verbHelp,verbSchema}from"./chunk-ctkf2m9h.js";import"./chunk-466djpa8.js";import"./chunk-83e13qdq.js";import"./chunk-hr2xky15.js";import"./chunk-pa6teq2c.js";import"./chunk-vrzb7s42.js";import"./chunk-17e2tgyg.js";import"./chunk-cbc1h345.js";import"./chunk-4xns5j33.js";import"./chunk-4hestsjp.js";import"./chunk-2bgmby1d.js";import"./chunk-rqyaf0dw.js";import"./chunk-41x1hrqe.js";import"./chunk-5mhvjytn.js";import"./chunk-atn06ncf.js";import"./chunk-qkehg2c1.js";import"./chunk-m9q1whep.js";import"./chunk-t64hwtzt.js";import"./chunk-e5k2j2x8.js";import"./chunk-cbjh43yc.js";import"./chunk-2apszxn2.js";import"./chunk-sak1tphp.js";import"./chunk-4r3s1z14.js";import"./chunk-rxqdw6s5.js";import"./chunk-4250pw6r.js";import"./chunk-h29wy8y3.js";import"./chunk-vjvkkrzc.js";import"./chunk-pdvyfz3n.js";import"./chunk-gcxcq22q.js";import"./chunk-hpay0ax8.js";import"./chunk-catrr0z3.js";import"./chunk-wam8sr6a.js";import"./chunk-ctmbj2y3.js";import"./chunk-k4374n9t.js";import"./chunk-w7zv47mp.js";import"./chunk-2tfnscxk.js";import"./chunk-etfh8f4z.js";import"./chunk-6xppe9bj.js";import{ApiError}from"./chunk-f7zjggfv.js";import"./chunk-zsfyj36g.js";import"./chunk-96cjpssp.js";import"./chunk-9w5g7frx.js";import"./chunk-gvrtrvnz.js";import"./chunk-ag3txfg2.js";import"./chunk-2brn8e1y.js";import"./chunk-60fy3ckp.js";import"./chunk-znjcesdr.js";import"./chunk-7kysxgbq.js";import"./chunk-qzsahxd9.js";import"./chunk-xy8be792.js";import"./chunk-h2m7em1e.js";import"./chunk-tdhk4dz8.js";import"./chunk-8p579jct.js";import"./chunk-h9cg0j3a.js";import"./chunk-40dgv828.js";import"./chunk-3342qzve.js";import"./chunk-7q43tysm.js";import"./chunk-mw0ysrpd.js";import"./chunk-a7kq09ke.js";import"./chunk-prqeddy7.js";import"./chunk-zt7wfnva.js";import"./chunk-yt15y085.js";import"./chunk-wvwkmj3t.js";import"./chunk-jxyvk9c0.js";import"./chunk-nhakrm1f.js";import"./chunk-pxk2c2vj.js";import"./chunk-adkva3ep.js";import"./chunk-0422g8ac.js";import"./chunk-hq9tbzqa.js";import"./chunk-pn5ygqn6.js";import"./chunk-npwzmmv9.js";import"./chunk-b39sdtt0.js";import"./chunk-nc46gddg.js";import"./chunk-wqfp8d9w.js";export{verbSchema,verbHelp,validateAgainstSpec,toApiError,schemaIndex,runApiSubcommand,parseFlags,parseAgentsSpec,invokeVerb,fullSchema,findVerb,deliverPrompt,defaultApiRuntime,buildCountPlan,apiUsage,VerbArgs,VERB_GROUPS,VERBS,ApiError,API_VERBS,API_SCHEMA_VERSION};
@@ -1,3 +0,0 @@
1
- // @bun
2
- import{isRowTokenTone}from"./chunk-atn06ncf.js";import{sameWorktreeChanges}from"./chunk-55ajzrj6.js";import{ensureDaemonReachable,isStaleInstallError}from"./chunk-96cjpssp.js";import{toTaskId}from"./chunk-xy8be792.js";import{CURRENT_VERSION}from"./chunk-8p579jct.js";import{homeDir}from"./chunk-prqeddy7.js";import{createStateCell,mapReadableState}from"./chunk-nhakrm1f.js";import{DAEMON_PROTOCOL_VERSION,MIN_COMPATIBLE_PROTOCOL_VERSION,isAttentionInboxState,isDaemonVersionStale,isForeignDaemonHome,isProtocolCompatible,isRoutineInboxState,parseDaemonStopReason}from"./chunk-adkva3ep.js";import{logClient,logClientError}from"./chunk-hq9tbzqa.js";function parseWorktreeChangesPayload(payload){let changes=payload?.changes;if(!changes||typeof changes!=="object"||Array.isArray(changes))return null;let map=new Map;for(let[path,value]of Object.entries(changes)){let counts=value;if(typeof counts?.added!=="number"||typeof counts.deleted!=="number")return null;map.set(path,{added:counts.added,deleted:counts.deleted,...typeof counts.behind==="number"?{behind:counts.behind}:{},...typeof counts.ahead==="number"?{ahead:counts.ahead}:{}})}let unreadable=payload?.unreadable;if(Array.isArray(unreadable)){for(let path of unreadable)if(typeof path==="string"&&path.length>0)map.set(path,null)}return map}function sameWorktreeChangesMap(a,b){if(a.size!==b.size)return!1;for(let[path,counts]of a){if(!b.has(path))return!1;if(!sameWorktreeChanges(counts,b.get(path)??null))return!1}return!0}function parseToken(raw){if(!raw||typeof raw!=="object")return null;let t=raw;if(typeof t.source!=="string"||typeof t.key!=="string")return null;if(typeof t.text!=="string"||t.text.length===0)return null;if(typeof t.expiresAt!=="number"||!Number.isFinite(t.expiresAt))return null;return{source:t.source,key:t.key,text:t.text,...isRowTokenTone(t.tone)?{tone:t.tone}:{},expiresAt:t.expiresAt}}function parseRowTokensPayload(payload){let tokens=payload?.tokens;if(!tokens||typeof tokens!=="object"||Array.isArray(tokens))return null;let out=new Map;for(let[taskId,list]of Object.entries(tokens)){if(!Array.isArray(list))continue;let parsed=list.map(parseToken).filter((token)=>token!==null);if(parsed.length>0)out.set(taskId,parsed)}return out}function sameRowTokenMap(a,b){if(a.size!==b.size)return!1;for(let[taskId,left]of a){let right=b.get(taskId);if(!right||right.length!==left.length)return!1;for(let[i,token]of left.entries()){let other=right[i];if(!other||other.source!==token.source||other.key!==token.key||other.text!==token.text||other.tone!==token.tone||other.expiresAt!==token.expiresAt)return!1}}return!0}function liveRowTokens(map,taskId,now){let tokens=map?.get(taskId);if(!tokens||tokens.length===0)return[];return tokens.filter((token)=>token.expiresAt>now)}function shouldLogReconnectAttempt(attempt){if(attempt>100)return!1;return attempt===1||attempt%10===0}function describePayload(value){if(value===void 0)return"undefined";if(value===null)return"null";let type=Array.isArray(value)?"array":typeof value,text;try{text=typeof value==="string"?value:JSON.stringify(value)}catch{text=String(value)}if(text.length>120)text=`${text.slice(0,120)}\u2026`;return`${type}:${text}`}function decodeUiPrefsPayload(payload){let p=payload;if(!p||typeof p!=="object"||!("theme"in p))return null;if(p.theme!==null&&typeof p.theme!=="string")return null;return{theme:typeof p.theme==="string"&&p.theme.length>0?p.theme:null,..."themeMode"in p?{themeMode:typeof p.themeMode==="string"?p.themeMode:null}:{},transparentBackground:p.transparentBackground!==!1,focusAccent:typeof p.focusAccent==="string"?p.focusAccent:null,locale:typeof p.locale==="string"?p.locale:"",sortMode:p.sortMode==="recent"||p.sortMode==="attention"||p.sortMode==="name"?p.sortMode:"default",keysCollapsed:p.keysCollapsed===!0,projectFilter:typeof p.projectFilter==="string"&&p.projectFilter.length>0?p.projectFilter:null}}var TOKEN_TOTAL_FIELDS=["inputTokens","outputTokens","cacheReadTokens","cacheCreationTokens"];function parseContextUsagePayload(payload){let context=payload?.context;if(!context||typeof context!=="object"||Array.isArray(context))return null;let map=new Map;for(let[key,value]of Object.entries(context)){let v=value;if(typeof v?.contextTokens!=="number")return null;if(v.contextWindowTokens!==void 0&&typeof v.contextWindowTokens!=="number")return null;let totals={};for(let field of TOKEN_TOTAL_FIELDS){let raw=v[field];if(raw===void 0)continue;if(typeof raw!=="number")return null;totals[field]=raw}map.set(key,{contextTokens:v.contextTokens,...typeof v.contextWindowTokens==="number"?{contextWindowTokens:v.contextWindowTokens}:{},...v.approximate===!0?{approximate:!0}:{},...totals})}return map}function sameContextUsageMap(a,b){if(a.size!==b.size)return!1;for(let[key,value]of a){let other=b.get(key);if(!other||other.contextTokens!==value.contextTokens||other.contextWindowTokens!==value.contextWindowTokens||other.approximate!==value.approximate||TOKEN_TOTAL_FIELDS.some((field)=>other[field]!==value[field]))return!1}return!0}function parseUsageSnapshotPayload(payload){let usage=payload?.usage;if(!usage||typeof usage!=="object"||Array.isArray(usage))return null;let map=new Map;for(let[vendor,value]of Object.entries(usage)){let snapshot=value;if(typeof snapshot?.capturedAt!=="number"||!Array.isArray(snapshot.windows))return null;let windows=[];for(let raw of snapshot.windows){let w=raw;if(typeof w?.kind!=="string"||typeof w.label!=="string"||typeof w.percent!=="number")return null;if(w.resetsAt!==null&&typeof w.resetsAt!=="number")return null;windows.push({kind:w.kind,label:w.label,percent:w.percent,resetsAt:w.resetsAt})}map.set(vendor,{windows,capturedAt:snapshot.capturedAt})}return map}function sameUsageSnapshotMap(a,b){if(a.size!==b.size)return!1;for(let[vendor,usage]of a){let other=b.get(vendor);if(!other||other.capturedAt!==usage.capturedAt||other.windows.length!==usage.windows.length)return!1;for(let i=0;i<usage.windows.length;i++){let x=usage.windows[i],y=other.windows[i];if(!x||!y||x.kind!==y.kind||x.label!==y.label||x.percent!==y.percent||x.resetsAt!==y.resetsAt)return!1}}return!0}function parseTranscriptActivityPayload(payload){let activity=payload?.activity;if(!activity||typeof activity!=="object"||Array.isArray(activity))return null;let map=new Map;for(let[path,value]of Object.entries(activity)){let v=value;if(typeof v?.mtimeMs!=="number"||typeof v.completionAt!=="number")return null;if(v.completionId!==null&&typeof v.completionId!=="string")return null;map.set(path,{mtimeMs:v.mtimeMs,completionId:v.completionId,completionAt:v.completionAt})}return map}function sameTranscriptActivityMap(a,b){if(a.size!==b.size)return!1;for(let[path,v]of a){let other=b.get(path);if(!other||other.mtimeMs!==v.mtimeMs||other.completionId!==v.completionId||other.completionAt!==v.completionAt)return!1}return!0}function deserializeTask(s){return{id:toTaskId(s.id),title:s.title,repo:s.repo,branch:s.branch,worktreePath:s.worktreePath,kind:s.kind,...s.scratch?{scratch:!0}:{},...s.routine?{routine:s.routine}:{},status:s.status,pinned:s.pinned,vendor:s.vendor,command:s.command,prStatus:s.prStatus,modelEffort:s.modelEffort,model:s.model,tier:s.tier,groupId:s.groupId,observedLanguage:s.observedLanguage,deletion:s.deletion,quotaResume:s.quotaResume,linkedWorkItem:s.linkedWorkItem,dispatcher:s.dispatcher,prompt:s.prompt,baseRef:s.baseRef,worktreeName:s.worktreeName,report:s.report,createdAt:s.createdAt,updatedAt:s.updatedAt}}async function runReconnectLoop(deps){let delayMs=deps.spawnDaemon?Math.floor(Math.random()*400):500,attempt=0;while(!deps.isDisposed()){if(delayMs>0)await new Promise((r)=>setTimeout(r,delayMs));if(deps.isDisposed())break;attempt++;try{if(deps.spawnDaemon)await deps.ensureReachable();await deps.init(),logClient("orch",`reconnected and re-subscribed after ${attempt} attempt(s) \u2014 task list re-synced`);return}catch(err){if(isStaleInstallError(err)){logClientError("orch-reconnect-fatal",err),deps.onFatal?.(err);return}if(deps.shouldLogAttempt(attempt))logClientError("orch-reconnect",err);delayMs=delayMs===0?500:Math.min(delayMs*2,3000)}}}async function performInit(client,opts,signals){let hello=await client.request("hello",{protocolVersion:DAEMON_PROTOCOL_VERSION,minProtocolVersion:MIN_COMPATIBLE_PROTOCOL_VERSION}),daemonVersion=typeof hello.protocolVersion==="number"?hello.protocolVersion:DAEMON_PROTOCOL_VERSION,daemonMin=typeof hello.minProtocolVersion==="number"?hello.minProtocolVersion:daemonVersion;if(!isProtocolCompatible({localVersion:DAEMON_PROTOCOL_VERSION,localMin:MIN_COMPATIBLE_PROTOCOL_VERSION,remoteVersion:daemonVersion,remoteMin:daemonMin}))throw Error(`Rove daemon is protocol v${daemonVersion} (min v${daemonMin}); this client is v${DAEMON_PROTOCOL_VERSION} (min v${MIN_COMPATIBLE_PROTOCOL_VERSION}). Restart the daemon (\`rove daemon restart\`) or upgrade Rove.`);let clientHome=homeDir();if(!opts.expectForeignHome&&isForeignDaemonHome(hello.homeDir,clientHome))throw Error(`Rove daemon on this socket serves ${hello.homeDir}, but this client uses ${clientHome}. A sandbox or dev daemon has taken the production socket \u2014 stop it (\`rove daemon stop\`), or unset ROVE_DAEMON_SOCKET_PATH / ROVE_DAEMON_SOCKET_PATH before starting it.`);if(signals.setDaemonVersionSig(typeof hello.roveVersion==="string"?hello.roveVersion:null),opts.onPeerIdentity?.({hostname:typeof hello.hostname==="string"?hello.hostname:"",homeDir:typeof hello.homeDir==="string"?hello.homeDir:"",daemonPid:typeof hello.daemonPid==="number"?hello.daemonPid:0,roveVersion:typeof hello.roveVersion==="string"?hello.roveVersion:""}),signals.setDaemonRestartingSig(!1),hello.tasks&&opts.subscribesTasks)signals.setTasks(hello.tasks.map(deserializeTask));if(await client.subscribe({role:opts.role,channels:opts.channels,cellPixelSize:opts.cellPixelSize}),hello.capabilities?.includes("worktree.changes")){if(signals.worktreeChangesAcc()===null)signals.setWorktreeChangesSig(new Map)}else signals.setWorktreeChangesSig(null);if(hello.capabilities?.includes("transcript.activity")){if(signals.transcriptActivityAcc()===null)signals.setTranscriptActivitySig(new Map)}else signals.setTranscriptActivitySig(null);signals.setConnectionState("online"),logClient("orch",`subscribed as ${opts.role} (${signals.tasksAcc().length} tasks)`)}var MAP_CHANNELS={"usage.snapshot":{what:"usage payload",parse:parseUsageSnapshotPayload,same:sameUsageSnapshotMap,read:(signals)=>signals.usageSnapshotAcc(),write:(signals,next)=>signals.setUsageSnapshotSig(next)},"usage.context":{what:"context payload",parse:parseContextUsagePayload,same:sameContextUsageMap,read:(signals)=>signals.contextUsageAcc(),write:(signals,next)=>signals.setContextUsageSig(next)},"worktree.changes":{what:"changes payload",parse:parseWorktreeChangesPayload,same:sameWorktreeChangesMap,read:(signals)=>signals.worktreeChangesAcc(),write:(signals,next)=>signals.setWorktreeChangesSig(next)},"transcript.activity":{what:"activity payload",parse:parseTranscriptActivityPayload,same:sameTranscriptActivityMap,read:(signals)=>signals.transcriptActivityAcc(),write:(signals,next)=>signals.setTranscriptActivitySig(next)},"task.tokens":{what:"token map",parse:parseRowTokensPayload,same:sameRowTokenMap,read:(signals)=>signals.rowTokensAcc(),write:(signals,next)=>signals.setRowTokensSig(next)}};function handleMapChannel(name,payload,signals){let channel=MAP_CHANNELS[name];if(!channel)return!1;let next=channel.parse(payload);if(!next)return logClientError("orch",`dropped ${name} event: malformed ${channel.what} (${describePayload(payload)})`),!0;let current=channel.read(signals);if(current&&channel.same(current,next))return!0;return channel.write(signals,next),!0}function writeGraphicsToStdout(data){if(!process.stdout.isTTY)return;process.stdout.write(data)}function pruneEngineState(tasks,signals){let live=new Set(tasks.map((t)=>t.id)),current=signals.engineStateAcc();if(current.size>0){let next=null;for(let key of current.keys()){if(live.has(key))continue;if(!next)next=new Map(current);next.delete(key)}if(next)signals.setEngineStateSig(next)}let tabs=signals.engineTabStateAcc();if(tabs.size>0){let nextTabs=null;for(let key of tabs.keys()){if(live.has(key))continue;if(!nextTabs)nextTabs=new Map(tabs);nextTabs.delete(key)}if(nextTabs)signals.setEngineTabStateSig(nextTabs)}let lifecycle=signals.engineLifecycleAcc();if(lifecycle.size>0){let nextLifecycle=null;for(let key of lifecycle.keys()){if(live.has(key))continue;if(!nextLifecycle)nextLifecycle=new Map(lifecycle);nextLifecycle.delete(key)}if(nextLifecycle)signals.setEngineLifecycleSig(nextLifecycle)}}function pruneTaskJobs(tasks,signals){let current=signals.taskJobsAcc();if(current.size===0)return;let live=new Set(tasks.map((t)=>t.id)),next=null;for(let key of current.keys()){if(live.has(key))continue;if(!next)next=new Map(current);next.delete(key)}if(next)signals.setTaskJobsSig(next)}function handleOrchestratorEvent(name,payload,signals){if(name==="task.snapshot"){let value=payload?.tasks;if(Array.isArray(value))signals.setTasks(value.map(deserializeTask)),pruneEngineState(value,signals),pruneTaskJobs(value,signals);else logClientError("orch",`dropped task.snapshot event: tasks is not an array (got ${describePayload(value)})`);return}if(name==="daemon.stopping"){let p=payload;if(typeof p?.roveVersion==="string")signals.setDaemonVersionSig(p.roveVersion);if(parseDaemonStopReason(p?.reason)==="restart")signals.setDaemonRestartingSig(!0);return}if(name==="active-task"){let id=payload?.taskId;signals.setActiveTaskSig(typeof id==="string"?id:null);return}if(name==="update"){let info=payload?.info;signals.setUpdateSig(info??null);return}if(name==="engine-state"){let p=payload;if(typeof p?.taskId!=="string"||typeof p.state!=="string"){logClientError("orch",`dropped engine-state event: taskId/state must be strings (taskId=${describePayload(p?.taskId)}, state=${describePayload(p?.state)})`);return}let tabId=typeof p.tabId==="string"&&p.tabId?p.tabId:void 0,entry={state:p.state,detail:p.detail,...typeof p.sessionId==="string"&&p.sessionId?{sessionId:p.sessionId}:{},...typeof p.transcriptPath==="string"&&p.transcriptPath?{transcriptPath:p.transcriptPath}:{},...tabId?{tabId}:{},at:typeof p.at==="number"?p.at:0},prevTaskState=signals.engineStateAcc().get(p.taskId)?.state;if(!tabId){let next=new Map(signals.engineStateAcc());if(p.state==="idle")next.delete(p.taskId);else next.set(p.taskId,entry);signals.setEngineStateSig(next)}if((p.state==="idle"||p.state==="turn_complete"||p.state==="error"||p.state==="running"&&prevTaskState!=="running")&&signals.engineLifecycleAcc().has(p.taskId)){let lifecycle=new Map(signals.engineLifecycleAcc());lifecycle.delete(p.taskId),signals.setEngineLifecycleSig(lifecycle)}if(tabId){let nextTabs=new Map(signals.engineTabStateAcc()),tabs=new Map(nextTabs.get(p.taskId)??[]);tabs.set(tabId,entry),nextTabs.set(p.taskId,tabs),signals.setEngineTabStateSig(nextTabs)}return}if(name==="attention.inbox"){let items=payload?.items;if(!Array.isArray(items)){logClientError("orch",`dropped attention.inbox event: items is not an array (${describePayload(items)})`);return}let kept=items.filter((item)=>{if(!item||typeof item!=="object"||Array.isArray(item))return!1;let p=item;return(typeof p.taskId==="string"||p.taskId===null&&isRoutineInboxState(p.state))&&(p.tabId===null||typeof p.tabId==="string")&&isAttentionInboxState(p.state)&&(p.unread===void 0||typeof p.unread==="boolean")&&typeof p.at==="number"});if(kept.length!==items.length)logClientError("orch",`dropped ${items.length-kept.length} malformed attention.inbox item(s) of ${items.length} (${describePayload(items)})`);if(kept.length===0&&items.length>0)return;signals.setAttentionInboxSig(kept.map((item)=>({...item,unread:item.unread!==!1})));return}if(name==="task.jobs"){let p=payload;if(typeof p?.taskId!=="string"||p.kind!=="ensureWorktree"){logClientError("orch",`dropped task.jobs event: expected string taskId + kind "ensureWorktree" (taskId=${describePayload(p?.taskId)}, kind=${describePayload(p?.kind)})`);return}let current=signals.taskJobsAcc();if(p.phase==="running"){let next=new Map(current);next.set(p.taskId,{kind:p.kind}),signals.setTaskJobsSig(next);return}if((p.phase==="done"||p.phase==="error")&&current.has(p.taskId)){let next=new Map(current);next.delete(p.taskId),signals.setTaskJobsSig(next)}return}if(handleMapChannel(name,payload,signals))return;if(name==="tab.open"){let p=payload;if(typeof p?.taskId!=="string"||typeof p.at!=="number"||typeof p.title!=="string"||!Array.isArray(p.argv)){logClientError("orch",`dropped tab.open event: malformed payload (${describePayload(payload)})`);return}signals.setTabOpenSig(p);return}if(name==="tab.close"){let p=payload;if(!(p&&"kind"in p&&p.kind==="terminal-tab"?typeof p.taskId==="string"&&typeof p.tabId==="string"&&typeof p.requestId==="string"&&typeof p.at==="number":typeof p?.taskId==="string"&&typeof p.at==="number"&&("title"in p)&&typeof p.title==="string")){logClientError("orch",`dropped tab.close event: malformed payload (${describePayload(payload)})`);return}signals.setTabCloseSig(p);return}if(name==="tab.rename"){let p=payload;if(typeof p?.taskId!=="string"||typeof p.tabId!=="string"||typeof p.title!=="string"||typeof p.at!=="number"){logClientError("orch",`dropped tab.rename event: malformed payload (${describePayload(payload)})`);return}signals.setTabRenameSig(p);return}if(name==="ui.prompt"){let p=payload;if(typeof p?.promptId!=="string"||typeof p.title!=="string"||typeof p.at!=="number"){logClientError("orch",`dropped ui.prompt event: malformed payload (${describePayload(payload)})`);return}signals.setUiPromptSig(p);return}if(name==="engine.lifecycle"){let p=payload;if(typeof p?.taskId!=="string"||typeof p.kind!=="string"){logClientError("orch",`dropped engine.lifecycle event: malformed payload (${describePayload(payload)})`);return}let prev=signals.engineLifecycleAcc(),cur=prev.get(p.taskId)??{subagents:0},entry=p.kind==="subagent-start"?{subagents:cur.subagents+1}:p.kind==="subagent-stop"?{subagents:Math.max(0,cur.subagents-1)}:cur;if(entry===cur&&!prev.has(p.taskId))return;let map=new Map(prev);if(entry.subagents===0)map.delete(p.taskId);else map.set(p.taskId,entry);signals.setEngineLifecycleSig(map);return}if(name==="graphics.write"){let p=payload;if(typeof p?.data!=="string"||typeof p.imageId!=="number"||typeof p.at!=="number"){logClientError("orch",`dropped graphics.write event: malformed payload (${describePayload(payload)})`);return}signals.writeGraphics(Buffer.from(p.data,"base64"));return}if(name==="notice.event"){let p=payload;if(typeof p?.title!=="string"||typeof p.at!=="number"||typeof p.kind!=="string"){logClientError("orch",`dropped notice.event: malformed payload (${describePayload(payload)})`);return}signals.setNoticeSig(p);return}if(name==="ui-prefs"){let decoded=decodeUiPrefsPayload(payload);if(!decoded){let theme=payload?.theme;logClientError("orch",`dropped ui-prefs event: theme must be a string (got ${describePayload(theme)})`);return}signals.setUiPrefsSig(decoded);return}if(name==="keybindings"){let p=payload;if(typeof p?.rev!=="number"){logClientError("orch",`dropped keybindings event: rev must be a number (got ${describePayload(p?.rev)})`);return}signals.setKeybindingsRevSig(p.rev)}}function subscribeTasksOp(tasks,listener){try{listener(tasks())}catch(err){console.error("[rove RemoteOrchestrator] task listener threw on subscribe:",err)}return tasks.subscribe(()=>{try{listener(tasks.get())}catch(err){console.error("[rove RemoteOrchestrator] task listener threw:",err)}})}async function createTaskOp(client,input){let{modelEffort,...rest}=input,res=await client.request("task.create",{...rest,effort:modelEffort});return deserializeTask(res.task)}async function ensureMainTaskOp(client,repo){let res=await client.request("task.ensureMain",{repo});return deserializeTask(res.task)}async function openDirectoryTaskOp(client,input){let res=await client.request("task.openDir",input);return deserializeTask(res.task)}async function adoptScratchRepoOp(client,id,repo){await client.request("task.adoptScratchRepo",{taskId:String(id),repo})}async function ensureWorktreeOp(client,id){return(await client.request("task.ensureWorktree",{taskId:String(id)})).worktreePath}async function forgetProjectOp(client,repo){await client.request("project.forget",{repo})}function reportEngineInterruptOp(client,taskId,tabId){client.request("engine.reportEvent",{kind:"turn-interrupted",taskId,tabId}).catch(()=>{})}async function setTitleOp(client,id,title){await client.request("task.rename",{taskId:String(id),title})}async function setBranchOp(client,id,branch){await client.request("task.setBranch",{taskId:String(id),branch})}async function setVendorOp(client,id,vendor,effort,model){await client.request("task.setVendor",{taskId:String(id),vendor,...effort!==void 0?{effort}:{},...model!==void 0?{model}:{}})}async function setCommandOp(client,id,command,vendor){await client.request("task.setCommand",{taskId:String(id),command,...vendor?{vendor}:{}})}async function setPinnedOp(client,id,pinned){await client.request("task.pin",{taskId:String(id),pinned})}async function moveTaskOp(client,id,delta){await client.request("task.move",{taskId:String(id),direction:delta<0?"up":"down"})}async function moveTaskToTopOp(client,id){await client.request("task.move",{taskId:String(id),direction:"top"})}async function setPromptOp(client,id,prompt){await client.request("task.setPrompt",{taskId:String(id),prompt})}async function setStatusOp(client,id,status){await client.request("task.status",{taskId:String(id),status})}async function deleteTaskOp(client,id,opts){await client.request("task.delete",{taskId:String(id),force:opts?.force,deleteBranch:opts?.deleteBranch})}async function dismissAttentionOp(client,taskId,tabId,at){return(await client.request("attention.dismiss",{taskId:String(taskId),...tabId!==null?{tabId}:{},at})).deleted}async function dismissRoutineAttentionOp(client,automationId){return(await client.request("attention.dismissRoutine",{automationId})).deleted}async function markAttentionReadOp(client,taskId,tabId,at){return(await client.request("attention.read",{taskId:String(taskId),...tabId!==null?{tabId}:{},at})).updated}async function landPreflightOp(client,id){return(await client.request("task.landPreflight",{taskId:String(id)})).result}async function landTaskOp(client,id,opts){return(await client.request("task.land",{taskId:String(id),strategy:opts?.strategy,deleteBranch:opts?.deleteBranch,removeWorktree:opts?.removeWorktree,callerCwd:opts?.callerCwd})).result}async function discoverAdoptableWorktreesOp(client,repo){return(await client.request("worktree.discoverAdoptable",{repo})).worktrees}async function adoptWorktreeOp(client,input){let res=await client.request("worktree.adopt",input);return deserializeTask(res.task)}async function listWorktreesOp(client,opts){return(await client.request("worktree.list",{network:opts?.network!==!1})).projects}async function removeWorktreeOp(client,path,force){return(await client.request("worktree.remove",{path,force})).residue??null}async function syncBaseOp(client,taskId){return(await client.request("task.syncBase",{taskId})).result}async function failingChecksOp(client,taskId){let res=await client.request("pr.failingChecks",{taskId});return{checks:res.checks??[],totalFailing:res.totalFailing??0,...res.unavailable?{unavailable:res.unavailable}:{}}}async function listIssuesOp(client,repoRoot){return client.request("issue.list",{repoRoot})}async function listIssueReposOp(client){return(await client.request("issue.repos",{})).repos??[]}async function listFieldNotesOp(client,repo){return(await client.request("note.list",{repo})).notes??[]}async function deleteFieldNoteOp(client,repo,id){return(await client.request("note.delete",{repo,id})).deleted===!0}async function mutateIssueOp(client,repoRoot,op){return client.request("issue.mutate",{repoRoot,op})}async function listAutomationsOp(client){return client.request("automation.list",{})}async function createAutomationOp(client,input){return client.request("automation.create",input)}async function automationRunsOp(client,id){return client.request("automation.runs",{id})}async function setAutomationEnabledOp(client,id,enabled){return client.request("automation.update",{id,enabled})}async function runAutomationNowOp(client,id){return client.request("automation.runNow",{id})}async function deleteAutomationOp(client,id){return client.request("automation.delete",{id})}async function listWorkItemsOp(client,args){return client.request("workitem.list",args)}async function startWorkItemOp(client,args){return client.request("workitem.start",args)}async function setActiveTaskOp(client,id){await client.request("task.setActive",{taskId:id===null?null:String(id)})}function replyTabCloseOp(client,requestId,closed){client.request("terminalTab.closeReply",{requestId,closed}).catch(()=>{})}function replyPromptOp(client,promptId,value){client.request("ui.promptReply",{promptId,...value!==void 0?{value}:{}}).catch(()=>{})}function reportUiEventOp(client,kind,taskId,detail){client.request("ui.reportEvent",{kind,...taskId?{taskId}:{},...detail?{detail}:{}}).catch(()=>{})}function recentTaskEventsOp(client,id){return client.request("task.recentEvents",{taskId:String(id)})}class RemoteOrchestrator{client;tasksAcc=createStateCell([],"orchestrator.tasks");activeTaskAcc=createStateCell(null,"orchestrator.active-task");updateAcc=createStateCell(null);daemonVersionAcc=createStateCell(null);daemonRestartingAcc=createStateCell(!1);daemonStaleAcc=mapReadableState(this.daemonVersionAcc,(version)=>isDaemonVersionStale(version??void 0,CURRENT_VERSION));engineStateAcc=createStateCell(new Map);engineTabStateAcc=createStateCell(new Map);attentionInboxAcc=createStateCell([]);taskJobsAcc=createStateCell(new Map);rowTokensAcc=createStateCell(new Map);worktreeChangesAcc=createStateCell(null);usageSnapshotAcc=createStateCell(null);contextUsageAcc=createStateCell(null);transcriptActivityAcc=createStateCell(null);noticeAcc=createStateCell(null);tabOpenAcc=createStateCell(null);tabCloseAcc=createStateCell(null);tabRenameAcc=createStateCell(null);uiPromptAcc=createStateCell(null);engineLifecycleAcc=createStateCell(new Map);uiPrefsAcc=createStateCell(null);keybindingsRevAcc=createStateCell(null);connectionStateAcc=createStateCell("online","orchestrator.connection");staleInstallAcc=createStateCell(null);ensureReachable;role;cellPixelSize;channels;subscribesTasks;expectForeignHome;onPeerIdentity;reconnectTask=null;signals;constructor(client,options={}){this.client=client;this.ensureReachable=options.ensureReachable??ensureDaemonReachable,this.role=options.role??"pane",this.cellPixelSize=options.cellPixelSize??null,this.channels=options.channels,this.subscribesTasks=!options.channels||options.channels.includes("task.snapshot"),this.expectForeignHome=options.expectForeignHome===!0,this.onPeerIdentity=options.onPeerIdentity,this.signals={writeGraphics:options.graphicsOut??(this.role==="gui"?writeGraphicsToStdout:()=>{}),tasksAcc:this.tasksAcc,setTasks:this.tasksAcc.set,setActiveTaskSig:this.activeTaskAcc.set,setUpdateSig:this.updateAcc.set,setDaemonVersionSig:this.daemonVersionAcc.set,setDaemonRestartingSig:this.daemonRestartingAcc.set,engineStateAcc:this.engineStateAcc,setEngineStateSig:this.engineStateAcc.set,engineTabStateAcc:this.engineTabStateAcc,setEngineTabStateSig:this.engineTabStateAcc.set,setAttentionInboxSig:this.attentionInboxAcc.set,taskJobsAcc:this.taskJobsAcc,setTaskJobsSig:this.taskJobsAcc.set,rowTokensAcc:this.rowTokensAcc,setRowTokensSig:this.rowTokensAcc.set,worktreeChangesAcc:this.worktreeChangesAcc,setWorktreeChangesSig:this.worktreeChangesAcc.set,usageSnapshotAcc:this.usageSnapshotAcc,setUsageSnapshotSig:this.usageSnapshotAcc.set,contextUsageAcc:this.contextUsageAcc,setContextUsageSig:this.contextUsageAcc.set,transcriptActivityAcc:this.transcriptActivityAcc,setTranscriptActivitySig:this.transcriptActivityAcc.set,setNoticeSig:this.noticeAcc.set,setTabOpenSig:this.tabOpenAcc.set,setTabCloseSig:this.tabCloseAcc.set,setTabRenameSig:this.tabRenameAcc.set,setUiPromptSig:this.uiPromptAcc.set,engineLifecycleAcc:this.engineLifecycleAcc,setEngineLifecycleSig:this.engineLifecycleAcc.set,setUiPrefsSig:this.uiPrefsAcc.set,setKeybindingsRevSig:this.keybindingsRevAcc.set,setConnectionState:this.connectionStateAcc.set},this.client.on("*",(frame)=>handleOrchestratorEvent(frame.name,frame.payload,this.signals)),this.client.onLifecycle("close",()=>{this.connectionStateAcc.set("disconnected");let spawnDaemon=this.role==="gui";logClient("orch",spawnDaemon?"daemon socket closed \u2014 starting silent spawning reconnect loop":"daemon socket closed \u2014 starting non-spawning reconnect loop"),this.reconnectLoop(spawnDaemon)})}reconnectLoop(spawnDaemon){if(this.reconnectTask)return this.reconnectTask;let task=runReconnectLoop({isDisposed:()=>this.client.isDisposed,spawnDaemon,ensureReachable:this.ensureReachable,init:()=>this.init(),shouldLogAttempt:shouldLogReconnectAttempt,onFatal:(err)=>this.staleInstallAcc.set(err instanceof Error?err.message:String(err))});this.reconnectTask=task;let clear=()=>{if(this.reconnectTask===task)this.reconnectTask=null};return task.then(clear,clear),task}writeGraphics(data){this.signals.writeGraphics(data)}async init(){await performInit(this.client,{role:this.role,cellPixelSize:this.cellPixelSize,channels:this.channels,subscribesTasks:this.subscribesTasks,expectForeignHome:this.expectForeignHome,onPeerIdentity:this.onPeerIdentity},this.signals)}connectionStateSignal(){return this.connectionStateAcc}staleInstallSignal(){return this.staleInstallAcc}async manualReconnect(){this.client.forceDisconnect(),await this.reconnectLoop(!0)}dispose(){this.client.close()}tasksSignal=()=>this.tasksAcc;activeTaskSignal=()=>this.activeTaskAcc;updateSignal=()=>this.updateAcc;daemonVersionSignal=()=>this.daemonVersionAcc;daemonStaleSignal=()=>this.daemonStaleAcc;daemonRestartingSignal=()=>this.daemonRestartingAcc;async restartDaemon(){await this.client.request("daemon.stop",{reason:"restart"}).catch(()=>{})}engineStateSignal=()=>this.engineStateAcc;engineTabStatesSignal=()=>this.engineTabStateAcc;attentionInboxSignal=()=>this.attentionInboxAcc;taskJobsSignal=()=>this.taskJobsAcc;rowTokensSignal=()=>this.rowTokensAcc;worktreeChangesSignal=()=>this.worktreeChangesAcc;usageSnapshotSignal=()=>this.usageSnapshotAcc;contextUsageSignal=()=>this.contextUsageAcc;transcriptActivitySignal=()=>this.transcriptActivityAcc;transcriptActivityStore=()=>this.transcriptActivityAcc;noticeStore=()=>this.noticeAcc;tabOpenStore=()=>this.tabOpenAcc;tabCloseStore=()=>this.tabCloseAcc;tabRenameStore=()=>this.tabRenameAcc;rpc={request:(name,payload)=>this.client.request(name,payload)};replyTerminalTabClose=(requestId,closed)=>replyTabCloseOp(this.client,requestId,closed);uiPromptStore=()=>this.uiPromptAcc;replyPrompt=(promptId,value)=>replyPromptOp(this.client,promptId,value);engineLifecycleSignal=()=>this.engineLifecycleAcc;recentTaskEvents(id){return recentTaskEventsOp(this.client,id)}reportUiEvent=(kind,taskId,detail)=>reportUiEventOp(this.client,kind,taskId,detail);reportScreenInput=async(taskId,tabId,blocked)=>{await this.client.request("engine.reportEvent",{source:"screen",taskId,tabId,blocked})};reportEngineInterrupt=(taskId,tabId)=>reportEngineInterruptOp(this.client,String(taskId),tabId);uiPrefsSignal=()=>this.uiPrefsAcc;uiPrefsStore=()=>this.uiPrefsAcc;keybindingsRevSignal=()=>this.keybindingsRevAcc;keybindingsRevStore=()=>this.keybindingsRevAcc;listTasks=()=>this.tasksAcc();getTask=(id)=>this.tasksAcc().find((task)=>task.id===id);subscribeTasks(listener){return subscribeTasksOp(this.tasksAcc,listener)}createTask=(input)=>createTaskOp(this.client,input);ensureMainTask=(repo)=>ensureMainTaskOp(this.client,repo);openDirectoryTask=(input)=>openDirectoryTaskOp(this.client,input);adoptScratchRepo=(id,repo)=>adoptScratchRepoOp(this.client,id,repo);ensureWorktree=(id)=>ensureWorktreeOp(this.client,id);forgetProject=(repo)=>forgetProjectOp(this.client,repo);setTitle=(id,title)=>setTitleOp(this.client,id,title);setBranch=(id,branch)=>setBranchOp(this.client,id,branch);setVendor=(id,vendor,effort,model)=>setVendorOp(this.client,id,vendor,effort,model);setCommand=(id,command,vendor)=>setCommandOp(this.client,id,command,vendor);setPinned=(id,pinned)=>setPinnedOp(this.client,id,pinned);moveTask=(id,delta)=>moveTaskOp(this.client,id,delta);moveTaskToTop=(id)=>moveTaskToTopOp(this.client,id);setStatus=(id,status)=>setStatusOp(this.client,id,status);setPrompt=(id,prompt)=>setPromptOp(this.client,id,prompt);deleteTask=(id,opts)=>deleteTaskOp(this.client,id,opts);dismissAttention=(taskId,tabId,at)=>dismissAttentionOp(this.client,taskId,tabId,at);dismissRoutineAttention=(automationId)=>dismissRoutineAttentionOp(this.client,automationId);markAttentionRead=(taskId,tabId,at)=>markAttentionReadOp(this.client,taskId,tabId,at);landPreflight(id){return landPreflightOp(this.client,id)}landTask(id,opts){return landTaskOp(this.client,id,opts)}discoverAdoptableWorktrees(repo){return discoverAdoptableWorktreesOp(this.client,repo)}adoptWorktree(input){return adoptWorktreeOp(this.client,input)}listWorktrees(opts){return listWorktreesOp(this.client,opts)}listIssues(repoRoot){return listIssuesOp(this.client,repoRoot)}listIssueRepos(){return listIssueReposOp(this.client)}mutateIssue(repoRoot,op){return mutateIssueOp(this.client,repoRoot,op)}listAutomations=()=>listAutomationsOp(this.client);createAutomation=(i)=>createAutomationOp(this.client,i);automationRuns=(id)=>automationRunsOp(this.client,id);setAutomationEnabled=(id,on)=>setAutomationEnabledOp(this.client,id,on);runAutomationNow=(id)=>runAutomationNowOp(this.client,id);deleteAutomation=(id)=>deleteAutomationOp(this.client,id);listWorkItems=(a)=>listWorkItemsOp(this.client,a);listFieldNotes=(repo)=>listFieldNotesOp(this.client,repo);deleteFieldNote=(repo,id)=>deleteFieldNoteOp(this.client,repo,id);failingChecks=(taskId)=>failingChecksOp(this.client,taskId);syncBase=(taskId)=>syncBaseOp(this.client,taskId);startWorkItem=(a)=>startWorkItemOp(this.client,a);removeWorktree(path,force){return removeWorktreeOp(this.client,path,force)}setActiveTask(id){return setActiveTaskOp(this.client,id)}}
3
- export{liveRowTokens,RemoteOrchestrator};
@@ -1,3 +0,0 @@
1
- // @bun
2
- import{activityHookAdapters}from"./chunk-ctmbj2y3.js";import{t}from"./chunk-wvwkmj3t.js";import{readFileSync}from"fs";function hookConfigIssues(){let issues=[];for(let adapter of activityHookAdapters()){if(!adapter.hookConfigRefusal)continue;let file=adapter.globalSettingsPath();if(!file)continue;let raw;try{raw=readFileSync(file,"utf8")}catch{continue}let reason=adapter.hookConfigRefusal(raw);if(reason)issues.push({file,reason})}return issues}function formatBytes(n){if(n<1024)return`${n} B`;let units=["KB","MB","GB","TB"],v=n/1024,i=0;while(v>=1023.5&&i<units.length-1)v/=1024,i++;let oneDecimal=v.toFixed(1);return`${Number(oneDecimal)>=100?Math.round(v):oneDecimal} ${units[i]}`}import{createInterface}from"readline";function daemonRestartFix(cliName,reason){return{kind:"run",id:"daemon-restart",label:t(`doctor.fix.${reason}`),command:[cliName,"daemon","restart"],why:t("doctor.fix.daemonRestartWhy")}}function skillInstallFix(installCommand,stale){return{kind:"run",id:"skill-install",label:t(stale?"doctor.fix.skillStale":"doctor.fix.skillMissing"),command:installCommand.split(" "),why:t("doctor.fix.skillInstallWhy")}}function resetManualFix(cliName,reason){return{kind:"manual",id:`reset:${reason}`,label:t(`doctor.fix.${reason}`),action:`${cliName} reset`,why:t("doctor.fix.resetWhy")}}function killOrphansManualFix(cliName,count){return{kind:"manual",id:"kill-orphans",label:t("doctor.fix.orphans",{count}),action:`${cliName} doctor --kill-orphans`,why:t("doctor.fix.orphansWhy")}}function engineTabsManualFix(){return{kind:"manual",id:"engine-tabs",label:t("doctor.fix.engineTabs"),action:t("doctor.fix.engineTabsAction"),why:t("doctor.fix.engineTabsWhy")}}function reinstallManualFix(){return{kind:"manual",id:"reinstall",label:t("doctor.fix.staleInstall"),action:t("doctor.fix.staleInstallAction"),why:t("doctor.fix.staleInstallWhy")}}function spawnHelperFix(paths){return{kind:"run",id:"spawn-helper-chmod",label:t("doctor.fix.spawnHelper"),command:["chmod","755",...paths],why:t("doctor.fix.spawnHelperWhy")}}function humanOnlyFix(reason,vars){return{kind:"manual",id:reason,label:t(`doctor.fix.${reason}`),action:t(`doctor.fix.${reason}Action`,vars),why:t("doctor.fix.humanOnlyWhy")}}function noEngineFix(signedOut){return signedOut.length>0?humanOnlyFix("noEngineLogin",{list:signedOut.join(", ")}):humanOnlyFix("noEngine")}function dedupeFixes(fixes){let seen=new Set;return fixes.filter((fix)=>{if(seen.has(fix.id))return!1;return seen.add(fix.id),!0})}async function applyFixes(collected,rt){let fixes=dedupeFixes(collected);if(fixes.length===0){rt.out(""),rt.out(t("doctor.fix.none"));return}let runnable=fixes.filter((fix)=>fix.kind==="run");if(runnable.length>0){rt.out(""),rt.out(t("doctor.fix.header"));for(let fix of runnable){if(rt.out(` ${fix.label}`),rt.out(` ${t("doctor.fix.willRun",{command:fix.command.join(" ")})}`),rt.out(` ${fix.why}`),!rt.interactive)continue;if(!await rt.confirm(` ${t("doctor.fix.confirmPrompt")}`)){rt.out(` ${t("doctor.fix.skipped")}`);continue}let code=await rt.exec(fix.command);rt.out(code===0?` ${t("doctor.fix.done")}`:` ${t("doctor.fix.failed",{code})}`)}if(!rt.interactive)rt.out(` ${t("doctor.fix.nonInteractive")}`)}let manual=fixes.filter((fix)=>fix.kind==="manual");if(manual.length>0){rt.out(""),rt.out(t("doctor.fix.manualHeader"));for(let fix of manual)rt.out(` ${fix.label}`),rt.out(` \u2192 ${fix.action}`),rt.out(` ${fix.why}`)}}async function confirmTty(question){let readline=createInterface({input:process.stdin,output:process.stdout});try{let answer=await new Promise((resolve)=>readline.question(question,resolve));return answer.trim().toLowerCase()==="y"||answer.trim().toLowerCase()==="yes"}finally{readline.close()}}async function execInherited(command){try{return await Bun.spawn([...command],{stdin:"inherit",stdout:"inherit",stderr:"inherit"}).exited}catch{return 127}}function defaultFixRuntime(){return{confirm:confirmTty,exec:execInherited,out:(line)=>console.log(line),interactive:process.stdin.isTTY===!0}}
3
- export{hookConfigIssues,formatBytes,daemonRestartFix,skillInstallFix,resetManualFix,killOrphansManualFix,engineTabsManualFix,reinstallManualFix,spawnHelperFix,humanOnlyFix,noEngineFix,applyFixes,defaultFixRuntime};
@@ -1,9 +0,0 @@
1
- // @bun
2
- import{classifyTier,describeTierBlock,isAutoRoutingTier,readAutoRoutingTable,readClassifierConfig,screenErrorLine,taskGroupOf,tierBlock}from"./chunk-466djpa8.js";import{ensurePluginEnginesLoaded,registeredEngineIds}from"./chunk-83e13qdq.js";import{resolveActiveTaskId}from"./chunk-hr2xky15.js";import{detectEngineStatus}from"./chunk-pa6teq2c.js";import{DEFAULT_FEEDBACK_CATEGORY_SLUG,submitFeedback}from"./chunk-vrzb7s42.js";import{ENGINE_ACTIVITY_KINDS,TASK_ACTIVITY_STATES}from"./chunk-17e2tgyg.js";import{deriveTitleFromPrompt}from"./chunk-cbc1h345.js";import{DIRTY_WORKTREE_CODE,EMPTY_BRANCH_DIRTY_WORKTREE_CODE,canonicalize}from"./chunk-4xns5j33.js";import{ISSUE_STATUSES}from"./chunk-4hestsjp.js";import{ROW_TOKEN_MAX_TEXT,ROW_TOKEN_TONES}from"./chunk-atn06ncf.js";import{ulid}from"./chunk-qkehg2c1.js";import{ENGINE_NOT_OBSERVED_REASON,ENGINE_START_POLL_MS,ENGINE_START_PROBE_MS,closeTabsSnapshot,deliverHostedPrompt,deliverToKey,ensurePtyHost,findEngineKey,joinTaskTabs,killTaskSessions,listSessions,listSessionsOrNull,markCliTabSession,mintCliTab,openAdmittedEngine,openPtyHost,outcomeFields,publishCliTabSnapshot,readTabsSnapshot,renameTabsSnapshot,restoredTabLaunch,taskEngineArgv,taskKeys,unregisteredTabIds}from"./chunk-m9q1whep.js";import{tabPtyKeyFor}from"./chunk-t64hwtzt.js";import{awaitEngineProcess,buildEngineSessionLaunch,enginePresence,findHostedEngineKey,hostedSessionFailureLine,isHostedTaskKey,listHostedSessions,openHostedSessionHost,readPersistedTerminalDefaultColors,sessionArgvNamesEngine,trustEngineWorktree}from"./chunk-e5k2j2x8.js";import{GENERIC_PROTOCOL,describePreset,engineLaunchArgv,listEnginePresets,protocolEntry,resolveCommandProtocol,roveApiInvocation,sessionProtocol,withPinnedSessionId}from"./chunk-4r3s1z14.js";import{resolveLoginShell}from"./chunk-vjvkkrzc.js";import{foregroundEngineIn,parsePsSnapshot,psSnapshot}from"./chunk-wam8sr6a.js";import{engineEntry,getCapabilities,supportsStructuredHistory,vendorsWithEffortLevels,vendorsWithTurnReader}from"./chunk-ctmbj2y3.js";import{CONTRIB_ENGINE_IDS,installedEngineIds}from"./chunk-k4374n9t.js";import{terminalRows}from"./chunk-etfh8f4z.js";import{readOnlyGitProcessEnv}from"./chunk-6xppe9bj.js";import{ApiError,VERB_GROUP_IDS,helpStep,splitDaemonCode}from"./chunk-f7zjggfv.js";import{parseBoolLiteral}from"./chunk-zsfyj36g.js";import{expandTilde}from"./chunk-ag3txfg2.js";import{errorMessage}from"./chunk-2brn8e1y.js";import{DEFAULT_TASK_VENDOR}from"./chunk-xy8be792.js";import{ALL_VENDORS,DEFAULT_VENDOR,coerceVendorId}from"./chunk-h2m7em1e.js";import{CURRENT_VERSION}from"./chunk-8p579jct.js";import{isRemoteRepoKey}from"./chunk-h9cg0j3a.js";import{loadStateFile}from"./chunk-7q43tysm.js";import{homeDir}from"./chunk-prqeddy7.js";import{ROVE_PRODUCT_NAME}from"./chunk-zt7wfnva.js";import{ROUTINE_RESPONSE_MAX_CHARS}from"./chunk-adkva3ep.js";import{pathWithin,samePath}from"./chunk-pn5ygqn6.js";import{__require}from"./chunk-wqfp8d9w.js";async function listAllEnginePresets(){let presets=await enginePresetsInList(),byProtocol=new Map,modelsOf=(protocol)=>{let pending=byProtocol.get(protocol);if(!pending){let list=engineEntry(protocol).listModels;pending=list?list().catch(()=>null):Promise.resolve(null),byProtocol.set(protocol,pending)}return pending};return Promise.all(presets.map(async(preset)=>({...preset,models:await modelsOf(preset.protocol)})))}async function enginePresetsInList(){ensurePluginEnginesLoaded();let presets=[...listEnginePresets()],seen=new Set(presets.map((p)=>p.id));for(let id of await installedEngineIds())if(!seen.has(id))presets.push(describePreset(id));return presets}async function engineListIds(){return(await enginePresetsInList()).map((p)=>p.id)}var ENGINE_LIST_VERB={name:"engine-list",group:"discover",summary:`List every engine Rove can launch \u2014 built-ins, registered presets, the shipped contrib engines whose CLI is on PATH (${CONTRIB_ENGINE_IDS.join(", ")}), and engines contributed by enabled plugins \u2014 each with its RAW launch command, exactly as it runs. Copy one into \`add --command\` / \`send --tab new --command\` verbatim, or edit its flags first. \`protocol\` is the adapter Rove speaks to it (history, trust, delivery); \`generic\` = none, which still runs fine but loses transcript reads. \`models\` is what the engine can name for \`--model\` (suggestions, not a closed set); null = Rove cannot list them for this engine. Returns { engines }.`,flags:[],offline:!0,handler:async()=>({engines:await listAllEnginePresets()})};function taskEngine(task){let command=task.command?.trim();if(command){let resolved=resolveCommandProtocol(command);if(resolved!==GENERIC_PROTOCOL)return resolved}return sessionProtocol(coerceVendorId(task.vendor))}function assertEngineAcceptsEffort(engine,level,recover){let levels=engineEntry(engine).effortLevels??[];if(levels.length===0)throw new ApiError(`engine ${engine} declares no reasoning effort levels`,"BAD_EFFORT",{engine,hint:`Only engines with declared reasoning levels accept one (${vendorsWithEffortLevels().join(", ")} today). Check the task's engine with \`get-task\`.`,nextCommandArgs:[...recover]});if(!levels.includes(level))throw new ApiError(`engine ${engine} does not accept effort level ${JSON.stringify(level)} \u2014 it declares ${levels.join(", ")}`,"BAD_EFFORT",{engine,levels,hint:`Pass one of: ${levels.join(", ")}.`,nextCommandArgs:[...recover]})}function assertEngineAcceptsModel(engine,model,recover){if(engineEntry(engine).modelArgv)return;throw new ApiError(`engine ${engine} declares no model flag \u2014 it does not accept a model`,"BAD_MODEL",{engine,hint:"Only engines that declare a model flag accept one (claude, codex, kimi, pi, omp today); `engine-list` shows `models` per engine.",nextCommandArgs:[...recover]})}function vendorToRecord(task,engine){let recorded=coerceVendorId(task.vendor);return sessionProtocol(recorded)===engine?recorded:engine}var API_SCHEMA_VERSION=2,GLOBAL_FLAGS=[{name:"pretty",type:"bool",description:"Pretty-print stdout JSON."},{name:"help",type:"bool",description:"Show usage for the verb and exit."}];function displayValues(f){if(!f.values)return;if(f.name!=="vendor")return f.values;let registered=registeredEngineIds().filter((id)=>!f.values.includes(id));return registered.length>0?[...f.values,...registered]:f.values}function flagJson(f){let values=displayValues(f);return{name:f.name,type:f.type,required:f.required??!1,...values?{values}:{},...f.default!==void 0?{default:f.default}:{},...f.placeholder?{placeholder:f.placeholder}:{},description:f.description}}function verbSchema(v){return{name:v.name,group:v.group,summary:v.summary,offline:v.offline??!1,flags:v.flags.map(flagJson)}}function schemaIndex(){let cliName=ROVE_PRODUCT_NAME;return{apiVersion:API_SCHEMA_VERSION,roveVersion:CURRENT_VERSION,hint:`Compact index. Drill into ONE verb: \`${cliName} api schema --verb <name>\` (or \`${cliName} api <verb> --help\`). One group: \`--group <g>\`. Whole spec: \`--all\`.`,groups:VERB_GROUPS,verbs:VERBS.map((v)=>({name:v.name,group:v.group,summary:v.summary})),globalFlags:GLOBAL_FLAGS,aliases:VERB_ALIASES}}function groupSchema(group){let verbs=VERBS.filter((v)=>v.group===group);if(verbs.length===0)throw new ApiError(`unknown group: ${group}. Groups: ${Object.keys(VERB_GROUPS).join(", ")}`,"BAD_VERB",{hint:"that group does not exist \u2014 the compact index lists every group and the verbs in it",nextCommandArgs:["api","schema"]});return{group,verbs:verbs.map((v)=>({name:v.name,summary:v.summary}))}}function fullSchema(){return{apiVersion:API_SCHEMA_VERSION,roveVersion:CURRENT_VERSION,output:{success:"one JSON object on stdout, newline-terminated, exit 0",error:'{"error":{"message","code"}} on stderr, exit != 0',pretty:"--pretty indents stdout JSON"},globalFlags:GLOBAL_FLAGS,aliases:VERB_ALIASES,groups:VERB_GROUPS,verbs:VERBS.map(verbSchema)}}function flagSignature(verb){return verb.flags.map((f)=>{let meta=f.type==="enum"&&f.values?displayValues(f).join("|"):f.placeholder??(f.type==="bool"?"":"X"),core=meta?`--${f.name} ${meta}`:`--${f.name}`;return f.required?core:`[${core}]`}).join(" ")}function verbHelp(verb){let lines=[`${ROVE_PRODUCT_NAME} api ${verb.name} ${flagSignature(verb)}`.trimEnd(),"",verb.summary,""],alias=Object.entries(VERB_ALIASES).find(([,canon])=>canon===verb.name)?.[0];if(alias)lines.push(`Alias: ${alias}`,"");if(verb.flags.length>0){lines.push("Flags:");for(let f of verb.flags){let req=f.required?" (required)":"",def=f.default!==void 0?` [default: ${f.default}]`:"",vals=f.type==="enum"&&f.values?` {${displayValues(f).join("|")}}`:"";lines.push(` --${f.name}${vals}${req}${def} ${f.description}`)}lines.push("")}return lines.push("Global: [--pretty] [--help]"),lines.join(`
3
- `)}function apiUsage(){let cliName=ROVE_PRODUCT_NAME,rows=VERBS.map((v)=>` ${v.name.padEnd(18)} ${v.summary}`);return[`usage: ${cliName} api <verb> [flags] [--pretty] [--help]`,"",`Explore the full surface (names, flags, types) with: ${cliName} api schema`,"","verbs:",...rows,"","Output is one JSON object on stdout (exit 0); errors are JSON on stderr (exit != 0)."].join(`
4
- `)}import{readFileSync}from"fs";import{resolve}from"path";var FANOUT_CAP=10;function parsePositiveInt(raw){if(!/^\d+$/.test(raw.trim()))return;let n=Number.parseInt(raw,10);return Number.isSafeInteger(n)&&n>0?n:void 0}function parseNonNegativeInt(raw){if(!/^\d+$/.test(raw.trim()))return;let n=Number.parseInt(raw,10);return Number.isSafeInteger(n)&&n>=0?n:void 0}var FANOUT_STEP=helpStep("add"),F={repo:(required=!0)=>({name:"repo",type:"string",required,placeholder:"PATH",description:"Repo root (git toplevel). Relative paths resolve against $PWD."}),taskId:(required=!0)=>({name:"task-id",type:"string",required,placeholder:"ID",description:"Target task id (from `list` / `add`)."}),vendor:()=>({name:"vendor",type:"enum",values:ALL_VENDORS,placeholder:"V",description:"Engine vendor for the task."}),command:()=>({name:"command",type:"string",placeholder:"CMD",description:"Engine launch command, verbatim \u2014 an engine id from `engine-list` (e.g. claude) or a full command line (e.g. 'codex --search'). Unvalidated: probe an unfamiliar engine's flags with `<cmd> --help` first. Omitted = the repo's default engine."}),title:()=>({name:"title",type:"string",placeholder:"T",description:"Human task title."}),prompt:(required,desc)=>({name:"prompt",type:"string",required,placeholder:"TEXT",description:required?`${desc} Required unless --prompt-file is given.`:desc}),promptFile:()=>({name:"prompt-file",type:"string",placeholder:"PATH",description:"Read the prompt from this file instead of --prompt (`-` = stdin). Use it whenever the text has backticks, $vars, or quotes you don't want the shell to touch. Exactly one of --prompt / --prompt-file."})};function parseFlags(argv,booleanFlags=new Set){let flags=new Map,pretty=!1,help=!1;for(let i=0;i<argv.length;i++){let arg=argv[i];if(!arg.startsWith("--")&&arg!=="-h")throw new ApiError(`unexpected positional arg: ${arg}`,"BAD_FLAG");if(arg==="-h"){help=!0;continue}let eq=arg.indexOf("=");if(eq!==-1){let key2=arg.slice(2,eq),value=arg.slice(eq+1);if(key2==="pretty")pretty=value!=="false"&&value!=="0";else if(key2==="help")help=value!=="false"&&value!=="0";else flags.set(key2,value);continue}let key=arg.slice(2);if(key==="pretty"){pretty=!0;continue}if(key==="help"){help=!0;continue}if(booleanFlags.has(key)){let next2=argv[i+1];if(next2!==void 0&&parseBoolLiteral(next2)!==void 0)flags.set(key,next2),i+=1;else flags.set(key,"true");continue}let next=argv[i+1];if(next===void 0||next.startsWith("--"))throw new ApiError(`flag --${key} requires a value`,"BAD_FLAG");flags.set(key,next),i+=1}return{flags,pretty,help}}function validateAgainstSpec(verb,flags){let known=new Set(verb.flags.map((f)=>f.name));for(let key of flags.keys())if(!known.has(key))throw new ApiError(`unknown flag --${key} for "${verb.name}"`,"BAD_FLAG",helpStep(verb.name));for(let f of verb.flags){let satisfied=flags.get(f.name)||f.name==="prompt"&&flags.get("prompt-file");if(f.required&&!satisfied)throw new ApiError(`--${f.name} is required for "${verb.name}"`,"MISSING_FLAG",helpStep(verb.name));if(f.type==="enum"&&f.values){let raw=flags.get(f.name);if(raw!==void 0&&!f.values.includes(raw)){if(f.name==="vendor"&&registeredEngineIds().includes(raw))continue;throw new ApiError(`--${f.name} must be one of ${f.values.join(", ")}`,"BAD_FLAG",helpStep(verb.name))}}if(f.type==="int"){let raw=flags.get(f.name);if(raw!==void 0&&parsePositiveInt(raw)===void 0)throw new ApiError(`--${f.name} must be a positive integer`,"BAD_FLAG")}if(f.type==="uint"){let raw=flags.get(f.name);if(raw!==void 0&&parseNonNegativeInt(raw)===void 0)throw new ApiError(`--${f.name} must be a non-negative integer`,"BAD_FLAG")}}}class VerbArgs{verb;flags;constructor(verb,flags){this.verb=verb;this.flags=flags}spec(name){let f=this.verb.flags.find((s)=>s.name===name);if(!f)throw Error(`internal: --${name} is not declared on verb "${this.verb.name}"`);return f}str(name){this.spec(name);let v=this.flags.get(name);return v&&v.length>0?v:void 0}present(name){return this.spec(name),this.flags.get(name)!==void 0}promptText(){if(this.promptMemo!==void 0)return this.promptMemo.value;let value=this.readPromptText();return this.promptMemo={value},value}promptMemo;readPromptText(){let inline=this.verb.flags.some((f)=>f.name==="prompt")?this.str("prompt"):void 0,file=this.str("prompt-file");if(inline!==void 0&&file!==void 0)throw new ApiError("pass --prompt or --prompt-file, not both","BAD_FLAG",helpStep(this.verb.name));if(file===void 0)return inline;let text=readFileSync(file==="-"?0:resolve(process.cwd(),expandTilde(file)),"utf8");if(text.trim().length===0)throw new ApiError(`--prompt-file ${file} is empty`,"BAD_FLAG");return text}require(name){let v=this.str(name);if(v===void 0)throw new ApiError(`--${name} is required`,"MISSING_FLAG");return v}enumOf(name){let f=this.spec(name),v=this.str(name);if(v===void 0)return;if(f.values&&!f.values.includes(v))throw new ApiError(`--${name} must be one of ${f.values.join(", ")}`,"BAD_FLAG",helpStep(this.verb.name));return v}requireEnum(name){return this.require(name),this.enumOf(name)}vendor(){let value=this.str("vendor");if(value===void 0)return;let builtins=this.spec("vendor").values??ALL_VENDORS;if(builtins.includes(value))return value;if(registeredEngineIds().includes(value))return value;throw new ApiError(`--vendor must be a built-in (${builtins.join(", ")}) or a registered engine id \u2014 see \`engine-list\``,"BAD_FLAG",helpStep(this.verb.name))}bool(name){this.spec(name);let raw=this.str(name);if(raw===void 0)return;let value=parseBoolLiteral(raw);if(value===void 0)throw new ApiError(`--${name} must be a boolean (true/false)`,"BAD_FLAG");return value}int(name){this.spec(name);let raw=this.str(name);if(raw===void 0)return;let n=parsePositiveInt(raw);if(n===void 0)throw new ApiError(`--${name} must be a positive integer`,"BAD_FLAG");return n}nonNegativeInt(name){this.spec(name);let raw=this.str(name);if(raw===void 0)return;let n=parseNonNegativeInt(raw);if(n===void 0)throw new ApiError(`--${name} must be a non-negative integer`,"BAD_FLAG");return n}path(name){let v=this.str(name);return v===void 0?void 0:resolve(process.cwd(),expandTilde(v))}requirePath(name){return resolve(process.cwd(),expandTilde(this.require(name)))}requireRepo(name){let raw=this.require(name);return isRemoteRepoKey(raw)?raw:resolve(process.cwd(),expandTilde(raw))}}function parseAgentsSpec(spec){let out=[];for(let part of spec.split(",")){let trimmed=part.trim();if(!trimmed)continue;let colon=trimmed.indexOf(":");if(colon===-1)throw new ApiError(`--agents entry "${trimmed}" must be engine:count`,"BAD_FLAG",FANOUT_STEP);let vendor=trimmed.slice(0,colon);if(!ALL_VENDORS.includes(vendor)&&!registeredEngineIds().includes(vendor))throw new ApiError(`--agents engine "${vendor}" must be a built-in (${ALL_VENDORS.join(", ")}) or a registered engine id \u2014 see \`engine-list\``,"BAD_FLAG",FANOUT_STEP);let count=parsePositiveInt(trimmed.slice(colon+1));if(count===void 0)throw new ApiError(`--agents count for "${vendor}" must be a positive integer`,"BAD_FLAG",FANOUT_STEP);if(out.length+count>FANOUT_CAP)throw new ApiError(`--agents requests ${out.length+count} agents, exceeds the cap of ${FANOUT_CAP}`,"BAD_FLAG",FANOUT_STEP);for(let i=0;i<count;i++)out.push(vendor)}if(out.length===0)throw new ApiError('--agents specified no agents (e.g. "claude:2,codex:1")',"BAD_FLAG",FANOUT_STEP);return out}function buildCountPlan(count,vendor){if(count>FANOUT_CAP)throw new ApiError(`--count ${count} exceeds the parallel cap of ${FANOUT_CAP} \u2014 spawn in batches`,"BAD_FLAG",FANOUT_STEP);return Array(count).fill(vendor)}function daemonOf(ctx){if(!ctx.client)throw new ApiError("daemon required","BAD_DAEMON");return ctx.client}async function repoFilter(runtime,repoFlag,repos){let target=await runtime.resolveRepoRoot(repoFlag);if(!await runtime.isUsableRepo(target))throw new ApiError(`--repo ${repoFlag} is not a readable git repository (resolved to ${target}) \u2014 the project may have moved or been deleted; \`rove api list\` still shows its tasks`,"REPO_UNRESOLVABLE");let resolved=new Map,unresolvable=new Set;for(let repo of new Set(repos)){let root=await runtime.resolveRepoRoot(repo);if(resolved.set(repo,root),!samePath(root,target)&&!await runtime.isUsableRepo(root))unresolvable.add(repo)}return{target,matches:(repo)=>samePath(resolved.get(repo),target),unresolvableRepos:[...unresolvable].sort()}}async function simpleRpc(ctx,name,payload){return daemonOf(ctx).request(name,payload)}async function handlePtyList(){let[{RoveDaemonClient},{defaultPtyHostSocketPath}]=await Promise.all([import("./chunk-zeb0sttx.js"),import("./chunk-pvm3fc44.js")]),client=new RoveDaemonClient(defaultPtyHostSocketPath());try{return await client.connect(),await client.request("pty.list",{})}catch{return{sessions:null}}finally{client.close()}}var DEFAULT_PAGE_MESSAGES=40,MAX_PAGE_MESSAGES=50,PAGE_BYTE_BUDGET=131072,STRING_CLIP_CHARS=16384,DEFAULT_TAIL_LINES=40,TERMINAL_TAIL_LINES=200,TERMINAL_TAIL_BYTES=65536;function encodeCursor(cursor){return Buffer.from(JSON.stringify(cursor),"utf8").toString("base64url")}function decodeCursor(raw,taskId){let parsed;try{parsed=JSON.parse(Buffer.from(raw,"base64url").toString("utf8"))}catch{throw new ApiError("invalid cursor (not a read-output cursor)","CURSOR_INVALID")}let c=parsed;if(!(c!==null&&typeof c==="object"&&c.v===1&&typeof c.task==="string"&&(c.tab===void 0||typeof c.tab==="string")&&(c.src==="history"&&typeof c.sid==="string"&&typeof c.idx==="number"||c.src==="terminal"&&typeof c.off==="number")))throw new ApiError("invalid cursor (unknown version or shape)","CURSOR_INVALID");if(c.task!==taskId)throw new ApiError(`cursor belongs to task ${c.task}, not ${taskId}`,"CURSOR_TASK_MISMATCH");return c}function clipStrings(value){if(typeof value==="string"){if(value.length<=STRING_CLIP_CHARS)return value;let points=[...value];if(points.length<=STRING_CLIP_CHARS)return value;return`${points.slice(0,STRING_CLIP_CHARS).join("")}\u2026[+${points.length-STRING_CLIP_CHARS} chars clipped]`}if(Array.isArray(value))return value.map(clipStrings);if(value!==null&&typeof value==="object"){let out={};for(let[k,v]of Object.entries(value))out[k]=clipStrings(v);return out}return value}function buildHistoryPage(messages,at,limit){let forward="from"in at,page=[],bytes=0,limited=!1,i=forward?at.from:at.before-1;for(;forward?i<messages.length:i>=0;forward?i++:i--){if(page.length>=limit)break;let clipped=clipStrings(messages[i]),size=JSON.stringify(clipped).length;if(page.length>0&&bytes+size>PAGE_BYTE_BUDGET){limited=!0;break}page.push(clipped),bytes+=size}if(forward)return{page,start:at.from,end:i,limited};return page.reverse(),{page,start:i+1,end:at.before,limited}}function terminalLines(text){return terminalRows(text)}function boundedTail(text,maxLines=TERMINAL_TAIL_LINES){let lines=terminalLines(text),start=Math.max(0,lines.length-Math.min(maxLines,TERMINAL_TAIL_LINES)),bytes=0;for(let i=lines.length-1;i>=start;i--)if(bytes+=(lines[i]?.length??0)+1,bytes>TERMINAL_TAIL_BYTES&&i<lines.length-1){start=i+1;break}return{tail:lines.slice(start),truncated:start>0}}async function peekTaskTerminal(taskId,engineBin,tab,sinceOffset){let host=await openPtyHost();if(!host)return"host-unreachable";try{let key;if(tab)key=`${taskId}::${tab}`;else{let sessions=await listSessionsOrNull(host.rpc);if(sessions===null)return"host-unreachable";key=findEngineKey(sessions,taskId,engineBin)??sessions.find((s)=>s.key===`${taskId}::tab-1`)?.key}if(!key)return null;let res;try{res=await host.rpc.request("pty.peek",{key,sinceOffset})}catch{return"host-unreachable"}if(!res.exists){if(tab)throw new ApiError(`tab ${tab} has no hosted session on task ${taskId} \u2014 see \`rove api pty-list\` for live tabs`,"TAB_NOT_FOUND");return null}return{pid:res.pid,offset:res.offset,text:Buffer.from(res.data,"base64").toString("utf8"),sinceValid:res.sinceValid,live:res.alive,exit:res.exit??null}}catch(err){if(err instanceof ApiError)throw err;return null}finally{host.close()}}async function engineScreenError(task,opts={}){let pattern=protocolEntry(task.vendor).errorLine;if(!pattern)return;try{let page=await peekTaskTerminal(task.id,taskEngineArgv(task)[0],void 0);if(!page||page==="host-unreachable")return;return screenErrorLine(pattern,boundedTail(page.text).tail,opts)}catch{return}}var ACTIVITY_STATES_DOC="`.activity.state` is one of: `idle` (at rest), `running` (a turn is in progress), `turn_complete` (a turn finished \u2014 also at rest; treat it like idle when deciding whether the engine is free), `permission_needed` (blocked on an approval prompt), `rate_limited` (clears on its own), `error` (the last turn failed; `.activity.detail.note` carries the engine's text, and `.activity.source: \"screen\"` means it was read off the engine's own screen because the engine reported nothing), `dead` (the engine process exited; the tab's exit record says how).",AT_REST=new Set(["idle","turn_complete"]);async function readActivityRegistry(daemon){try{let dbg=await daemon.request("debug.inspect");return{tasks:dbg?.activity?.tasks??{},tabs:dbg?.activity?.tabs??{}}}catch{return null}}async function activityView(registry,task,tab){let entry=tab?registry?.tabs[task.id]?.[tab]:registry?.tasks[task.id];if(!entry)return null;let base={state:entry.state,at:new Date(entry.at).toISOString(),forMs:Math.max(0,Date.now()-entry.at),...entry.detail?{detail:entry.detail}:{}},atRest=AT_REST.has(entry.state);if(tab||!(atRest||entry.state==="running"))return base;let note=await engineScreenError(task,{last:!atRest});return note?{...base,state:"error",detail:{failure:"other",note},source:"screen"}:base}var selfSessionOnce,identityWarning=null;async function realProbe(){let[{openPtyHost:openPtyHost2,listSessions:listSessions2},{psSnapshot:psSnapshot2}]=await Promise.all([import("./chunk-szx3wehf.js"),import("./chunk-2md5j9ws.js")]);return{sessions:async()=>{let host=await openPtyHost2();if(!host)return[];try{return await listSessions2(host.rpc)}finally{host.close()}},ps:psSnapshot2,pid:process.pid}}async function verifiedSelfSession(env=process.env,probe){if(!probe&&selfSessionOnce)return selfSessionOnce;let run=resolveSelfSession(env,probe);return selfSessionOnce=run,run}async function resolveSelfSession(env,probe){let taskId=env.ROVE_TASK_ID;if(!taskId)return null;let tabId=env.ROVE_TAB_ID||"tab-1";try{let p=probe??await realProbe(),key=`${taskId}::${tabId}`,session=(await p.sessions()).find((s)=>s.key===key&&s.alive);if(session?.pid){let{hasAncestor,parsePsSnapshot:parsePsSnapshot2}=await import("./chunk-2md5j9ws.js");if(hasAncestor(parsePsSnapshot2(await p.ps([session.pid,p.pid])),p.pid,session.pid))return identityWarning=null,{taskId,tabId}}}catch{}return identityWarning=`$ROVE_TASK_ID/$ROVE_TASK_ID names task ${taskId} ${tabId}, but this process is not running inside that tab (an inherited env, not an identity) \u2014 dispatcher/peer provenance omitted`,null}function takeIdentityWarning(){let warning=identityWarning;return identityWarning=null,warning}async function dispatcherEnvPayload(env=process.env,probe){let self=await verifiedSelfSession(env,probe);if(!self)return{};return{dispatcherTaskId:self.taskId,dispatcherTabId:self.tabId}}async function readOwnDispatcher(daemon){let self=await verifiedSelfSession();if(!self)return null;try{return(await daemon.request("task.get",{taskId:self.taskId})).task.dispatcher??null}catch{return null}}async function resolveDispatcherTab(runtime,dispatcher){let{tabs,running}=await runtime.taskTabs(dispatcher.taskId);if(tabs.some((t)=>t.id===dispatcher.tabId&&t.alive))return dispatcher.tabId;if(running)return;throw new ApiError(`dispatcher tab ${dispatcher.tabId} on task ${dispatcher.taskId} is dead and the task has no live engine tab \u2014 the reply has nowhere to land`,"DISPATCHER_UNREACHABLE",{dispatcher,hint:`address an alive target explicitly with --task-id/--tab (see \`${ROVE_PRODUCT_NAME} api pty-list\`), or notify the user with \`${ROVE_PRODUCT_NAME} api notify\``,nextCommandArgs:["api","pty-list"]})}async function withPeerProvenance(daemon,targetTaskId,prompt){let self=await verifiedSelfSession(),senderId=self?.taskId;if(!senderId||senderId===targetTaskId)return prompt;let label=senderId;try{let res=await daemon.request("task.get",{taskId:senderId});label=res.task.title||res.task.branch||senderId}catch{}let api=roveApiInvocation(),replyTarget=`--task-id ${senderId} --tab ${self.tabId}`;return`[ROVE PEER] from "${label}" (task ${senderId} \u2014 Rove agent skill /rove, read it once per session; reply only if it changes what I do next: \`${api} send ${replyTarget} --prompt "<text>"\`; verb reference: \`${api} schema\`)
5
-
6
- ${prompt}`}var TASK_STATUSES=["backlog","in_progress","in_review","done","canceled","error"];function csvOf(ctx,name,allowed,verb="list"){let raw=ctx.args.str(name);if(raw===void 0)return;let values=raw.split(",").map((s)=>s.trim()).filter(Boolean);if(values.find((v)=>!allowed.includes(v))!==void 0||values.length===0)throw new ApiError(`--${name} takes a comma list of ${allowed.join(", ")}`,"BAD_FLAG",helpStep(verb));return new Set(values)}function listFilters(ctx){let repo=ctx.args.path("repo"),statuses=csvOf(ctx,"status",TASK_STATUSES),activities=csvOf(ctx,"activity",TASK_ACTIVITY_STATES);if(!repo&&!statuses&&!activities)return null;return{repo,statuses,activities}}async function filterTaskList(ctx,daemon,tasks,filters){let isLocal=(t)=>!t.origin||t.origin.machineId==="local",repo=filters.repo?await repoFilter(ctx.runtime,filters.repo,tasks.filter(isLocal).map((t)=>t.repo)):null,kept=tasks.filter((t)=>(!repo||isLocal(t)&&repo.matches(t.repo))&&(!filters.statuses||filters.statuses.has(t.status))),out=kept;if(filters.activities){let wanted=filters.activities,registry=await readActivityRegistry(daemon),local=kept.filter(isLocal),views=await Promise.all(local.map((t)=>activityView(registry,t)));out=local.flatMap((task,i)=>{let activity=views[i];return activity&&wanted.has(activity.state)?[{...task,activity}]:[]})}return{tasks:out,...repo&&repo.unresolvableRepos.length>0?{unresolvableRepos:repo.unresolvableRepos}:{}}}var MAX_LIVE_ENGINES_KEY="engine.maxLiveSessions";function normalizeMaxLiveEngines(value){let n=typeof value==="number"?value:typeof value==="string"&&value.trim()!==""?Number(value.trim()):Number.NaN;return Number.isInteger(n)&&n>0?n:void 0}function persistedMaxLiveEngines(){return normalizeMaxLiveEngines(loadStateFile()[MAX_LIVE_ENGINES_KEY])}async function deliverToExactTab(rpc,taskId,tabId,cwd,prompt,opts){let key=`${taskId}::${tabId}`,{sessions=[]}=await rpc.request("pty.list",{}),session=sessions.find((s)=>s.key===key);if(!session?.alive){if(session?.restored===!0){if(!opts?.respawn)throw restoredTabError(taskId,tabId,prompt);let launch=opts.respawn(),open=await openAdmittedEngine(rpc,{key,cwd,...launch?{command:launch.command}:{},defaultColors:readPersistedTerminalDefaultColors()},{maxLiveEngines:opts.maxLiveEngines,snapshot:opts.snapshot,taskId,retryArgs:["api","send","--task-id",taskId,"--tab",tabId,"--respawn","--plain","--prompt",prompt]});if(open.respawned!==!0&&open.alive!==!0)throw new ApiError(`tab ${tabId} on task ${taskId} could not be respawned`,"SESSION_FAILED",{taskId,tabId,hint:"the frozen record is still on disk; open the task in the TUI to see the session's own output",nextCommandArgs:["api","read-output","--task-id",taskId,"--tab",tabId,"--source","terminal"]});let enginePid=await awaitEngineProcess(rpc,key,opts.engineBin,{timeoutMs:ENGINE_START_PROBE_MS,intervalMs:ENGINE_START_POLL_MS,snapshot:opts.snapshot});if(enginePid===null)return{session:key,pane:key,started:!1,respawned:!0,engineReady:!1,delivered:!1,reason:await hostedSessionFailureLine(rpc,key)??ENGINE_NOT_OBSERVED_REASON};return await deliverRespawned(rpc,key,prompt,enginePid.vendor)}throw new ApiError(`tab ${tabId} has no live session on task ${taskId} \u2014 see \`rove api pty-list\` for alive tabs`,"TAB_NOT_FOUND")}let presence=await enginePresence(session.pid,opts?.engineBin,opts?.snapshot);if(presence.kind==="unknown")throw new ApiError(`could not read the process table, so tab ${tabId} on task ${taskId} could not be checked for a live engine`,"ENGINE_PROBE_FAILED",{hint:"the `ps` probe failed or timed out; retry, or check the machine's process table",nextCommandArgs:["api","pty-list"]});if(presence.kind!=="engine")throw new ApiError(`tab ${tabId} on task ${taskId} has no live engine process \u2014 it is a plain shell right now`,"ENGINE_NOT_RUNNING",{hint:"spawn a fresh engine tab for this prompt with --tab new, or pick an engine tab from pty-list",nextCommandArgs:["api","pty-list"]});let outcome=await deliverToKey(rpc,key,prompt,{vendor:presence.vendor});return{session:key,pane:key,started:!1,...outcomeFields(outcome)}}async function deliverRespawned(rpc,key,prompt,vendor){let outcome=await deliverToKey(rpc,key,prompt,{vendor});return{session:key,pane:key,started:!1,respawned:!0,...outcomeFields(outcome)}}function restoredTabError(taskId,tabId,prompt){return new ApiError(`tab ${tabId} on task ${taskId} is a freeze-restored session \u2014 its pty host restarted, so the tab exists with its scrollback but nothing is running in it`,"TAB_RESTORED",{taskId,tabId,hint:"revive it with --respawn (resumes the tab's pinned conversation when it has one \u2014 `get-task` shows `sessionId`; a tab without one replays its recorded launch command)",nextCommandArgs:["api","send","--task-id",taskId,"--tab",tabId,"--respawn","--plain","--prompt",prompt]})}function hasLiveEngineTab(snapshot,taskId,sessions,engineAlive,engineBin){let labelled=new Set((snapshot?.tabs??[]).filter((t)=>t.kind==="engine").map((t)=>t.id));return sessions.some((s)=>{if(s.alive!==!0||!isHostedTaskKey(s.key,taskId))return!1;if(engineAlive?.get(s.key)===!1)return!1;if(s.key===`${taskId}::tab-1`)return!0;let tabId=s.key.slice(taskId.length+2);return labelled.has(tabId)||sessionArgvNamesEngine(s.command,engineBin)})}function sessionFailed(taskId,message,hint,extra){return new ApiError(message,"SESSION_FAILED",{taskId,...extra,hint,nextCommandArgs:["api","read-output","--task-id",taskId,"--source","terminal"]})}async function deliverHosted(target,worktree,prompt){let host;try{host=await ensurePtyHost()}catch(error){throw sessionFailed(target.id,`failed to start PTY host for ${target.id}: ${error instanceof Error?error.message:String(error)}`,"no PTY host to run the engine in \u2014 nothing was started; check `rove daemon status`, then retry the same send")}let newTab;try{if(target.tab&&target.tab!=="new"){let engineBin=engineLaunchArgv({command:target.command,vendor:target.vendor,effort:target.modelEffort,model:target.model})[0],tabId=target.tab;return await deliverToExactTab(host.rpc,target.id,tabId,worktree,prompt,{engineBin,maxLiveEngines:persistedMaxLiveEngines(),...target.respawn?{respawn:()=>restoredTabLaunch(target,tabId,worktree,resolveLoginShell({fallback:"/bin/zsh"}))}:{}})}newTab=target.tab==="new"?mintCliTab(target.id,target.tabVendor,target.tabCommand):void 0;let launchVendor=target.tabVendor??target.vendor,launchCommand=target.tabCommand??(target.tabVendor?void 0:target.command),{argv,sessionId}=withPinnedSessionId(engineLaunchArgv({command:launchCommand,vendor:launchVendor,effort:target.modelEffort,model:target.model}),launchVendor);trustEngineWorktree(launchVendor,worktree);let launch=buildEngineSessionLaunch({task:{id:target.id,kind:target.kind,vendor:launchVendor,repo:target.repo},worktreePath:worktree,shell:resolveLoginShell({fallback:"/bin/zsh"}),argv,promptIntent:target.newTask?{kind:"new-task",prompt}:{kind:"explicit",prompt},tabId:newTab}),result=await deliverHostedPrompt(host.rpc,{id:target.id,engineBin:argv[0]},worktree,prompt,launch,{forceNew:newTab!==void 0,maxLiveEngines:persistedMaxLiveEngines(),retryArgs:["api","send","--task-id",target.id,...target.tab?["--tab",target.tab]:[],...target.tabCommand?["--command",target.tabCommand]:[],"--plain","--prompt",prompt]});if(result.started&&!result.delivered)throw sessionFailed(target.id,`failed to start hosted engine session for ${target.id}`,"the session was created but no engine ran in it \u2014 fix the task's launch command (`api update --command`), then retry with `api send --tab new`",{session:result.session,engineReady:result.engineReady,...result.reason?{reason:result.reason}:{}});if(!newTab)publishCliTabSnapshot(target.id,result.started?sessionId:void 0);if(result.started&&sessionId){let startedTab=newTab??result.session.split("::")[1];if(startedTab)markCliTabSession(target.id,startedTab,sessionId)}return result}catch(error){if(error instanceof ApiError&&newTab&&["ENGINE_LIMIT","ENGINE_ADMISSION_BUSY","ENGINE_ADMISSION_UNAVAILABLE"].includes(error.code))closeTabsSnapshot(target.id,newTab);if(error instanceof ApiError)throw error;throw sessionFailed(target.id,`hosted engine session failed for ${target.id}: ${error instanceof Error?error.message:String(error)}`,"the hosted session threw while starting or delivering \u2014 read its terminal tail for the cause, then retry the send")}finally{host.close()}}var realPromptDeliveryOps={deliverHosted};async function closeHeadlessTerminalTab(taskId,tabId){let host=await openPtyHost();try{let sessions=host?await listSessions(host.rpc):[],saved=readTabsSnapshot(taskId)?.tabs.find((tab)=>tab.id===tabId),directKey=`${taskId}::${tabId}`,unregisteredAlive=sessions.some((session)=>session.key===directKey&&session.alive);if(!saved&&!unregisteredAlive)throw new ApiError(`tab ${tabId} does not exist on task ${taskId}`,"TAB_NOT_FOUND",{hint:"refresh the task's tab ids with get-task, then retry with one of its .tabs[].id values",nextCommandArgs:["api","get-task","--task-id",taskId]});let closing=saved?closeTabsSnapshot(taskId,tabId):void 0;if(saved&&!closing)throw new ApiError(`tab ${tabId} no longer exists on task ${taskId}`,"TAB_NOT_FOUND",{hint:"the tab closed while this command was running; refresh with get-task before retrying",nextCommandArgs:["api","get-task","--task-id",taskId]});let baseKey=closing?tabPtyKeyFor(taskId,closing):directKey,keys=!(closing?.kind==="engine"&&closing.ptyTask)?sessions.filter((session)=>session.key===baseKey||session.key.startsWith(`${baseKey}::`)).map((session)=>session.key):[],wasAlive=sessions.some((session)=>keys.includes(session.key)&&session.alive);if(host)await killTaskSessions(host.rpc,keys);return{kind:closing?.kind??"engine",wasAlive}}finally{host?.close()}}async function deliverPrompt(client,target,prompt,ops=realPromptDeliveryOps){let worktree=target.worktreePath;if(!worktree)worktree=(await client.request("task.ensureWorktree",{taskId:target.id})).worktreePath;if(!worktree)throw new ApiError(`task ${target.id} has no worktree`,"NO_WORKTREE");if(target.newTask)await client.request("task.observeLanguage",{taskId:target.id,text:prompt}).catch(()=>{});let hosted=await ops.deliverHosted(target,worktree,prompt);if(!hosted)throw sessionFailed(target.id,`failed to start hosted engine session for ${target.id}`,"delivery returned nothing \u2014 the task exists but has no session; retry with `api send --tab new`");return hosted}async function readLiveTaskIds(){let listed=await handlePtyList();if(!listed?.sessions)return null;let live=new Set;for(let row of listed.sessions){if(row.alive===!1||typeof row.key!=="string")continue;let taskId=row.key.split("::")[0];if(taskId)live.add(taskId)}return live}var defaultApiRuntime={liveTaskIds:readLiveTaskIds,isTaskRunning:async(taskId,engineArgv)=>(await defaultApiRuntime.taskTabs(taskId,engineArgv)).running,taskTabs:async(taskId,engineArgv)=>{let listed=null,host=await openPtyHost();if(host)try{listed=await listSessionsOrNull(host.rpc)}finally{host.close()}let sessions=listed??[],hostReachable=listed!==null,liveVendors,engineAlive;try{let{engineProcessIn,foregroundEngineIn:foregroundEngineIn2,parsePsSnapshot:parsePsSnapshot2,psSnapshot:psSnapshot2}=await import("./chunk-2md5j9ws.js"),walkable=sessions.filter((s)=>s.alive&&typeof s.pid==="number"&&s.pid>0);if(walkable.length>0){let rows=parsePsSnapshot2(await psSnapshot2(walkable.map((s)=>s.pid)));liveVendors=new Map(walkable.map((s)=>[s.key,foregroundEngineIn2(rows,s.pid)?.vendor??null])),engineAlive=new Map(walkable.map((s)=>[s.key,engineProcessIn(rows,s.pid,engineArgv)]))}else engineAlive=new Map}catch{}let exits={};try{exits=(await import("./chunk-des00203.js")).readPtyExitRecords()}catch{}let snapshot=readTabsSnapshot(taskId),sessionIds=new Map((snapshot?.tabs??[]).map((t)=>[t.id,t.sessionId??void 0]));return{tabs:joinTaskTabs(snapshot,taskId,listed,exits,liveVendors,engineAlive).map((row)=>{let sessionId=sessionIds.get(row.id);return sessionId?{...row,sessionId}:row}),running:hostReachable?hasLiveEngineTab(snapshot,taskId,sessions,engineAlive,engineArgv?.[0]):null}},closeTerminalTab:closeHeadlessTerminalTab,deliverPrompt:(client,target,prompt)=>deliverPrompt(client,target,prompt),resolveRepoRoot:async(absPath)=>(await import("./chunk-ng12dprz.js")).resolveMainRepoRoot(absPath),isUsableRepo:async(absPath)=>{let{isGitRepo,isRemoteRepoKey:isRemoteRepoKey2}=await import("./chunk-ng12dprz.js");return isRemoteRepoKey2(absPath)||isGitRepo(absPath)},isValidBranchName:async(branch)=>(await import("./chunk-ng12dprz.js")).isValidBranchName(branch),defaultVendor:async(repo)=>{let{getGlobalDefaultVendor,getRepoLastActiveVendor}=await import("./chunk-s57vy3k3.js");return(repo?getRepoLastActiveVendor(repo):void 0)??getGlobalDefaultVendor()},readWorktreeChanges:async(worktreePath)=>(await import("./chunk-5bfern2b.js")).readWorktreeChanges(worktreePath),readBranchSignals:async(worktreePath,recordedBaseRef)=>(await import("./chunk-tbrkck2f.js")).readBranchSignals(worktreePath,recordedBaseRef),tearDownSession:async(taskId)=>{let host=await openPtyHost();if(host)try{await killTaskSessions(host.rpc,taskKeys(await listSessions(host.rpc),taskId))}catch{}finally{host.close()}}};async function issueUpdate(ctx){let title=ctx.args.str("title"),body=ctx.args.str("body"),task=ctx.args.str("task"),status=ctx.args.enumOf("status");if(title===void 0&&body===void 0&&task===void 0&&status===void 0)throw new ApiError("issue-update requires --title, --body, --task, and/or --status","MISSING_FLAG");let repoRoot=ctx.args.requireRepo("repo"),id=ctx.args.int("id"),state;if(title!==void 0||body!==void 0||task!==void 0)state=await simpleRpc(ctx,"issue.mutate",{repoRoot,op:{type:"update",id,title,body,...task===void 0?{}:{taskId:task==="none"?null:task}}});if(status!==void 0)state=await simpleRpc(ctx,"issue.mutate",{repoRoot,op:{type:"setStatus",id,status}});return state}async function assertNotEmptySuccess(daemon,ctx,prompt){if(ctx.args.bool("allow-empty"))return;if(!/^\s*succeeded\s*[:\uff1a]/i.test(prompt))return;let self=await verifiedSelfSession();if(!self)return;let sender;try{sender=(await daemon.request("task.get",{taskId:self.taskId})).task}catch{return}if(sender.kind==="main"||sender.kind==="dir")return;if(!sender.worktreePath)return;let ahead;try{ahead=(await ctx.runtime.readBranchSignals(sender.worktreePath,sender.baseRef)).ahead}catch{return}if(ahead!==0)return;let branch=sender.branch||"your branch";throw new ApiError(`refusing to report success: ${branch} has 0 commits \u2014 "succeeded" means COMMITTED, and this report would reach the coordinator as a clean success with nothing to land`,"EMPTY_SUCCESS_REPORT",{taskId:self.taskId,branch,hint:"commit your work with a real message and send again \u2014 or, if this task genuinely produced no commits (an investigation or a review), re-send with --allow-empty to say so explicitly",nextCommandArgs:["api","send",...ctx.args.str("task-id")?["--task-id",ctx.args.str("task-id")]:[],...ctx.args.str("tab")?["--tab",ctx.args.str("tab")]:[],"--allow-empty","--prompt",prompt]})}function requirePromptText(ctx,verb){let text=ctx.args.promptText();if(text===void 0)throw new ApiError("--prompt (or --prompt-file) is required","MISSING_FLAG",helpStep(verb));return text}async function send(ctx){let daemon=daemonOf(ctx),prompt=requirePromptText(ctx,"send"),tab=ctx.args.str("tab");if(tab&&tab!=="new"&&!/^tab-[A-Za-z0-9-]+$/.test(tab))throw new ApiError(`--tab must be "new" or a tab id like tab-2 (got ${JSON.stringify(tab)})`,"BAD_TAB");let tabCommand=ctx.args.str("command");if(tabCommand&&tab!=="new")throw new ApiError(`--command only applies to a new tab; pass --tab new (got --tab ${tab??"<canonical>"})`,"BAD_FLAG",helpStep("send"));let respawn=ctx.args.bool("respawn");if(respawn&&(tab===void 0||tab==="new"))throw new ApiError(`--respawn addresses one frozen tab; pass --tab tab-N (got --tab ${tab??"<canonical>"})`,"BAD_FLAG",helpStep("send"));let tabVendor=tabCommand?resolveCommandProtocol(tabCommand):void 0,taskId=ctx.args.str("task-id");if(!taskId){let dispatcher=await readOwnDispatcher(daemon);if(dispatcher){if(taskId=dispatcher.taskId,tab===void 0)tab=await resolveDispatcherTab(ctx.runtime,dispatcher)}else{let active=await resolveActiveTaskId(daemon);if(!active)throw new ApiError("no --task-id given and no active task \u2014 open a task first or pass --task-id","MISSING_TARGET");taskId=active}}let res=await daemon.request("task.get",{taskId});await assertNotEmptySuccess(daemon,ctx,prompt);let text=ctx.args.bool("plain")?prompt:await withPeerProvenance(daemon,taskId,prompt),target=tab==="new"?null:await activityView(await readActivityRegistry(daemon),res.task,tab),delivered=await ctx.runtime.deliverPrompt(daemon,{id:taskId,worktreePath:res.task.worktreePath,kind:res.task.kind,vendor:res.task.vendor,command:res.task.command,modelEffort:tabCommand?void 0:res.task.modelEffort,model:tabCommand?void 0:res.task.model,repo:res.task.repo,tab,tabVendor,tabCommand,respawn},text);if(!delivered.delivered)throw new ApiError(`prompt was not confirmed in ${taskId}'s engine (paste did not land)`,"NOT_DELIVERED");return{ok:!0,taskId,session:delivered.session,started:delivered.started,engineReady:delivered.engineReady,delivered:delivered.delivered,...delivered.bytes===void 0?{}:{bytes:delivered.bytes},...delivered.promptEcho?{promptEcho:delivered.promptEcho}:{},...delivered.reason?{reason:delivered.reason}:{},...delivered.respawned?{respawned:!0}:{},...delivered.frozenTabs?.length?{frozenTabs:delivered.frozenTabs}:{},...target&&TARGET_FAILED.has(target.state)?{targetState:target.state,...target.detail?{targetDetail:target.detail}:{}}:{}}}var TARGET_FAILED=new Set(["error","dead"]);async function dispatch(ctx){let daemon=daemonOf(ctx),taskId=ctx.args.require("task-id"),text=requirePromptText(ctx,"dispatch"),tabId=ctx.args.str("tab"),reply=await daemon.request("session.deliver",{taskId,text,...tabId!==void 0?{tabId}:{},source:"dispatcher"});return{ok:!0,taskId,...reply?.tabId!==void 0?{tabId:reply.tabId}:tabId!==void 0?{tabId}:{},routed:"session.deliver",...reply?.delivered!==void 0?{delivered:reply.delivered}:{},...reply?.reason!==void 0?{reason:reply.reason}:{},...reply?.layer!==void 0?{layer:reply.layer}:{},...reply?.clients!==void 0?{clients:reply.clients}:{}}}var DISPATCH_VERB={name:"dispatch",group:"drive",summary:'Route text into a task\'s live session. The dispatcher\'s messenger (docs/design/dispatcher.md); unlike `send`, it never starts an engine \u2014 it requires an already-hosted session. The daemon pastes into it and reports `delivered`; `delivered:false` with `reason:"busy"` means a human is mid-message, and `reason:"broadcast"` means no hosted session answered and the text went out on the session.deliver channel for a browser to pick up (unconfirmable; `clients: 0` proves it reached nobody).',flags:[F.taskId(!0),F.prompt(!0,"Text delivered into the task's engine session."),F.promptFile(),{name:"tab",type:"string",required:!1,placeholder:"TAB",description:"Deliver into exactly this tab (e.g. tab-3) instead of the canonical engine tab."}],handler:dispatch};async function note(ctx){let daemon=daemonOf(ctx),taskId=ctx.args.require("task-id"),text=ctx.args.require("text");return await daemon.request("note.file",{taskId,text})}async function getTask(ctx){let daemon=daemonOf(ctx),taskId=ctx.args.require("task-id"),res=await daemon.request("task.get",{taskId}),{tabs,running}=await ctx.runtime.taskTabs(taskId,taskEngineArgv(res.task));return{task:res.task,running,tabs}}async function list(ctx){let daemon=daemonOf(ctx),local=await daemon.request("task.list"),{mergeTaskList}=await import("./chunk-21fm5tks.js"),merged=await mergeTaskList(local),filters=listFilters(ctx);if(!filters)return merged;return{...merged,...await filterTaskList(ctx,daemon,merged.tasks??[],filters)}}async function setActive(ctx){let daemon=daemonOf(ctx),taskId=ctx.args.bool("none")?null:ctx.args.require("task-id");return await daemon.request("task.setActive",{taskId}),{ok:!0,activeTaskId:taskId}}var RECOVER=["api","engine-list"];async function routineRunEngine(ctx,repo,vendor){return vendor??await ctx.runtime.defaultVendor(repo)??DEFAULT_TASK_VENDOR}async function pinnedEngine(ctx,repo,vendor,model,effort){let engine=await routineRunEngine(ctx,repo,vendor),protocol=taskEngine({vendor:engine});if(model)assertEngineAcceptsModel(protocol,model,RECOVER);if(effort)assertEngineAcceptsEffort(protocol,effort,RECOVER);return engine}async function createEngineFields(ctx,repo){let vendor=ctx.args.vendor(),model=ctx.args.str("model")?.trim(),effort=ctx.args.str("effort")?.trim();if(!model&&!effort)return vendor?{vendor}:{};return{vendor:await pinnedEngine(ctx,repo,vendor,model,effort),...model?{model}:{},...effort?{effort}:{}}}async function updateEngineFields(ctx,id){let hasVendor=ctx.args.present("vendor"),hasModel=ctx.args.present("model"),hasEffort=ctx.args.present("effort");if(!hasVendor&&!hasModel&&!hasEffort)return{};let vendor=ctx.args.vendor(),model=ctx.args.str("model")?.trim()||void 0,effort=ctx.args.str("effort")?.trim()||void 0,fields={...hasVendor?{vendor:vendor??null}:{},...hasModel?{model:model??null}:{},...hasEffort?{effort:effort??null}:{}},{automations}=await daemonOf(ctx).request("automation.list",{}),routine=automations.find((a)=>a.id===id);if(!routine)return fields;let nextModel=hasModel?model:routine.model,nextEffort=hasEffort?effort:routine.modelEffort;if(!nextModel&&!nextEffort)return fields;let nextVendor=hasVendor?vendor:routine.vendor,engine=await pinnedEngine(ctx,routine.repo,nextVendor,nextModel,nextEffort);return engine===routine.vendor&&!hasVendor?fields:{...fields,vendor:engine}}var SCHEDULE_FLAG={name:"schedule",type:"string",placeholder:"CRON",description:"Five-field cron in the daemon host's local time, e.g. '0 9 * * MON-FRI'."},PRECHECK_FLAGS=[{name:"precheck",type:"string",placeholder:"CMD",description:"Shell command run in the repo BEFORE the engine starts. Non-zero exit skips the run without spawning an agent \u2014 the cheap way to avoid burning a turn when nothing changed."},{name:"precheck-timeout",type:"int",placeholder:"SEC",default:"120",description:"Seconds before the precheck is killed and the run skipped."}],GRACE_FLAG={name:"grace",type:"uint",placeholder:"MIN",default:"60",description:"How late a missed occurrence may still run when the daemon was down. Only the most recent missed occurrence is ever run."},PERSISTENT_FLAG={name:"persistent-session",type:"bool",description:"Re-deliver into ONE standing task instead of a fresh worktree per run \u2014 for a routine that needs yesterday's context (a trend check). Its task is folded behind the sidebar's routine count row. Leave off for a routine that EDITS code: a week of runs on one branch is a branch nobody can land."},MODEL_FLAG={name:"model",type:"string",placeholder:"MODEL",description:"Model every run's engine launches on, in the engine's own spelling (`engine-list` shows suggestions). Rejected (BAD_MODEL) on an engine that declares no model flag."},EFFORT_FLAG={name:"effort",type:"string",placeholder:"LEVEL",description:"Reasoning effort every run's engine launches at. Validated against the engine's declared levels (BAD_EFFORT otherwise)."},TARGET_FLAGS=[{name:"target-task",type:"string",placeholder:"ID",description:"Existing task id. Requires --target-tab; never creates or revives a session. Pass both empty to clear on update."},{name:"target-tab",type:"string",placeholder:"TAB",description:"Exact existing engine tab id, e.g. tab-2. Requires --target-task."}];function targetPayload(ctx){let hasTask=ctx.args.present("target-task"),hasTab=ctx.args.present("target-tab");if(!hasTask&&!hasTab)return{};if(!hasTask||!hasTab)throw new ApiError("--target-task and --target-tab must be supplied together","BAD_FLAG");let taskId=ctx.args.str("target-task"),tabId=ctx.args.str("target-tab");if(!taskId&&!tabId)return{target:null};if(!taskId||!tabId)throw new ApiError("both target values must be nonempty, or both empty to clear","BAD_FLAG");return{target:{kind:"existing-tab",taskId,tabId}}}function precheckPayload(ctx){if(!ctx.args.present("precheck")){if(ctx.args.present("precheck-timeout"))throw new ApiError("--precheck-timeout requires --precheck (pass the command again to change its timeout)","BAD_FLAG");return{}}let command=ctx.args.str("precheck");if(command===void 0)return{precheck:null};return{precheck:{command,timeoutSeconds:ctx.args.int("precheck-timeout")??120}}}var ROUTINE_VERBS=[{name:"routine-list",group:"routine",summary:"List scheduled routines with their next run time.",flags:[],handler:(ctx)=>simpleRpc(ctx,"automation.list",{})},{name:"routine-create",group:"routine",summary:"Schedule a prompt. Each firing creates a fresh task (worktree + engine) and delivers it. Use --target-task and --target-tab for an existing conversation. An enabled routine keeps the daemon alive.",flags:[F.repo(),{name:"name",type:"string",required:!0,placeholder:"N",description:"Routine name."},F.prompt(!0,"Text delivered to the selected conversation or a new session."),F.promptFile(),{...SCHEDULE_FLAG,required:!0},{...F.vendor(),description:"Engine every run launches. Omitted with --model/--effort: the repo's default engine (falling back to the built-in default) is resolved, validated and stored. Omitted without pins: runs launch the built-in default engine."},MODEL_FLAG,EFFORT_FLAG,{name:"base-branch",type:"string",placeholder:"B",description:"Base ref each run's worktree branches from."},...PRECHECK_FLAGS,GRACE_FLAG,PERSISTENT_FLAG,...TARGET_FLAGS,{name:"disabled",type:"bool",description:"Create it paused instead of active."}],handler:async(ctx)=>{let repo=ctx.args.requireRepo("repo");return simpleRpc(ctx,"automation.create",{repo,name:ctx.args.require("name"),prompt:requirePromptText(ctx,"routine-create"),schedule:ctx.args.require("schedule"),...await createEngineFields(ctx,repo),...ctx.args.str("base-branch")?{baseRef:ctx.args.str("base-branch")}:{},...precheckPayload(ctx),...targetPayload(ctx),...ctx.args.nonNegativeInt("grace")!==void 0?{missedRunGraceMinutes:ctx.args.nonNegativeInt("grace")}:{},...ctx.args.bool("persistent-session")?{persistentSession:!0}:{},...ctx.args.bool("disabled")?{enabled:!1}:{}})}},{name:"routine-update",group:"routine",summary:"Change a routine. A new --schedule re-anchors its next run; --precheck '' clears the precheck; --enabled false pauses it (disabling the last active one releases the daemon's keep-alive hold) and --enabled true resumes it.",flags:[{name:"id",type:"string",required:!0,placeholder:"ID",description:"Routine id."},{name:"name",type:"string",placeholder:"N",description:"New name."},F.prompt(!1,"New prompt."),F.promptFile(),SCHEDULE_FLAG,{...F.vendor(),type:"string",description:"New engine vendor; empty clears it (before binding an existing target). While model/effort stay pinned, a cleared vendor is re-resolved from the repo default and stored."},{...MODEL_FLAG,description:`${MODEL_FLAG.description} '' clears it.`},{...EFFORT_FLAG,description:`${EFFORT_FLAG.description} '' clears it.`},{name:"base-branch",type:"string",placeholder:"B",description:"New base ref ('' to clear)."},...PRECHECK_FLAGS,GRACE_FLAG,PERSISTENT_FLAG,...TARGET_FLAGS,{name:"enabled",type:"bool",placeholder:"BOOL",description:"false pauses the routine, true resumes it."}],handler:async(ctx)=>{let id=ctx.args.require("id");return simpleRpc(ctx,"automation.update",{id,...ctx.args.str("name")!==void 0?{name:ctx.args.str("name")}:{},...ctx.args.promptText()!==void 0?{prompt:ctx.args.promptText()}:{},...ctx.args.str("schedule")!==void 0?{schedule:ctx.args.str("schedule")}:{},...await updateEngineFields(ctx,id),...ctx.args.present("base-branch")?{baseRef:ctx.args.str("base-branch")??null}:{},...precheckPayload(ctx),...targetPayload(ctx),...ctx.args.nonNegativeInt("grace")!==void 0?{missedRunGraceMinutes:ctx.args.nonNegativeInt("grace")}:{},...ctx.args.present("persistent-session")?{persistentSession:ctx.args.bool("persistent-session")??!0}:{},...ctx.args.present("enabled")?{enabled:ctx.args.bool("enabled")??!0}:{}})}},{name:"routine-delete",group:"routine",summary:"Delete a routine and its run history. Tasks it already created are untouched.",flags:[{name:"id",type:"string",required:!0,placeholder:"ID",description:"Routine id."}],handler:(ctx)=>simpleRpc(ctx,"automation.delete",{id:ctx.args.require("id")})},{name:"routine-run-now",group:"routine",summary:"Run a routine immediately, skipping its precheck (asking for it IS the answer). Does not shift its schedule.",flags:[{name:"id",type:"string",required:!0,placeholder:"ID",description:"Routine id."}],handler:(ctx)=>simpleRpc(ctx,"automation.runNow",{id:ctx.args.require("id")})},{name:"routine-respond",group:"routine",summary:`Report this run's result. Every delivered routine prompt starts with a [ROVE ROUTINE] line naming its run id; answer it here once you're done. One response per run \u2014 responding again replaces it. Cap ${ROUTINE_RESPONSE_MAX_CHARS} characters; markdown renders on the Routines page.`,flags:[{name:"run",type:"string",required:!0,placeholder:"RUN_ID",description:"Run id from the [ROVE ROUTINE] line."},{name:"text",type:"string",placeholder:"TEXT",description:"The response. Exactly one of --text / --prompt-file."},{...F.promptFile(),description:"Read the response from this file (`-` = stdin) \u2014 use it for anything multi-line or with backticks/$vars."}],handler:async(ctx)=>{let runId=ctx.args.require("run"),inline=ctx.args.str("text"),file=ctx.args.promptText();if(inline!==void 0&&file!==void 0)throw new ApiError("pass --text or --prompt-file, not both","BAD_FLAG");let text=inline??file;if(text===void 0)throw new ApiError("--text (or --prompt-file) is required","MISSING_FLAG");if(text.length>ROUTINE_RESPONSE_MAX_CHARS)throw new ApiError(`response is ${text.length} characters; the cap is ${ROUTINE_RESPONSE_MAX_CHARS}`,"RESPONSE_TOO_LARGE",{hint:"Summarize: lead with the conclusion, link or name the files that hold the detail."});let res=await simpleRpc(ctx,"automation.respond",{runId,text});if(res.run===null)throw new ApiError(`no routine run ${runId}`,"RUN_NOT_FOUND",{hint:"Copy the id from the [ROVE ROUTINE] line of the prompt you were given; `routine-runs --id <routine>` lists run ids."});return res}},{name:"routine-runs",group:"routine",summary:"Run history, newest first. Statuses: dispatched, revived (standing session respawned \u2014 files kept, conversation did not), skipped_cancelled (disabled, changed or stopped before delivery), skipped_precheck (nothing to do), skipped_missed, skipped_unavailable, dispatch_failed. Each run carries `response` ({text, at}) once its agent called routine-respond.",flags:[{name:"id",type:"string",required:!0,placeholder:"ID",description:"Routine id."}],handler:(ctx)=>simpleRpc(ctx,"automation.runs",{id:ctx.args.require("id")})}];async function engineChoice(ctx,repo){let command=ctx.args.str("command");if(command)return{command,vendor:resolveCommandProtocol(command)};let fallback=await ctx.runtime.defaultVendor(repo);return fallback?{command:fallback,vendor:fallback}:{}}function enginePayload(choice,effort,model,tier){return{...choice.command?{command:choice.command}:{},...choice.vendor?{vendor:choice.vendor}:{},...effort?{effort}:{},...model?{model}:{},...tier?{tier}:{}}}async function autoTier(prompt){if(!prompt?.trim())throw new ApiError("--tier auto classifies the first message \u2014 pass --prompt/--prompt-file, or name a tier explicitly","BAD_FLAG",helpStep("add"));let outcome=await classifyTier(prompt,readClassifierConfig());if(outcome.kind==="picked"){let{tier,confidence}=outcome.verdict;return{tier,note:`auto \u2192 ${tier} (confidence ${confidence.toFixed(2)})`}}let detail=outcome.detail?`: ${outcome.detail}`:"";return{note:`auto \u2192 no tier (${outcome.reason}${detail})`}}async function tierFields(ctx,prompt){let requested=ctx.args.str("tier")?.trim();if(!requested)return{};if(requested!=="auto"&&!isAutoRoutingTier(requested))throw new ApiError(`--tier must be one of swift, standard, deep, auto (got ${JSON.stringify(requested)})`,"BAD_FLAG",helpStep("add"));for(let flag of["command","model","effort","agents"])if(ctx.args.str(flag))throw new ApiError(`--${flag} conflicts with --tier, which already fills the engine, model and effort from the auto-routing table \u2014 pass one or the other`,"CONFLICTING_FLAGS",helpStep("add"));let table=readAutoRoutingTable();if(!table){if(requested==="auto")return{note:"auto \u2192 no tier (auto routing is not configured)"};throw new ApiError("auto routing is not configured \u2014 a tier has no engine (autoRouting.<tier>.engine in state.json); set it in Settings \u2192 Auto routing","TIER_UNAVAILABLE",helpStep("add"))}let tier,note2;if(requested==="auto"){let picked=await autoTier(prompt);if(note2=picked.note,!picked.tier)return{note:note2};tier=picked.tier}else tier=requested;let target=table[tier],status=await detectEngineStatus(target.engine),block=tierBlock(target,{engineIds:new Set(await engineListIds()),accountKind:()=>status.account?.kind??null});if(block){if(requested==="auto")return{note:`${note2} \u2014 unusable: ${describeTierBlock(block)}`};throw new ApiError(`tier ${tier} cannot start: ${describeTierBlock(block)}`,"TIER_UNAVAILABLE",{tier,block,hint:"Retarget the tier in Settings \u2192 Auto routing, or pass --command/--model/--effort by hand.",nextCommandArgs:["api","engine-list"]})}let vendor=resolveCommandProtocol(target.engine);try{if(target.model)assertEngineAcceptsModel(vendor,target.model,["api","engine-list"]);if(target.effort)assertEngineAcceptsEffort(vendor,target.effort,["api","engine-list"])}catch(err){if(requested!=="auto")throw err;return{note:`${note2} \u2014 unusable: ${err instanceof Error?err.message:String(err)}`}}return{fields:{choice:{command:target.engine,vendor},effort:target.effort,model:target.model,tier},...note2?{note:note2}:{}}}async function withTierNote(note2,body){if(Object.keys(note2).length===0)return body();try{return await body()}catch(err){if(!(err instanceof ApiError))throw err;throw new ApiError(err.message,err.code,{...err.data,...note2})}}function modelFor(ctx,engines){let model=ctx.args.str("model")?.trim();if(!model)return;for(let engine of new Set(engines))assertEngineAcceptsModel(engine,model,["api","engine-list"]);return model}function effortFor(ctx,engines){let level=ctx.args.str("effort")?.trim();if(!level)return;for(let engine of new Set(engines))assertEngineAcceptsEffort(engine,level,["api","engine-list"]);return level}function foreignModelWarnings(engines,model){if(!model)return[];return[...new Set(engines)].filter((engine)=>protocolEntry(engine).foreignModel?.(model)).map((engine)=>`engine ${engine} is unlikely to run model ${JSON.stringify(model)} \u2014 that spelling belongs to another vendor; \`engine-list\` shows ${engine}'s models`)}function launchedEngine(task){return{vendor:task.vendor??null,command:task.command??null,model:task.model??null,effort:task.modelEffort??null}}async function applyPostCreateFlags(daemon,taskId,args){let status=args.enumOf("status");if(status)await daemon.request("task.status",{taskId,status});let pin=args.bool("pin");if(pin!==void 0)await daemon.request("task.pin",{taskId,pinned:pin});return Boolean(status)||pin!==void 0}async function add(ctx){let{args,runtime}=ctx,requestedRepo=args.requireRepo("repo"),repo=await runtime.resolveRepoRoot(requestedRepo);if(!await runtime.isUsableRepo(repo))throw new ApiError(`--repo ${repo} is not a git repository \u2014 a task needs one to cut its worktree and branch from (run \`git init\` there, or point --repo at a checkout)`,"NOT_A_REPO",helpStep("add"));let count=args.int("count"),agentsSpec=args.str("agents"),parallel=count!==void 0||agentsSpec!==void 0,branch=parallel?void 0:args.str("branch");if(branch&&!await runtime.isValidBranchName(branch))throw new ApiError(`--branch ${JSON.stringify(branch)} is not a valid git branch name (no spaces, no leading "-", no "..", "~^:?*[\\", no trailing ".lock") \u2014 see \`git check-ref-format --branch\``,"INVALID_BRANCH",helpStep("add"));let resolvedFrom=pathWithin(repo,requestedRepo)?{repoResolvedFrom:requestedRepo}:void 0,result=parallel?await addParallel(ctx,repo,count,agentsSpec):await addOne(ctx,repo);return resolvedFrom&&result&&typeof result==="object"?{...result,...resolvedFrom}:result}async function typedEngineFields(ctx,repo){let choice=await engineChoice(ctx,repo),engines=choice.vendor?[choice.vendor]:[];return{choice,effort:effortFor(ctx,engines),model:modelFor(ctx,engines)}}async function addOne(ctx,repo){let daemon=daemonOf(ctx),{args}=ctx,prompt=args.promptText(),picked=await tierFields(ctx,prompt),fields=picked.fields??await typedEngineFields(ctx,repo),tierNote=picked.note?{tierAuto:picked.note}:{},warnings=foreignModelWarnings([fields.choice.vendor??DEFAULT_VENDOR],fields.model),warned=warnings.length>0?{warnings}:{},payload={repo,...await dispatcherEnvPayload(),...enginePayload(fields.choice,fields.effort,fields.model,fields.tier)},title=args.str("title")||(prompt?deriveTitleFromPrompt(prompt):"");if(title)payload.title=title;let branch=args.str("branch");if(branch)payload.branch=branch;let baseRef=args.str("base-branch");if(baseRef)payload.baseRef=baseRef;let worktreeName=args.str("worktree-name");if(worktreeName)payload.worktreeName=worktreeName;let res=await daemon.request("task.create",payload),taskId=res.taskId;if(args.bool("activate"))await daemon.request("task.setActive",{taskId});let task=res.task;if(await applyPostCreateFlags(daemon,taskId,args))task=(await daemon.request("task.get",{taskId})).task;if(!prompt)return{taskId,task,engine:launchedEngine(task),home:homeDir(),started:!1,...warned};let brief=await withPeerProvenance(daemon,taskId,prompt),delivered=await withTierNote(tierNote,()=>ctx.runtime.deliverPrompt(daemon,{id:taskId,worktreePath:task.worktreePath,kind:task.kind,vendor:task.vendor,command:task.command,modelEffort:task.modelEffort,model:task.model,repo:task.repo,newTask:!0},brief));if(!delivered.delivered)throw new ApiError(`task ${taskId} created but the prompt was not delivered (paste did not land)`,"NOT_DELIVERED",{taskId,engine:launchedEngine(task),...tierNote,...warned});let promptPersisted=await persistPrompt(daemon,taskId,prompt);return task=(await daemon.request("task.get",{taskId})).task,{taskId,task,engine:launchedEngine(task),home:homeDir(),started:delivered.started,engineReady:delivered.engineReady,session:delivered.session,delivered:delivered.delivered,...delivered.bytes===void 0?{}:{bytes:delivered.bytes},...delivered.promptEcho?{promptEcho:delivered.promptEcho}:{},...delivered.reason?{reason:delivered.reason}:{},...promptPersisted?{}:{promptPersisted:!1},...tierNote,...warned}}async function persistPrompt(daemon,taskId,prompt){try{return await daemon.request("task.setPrompt",{taskId,prompt}),!0}catch(err){return console.error(`[rove api add] task.setPrompt failed for ${taskId} \u2014 no "Run again" for this task:`,err),!1}}async function addParallel(ctx,repo,count,agentsSpec){let daemon=daemonOf(ctx),{args}=ctx,prompt=args.promptText();if(!prompt)throw new ApiError("--count/--agents spawn parallel attempts of ONE prompt \u2014 pass --prompt","MISSING_FLAG",helpStep("add"));if(args.str("branch"))throw new ApiError("--branch names ONE branch and cannot be shared by parallel siblings \u2014 drop it (each sibling gets its own auto branch) or spawn them one at a time","BAD_FLAG",helpStep("add"));if(args.str("worktree-name"))throw new ApiError("--worktree-name names ONE directory and cannot be shared by parallel siblings \u2014 drop it (each sibling gets its own generated name) or spawn them one at a time","BAD_FLAG",helpStep("add"));if(agentsSpec){let conflict=count!==void 0?"--count":args.str("command")?"--command":null;if(conflict)throw new ApiError(`${conflict} conflicts with --agents, which already names each sibling's engine and how many \u2014 pass one or the other`,"BAD_FLAG",helpStep("add"))}let title=args.str("title")||deriveTitleFromPrompt(prompt),baseRef=args.str("base-branch"),picked=await tierFields(ctx,prompt),tier=picked.fields,choice=tier?.choice??await engineChoice(ctx,repo),plan=agentsSpec?parseAgentsSpec(agentsSpec):buildCountPlan(count??1,choice.vendor??DEFAULT_VENDOR);if(plan.length>FANOUT_CAP)throw new ApiError(`a parallel round of ${plan.length} exceeds the cap of ${FANOUT_CAP} \u2014 spawn in batches`,"BAD_FLAG");let effort=tier?tier.effort:effortFor(ctx,plan),model=tier?tier.model:modelFor(ctx,plan),warnings=foreignModelWarnings(plan,model),groupId=ulid(),created=[],createFailure=null,dispatcher=await dispatcherEnvPayload();for(let[i,vendor]of plan.entries()){let engine=agentsSpec?{command:vendor,vendor}:{...choice,vendor},payload={repo,groupId,...dispatcher,...enginePayload(engine,effort,model,tier?.tier)};if(title)payload.title=plan.length>1?`${title} #${i+1}/${plan.length}`:title;if(baseRef)payload.baseRef=baseRef;try{let res=await daemon.request("task.create",payload);created.push({taskId:res.taskId,vendor,task:res.task})}catch(err){let code=err instanceof ApiError?err.code:"CREATE_FAILED",message=err instanceof Error?err.message:String(err);createFailure={vendor,error:{message,code}};break}}for(let{taskId}of created)await applyPostCreateFlags(daemon,taskId,args);let settled=await Promise.allSettled(created.map(({taskId,vendor,task})=>ctx.runtime.deliverPrompt(daemon,{id:taskId,worktreePath:task.worktreePath,kind:task.kind,vendor,command:task.command,modelEffort:task.modelEffort,model:task.model,repo:task.repo,newTask:!0},prompt))),tasks=[],failures=[],persistedPrompts=[];if(settled.forEach((r,i)=>{let{taskId,vendor,task}=created[i];if(r.status==="fulfilled"&&r.value.delivered){let row={ok:!0,taskId,title:task.title,branch:task.branch,vendor,engine:launchedEngine(task),started:r.value.started,engineReady:r.value.engineReady,session:r.value.session,...r.value.reason?{reason:r.value.reason}:{}};tasks.push(row),persistedPrompts.push(persistPrompt(daemon,taskId,prompt).then((ok)=>{if(!ok)row.promptPersisted=!1}));return}let err=r.status==="rejected"?r.reason:new ApiError(`prompt was not confirmed in ${taskId}'s engine`,"NOT_DELIVERED"),code=err instanceof ApiError?err.code:"DELIVER_FAILED",message=err instanceof Error?err.message:String(err);failures.push({ok:!1,taskId,vendor,engine:launchedEngine(task),error:{message,code}})}),createFailure)failures.push({ok:!1,vendor:createFailure.vendor,error:createFailure.error});await Promise.all(persistedPrompts);let result={count:created.length,requested:plan.length,groupId,home:homeDir(),tasks,failures,...picked.note?{tierAuto:picked.note}:{},...warnings.length>0?{warnings}:{}};if(failures.length>0)throw new ApiError(`add delivered ${tasks.length}/${plan.length}`,"PARTIAL_FANOUT",result);return result}var CREATE_VERBS=[{name:"add",group:"create",summary:`Create a task (shows in the sidebar immediately). With --prompt it also starts the engine and delivers it. PARALLEL ATTEMPTS: --count N spawns N sibling tasks of the SAME prompt, each in its own worktree/branch (--agents claude:2,codex:1 for a mixed fleet); capped at ${FANOUT_CAP}, prefer 3-4. Does NOT steal focus \u2014 pass --activate to make it the active task. The sidebar shows .task.title and .task.branch \u2014 name a task by those (or its id), never by the directory in .task.worktreePath, which the UI never renders. \`.engine\` ({ vendor, command, model, effort }; per row with --count/--agents) is what the task actually launches once the flags and the repo default are resolved \u2014 check it before trusting the run. \`.warnings\` flags a --model that plainly belongs to another engine's vendor (e.g. codex given a claude-* id); the task is still created. Alias: spawn-task.`,flags:[F.repo(),F.title(),{name:"branch",type:"string",placeholder:"B",description:"Explicit branch name (else derived from the title in the repo's own style). Single task only."},{name:"base-branch",type:"string",placeholder:"B",description:"Base ref the worktree branches from."},{name:"worktree-name",type:"string",placeholder:"NAME",description:"Name the worktree DIRECTORY instead of taking one from the animal pool \u2014 so the caller can predict `.task.worktreePath` (`<worktrees root>/<NAME>`) rather than reading it back with get-task. One path segment of letters/digits/`.`/`_`/`-`, not starting with `.` (INVALID_WORKTREE_NAME otherwise). A name already in use in this repo \u2014 by a live task, a directory on disk, or a concurrent create \u2014 is REFUSED with WORKTREE_NAME_TAKEN, never silently suffixed `-v2`. Single task only."},F.command(),{name:"effort",type:"string",placeholder:"LEVEL",description:"Reasoning effort for the FIRST session, not just later ones. Validated against the engine's declared levels (codex: none/low/medium/high/xhigh/max; pi and OMP: off/minimal/low/medium/high/xhigh/max; claude declares none) \u2014 free-form, since a plugin engine may declare its own. With --agents, every engine in the plan must declare it."},{name:"tier",type:"enum",values:["swift","standard","deep","auto"],description:"Auto-routing tier: fills the engine, model and effort from the table in Settings \u2192 Auto routing (autoRouting.<tier>.* in state.json) and records the tier on the task (.task.tier). Exclusive with --command/--model/--effort/--agents (CONFLICTING_FLAGS). A tier whose target cannot start \u2014 engine not in engine-list, not logged in, model or effort its engine cannot carry \u2014 is refused up front (TIER_UNAVAILABLE) with the reason. `auto` asks the classifier named by `autoRouting.classifier` (OFF by default \u2014 nothing is sent anywhere until you set it, and with `jev` the prompt goes to TypeSafe, a third party that is not your engine vendor) to read --prompt and pick the tier; needs --prompt/--prompt-file, and NEVER fails the create \u2014 off, no key, a timeout, or an answer below `autoRouting.classifierThreshold` all create the task with the ordinary defaults and say so in `.tierAuto`."},{name:"model",type:"string",placeholder:"MODEL",description:"Model for the FIRST session and every rebuild, in the engine's own spelling (claude alias/full id, codex slug, pi/omp pattern or provider/id) \u2014 passed verbatim, so `engine-list`'s `models` are suggestions, not a closed list. Rejected (BAD_MODEL) on an engine that declares no model flag; with --agents, every engine in the plan must."},{name:"count",type:"int",placeholder:"N",description:`Spawn N sibling tasks of one prompt (parallel attempts, cap ${FANOUT_CAP}). Requires --prompt.`},{name:"agents",type:"string",placeholder:"claude:2,codex:1",description:"Per-ENGINE counts for a mixed parallel round (alternative to --count). Engine ids only \u2014 see `engine-list`."},{name:"status",type:"enum",values:TASK_STATUSES,default:"backlog",description:"Initial lifecycle status."},{name:"pin",type:"bool",description:"Pin the task to the top of the sidebar."},{name:"activate",type:"bool",default:"false",description:"Make this the active task (pulls every mounted TUI's Tasks-pane focus). Off by default."},F.prompt(!1,"Optional first message \u2014 when set, materializes the worktree, starts the engine, and pastes it. Required with --count/--agents."),F.promptFile()],handler:add}];async function readStdin(){let chunks=[];for await(let chunk of process.stdin)chunks.push(Buffer.from(chunk));return Buffer.concat(chunks)}var PANE_GRAPHICS_VERB={name:"pane-graphics",group:"drive",summary:"Write opaque graphics bytes (read from stdin) to the terminal of every TUI attached to this task \u2014 the transport a pane needs because its own tty is a PTY slave, not the emulator, so it cannot reach the real terminal or measure it. Rove parses nothing: the bytes are your protocol. Call it ONCE with nothing on stdin to be given `imageId` (allocated per tab, so two panes never collide) plus `cellWidth`/`cellHeight` in pixels; build your payload around those, then pipe each frame in with --image-id to replace that picture in place. `wrote` says whether anything was broadcast. Returns `ok: false` with `unsupported` when no attached terminal reported a cell size (`no-cell-size`) or two reported different ones (`mixed-cell-size`) \u2014 fall back to whatever you draw without graphics. Task defaults to $ROVE_TASK_ID, then the active task.",flags:[F.taskId(!1),{name:"tab",type:"string",required:!0,placeholder:"TAB",description:"Terminal Tab whose cells the picture is for (e.g. tab-3), from get-task .tabs[].id."},{name:"image-id",type:"int",placeholder:"N",description:"Reuse an id this tab was already given, replacing that picture in place. Omit to be handed a fresh one."}],handler:async(ctx)=>{let client=daemonOf(ctx),taskId=ctx.args.str("task-id")??process.env.ROVE_TASK_ID??await resolveActiveTaskId(client);if(!taskId)throw new ApiError("no target task: pass --task-id (no $ROVE_TASK_ID, no active task)","MISSING_TARGET");let data=process.stdin.isTTY?Buffer.alloc(0):await readStdin(),imageId=ctx.args.int("image-id");return simpleRpc(ctx,"graphics.write",{taskId,tabId:ctx.args.require("tab"),...data.length>0?{data:data.toString("base64")}:{},...imageId!==void 0?{imageId}:{}})}};function interruptSequenceFor(task){let vendor=task.vendor,sequence=vendor?getCapabilities(vendor)?.interruptSequence:void 0;if(sequence)return sequence;throw new ApiError(`engine ${vendor??"(unknown)"} has not declared how it is interrupted`,"UNSUPPORTED",{taskId:task.id,...vendor?{vendor}:{},hint:"only engines that declare an interrupt sequence can be interrupted \u2014 stop this one by hand in its tab, or close the tab with `tab-close`",nextCommandArgs:["api","get-task","--task-id",task.id]})}async function interruptTask(ctx){let taskId=ctx.args.require("task-id"),tabFlag=ctx.args.str("tab"),{task}=await daemonOf(ctx).request("task.get",{taskId}),sequence=interruptSequenceFor({...task,id:taskId}),host=await openHostedSessionHost();if(!host)throw new ApiError(`no pty host to reach task ${taskId}`,"NO_ENGINE_TAB",{taskId,hint:"nothing is hosting this task's engine \u2014 `pty-list` says whether a host is up at all",nextCommandArgs:["api","pty-list"]});try{let sessions=await listHostedSessions(host.rpc),key=tabFlag?`${taskId}::${tabFlag}`:findHostedEngineKey(sessions,taskId,taskEngineArgv(task)[0]);if(!key||!sessions.some((session)=>session.key===key&&session.alive))throw new ApiError(tabFlag?`task ${taskId} has no live tab ${tabFlag}`:`task ${taskId} has no live engine tab to interrupt`,tabFlag?"TAB_NOT_FOUND":"NO_ENGINE_TAB",{taskId,...tabFlag?{tabId:tabFlag}:{},hint:"`get-task` lists the live tabs in .tabs[]",nextCommandArgs:["api","get-task","--task-id",taskId]});return await host.rpc.request("pty.write",{key,data:sequence}),{ok:!0,taskId,tabId:key.split("::")[1]??"tab-1",vendor:task.vendor,interrupted:!0,bytes:Buffer.byteLength(sequence,"utf8")}}finally{host.close()}}var INTERRUPT_VERB={name:"interrupt",group:"drive",summary:"Stop the turn a task's engine is currently running \u2014 the headless twin of pressing the engine's own interrupt key. Writes the ENGINE-declared interrupt bytes to its pty; the session, its conversation and its worktree all survive, which is what separates this from `tab-close` and `delete`. Use it when a worker runs away: `send` cannot reach it (a busy engine's composer is not quiet), and until now the only levers left destroyed something. Engines that have not declared an interrupt sequence (every `generic` protocol engine) are refused with UNSUPPORTED rather than guessed at \u2014 Esc and ctrl-C mean opposite things across engines. Returns { taskId, tabId, vendor, interrupted, bytes }; `interrupted` reports the WRITE, so read `collect`'s .activity.state for the effect.",flags:[F.taskId(),{name:"tab",type:"string",placeholder:"TAB",description:"Interrupt this exact tab (id from `get-task` .tabs[].id). Omitted = the task's canonical engine tab, the same one a bare `send` targets."}],handler:interruptTask};var PANE_VERB={name:"pane-open",group:"drive",summary:"Open a terminal pane in a task's workspace: split the focused tab (default, or --tab's tab) or open a separate command tab, optionally running a command. Broadcast over the daemon's tab.open channel \u2014 an attached TUI showing the task performs the split. Task defaults to $ROVE_TASK_ID, then the active task. Returns the resolved `title` (the label `pane-close --title` must match \u2014 derived from the command's first word when --title is omitted) plus `clients` (attached connections; 0 = nobody performed the split).",flags:[F.taskId(!1),{name:"tab",type:"string",placeholder:"TAB",description:"Host tab for the split (e.g. tab-3) instead of the focused tab (split placement only)."},{name:"command",type:"string",placeholder:"CMD",description:"Shell command the pane runs (via the login shell's `-ilc`, so pipes/args and your shell rc's PATH/exports work); the pane closes when it exits. Omit for an interactive shell."},{name:"direction",type:"enum",values:["right","down"],default:"right",description:"Split orientation relative to the active pane (split placement only)."},{name:"placement",type:"enum",values:["split","tab"],default:"split",description:"`split` joins the focused tab's split group; `tab` opens a separate command tab."},{name:"title",type:"string",placeholder:"TEXT",description:`Pane label (default: the command's first word, else "shell").`}],handler:async(ctx)=>{let client=daemonOf(ctx),taskId=ctx.args.str("task-id")??process.env.ROVE_TASK_ID??await resolveActiveTaskId(client);if(!taskId)throw new ApiError("no target task: pass --task-id (no $ROVE_TASK_ID, no active task)","MISSING_TARGET");let command=ctx.args.str("command"),shell=resolveLoginShell({fallback:"/bin/sh"}),argv=command?[shell,"-ilc",command]:[shell,"-il"],title=ctx.args.str("title")??(command?command.trim().split(/\s+/)[0]??"shell":"shell"),tabId=ctx.args.str("tab");return{...await simpleRpc(ctx,"tab.open",{taskId,argv,title,...tabId!==void 0?{tabId}:{},placement:ctx.args.str("placement")??"split",direction:ctx.args.str("direction")??"right"}),title}}},PANE_CLOSE_VERB={name:"pane-close",group:"drive",summary:"Close panes opened by pane-open: every split pane / command tab in the task whose label matches --title. Broadcast over the daemon's tab.close channel \u2014 an attached TUI showing the task performs the close (headless no-op). Task defaults to $ROVE_TASK_ID, then the active task. Returns `clients` (attached connections; 0 = nobody performed the close).",flags:[F.taskId(!1),{name:"title",type:"string",required:!0,placeholder:"TEXT",description:"Pane label to close \u2014 the --title the pane was opened with (engine panes are never closed)."},{name:"tab",type:"string",placeholder:"TAB",description:"Scope the title match to one tab (e.g. tab-3) instead of every tab of the task."}],handler:async(ctx)=>{let client=daemonOf(ctx),taskId=ctx.args.str("task-id")??process.env.ROVE_TASK_ID??await resolveActiveTaskId(client);if(!taskId)throw new ApiError("no target task: pass --task-id (no $ROVE_TASK_ID, no active task)","MISSING_TARGET");let tabId=ctx.args.str("tab");return simpleRpc(ctx,"tab.close",{taskId,title:ctx.args.str("title"),...tabId!==void 0?{tabId}:{}})}},TAB_CLOSE_VERB={name:"tab-close",group:"drive",summary:"Close one Terminal Tab by the id returned in get-task .tabs[]. Runs the same close path as ctrl+w when a TUI is attached; otherwise removes the persisted tab snapshot and ends its hosted PTYs directly. Engine, shell/command, and content tabs are all valid. Closing the last tab leaves the task open with no session.",flags:[F.taskId(!0),{name:"tab",type:"string",required:!0,placeholder:"TAB",description:"Exact Terminal Tab id from get-task .tabs[].id (for example tab-3)."}],handler:async(ctx)=>{let taskId=ctx.args.require("task-id"),tabId=ctx.args.require("tab");if((await daemonOf(ctx).request("terminalTab.close",{taskId,tabId})).handled)return{ok:!0,taskId,tabId,handledBy:"tui"};let result=await ctx.runtime.closeTerminalTab(taskId,tabId);return{ok:!0,taskId,tabId,handledBy:"headless",...result}}};var DEFAULT_TTL_SECONDS=60;function sourceOf(env=process.env){return env.ROVE_PLUGIN_ID??"cli"}async function rowToken(ctx){let{args}=ctx,taskId=args.require("task-id"),clear=args.bool("clear")===!0,text=args.str("text");if(!clear&&!text)throw new ApiError("row-token needs --text, or --clear to remove one","MISSING_TEXT");let key=args.str("key"),tone=args.str("tone"),ttl=args.int("ttl");return simpleRpc(ctx,"task.rowToken",{taskId,source:sourceOf(),...key?{key}:{},...clear?{clear:!0}:{text,...tone?{tone}:{}},...ttl!==void 0?{ttlMs:ttl*1000}:{}})}var ROW_TOKEN_VERB={name:"row-token",group:"drive",summary:`Put one short label on a task's sidebar/board row, with a TTL \u2014 the surface a coordination plugin paints its own state on. Every token EXPIRES (default ${DEFAULT_TTL_SECONDS}s, max 1h): a plugin refreshes the label to keep it, and one that dies has its labels fade instead of leaving stale state on screen. A plugin writes into its own slot (\`ROVE_PLUGIN_ID\` + --key, 2 slots per plugin per task) and can say nothing else: the derived group, the activity badge, the PR chip, the title and the branch are host-owned. \`--tone\` names a ROLE, not a colour, so the active theme still decides how it looks. Text is trimmed to ${ROW_TOKEN_MAX_TEXT} characters. Returns { ok, token } (or { ok, cleared } with --clear).`,flags:[F.taskId(!0),{name:"text",type:"string",placeholder:"LABEL",description:`The label, trimmed to ${ROW_TOKEN_MAX_TEXT} characters. Required unless --clear.`},{name:"ttl",type:"int",default:String(DEFAULT_TTL_SECONDS),placeholder:"SECONDS",description:"How long the label survives without a refresh (1\u20263600, clamped)."},{name:"key",type:"string",default:"default",placeholder:"SLOT",description:"Which of your two slots on this row to write. Writing the same key again replaces the token."},{name:"tone",type:"enum",values:ROW_TOKEN_TONES,description:"Semantic role the theme colours. Omitted = the row's own muted tone."},{name:"clear",type:"bool",description:"Remove the token at --key now, or every token of yours on this row when --key is omitted."}],handler:rowToken};var DRIVE_VERBS=[{name:"send",group:"drive",summary:"Paste a follow-up prompt into a task's running engine (one full turn). Without --task-id, a task spawned from another Rove session replies to its dispatcher's tab (then that task's live canonical engine; nothing alive = DISPATCHER_UNREACHABLE, never a silent spawn); otherwise the active task. Sent from inside another Rove task ($ROVE_TASK_ID), the prompt is prefixed with [ROVE PEER] provenance \u2014 who sent it and how to reply (tab-precise) \u2014 so agent-to-agent messaging needs no coordinator. The prompt is pasted into the target composer and submitted; delivery is refused only when there is physically nothing to write to (no such tab, dead PTY, no engine process). A `succeeded:` report sent from a managed task whose branch has 0 commits is REFUSED (EMPTY_SUCCESS_REPORT) \u2014 commit first, or pass --allow-empty when the task genuinely produced no commits. `delivered: true` means the bytes landed, nothing more: when the target session was already in `error` or `dead`, the reply carries `targetState` (+ `targetDetail`) \u2014 read it before waiting on an answer.",flags:[F.taskId(!1),F.prompt(!0,"Text pasted + submitted into the engine pane."),F.promptFile(),{name:"tab",type:"string",required:!1,placeholder:"TAB",description:'Tab addressing: "new" spawns the prompt in a fresh engine tab; "tab-N" delivers to that exact alive tab (error when dead/absent). Omitted = the canonical engine tab.'},{...F.command(),description:"Engine launch command for a `--tab new` tab \u2014 the API twin of the TUI's ctrl+e pick. Lets one worktree run two agents on the same files (e.g. hand the stuck work to codex without leaving the branch). An engine id from `engine-list` or a full command line; pinned to that tab, so it survives restarts and a later `update --command` on the task. Only valid with --tab new."},{name:"respawn",type:"bool",required:!1,description:"Revive a FREEZE-RESTORED --tab tab-N before delivering. After a pty-host restart (reboot, crash) a tab keeps its scrollback and launch command but nothing runs in it; without this flag such a tab is refused (TAB_RESTORED) rather than silently re-run. With it, the tab is respawned in place and resumes its pinned conversation when it has one (`get-task` shows each tab's sessionId) \u2014 a tab with none replays its recorded launch command, which for claude carries the task's original first prompt. Only valid with --tab tab-N."},{name:"plain",type:"bool",required:!1,description:"Deliver the prompt verbatim \u2014 skip the [ROVE PEER] provenance prefix."},{name:"allow-empty",type:"bool",required:!1,description:"Report success from a task with zero commits (EMPTY_SUCCESS_REPORT is refused otherwise). For work that legitimately produces no commits \u2014 an investigation, a review, a question answered."}],handler:send},DISPATCH_VERB,INTERRUPT_VERB,{name:"note",group:"drive",summary:"File a one-line field note \u2014 a resolved, repo-level gotcha worth sharing. Appended to the repo's durable note store (every future session on this repo starts with it) and forwarded to the dispatcher session for live relay (docs/design/dispatcher.md).",flags:[F.taskId(!0),{name:"text",type:"string",required:!0,placeholder:"TEXT",description:"One line: the verified conclusion another session could act on."}],handler:note},{name:"note-list",group:"drive",summary:"Read a repo's accumulated field notes, newest first. Returns { notes } \u2014 each with the `id` note-delete takes.",flags:[F.repo(!0)],handler:(ctx)=>simpleRpc(ctx,"note.list",{repo:ctx.args.requireRepo("repo")})},{name:"note-delete",group:"drive",summary:"Retire one field note by the id `note-list` reports. The note store is not an archive: its newest entries are injected into every fresh session on the repo, so a note whose fact has stopped being true keeps being handed to agents as if it still were. Returns { deleted } \u2014 false (not an error) when the id names nothing, which is also what an already-evicted note answers.",flags:[F.repo(!0),{name:"id",type:"int",required:!0,placeholder:"N",description:"Note id from `note-list`."}],handler:(ctx)=>simpleRpc(ctx,"note.delete",{repo:ctx.args.requireRepo("repo"),id:ctx.args.int("id")})},PANE_VERB,PANE_CLOSE_VERB,PANE_GRAPHICS_VERB,TAB_CLOSE_VERB,{name:"notify",group:"drive",summary:"Show a toast in every attached Rove UI \u2014 broadcast over the daemon's notice.event channel. Agents/scripts use it to surface 'done / needs input / error' moments without touching the task's session. Returns `clients` (attached connections; 0 = no UI showed the toast).",flags:[{name:"title",type:"string",required:!0,placeholder:"TEXT",description:"Toast text (one line)."},{name:"body",type:"string",placeholder:"TEXT",description:"Optional second line under the title \u2014 context, not a second message."},{name:"kind",type:"string",default:"done",placeholder:"KIND",description:`Free-form kind tag. "done", "needs_input" and "error" get the TUI's severity styling/unread mark; any other value renders neutrally.`},F.taskId(!1),{name:"source",type:"string",placeholder:"TAG",description:"Free-form origin tag (e.g. an agent name) recorded on the event."}],handler:async(ctx)=>{return simpleRpc(ctx,"notice.send",{title:ctx.args.str("title"),body:ctx.args.str("body"),kind:ctx.args.str("kind")??"done",taskId:ctx.args.str("task-id"),source:ctx.args.str("source")})}},{name:"prompt",group:"drive",summary:"Ask the human for a line of text through the attached TUI's input dialog (plugins' host-provided prompt). Blocks until answered, cancelled, or timed out; returns { value } or { cancelled, reason }.",flags:[{name:"title",type:"string",required:!0,placeholder:"TEXT",description:"Dialog title (shown verbatim)."},{name:"placeholder",type:"string",placeholder:"TEXT",description:"Input placeholder."},{name:"initial",type:"string",placeholder:"TEXT",description:"Pre-filled input value."},{name:"timeout",type:"string",placeholder:"MS",description:"Give up after this many milliseconds (default 120000, max 600000)."}],handler:async(ctx)=>{let timeoutRaw=ctx.args.str("timeout"),timeoutMs=timeoutRaw?Number.parseInt(timeoutRaw,10):void 0;return simpleRpc(ctx,"ui.prompt",{title:ctx.args.str("title"),placeholder:ctx.args.str("placeholder"),initial:ctx.args.str("initial"),...timeoutMs&&Number.isFinite(timeoutMs)?{timeoutMs}:{}})}},{name:"engine-report",group:"drive",summary:"Report a normalized engine-activity verb for a task \u2014 the public face of the same engine.reportEvent RPC the built-in hook adapters use. Lets a plugin-contributed engine (or any wrapper script) drive the sidebar badge, attention inbox, and plugin event stream without a built-in hook adapter. Kinds: session-start|turn-start|turn-complete|turn-failed|turn-interrupted|awaiting-input|session-end (state kinds) plus tool-pre|tool-post|tool-failed|pre-compact|post-compact|subagent-start|subagent-stop (plugin-only).",flags:[F.taskId(!1),{name:"kind",type:"enum",values:ENGINE_ACTIVITY_KINDS,required:!0,placeholder:"KIND",description:"Normalized activity verb (see summary). Unknown kinds are rejected."},{name:"engine",type:"string",placeholder:"ID",description:"Engine id producing the report (a plugin engine id, or a built-in vendor)."},{name:"tab",type:"string",placeholder:"TAB",description:"Terminal tab id the session runs in (defaults to $ROVE_TAB_ID / $ROVE_TAB_ID)."},{name:"detail",type:"string",placeholder:"JSON",description:`Optional detail JSON, e.g. '{"failure":"rate_limit"}' or '{"waiting":"input"}'.`}],handler:async(ctx)=>{let taskId=ctx.args.str("task-id")??process.env.ROVE_TASK_ID,tabId=ctx.args.str("tab")??process.env.ROVE_TAB_ID,detailRaw=ctx.args.str("detail"),detail;if(detailRaw!==void 0)try{detail=JSON.parse(detailRaw)}catch{throw new ApiError("--detail must be valid JSON","BAD_FLAG",helpStep("engine-report"))}return simpleRpc(ctx,"engine.reportEvent",{kind:ctx.args.str("kind"),...taskId?{taskId}:{cwd:process.cwd()},...ctx.args.str("engine")?{engine:ctx.args.str("engine")}:{},...tabId?{tabId}:{},...detail!==void 0?{detail}:{}})}},ROW_TOKEN_VERB,{name:"set-active",group:"drive",summary:"Set the shared active task (the focus every Tasks pane highlights). Pass --none to clear.",flags:[F.taskId(!1),{name:"none",type:"bool",description:"Clear the active task instead of setting one."}],handler:setActive}];var TASK_FIELDS=["title","branch","command","model","effort","pinned","status"],REPORT_FLAGS=["report-branch","report-pr","report-summary"];async function renameTab(ctx,taskId,tabId){let title=ctx.args.str("title"),others=[...TASK_FIELDS,...REPORT_FLAGS].filter((f)=>f!=="title"&&ctx.args.str(f)!==void 0);if(title===void 0||others.length>0)throw new ApiError("--tab only renames a tab: pass --title and no other field","BAD_FLAG",{hint:"Update the task's own fields in a separate call without --tab."});if(!renameTabsSnapshot(taskId,tabId,title))throw new ApiError(`task ${taskId} has no tab ${tabId}`,"TAB_NOT_FOUND",{hint:"`get-task` lists the addressable tab ids in .tabs[].id",nextCommandArgs:["api","get-task","--task-id",taskId]});let reply=await daemonOf(ctx).request("terminalTab.rename",{taskId,tabId,title});return{ok:!0,taskId,tabId,title,renamed:!0,clients:reply.clients??0}}async function updateTask(ctx){let taskId=ctx.args.require("task-id"),tabId=ctx.args.str("tab");if(tabId!==void 0)return renameTab(ctx,taskId,tabId);let title=ctx.args.str("title"),branch=ctx.args.str("branch"),command=ctx.args.str("command")?.trim(),model=ctx.args.str("model")?.trim(),effort=ctx.args.str("effort")?.trim(),pinned=ctx.args.bool("pinned"),status=ctx.args.enumOf("status"),report={reportBranch:ctx.args.str("report-branch"),reportPr:ctx.args.int("report-pr"),reportSummary:ctx.args.str("report-summary")},reporting=Object.values(report).some((v)=>v!==void 0);if(TASK_FIELDS.every((f)=>ctx.args.str(f)===void 0)&&!reporting)throw new ApiError(`nothing to update \u2014 pass at least one of ${TASK_FIELDS.map((f)=>`--${f}`).join(", ")}`,"BAD_FLAG",{nextCommandArgs:["api","update","--help"]});if(reporting&&status===void 0)throw new ApiError("--report-* is recorded with a status: pass --status too","BAD_FLAG",{hint:"A worker reporting delivery usually passes --status in_review (or done)."});if(command==="")throw new ApiError("--command cannot be empty","BAD_FLAG");let daemon=daemonOf(ctx),protocol=command?resolveCommandProtocol(command):void 0,task,engine;if(model!==void 0||effort!==void 0){task=(await daemon.request("task.get",{taskId})).task,engine=taskEngine(command?{...task,command}:task);let recover=["api","get-task","--task-id",taskId];if(model!==void 0)assertEngineAcceptsModel(engine,model,recover);if(effort!==void 0)assertEngineAcceptsEffort(engine,effort,recover)}let applied=[],step=async(fields,method,payload)=>{try{await simpleRpc(ctx,method,{taskId,...payload})}catch(err){if(applied.length===0)throw err;let message=err instanceof Error?err.message:String(err),code=err instanceof ApiError?err.code:"PARTIAL_UPDATE";throw new ApiError(`--${fields.join("/--")} failed after ${applied.join(", ")} applied: ${message}`,code,{applied:[...applied],failed:fields})}applied.push(...fields)};if(branch!==void 0)await step(["branch"],"task.setBranch",{branch});if(command)await step(["command"],"task.setCommand",{command,vendor:protocol});if(task&&engine){let recorded=vendorToRecord(protocol?{...task,vendor:protocol}:task,engine),fields=[...model!==void 0?["model"]:[],...effort!==void 0?["effort"]:[]];await step(fields,"task.setVendor",{vendor:recorded,model,effort})}if(title!==void 0)await step(["title"],"task.rename",{title});if(pinned!==void 0)await step(["pinned"],"task.pin",{pinned});if(status!==void 0)await step(["status"],"task.status",{status,...report});return{ok:!0,taskId,updated:applied,...command?{command,protocol,...protocol===GENERIC_PROTOCOL?{generic:!0}:{}}:{},...engine?{engine}:{}}}var EDIT_VERBS=[{name:"update",group:"edit",summary:"Change a task's metadata \u2014 any combination of title, branch (git branch -m if materialized, else recorded), engine --command / --model / --effort (take effect on the next session rebuild), pin, and status + worker report \u2014 in one call. Every field is validated before anything is written; then they apply in order branch \u2192 engine \u2192 title/pin/status, and a refusal mid-way names what already applied (`applied`). With --tab, renames that Terminal Tab instead (the API twin of the TUI's f2) and takes only --title. The status is a cosmetic LABEL: `--status canceled` closes, stops and cleans up nothing \u2014 `delete` ends a task. The --report-* fields record what the WORKER CLAIMS it delivered (`.report` on `get-task` / `collect`), deliberately apart from `.prStatus`, which the daemon polled from the forge; they merge onto any previous report and need --status. Returns { updated } \u2014 the fields written, in order.",flags:[F.taskId(),{name:"title",type:"string",placeholder:"T",description:"New task title (or, with --tab, the tab's name)."},{name:"tab",type:"string",placeholder:"TAB",description:"Rename this Terminal Tab (id from `get-task` .tabs[].id, e.g. tab-2) instead of the task; only --title may accompany it. TAB_NOT_FOUND when the task's snapshot names no such tab."},{name:"branch",type:"string",placeholder:"B",description:"New branch name."},{...F.command(),description:"New engine launch command \u2014 an engine id from `engine-list` or a full command line. The protocol Rove speaks to it is derived from it; the result reports which (`generic: true` when the command names no engine Rove knows)."},{name:"model",type:"string",placeholder:"MODEL",description:"Pin a model, passed to the engine VERBATIM in its own spelling (claude alias or id, codex slug, pi/omp pattern); `engine-list` .models are suggestions, not a closed list. BAD_MODEL when the engine declares no model flag."},{name:"effort",type:"string",placeholder:"LEVEL",description:"Reasoning effort level the task's engine declares (codex: none/low/medium/high/xhigh/max; pi, omp: off/minimal/low/medium/high/xhigh/max; claude: none). BAD_EFFORT names the accepted levels."},{name:"pinned",type:"bool",placeholder:"BOOL",description:"true pins the task to the top of the sidebar, false unpins."},{name:"status",type:"enum",values:TASK_STATUSES,description:"New lifecycle label (cosmetic \u2014 see summary)."},{name:"report-branch",type:"string",placeholder:"B",description:"The branch the worker says holds the work (a CLAIM \u2014 `.branch` is the task's actual branch)."},{name:"report-pr",type:"int",placeholder:"N",description:"The PR number the worker says it opened (a CLAIM \u2014 `.prStatus.number` is what the daemon read from the forge)."},{name:"report-summary",type:"string",placeholder:"TEXT",description:"One line of what was delivered."}],handler:updateTask}];async function collect(ctx){let daemon=daemonOf(ctx),{args,runtime}=ctx,idsFlag=args.str("task-ids"),repoFlag=args.path("repo"),groupFlag=args.str("group"),taskIds,unresolvableRepos=[];if(idsFlag)taskIds=idsFlag.split(",").map((s)=>s.trim()).filter(Boolean);else if(repoFlag||groupFlag){let{tasks}=await daemon.request("task.list"),filter=repoFlag?await repoFilter(runtime,repoFlag,tasks.map((t)=>t.repo)):null;unresolvableRepos=filter?.unresolvableRepos??[],taskIds=[];for(let t of tasks){if(groupFlag&&t.groupId!==groupFlag)continue;if(filter&&!filter.matches(t.repo))continue;taskIds.push(t.id)}}else throw new ApiError("collect needs --task-ids id1,id2, --group GROUPID, or --repo PATH","MISSING_TARGET");let registry=await readActivityRegistry(daemon),out=[];for(let taskId of taskIds){let{task}=await daemon.request("task.get",{taskId}),{tabs,running}=await runtime.taskTabs(taskId,taskEngineArgv(task)),changes=task.worktreePath?await runtime.readWorktreeChanges(task.worktreePath):null,base=task.worktreePath?await runtime.readBranchSignals(task.worktreePath,task.baseRef):{baseRef:null,ahead:null,behind:null,diff:null},activity=await activityView(registry,task);out.push({taskId:task.id,title:task.title,branch:task.branch,worktreePath:task.worktreePath,vendor:task.vendor,status:task.status,...task.groupId?{groupId:task.groupId}:{},...task.report?{report:task.report}:{},...task.dispatcher?{dispatcher:task.dispatcher}:{},running,activity,tabs,changes,base})}return{tasks:out,...unresolvableRepos.length>0?{unresolvableRepos}:{}}}async function feedback(ctx){return{ok:!0,discussion:submitFeedback({title:ctx.args.require("title"),body:ctx.args.require("body"),categorySlug:ctx.args.str("category")})}}var FEEDBACK_VERBS=[{name:"feedback",group:"feedback",summary:"Create a GitHub Discussion in the Rove repo's Feedback category through `gh`.",flags:[{name:"title",type:"string",required:!0,placeholder:"T",description:"Discussion title."},{name:"body",type:"string",required:!0,placeholder:"TEXT",description:"Discussion body."},{name:"category",type:"string",default:DEFAULT_FEEDBACK_CATEGORY_SLUG,placeholder:"SLUG",description:"Discussion category slug."}],offline:!0,handler:feedback}];async function issueList(ctx){let repoRoot=ctx.args.requireRepo("repo"),statuses=csvOf(ctx,"status",ISSUE_STATUSES,"issue-list"),needle=ctx.args.str("search")?.trim().toLowerCase();if(!statuses&&!needle)return simpleRpc(ctx,"issue.list",{repoRoot});let state=await daemonOf(ctx).request("issue.list",{repoRoot}),issues=state.issues.filter((i)=>(!statuses||statuses.has(i.status))&&(!needle||i.title.toLowerCase().includes(needle)||i.body.toLowerCase().includes(needle)));return{...state,issues}}var ISSUE_VERBS=[{name:"issue-list",group:"issues",summary:"List daemon-owned issues for a repo. --status and --search AND together; `nextId` and `skipped` still describe the whole board.",flags:[F.repo(),{name:"status",type:"csv",placeholder:"S1,S2",description:`Only issues in these statuses (${ISSUE_STATUSES.join(", ")}).`},{name:"search",type:"string",placeholder:"Q",description:"Only issues whose title or body contains Q (case-insensitive)."}],handler:issueList},{name:"issue-create",group:"issues",summary:"Create a daemon-owned issue for a repo.",flags:[F.repo(),{name:"title",type:"string",required:!0,placeholder:"T",description:"Issue title."},{name:"body",type:"string",placeholder:"TEXT",description:"Issue body."}],handler:(ctx)=>simpleRpc(ctx,"issue.mutate",{repoRoot:ctx.args.requireRepo("repo"),op:{type:"create",title:ctx.args.require("title"),body:ctx.args.str("body")}})},{name:"issue-update",group:"issues",summary:"Update a daemon-owned issue's title, body, linked task, and/or status. Title, body and link land in one all-or-nothing write; the status follows as its own change.",flags:[F.repo(),{name:"id",type:"int",required:!0,placeholder:"N",description:"Issue id."},{name:"title",type:"string",placeholder:"T",description:"New title."},{name:"body",type:"string",placeholder:"TEXT",description:"New body."},{name:"task",type:"string",placeholder:"TASK_ID",description:"Link the issue to this task (kanban: In progress). Pass `none` to unlink."},{name:"status",type:"enum",values:ISSUE_STATUSES,description:"New issue status."}],handler:issueUpdate},{name:"issue-delete",group:"issues",summary:"Delete a daemon-owned issue. Removes ONLY the tracker record \u2014 a linked task, its branch and its worktree are left untouched. The same `delete` op the kanban page's `d` runs; use `issue-update --status done` when the story was finished rather than abandoned.",flags:[F.repo(),{name:"id",type:"int",required:!0,placeholder:"N",description:"Issue id."}],handler:(ctx)=>simpleRpc(ctx,"issue.mutate",{repoRoot:ctx.args.requireRepo("repo"),op:{type:"delete",id:ctx.args.int("id")}})}];import{spawnSync}from"child_process";var REMOTE_TIMEOUT_MS=30000;function git(cwd,args,timeoutMs){try{let out=spawnSync("git",[...args],{cwd,encoding:"utf8",stdio:["ignore","pipe","pipe"],env:readOnlyGitProcessEnv(),...timeoutMs===void 0?{}:{timeout:timeoutMs}});return{ok:out.status===0,stdout:(out.stdout??"").trim(),stderr:(out.stderr??"").trim()||(out.status===null?`git ${args[0]} did not finish`:"")}}catch(err){return{ok:!1,stdout:"",stderr:err instanceof Error?err.message:String(err)}}}function localBranchExists(repo,branch){return git(repo,["show-ref","--verify","--quiet",`refs/heads/${branch}`]).ok}function remoteFor(repo,branch){let configured=git(repo,["config","--get",`branch.${branch}.remote`]);return configured.ok&&configured.stdout.length>0?configured.stdout:"origin"}function deleteRemoteBranch(repo,branch){let name=remoteFor(repo,branch),push=git(repo,["push",name,"--delete",branch],REMOTE_TIMEOUT_MS);if(push.ok)return{name,deleted:!0};let remaining=git(repo,["ls-remote","--heads",name,branch],REMOTE_TIMEOUT_MS);if(remaining.ok&&remaining.stdout.length===0)return{name,deleted:!0};return{name,deleted:!1,error:push.stderr||"git push exited non-zero"}}function reportBranchDeletion(repo,branch,opts){if(!branch||!opts.deleteBranch&&!opts.deleteRemote)return;let deleted=!0,keptReason;if(opts.deleteBranch){if(localBranchExists(repo,branch)){let retry=git(repo,["branch",opts.force?"-D":"-d",branch]);if(deleted=retry.ok||!localBranchExists(repo,branch),!deleted)keptReason=retry.stderr||retry.stdout||"git branch refused the delete"}}else deleted=!localBranchExists(repo,branch);return{branch,deleted,...keptReason?{keptReason}:{},...opts.deleteRemote?{remote:deleteRemoteBranch(repo,branch)}:{}}}var DELETE_WAIT_TIMEOUT_MS=60000,DELETE_POLL_INTERVAL_MS=250;async function deleteTask(ctx){let daemon=daemonOf(ctx),groupId=ctx.args.str("group"),taskIdFlag=ctx.args.str("task-id");if(groupId&&taskIdFlag)throw new ApiError("pass --task-id or --group, not both","BAD_FLAG");if(!groupId&&!taskIdFlag)throw new ApiError("delete needs --task-id ID or --group GROUPID","MISSING_TARGET");let self=await verifiedSelfSession();if(!groupId)return deleteOne(ctx,taskIdFlag,self);let{tasks}=await daemon.request("task.list"),members=tasks.filter((t)=>t.groupId===groupId);if(members.length===0)throw new ApiError(`no tasks in group ${groupId}`,"TASK_NOT_FOUND",{hint:"the groupId is the one `add --count` returned; any surviving sibling's `.groupId` in `list` recovers it",nextCommandArgs:["api","list"]});let results=[];for(let task of members)try{results.push(await deleteOne(ctx,task.id,self))}catch(err){let message=errorMessage(err),code=err instanceof ApiError?err.code:splitDaemonCode(message)?.code;results.push({taskId:task.id,status:"failed",error:message,...code?{code}:{}})}let failures=results.filter((r)=>r.status==="failed").length;return{groupId,count:members.length,failures,results}}async function deleteOne(ctx,taskId,self){let daemon=daemonOf(ctx),force=ctx.args.bool("force")??!1,deleteBranch=ctx.args.bool("delete-branch")??!1,deleteRemote=ctx.args.bool("delete-remote")??!1,subject=deleteBranch||deleteRemote?(await daemon.request("task.get",{taskId})).task:void 0,res;try{res=await daemon.request("task.delete",{taskId,force,deleteBranch,...self?{requestedByTaskId:self.taskId,requestedByTabId:self.tabId}:{}})}catch(err){throw deleteRecoveryError(err,taskId)}if(await ctx.runtime.tearDownSession(taskId),!res.queued)return{...res,status:"not_found"};if(!ctx.args.bool("wait")&&!subject)return{...res,status:"queued"};let outcome=await awaitDeletion(daemon,taskId),branch=subject&&outcome.status==="removed"?reportBranchDeletion(subject.repo,subject.branch??"",{deleteBranch,force,deleteRemote}):void 0;return{...res,...outcome,...branch?{branch}:{}}}async function awaitDeletion(daemon,taskId){let deadline=Date.now()+DELETE_WAIT_TIMEOUT_MS;for(;;){let{tasks}=await daemon.request("task.list"),task=tasks.find((t)=>t.id===taskId);if(!task)return{status:"removed"};let deletion=task.deletion;if(deletion?.phase==="error")return{status:"failed",error:deletion.error??"worktree removal failed"};if(Date.now()>=deadline)return{status:"pending"};await new Promise((resolve2)=>setTimeout(resolve2,DELETE_POLL_INTERVAL_MS))}}async function land(ctx){let daemon=daemonOf(ctx),taskId=ctx.args.require("task-id");if(ctx.args.bool("dry-run")===!0)return(await daemon.request("task.landPreflight",{taskId})).result;let strategy=ctx.args.str("strategy")==="squash"?"squash":"merge",res;try{res=await daemon.request("task.land",{taskId,strategy,deleteBranch:ctx.args.bool("delete-branch")??!1,removeWorktree:ctx.args.bool("remove-worktree"),callerCwd:process.cwd()})}catch(err){throw landRecoveryError(err,taskId)}return{ok:!0,taskId,...res.result}}function landRecoveryError(err,taskId){let message=errorMessage(err);if(!message.includes(EMPTY_BRANCH_DIRTY_WORKTREE_CODE))return err;let branch=/EMPTY_BRANCH_DIRTY_WORKTREE: '([^']+)'/.exec(message)?.[1]??"your task branch";return new ApiError(splitDaemonCode(message)?.rest??message,EMPTY_BRANCH_DIRTY_WORKTREE_CODE,{hint:"the worker wrote files but never committed them \u2014 send it back to commit its own work, then land again",nextCommandArgs:["api","send","--task-id",taskId,"--prompt",`your work is uncommitted on ${branch} \u2014 commit it yourself with a proper message, then report back`]})}function deleteRecoveryError(err,taskId){let message=errorMessage(err),coded=splitDaemonCode(message);if(coded?.code!==DIRTY_WORKTREE_CODE)return err;return new ApiError(coded.rest,DIRTY_WORKTREE_CODE,{taskId,hint:"the worktree still holds work this delete would destroy \u2014 the message names it, and a gitignored path never shows in `git status`. Send the worker back to commit it, or pass --force to delete the task AND discard that work",nextCommandArgs:["api","send","--task-id",taskId,"--prompt","your worktree still holds work that this cleanup would destroy (it may be gitignored, so `git status` will not show it \u2014 check `git status --ignored`). Commit it yourself with a proper message, then report back"]})}async function removeTaskWorktree(ctx){let daemon=daemonOf(ctx),taskId=ctx.args.require("task-id"),force=ctx.args.bool("force")??!1,{task}=await daemon.request("task.get",{taskId}),worktreePath=(task.worktreePath??"").trim();if(!worktreePath)throw new ApiError(`task ${taskId} has no worktree on disk`,"NO_WORKTREE",{hint:"nothing to remove \u2014 `ensure-worktree` materialises one"});let wt=canonicalize(worktreePath);if(samePath(wt,canonicalize(task.repo)))throw new ApiError(`refusing to remove ${worktreePath} \u2014 it is the project's own checkout, not a Rove worktree`,"BASE_CHECKOUT");if(pathWithin(wt,canonicalize(process.cwd()))!==null)throw new ApiError(`refusing to remove the caller's own worktree (${worktreePath}) \u2014 re-run from outside it`,"CALLER_WORKTREE");let res=await daemon.request("worktree.remove",{path:worktreePath,force});return{ok:!0,taskId,worktreePath,branch:task.branch,...res}}async function adopt(ctx){let daemon=daemonOf(ctx),{args}=ctx,input={repo:args.requireRepo("repo"),worktreePath:args.requirePath("worktree")},branch=args.str("branch");if(branch)input.branch=branch;let command=args.str("command");if(command)input.command=command,input.vendor=resolveCommandProtocol(command);let title=args.str("title");if(title)input.title=title;return daemon.request("worktree.adopt",input)}var LIFECYCLE_VERBS=[{name:"land",group:"lifecycle",summary:"Merge a task's branch back into its base repo's current branch. Refuses a dirty base checkout, a branch that no longer resolves in the base repo (MISSING_REF \u2014 renamed or deleted outside Rove), and a branch with zero commits ahead of the base (EMPTY_BRANCH; EMPTY_BRANCH_DIRTY_WORKTREE with a send-back recovery path when the worktree still holds the uncommitted work). On conflict, aborts and returns the conflicted files (resolve by hand). Returns { landedOn, commit }.",flags:[F.taskId(),{name:"dry-run",type:"bool",description:"Report whether the land would proceed, and write nothing. Returns { branch, landedOn, ahead?, baseDirty?, refusal?, dirtyFiles?, baseDir } \u2014 `landedOn` is the base checkout's CURRENT branch (the merge destination), `ahead` the commits that would land, and `refusal` one of DETACHED_HEAD, UNREADABLE_BASE, UNBORN_BASE, SAME_BRANCH, MAIN_CHECKOUT_DIRTY, MISSING_REF, EMPTY_BRANCH, EMPTY_BRANCH_DIRTY_WORKTREE when the land would be refused. Ignores --strategy/--delete-branch/--remove-worktree."},{name:"strategy",type:"enum",values:["merge","squash"],default:"merge",description:"merge (--no-ff) or squash into one commit."},{name:"delete-branch",type:"bool",description:"Delete the task's branch after a successful land. Uses `git branch -D`, which drops the branch's reflog too; with --strategy squash the base's new commit does not reach the branch's own commits, so Rove first anchors the tip at refs/rove/salvage/<branch>-<stamp> and returns it as `branchAnchor`. Requires the worktree to be gone: git refuses to delete a branch a live worktree has checked out, so a land that kept the worktree keeps the branch too and says so in `branchKept`."},{name:"remove-worktree",type:"bool",default:"true",description:"Remove the task's worktree after a successful land (default; the branch always stays). Pass --remove-worktree=false to keep it. Dirty worktrees, the base checkout, and the caller's own worktree are refused \u2014 the outcome is reported in the result's `worktree` field, never thrown."}],handler:land},{name:"delete",group:"lifecycle",summary:"Remove a task and its worktree; the git branch stays unless --delete-branch. Needs --force on a dirty worktree. Returns { queued } \u2014 removal itself runs in the background; add --wait for the resolved outcome. With --delete-branch / --delete-remote the reply also carries `branch` \u2014 { branch, deleted, keptReason?, remote? } \u2014 so `the branch went with it` is a fact you read rather than an assumption. Pass --group instead of --task-id to close a whole fan-out round in one call: it returns { groupId, count, failures, results } with one entry per sibling, and a refusal on one (a dirty worktree) is recorded there rather than aborting the rest.",flags:[F.taskId(!1),{name:"group",type:"string",placeholder:"GROUPID",description:"Delete every task of one fan-out round (the `groupId` that `add --count` returns, the same selector `collect --group` takes). Mutually exclusive with --task-id."},{name:"force",type:"bool",description:"Delete even with uncommitted changes (never implies --delete-branch)."},{name:"delete-branch",type:"bool",description:"Also delete the task's LOCAL git branch (default: keep it). The outcome is reported in the result's `branch` field \u2014 `{ deleted, keptReason? }` \u2014 because git legitimately refuses (unmerged work, a sibling worktree holding the branch) and that refusal used to reach daemon.log and nowhere a caller could read it. Implies --wait: a branch verdict read before the worktree is gone describes the worktree, not the branch."},{name:"delete-remote",type:"bool",description:"Also `git push --delete` the branch on its remote (its own `branch.<name>.remote`, else origin). A SEPARATE opt-in that --delete-branch never implies: a local branch is recoverable from any clone that still has it, a remote one is recoverable by nobody and its deletion closes any open PR. Reported in `branch.remote` \u2014 `{ name, deleted, error? }`. Implies --wait."},{name:"wait",type:"bool",description:"Follow the background removal and report its OUTCOME (removed / failed / pending) instead of returning as soon as it is queued."}],handler:deleteTask}];var DEFAULT_SINCE_DAYS=7,DEFAULT_LIMIT=200;function summarizeTurns(turns){let byModel={},inputTokens=0,outputTokens=0,cacheReadTokens=0,cacheCreationTokens=0,durationMs=0;for(let turn of turns){inputTokens+=turn.usage?.input_tokens??0,outputTokens+=turn.usage?.output_tokens??0,cacheReadTokens+=turn.usage?.cache_read_input_tokens??0,cacheCreationTokens+=turn.usage?.cache_creation_input_tokens??0,durationMs+=Math.max(0,turn.endedAt-turn.startedAt);let model=turn.model??"unknown";byModel[model]=(byModel[model]??0)+1}return{turns:turns.length,inputTokens,outputTokens,cacheReadTokens,cacheCreationTokens,durationMs,byModel}}async function agentTurns(ctx){let daemon=daemonOf(ctx),taskId=ctx.args.str("task-id"),repoPath=ctx.args.path("repo"),repo=repoPath?await ctx.runtime.resolveRepoRoot(repoPath):void 0,sinceDays=ctx.args.int("since-days")??DEFAULT_SINCE_DAYS,since=Date.now()-sinceDays*24*60*60*1000,limit=ctx.args.int("limit")??DEFAULT_LIMIT,{turns}=await daemon.request("agentTurn.list",{...taskId?{taskId}:{},...repo?{repoRoot:repo}:{},since,limit});return{since:new Date(since).toISOString(),totals:summarizeTurns(turns),turns}}var AGENT_TURNS_VERB={name:"agent-turns",group:"read",summary:`Per-turn agent telemetry: one record per completed engine turn (task/tab/vendor/model/timings/tokens), newest first, plus totals. Engine-produced, daemon-stored; read-only. Engines that report turns: ${vendorsWithTurnReader().join(", ")} \u2014 a task on any other engine yields an empty page because that engine has no turn reader, not because it did no work.`,flags:[{name:"task-id",type:"string",placeholder:"ID",description:"Only this task's turns."},{name:"repo",type:"string",placeholder:"PATH",description:"Only turns of tasks in this repo. Relative paths resolve against $PWD."},{name:"since-days",type:"int",default:String(DEFAULT_SINCE_DAYS),placeholder:"N",description:"Look-back window in days."},{name:"limit",type:"int",default:String(DEFAULT_LIMIT),placeholder:"N",description:"Max turns returned."}],handler:agentTurns};var CONTEXT_NOTE_CAP=15,CONTEXT_TASK_LIMIT=20;function buildContext(input){let rows=[];for(let task of input.tasks){let entry=input.activity?.[task.id],activity=entry&&TASK_ACTIVITY_STATES.includes(entry.state)?{state:entry.state,at:entry.at}:null,{group,rank}=taskGroupOf({task,activity,tabAlive:input.liveTaskIds?input.liveTaskIds.has(task.id):null,now:input.now});rows.push({taskId:task.id,title:task.title,branch:task.branch,group,rank,activity:activity?{state:activity.state,forMs:Math.max(0,input.now-activity.at)}:null,...task.prStatus?{checkState:task.prStatus.checkState}:{},...task.prStatus?.number!==void 0?{pr:task.prStatus.number}:{},...task.report?{report:task.report}:{},at:activity?.at??0})}rows.sort((a,b)=>a.rank-b.rank||b.at-a.at);let kept=rows.slice(0,input.limit).map(({at:_at,...row})=>row),omitted=rows.length-kept.length;return{repo:input.repo,at:new Date(input.now).toISOString(),tasks:kept,...omitted>0?{omittedTasks:omitted}:{},attention:input.attention,notes:input.notes.slice(0,CONTEXT_NOTE_CAP)}}function age(ms){let m=Math.floor(ms/60000);if(m<60)return`${m}m`;let h=Math.floor(m/60);return h<48?`${h}h`:`${Math.floor(h/24)}d`}function shortId(id){return id.slice(-6)}function renderContext(payload){let lines=[`repo ${payload.repo} \xB7 ${payload.tasks.length} tasks \xB7 ${payload.attention.length} in the inbox`];if(payload.tasks.length===0)lines.push(" (no tasks)");for(let task of payload.tasks){let bits=[];if(task.activity)bits.push(`${task.activity.state} ${age(task.activity.forMs)}`);if(task.checkState)bits.push(`ci:${task.checkState}`);if(task.pr!==void 0)bits.push(`PR#${task.pr}`);if(task.report?.summary)bits.push(`\u201C${task.report.summary}\u201D`);else if(task.report)bits.push("reported");lines.push(` ${task.group.padEnd(16)} ${shortId(task.taskId)} ${task.title}${task.branch?` [${task.branch}]`:""}${bits.length>0?` \u2014 ${bits.join(" \xB7 ")}`:""}`)}if(payload.omittedTasks)lines.push(` (+${payload.omittedTasks} quieter tasks not shown \u2014 raise --limit)`);if(payload.attention.length>0){lines.push(`inbox (${payload.attention.length} unhandled):`);for(let item of payload.attention)lines.push(` ${item.state} ${item.taskId?shortId(item.taskId):"routine"}`)}if(payload.notes.length>0){lines.push(`field notes (${payload.notes.length}, newest first):`);for(let note2 of payload.notes)lines.push(` #${note2.id} ${note2.text}`)}return lines.join(`
7
- `)}async function tryRead(read){try{return await read()}catch{return null}}async function loadContext(daemon,runtime,opts){let{tasks:allTasks}=await daemon.request("task.list"),filter=opts.repo===null?null:await repoFilter(runtime,opts.repo,allTasks.map((t)=>t.repo)),inScope=(repo)=>filter===null||filter.matches(repo),repoTaskIds=new Set(allTasks.filter((task)=>inScope(task.repo)).map((task)=>task.id)),tasks=allTasks.filter((task)=>(task.kind??"task")==="task"&&inScope(task.repo)&&task.deletion?.phase!=="queued"&&task.deletion?.phase!=="running"),[inspect,inbox,notes,liveTaskIds]=await Promise.all([tryRead(()=>daemon.request("debug.inspect")),tryRead(()=>daemon.request("attention.list")),filter?tryRead(()=>daemon.request("note.list",{repo:filter.target})):null,tryRead(()=>runtime.liveTaskIds())]);return{payload:buildContext({repo:filter?.target??"*",tasks,activity:inspect?.activity?.tasks??null,liveTaskIds:liveTaskIds??null,attention:(inbox?.items??[]).filter((item)=>item.taskId===null||repoTaskIds.has(item.taskId)),notes:notes?.notes??[],now:Date.now(),limit:opts.limit}),unresolvableRepos:filter?.unresolvableRepos??[]}}async function context(ctx){let{args,runtime}=ctx,{payload,unresolvableRepos}=await loadContext(daemonOf(ctx),runtime,{repo:args.requireRepo("repo"),limit:args.int("limit")??CONTEXT_TASK_LIMIT});if(args.bool("text"))return{text:renderContext(payload)};return{...payload,...unresolvableRepos.length>0?{unresolvableRepos}:{}}}var CONTEXT_VERB={name:"context",group:"read",summary:"The coordinator's start-of-turn read: one composed snapshot of a repo \u2014 every worktree task with its DERIVED group (waiting-on-you|landing|ready-for-review|working|idle|unknown, sorted by rank so the first row is what needs a person next), title, branch, live engine activity + how long, `.checkState` (Rove's own CI truth), the worker's `.report` claim; plus the unhandled attention inbox and the repo's newest field notes (the same ones injected into fresh sessions here). The group is DERIVED from report/PR/activity/liveness \u2014 it is not the declared `status`, which a crashed worker leaves lying. `--text` renders it compactly instead of as structured JSON.",flags:[F.repo(!0),{name:"limit",type:"int",default:String(CONTEXT_TASK_LIMIT),placeholder:"N",description:"Max tasks to include. Sorted by rank, so the cap drops the quiet tail; `omittedTasks` reports it."},{name:"text",type:"bool",description:"Return { text } \u2014 one compact line per task instead of the structured rows."}],handler:context};var DEFAULT_SINCE_DAYS2=7;function epochOf(iso){if(!iso)return null;let ms=Date.parse(iso);return Number.isNaN(ms)?null:ms}function buildDigest(repo,sinceMs,tasks,runs){let byStatus={};for(let run of runs)byStatus[run.status]=(byStatus[run.status]??0)+1;return{repo,since:new Date(sinceMs).toISOString(),tasks:{total:tasks.length},routines:{runs:runs.length,byStatus}}}async function digest(ctx){let daemon=daemonOf(ctx),{args,runtime}=ctx,repoFlag=args.requireRepo("repo"),sinceMs=Date.now()-(args.int("since-days")??DEFAULT_SINCE_DAYS2)*86400000,{tasks:allTasks}=await daemon.request("task.list"),{automations}=await daemon.request("automation.list"),filter=await repoFilter(runtime,repoFlag,[...allTasks.map((t)=>t.repo),...automations.map((a)=>a.repo)]),repo=filter.target,tasks=[];for(let task of allTasks){if((task.kind??"task")!=="task")continue;if((epochOf(task.updatedAt)??0)<sinceMs)continue;if(filter.matches(task.repo))tasks.push(task)}let runs=[];for(let automation of automations){if(!filter.matches(automation.repo))continue;let page=await daemon.request("automation.runs",{id:automation.id});for(let run of page.runs)if((epochOf(run.at)??0)>=sinceMs)runs.push(run)}return{...buildDigest(repo,sinceMs,tasks,runs),...filter.unresolvableRepos.length>0?{unresolvableRepos:filter.unresolvableRepos}:{}}}var DIGEST_VERB={name:"digest",group:"read",summary:"Aggregate a repo's recent agent work: tasks touched in the window plus routine run outcomes. Reads state Rove already persists \u2014 the measurement any workflow change has to move.",flags:[{name:"repo",type:"string",required:!0,placeholder:"PATH",description:"Repo root (git toplevel). Relative paths resolve against $PWD."},{name:"since-days",type:"int",default:String(DEFAULT_SINCE_DAYS2),placeholder:"N",description:"Look-back window in days."}],handler:digest};async function daemonSection(){let{connectIfRunning}=await import("./chunk-cst86kt3.js"),client=null;try{if(client=await connectIfRunning(),!client)return null;return await client.request("debug.inspect",{})}catch(err){return{error:err instanceof Error?err.message:String(err)}}finally{client?.close()}}async function sessionsSection(taskId){let[{RoveDaemonClient},{defaultPtyHostSocketPath}]=await Promise.all([import("./chunk-zeb0sttx.js"),import("./chunk-pvm3fc44.js")]),client=new RoveDaemonClient(defaultPtyHostSocketPath()),sessions;try{await client.connect(),sessions=(await client.request("pty.list",{})).sessions??[]}catch{return null}finally{client.close()}if(taskId)sessions=sessions.filter((s)=>s.key.startsWith(`${taskId}::`));let rows=null;try{let pids=sessions.map((s)=>s.pid).filter((pid)=>typeof pid==="number"&&pid>0);rows=parsePsSnapshot(await psSnapshot(pids))}catch{rows=null}return sessions.map((s)=>{let walkable=rows!==null&&typeof s.pid==="number"&&s.pid>0,found=walkable&&rows?foregroundEngineIn(rows,s.pid):null;return{key:s.key,alive:s.alive,pid:s.pid??null,title:s.title||null,exit:s.exit??null,foreground:walkable?found?{vendor:found.vendor,pid:found.pid,argv:found.argv}:null:"unknown"}})}async function sessionExitsSection(taskId){try{let{compareByExitAtDesc,readPtyExitRecords}=await import("./chunk-des00203.js"),records=Object.values(readPtyExitRecords()).sort(compareByExitAtDesc);return taskId?records.filter((r)=>r.key.startsWith(`${taskId}::`)):records}catch(err){return{error:err instanceof Error?err.message:String(err)}}}function tabsSection(taskId,sessions){let live=Array.isArray(sessions)?sessions.filter((s)=>typeof s?.key==="string"):[];try{let state=loadStateFile(),out={},prefix="terminalTabs.";for(let[key,value]of Object.entries(state)){if(!key.startsWith("terminalTabs."))continue;let id=key.slice(13);if(taskId&&id!==taskId)continue;let snap=value;if(!snap||!Array.isArray(snap.tabs))continue;let unregistered=unregisteredTabIds(snap,id,live);out[id]={activeId:snap.activeId,tabs:snap.tabs.map((t)=>({id:t.id,kind:t.kind,title:t.title??null,vendor:t.vendor??null,liveVendor:t.liveVendor??null,lastTitle:t.lastTitle??null,autoTitle:t.autoTitle??null})),...unregistered.length>0?{unregistered}:{}}}for(let s of live){let id=s.key.split("::")[0]??"";if(!id||out[id]!==void 0||taskId&&id!==taskId)continue;let unregistered=unregisteredTabIds(void 0,id,live);if(unregistered.length>0)out[id]={activeId:null,tabs:[],unregistered}}return out}catch(err){return{error:err instanceof Error?err.message:String(err)}}}async function inspect(ctx){let taskId=ctx.args.str("task-id"),[daemon,sessions,sessionExits]=await Promise.all([daemonSection(),sessionsSection(taskId),sessionExitsSection(taskId)]);return{daemon,sessions,sessionExits,tabs:tabsSection(taskId,sessions),at:new Date().toISOString()}}var INSPECT_VERB={name:"inspect",group:"read",summary:"Production diagnostics in one read: daemon activity registry (raw states, probe vendors, watchdogs), pty-host sessions joined with a live process-tree engine walk, durable session death records (exit code/signal/output tail), and the persisted tab snapshots the sidebar renders from. Read-only; missing daemon/host degrade to null.",flags:[F.taskId(!1)],offline:!0,handler:inspect};var WATCHABLE_STATES=["idle","running","turn_complete","rate_limited","permission_needed","error","dead"],HEARTBEAT_MS=5000,DEFAULT_TIMEOUT_MS=300000;async function resolveTaskIds(ctx){let ids=ctx.args.str("task-ids"),group=ctx.args.str("group");if(ids&&group)throw new ApiError("pass --task-ids or --group, not both","BAD_FLAG");if(ids){let list2=ids.split(",").map((s)=>s.trim()).filter(Boolean);if(list2.length>0)return list2}if(!group)throw new ApiError("watch needs --task-ids id1,id2 or --group GROUPID","MISSING_TARGET");let{tasks}=await daemonOf(ctx).request("task.list"),members=tasks.filter((t)=>t.groupId===group).map((t)=>t.id);if(members.length===0)throw new ApiError(`no tasks in group ${group}`,"TASK_NOT_FOUND",{hint:"the groupId is the one `add --count` returned; any surviving sibling's `.groupId` in `list` recovers it",nextCommandArgs:["api","list"]});return members}function parseUntil(raw){let states=raw.split(",").map((s)=>s.trim()).filter(Boolean),unknown=states.filter((s)=>!WATCHABLE_STATES.includes(s));if(unknown.length>0)throw new ApiError(`--until names states that do not exist: ${unknown.join(", ")} (valid: ${WATCHABLE_STATES.join(", ")})`,"BAD_FLAG");if(states.length===0)throw new ApiError("--until needs at least one state","BAD_FLAG");return states}async function watch(ctx){let daemon=daemonOf(ctx),taskIds=new Set(await resolveTaskIds(ctx)),until=new Set(parseUntil(ctx.args.require("until"))),timeoutRaw=ctx.args.int("timeout"),timeoutMs=timeoutRaw&&timeoutRaw>0?timeoutRaw:DEFAULT_TIMEOUT_MS;return await new Promise((resolve2,reject)=>{let settled=!1,events=0,seen=new Set,SEEN_CAP=512,finish=(fn)=>{if(settled)return;settled=!0,clearInterval(heartbeat),clearTimeout(deadline),off(),fn()},off=daemon.onChannel("engine-state",(payload)=>{let event=payload;if(!taskIds.has(event.taskId))return;let key=`${event.taskId}::${event.tabId??""}::${event.state}::${event.at}`;if(seen.has(key))return;if(seen.size>=SEEN_CAP)seen.clear();if(seen.add(key),events++,process.stdout.write(`${JSON.stringify({taskId:event.taskId,...event.tabId?{tabId:event.tabId}:{},state:event.state,at:event.at})}
8
- `),!until.has(event.state))return;finish(()=>resolve2({ok:!0,watched:[...taskIds],until:[...until],events,matched:{taskId:event.taskId,...event.tabId?{tabId:event.tabId}:{},state:event.state}}))}),deadline=setTimeout(()=>{finish(()=>reject(new ApiError(`no watched task reached ${[...until].join("/")} within ${timeoutMs}ms`,"WATCH_TIMEOUT",{watched:[...taskIds],until:[...until],events,hint:"the state may simply not have happened yet \u2014 `collect --task-ids` shows where each task is right now",nextCommandArgs:["api","collect","--task-ids",[...taskIds].join(",")]})))},timeoutMs),heartbeat=setInterval(()=>{daemon.request("daemon.status").catch(()=>{finish(()=>reject(new ApiError("the daemon went away while watching","DAEMON_GONE",{watched:[...taskIds],events,hint:"the watch is over and nothing was missed on purpose \u2014 re-run it once the daemon is back",nextCommandArgs:["daemon","status"]})))})},HEARTBEAT_MS);daemon.subscribe({channels:["engine-state"]}).catch((err)=>{finish(()=>reject(err))})})}var WATCH_VERB={name:"watch",group:"read",summary:"Block until a watched task's engine reaches one of --until's states, streaming every transition as NDJSON on the way \u2014 the push-driven replacement for a `collect` polling loop. Each line is { taskId, tabId?, state, at }; the usual single result object follows when the watch ends. Exits 0 on a match (with `matched`), WATCH_TIMEOUT when --timeout elapses first, DAEMON_GONE when the daemon dies mid-watch (reconnect and re-run \u2014 this verb never reconnects on its own). `dead` is watchable and is the one state polling is worst at: a SIGKILLed engine fires no hook, so the daemon writes it from the pty exit record and pushes it here immediately.",flags:[{name:"task-ids",type:"csv",placeholder:"a,b,c",description:"Comma-separated task ids to watch."},{name:"group",type:"string",placeholder:"GROUPID",description:"Watch every task of one fan-out round (the `groupId` that `add --count` returns)."},{name:"until",type:"string",required:!0,placeholder:"STATE[,STATE]",description:`Stop at the first of these engine states: ${WATCHABLE_STATES.join(", ")}. A state that does not exist is refused up front \u2014 a typo would otherwise wait forever.`},{name:"timeout",type:"int",placeholder:"MS",description:`Give up after this many milliseconds (default ${DEFAULT_TIMEOUT_MS}). Timing out is WATCH_TIMEOUT, exit non-zero \u2014 it says nothing happened, never that nothing will.`}],handler:watch};function sourceChanged(detail){return new ApiError(`${detail} \u2014 restart the read without the cursor`,"SOURCE_CHANGED")}async function readTaskOutput(input,deps){let messageLimit=Math.min(Math.max(input.limit??DEFAULT_PAGE_MESSAGES,1),MAX_PAGE_MESSAGES),lineLimit=Math.min(Math.max(input.limit??DEFAULT_TAIL_LINES,1),TERMINAL_TAIL_LINES);if(input.cursor){let cursor=decodeCursor(input.cursor,input.taskId);if(input.source!=="auto"&&input.source!==cursor.src)throw new ApiError(`cursor is pinned to source "${cursor.src}" but --source is "${input.source}"`,"CURSOR_INVALID");if((cursor.tab??null)!==(input.tab??null))throw new ApiError(`cursor is pinned to tab ${cursor.tab??"canonical"} \u2014 pass the same --tab or restart without the cursor`,"CURSOR_INVALID");return cursor.src==="history"?continueHistory(input,deps,cursor,messageLimit):continueTerminal(input,deps,cursor,lineLimit)}if(input.source==="terminal")return firstTerminalPage(input,deps,null,lineLimit);let pinned=await resolveHistory(input,deps);if(pinned===null){if(input.source==="history")throw new ApiError(`${input.tab} is not an engine tab \u2014 it has no structured history`,"HISTORY_REQUIRED");return firstTerminalPage(input,deps,null,lineLimit)}if(typeof pinned==="string"){if(input.source==="history")throw new ApiError(`structured history unavailable for ${input.taskId}: ${pinned}`,"HISTORY_REQUIRED");return firstTerminalPage(input,deps,pinned,lineLimit)}let messages;try{messages=await pinned.reader.readHistory(pinned.sessionId)}catch{if(input.source==="history")throw new ApiError(`structured history unavailable for ${input.taskId}: history_unreadable`,"HISTORY_REQUIRED");return firstTerminalPage(input,deps,"history_unreadable",lineLimit)}if(messages.length===0&&input.source==="auto")return firstTerminalPage(input,deps,"history_missing",lineLimit);return historyEnvelope(input,pinned.sessionId,messages,{before:messages.length},messageLimit)}async function resolveHistory(input,deps){if(input.tab)return deps.tabHistory(input.tab);if(!deps.history)return"engine_unsupported";if(!input.worktree)return"history_missing";let ids;try{ids=await deps.history.listSessionIdsForWorktree(input.worktree)}catch{return"history_unreadable"}let sid=ids[ids.length-1];return sid?{reader:deps.history,sessionId:sid}:"history_missing"}async function continueHistory(input,deps,cursor,limit){let pinned=await resolveHistory(input,deps);if(pinned===null||pinned==="engine_unsupported")throw sourceChanged("the engine no longer provides structured history");if(pinned==="history_unreadable")throw new ApiError("history became unreadable \u2014 retry, or restart without the cursor","HISTORY_UNREADABLE");let sid=typeof pinned==="string"?null:pinned.sessionId;if(typeof pinned==="string"||sid!==cursor.sid)throw sourceChanged(`the engine session changed (was ${cursor.sid}, now ${sid??"none"})`);let messages;try{messages=await pinned.reader.readHistory(cursor.sid)}catch{throw new ApiError("history became unreadable \u2014 retry, or restart without the cursor","HISTORY_UNREADABLE")}if(cursor.idx>messages.length)throw sourceChanged("the pinned transcript shrank");return historyEnvelope(input,cursor.sid,messages,cursor.older?{before:cursor.idx}:{from:cursor.idx},limit)}function historyEnvelope(input,sessionId,messages,at,limit){let{page,start,end,limited}=buildHistoryPage(messages,at,limit),base={v:1,task:input.taskId,src:"history",sid:sessionId,tab:input.tab};return{taskId:input.taskId,source:"history",history:{sessionId,messages:page,returnedMessageCount:page.length,totalMessages:messages.length,firstIndex:start,limited},cursor:encodeCursor({...base,idx:end}),olderCursor:start>0?encodeCursor({...base,idx:start,older:!0}):null,fallbackReason:null,warnings:[]}}async function firstTerminalPage(input,deps,fallbackReason,lines){let t=await deps.peekTerminal(input.tab);if(!t||t==="host-unreachable"){let unreachable=t==="host-unreachable";return{taskId:input.taskId,source:"terminal",terminal:{tail:[],truncated:!1,live:!1,tab:input.tab},cursor:null,olderCursor:null,fallbackReason:unreachable?"pty_host_unreachable":fallbackReason,warnings:[unreachable?"the pty host could not be reached \u2014 this is 'could not look', not 'no session'":"no live terminal session for this task"]}}let{tail,truncated}=boundedTail(t.text,lines);return{taskId:input.taskId,source:"terminal",terminal:{tail,truncated,live:t.live,exit:t.exit??null,tab:input.tab},cursor:encodeCursor({v:1,task:input.taskId,src:"terminal",pid:t.pid,off:t.offset,fr:fallbackReason,tab:input.tab}),olderCursor:null,fallbackReason,warnings:[]}}async function continueTerminal(input,deps,cursor,lines){let t=await deps.peekTerminal(cursor.tab,cursor.off);if(t==="host-unreachable")throw sourceChanged("the pty host could not be reached");if(!t)throw sourceChanged("the terminal session is gone");if(t.pid!==cursor.pid)throw sourceChanged("the terminal session restarted (new process)");let warnings=t.sinceValid?[]:["scrollback trimmed \u2014 there is a gap before this page"],{tail,truncated}=boundedTail(t.text,lines),fr=cursor.fr??null;return{taskId:input.taskId,source:"terminal",terminal:{tail,truncated,live:t.live,exit:t.exit??null,tab:cursor.tab},cursor:encodeCursor({v:1,task:input.taskId,src:"terminal",pid:t.pid,off:t.offset,fr,tab:cursor.tab}),olderCursor:null,fallbackReason:fr,warnings}}function snapshotTabHistory(task,tabId){let tab=readTabsSnapshot(task.id)?.tabs.find((t)=>t.id===tabId);if(!tab||tab.kind!=="engine")return null;let vendor=tab.vendor??task.vendor;if(!vendor||!supportsStructuredHistory(sessionProtocol(vendor)))return"engine_unsupported";return tab.sessionId?{reader:protocolEntry(vendor).history,sessionId:tab.sessionId}:"history_missing"}async function handleReadOutput(ctx){let daemon=daemonOf(ctx),taskId=ctx.args.str("task-id");if(!taskId){let active=await resolveActiveTaskId(daemon);if(!active)throw new ApiError("no --task-id given and no active task \u2014 pass --task-id","MISSING_TARGET");taskId=active}let{task}=await daemon.request("task.get",{taskId}),vendor=task.vendor,engineBin=vendor||task.command?taskEngineArgv(task)[0]:void 0,tab=ctx.args.str("tab"),deps={history:vendor&&supportsStructuredHistory(sessionProtocol(vendor))?protocolEntry(vendor).history:null,tabHistory:(tabId)=>snapshotTabHistory(task,tabId),peekTerminal:(tabId,sinceOffset)=>peekTaskTerminal(taskId,engineBin,tabId,sinceOffset)},envelope=await readTaskOutput({taskId,worktree:task.worktreePath??null,source:ctx.args.enumOf("source")??"auto",tab,cursor:ctx.args.str("cursor"),limit:ctx.args.int("limit")},deps),running=await ctx.runtime.isTaskRunning(taskId,taskEngineArgv(task)),engineError=envelope.source==="history"&&!tab?await engineScreenError(task):void 0;return{vendor:vendor??null,running,...envelope,...engineError?{engineError}:{}}}var READ_OUTPUT_VERB={name:"read-output",group:"read",summary:"Read what a task's engine is doing, newest first, as bounded JSON: the engine's own structured history when available, else a labeled terminal tail (typed fallbackReason). The first page is the LATEST \u2014 the last --limit messages or terminal lines. `cursor` pages forward and, once caught up, is the poll point: re-read with it to get only what the session wrote since (pair with `watch` to know when). `olderCursor` pages back through history. --tab tab-N reads one exact tab: its own conversation when it is an engine tab with one, else its terminal. Read-only \u2014 never attaches or types; the cursor stays pinned to one source/session/tab (SOURCE_CHANGED when it moved). A history read also carries `engineError` when the engine's screen ends on an error row it never wrote to its transcript (a turn that failed before replying).",flags:[{name:"task-id",type:"string",placeholder:"ID",description:"Target task id (defaults to the active task)."},{name:"tab",type:"string",placeholder:"TAB",description:"Read exactly this tab (e.g. tab-3, ids from `get-task` .tabs[]) instead of the task's engine: an engine tab's own pinned conversation when it has one, else that tab's terminal."},{name:"source",type:"enum",values:["auto","history","terminal"],default:"auto",description:"auto = structured history else terminal fallback; history = require structured (typed error instead of fallback); terminal = bounded terminal tail."},{name:"cursor",type:"string",placeholder:"C",description:"Opaque `cursor` (forward / poll) or `olderCursor` (back) from a previous page. Pinned to that page's source, session and tab."},{name:"limit",type:"int",placeholder:"N",description:`Page size: history messages (default ${DEFAULT_PAGE_MESSAGES}, max ${MAX_PAGE_MESSAGES}) or terminal lines (default ${DEFAULT_TAIL_LINES}, max ${TERMINAL_TAIL_LINES}).`}],handler:handleReadOutput};var READ_VERBS=[{name:"list",group:"read",summary:"List all tasks. Returns { tasks, activeTaskId } \u2014 `activeTaskId` is the shared focus verbs default to when --task-id is omitted (null = no active task), the audit read for any implicit-target delivery. Filters AND together: --repo, --status, --activity (comma lists match any value). With --activity each task carries the `.activity` it matched; a task whose state Rove cannot read never matches. `unresolvableRepos` beside `tasks` names task repos --repo could not compare.",flags:[F.repo(!1),{name:"status",type:"csv",placeholder:"S1,S2",description:`Only tasks in these lifecycle statuses (${TASK_STATUSES.join(", ")}).`},{name:"activity",type:"csv",placeholder:"A1,A2",description:`Only tasks whose engine is in these states (${TASK_ACTIVITY_STATES.join(", ")}). \`permission_needed,error\` = tasks waiting on you.`}],handler:list},{name:"get-task",group:"read",summary:"Read one task's metadata + terminal tabs. `.running` = any hosted engine tab is live; `.tabs[]` (id/kind/vendor/liveVendor/lastTitle/alive) is the discovery read for `send --tab tab-N`; `.task.dispatcher` = the Rove session (task+tab) that created it, when one did; `.task.prStatus.checkState` (none|pending|passing|failing|unknown) is Rove's OWN CI truth for the branch's PR \u2014 `passing` is what \"CI is green\" means, never a local test run. `.task.worktreePath` is where the files live; the user only ever sees `.task.title` and `.task.branch`, so refer to the task by those.",flags:[F.taskId()],handler:getTask},{name:"pty-list",group:"read",summary:"List hosted PTY sessions (key, alive, pid, command, live OSC window title). Returns { sessions }: `[]` = a live pty host with nothing running (the fleet IS idle); `null` = no pty host to ask, so this is `couldn't look` and says NOTHING about what is running.",flags:[],offline:!0,handler:handlePtyList},{name:"collect",group:"read",summary:`Read-only health snapshot of a parallel round: identity, branch, lineage (.dispatcher, .groupId), .running (pty-host process truth, not a cached status), .activity (engine state + how long it has been in it, null when unknowable), per-tab .tabs with a dead tab's exit cause AND output tail, uncommitted .changes (non-zero = it cannot land; null = could not read git \u2014 do NOT treat as clean), and committed .base (ahead count + diffstat \u2014 ahead:0 is the \`succeeded but committed nothing\` tell). Select with --group (one fan-out round), --repo, or --task-ids. ${ACTIVITY_STATES_DOC}`,flags:[{name:"task-ids",type:"csv",placeholder:"a,b,c",description:"Comma-separated task ids."},{name:"group",type:"string",placeholder:"GROUPID",description:"Every task of one fan-out round (the `groupId` that `add --count` returns)."},F.repo(!1)],handler:collect},CONTEXT_VERB,DIGEST_VERB,AGENT_TURNS_VERB,INSPECT_VERB,READ_OUTPUT_VERB,WATCH_VERB];var WORK_ITEM_STATES=["open","closed","all"],WORK_ITEM_VERBS=[{name:"workitem-list",group:"workitems",summary:"List a repo's GitHub issues through the `gh` CLI. Read-only \u2014 nothing is copied into Rove's own issue store.",flags:[F.repo(),{name:"state",type:"enum",values:WORK_ITEM_STATES,default:"open",description:"Issue state filter."},{name:"limit",type:"int",placeholder:"N",default:"20",description:"Max items (1-50, max 50)."},{name:"search",type:"string",placeholder:"Q",description:"Free-text search passed to `gh --search`."},{name:"assignee",type:"string",placeholder:"USER",description:"Only items assigned to this user; `@me` for yourself."},{name:"label",type:"string",placeholder:"L",description:"Only items carrying this label."}],handler:(ctx)=>simpleRpc(ctx,"workitem.list",{repo:ctx.args.requireRepo("repo"),...ctx.args.str("state")?{state:ctx.args.str("state")}:{},...ctx.args.int("limit")!==void 0?{limit:ctx.args.int("limit")}:{},...ctx.args.str("search")?{search:ctx.args.str("search")}:{},...ctx.args.str("assignee")?{assignee:ctx.args.str("assignee")}:{},...ctx.args.str("label")?{labels:[ctx.args.str("label")]}:{}})},{name:"workitem-start",group:"workitems",summary:"Start a task on one GitHub issue: creates a worktree + engine session whose first message carries the issue title, body, and URL. The task keeps a link back to the issue.",flags:[F.repo(),{name:"number",type:"int",required:!0,placeholder:"N",description:"Issue number."},F.vendor(),{name:"base-branch",type:"string",placeholder:"B",description:"Base ref the worktree branches from."}],handler:(ctx)=>simpleRpc(ctx,"workitem.start",{repo:ctx.args.requireRepo("repo"),number:ctx.args.int("number"),...ctx.args.vendor()?{vendor:ctx.args.vendor()}:{},...ctx.args.str("base-branch")?{baseRef:ctx.args.str("base-branch")}:{}})}];var WORKTREE_VERBS=[{name:"ensure-worktree",group:"worktree",summary:"Materialize a task's git worktree on disk now (without starting an engine). Returns { worktreePath }.",flags:[F.taskId()],handler:(ctx)=>simpleRpc(ctx,"task.ensureWorktree",{taskId:ctx.args.require("task-id")})},{name:"remove-worktree",group:"worktree",summary:"Remove a task's git worktree directory, keeping the TASK and its BRANCH \u2014 the inverse of ensure-worktree, which can materialize it again. Same path as the Worktrees page's delete: the session is torn down first, a dirty tree is refused without --force, and every forced removal takes a salvage snapshot into refs/rove/salvage/<branch>-<stamp>. Refuses the project's own checkout (BASE_CHECKOUT) and the worktree the caller is running from (CALLER_WORKTREE). Use `delete` instead to end the task itself. Returns { worktreePath, branch, removed, residue? }.",flags:[F.taskId(),{name:"force",type:"bool",description:"Remove even with uncommitted changes (a salvage snapshot is taken first). Never deletes the branch."}],handler:removeTaskWorktree},{name:"discover-adoptable",group:"worktree",summary:"List existing git worktrees in a repo not yet tracked as Rove tasks. Returns { worktrees }.",flags:[F.repo()],handler:(ctx)=>simpleRpc(ctx,"worktree.discoverAdoptable",{repo:ctx.args.requireRepo("repo")})},{name:"adopt",group:"worktree",summary:"Import an existing git worktree as a Rove task. Returns { task }.",flags:[F.repo(),{name:"worktree",type:"string",required:!0,placeholder:"PATH",description:"Path of the worktree to adopt."},{name:"branch",type:"string",placeholder:"B",description:"Branch override (else the worktree's own)."},F.command(),F.title()],handler:adopt}];async function handleSchema(ctx){let verbName=ctx.args.str("verb");if(verbName){let v=findVerb(verbName);if(!v)throw unknownVerbError(verbName);return verbSchema(v)}let group=ctx.args.str("group");if(group)return groupSchema(group);if(ctx.args.bool("all"))return fullSchema();return schemaIndex()}var VERB_ALIASES={"spawn-task":"add"},RETIRED_VERBS={"fan-out":{hint:"fan-out was folded into `add`: pass --count N (or --agents claude:2,codex:1) to spawn N parallel tasks of one prompt",nextCommandArgs:["api","add","--help"]},"set-vendor":{hint:"set-vendor was replaced by `update --command`, which takes the engine's raw launch command (an engine id from `engine-list`, or a full command line)",nextCommandArgs:["api","update","--help"]},...Object.fromEntries([["rename","--title (with --tab to rename a tab)"],["set-branch","--branch"],["set-command","--command"],["set-effort","--effort (was --level)"],["set-model","--model"],["set-status","--status and --report-*"],["pin","--pinned true|false"]].map(([verb,flag])=>[verb,{hint:`${verb} was folded into \`update\`: pass ${flag}; one update call can change several fields at once`,nextCommandArgs:["api","update","--help"]}])),"issue-set-status":{hint:"issue-set-status was folded into `issue-update`: pass --status",nextCommandArgs:["api","issue-update","--help"]},"routine-set-enabled":{hint:"routine-set-enabled was folded into `routine-update`: pass --enabled true|false",nextCommandArgs:["api","routine-update","--help"]},archive:{hint:"archive was removed: there is no hide-without-delete anymore \u2014 use `delete` to remove a finished task and its worktree; the git branch survives (pass --delete-branch explicitly only when the history may go)",nextCommandArgs:["api","delete","--help"]}},SCHEMA_STEP={hint:"list every valid verb + flag as JSON, then retry with a real verb",nextCommandArgs:["api","schema"]};function unknownVerbError(verbName){let retired=RETIRED_VERBS[verbName];if(retired)return new ApiError(`unknown verb: ${verbName} (removed)`,"UNKNOWN_VERB",{hint:retired.hint,nextCommandArgs:[...retired.nextCommandArgs]});return new ApiError(`unknown verb: ${verbName}`,"BAD_VERB",SCHEMA_STEP)}var SCHEMA_VERB={name:"schema",group:"discover",summary:"Explore the API. Default = a COMPACT index (groups + verb summaries, no flags). Drill in with --verb / --group; --all for the full spec.",flags:[{name:"verb",type:"string",placeholder:"NAME",description:"Full flag detail for ONE verb."},{name:"group",type:"string",placeholder:"G",description:"List the verbs in one group (compact)."},{name:"all",type:"bool",description:"The COMPLETE spec \u2014 every verb AND every flag (large; avoid by default)."}],offline:!0,handler:handleSchema},VERBS=[SCHEMA_VERB,ENGINE_LIST_VERB,...READ_VERBS,...CREATE_VERBS,...DRIVE_VERBS,...FEEDBACK_VERBS,...ISSUE_VERBS,...ROUTINE_VERBS,...WORK_ITEM_VERBS,...EDIT_VERBS,...LIFECYCLE_VERBS,...WORKTREE_VERBS],API_VERBS=VERBS.map((v)=>v.name),VERB_GROUPS=(()=>{let byGroup=Object.fromEntries(VERB_GROUP_IDS.map((g)=>[g,[]]));for(let v of VERBS)byGroup[v.group].push(v.name);return byGroup})();function findVerb(name){let canonical=VERB_ALIASES[name]??name;return VERBS.find((v)=>v.name===canonical)}
9
- export{API_SCHEMA_VERSION,verbSchema,schemaIndex,fullSchema,verbHelp,apiUsage,parseFlags,validateAgainstSpec,VerbArgs,parseAgentsSpec,buildCountPlan,takeIdentityWarning,deliverPrompt,defaultApiRuntime,loadContext,VERB_ALIASES,unknownVerbError,VERBS,API_VERBS,VERB_GROUPS,findVerb};
@@ -1,2 +0,0 @@
1
- // @bun
2
- import{discoverMachine}from"./chunk-wtb8cycx.js";import{RemoteOrchestrator}from"./chunk-a10bkevn.js";import"./chunk-atn06ncf.js";import"./chunk-55ajzrj6.js";import"./chunk-t7j8g9f6.js";import"./chunk-dmzg9zdc.js";import"./chunk-6xppe9bj.js";import{startTunnel}from"./chunk-9jsm6h5y.js";import{dedupeMachines,duplicateAliasOf,listMachines,readMachines,setMachineIdentity,updateMachine}from"./chunk-9aydqazr.js";import"./chunk-96cjpssp.js";import"./chunk-9w5g7frx.js";import"./chunk-gvrtrvnz.js";import"./chunk-xy8be792.js";import"./chunk-8p579jct.js";import"./chunk-40dgv828.js";import{loadStateFile}from"./chunk-7q43tysm.js";import"./chunk-mw0ysrpd.js";import"./chunk-a7kq09ke.js";import"./chunk-prqeddy7.js";import"./chunk-zt7wfnva.js";import"./chunk-yt15y085.js";import{createStateCell}from"./chunk-nhakrm1f.js";import{RoveDaemonClient}from"./chunk-adkva3ep.js";import"./chunk-0422g8ac.js";import{logClient,logClientError}from"./chunk-hq9tbzqa.js";import"./chunk-pn5ygqn6.js";import"./chunk-npwzmmv9.js";import"./chunk-b39sdtt0.js";import"./chunk-nc46gddg.js";import"./chunk-wqfp8d9w.js";class MachineHub{local;slots=new Map;machinesAcc=createStateCell([],"machines.status");mergedAcc=createStateCell([],"machines.tasks");localUnsubscribe=null;disposed=!1;constructor(local){this.local=local}machinesSignal(){return this.machinesAcc}tasksSignal(){if(this.slots.size===0)return this.local.tasksSignal();return this.mergedAcc}remoteTaskIds(){let ids=new Set;for(let slot of this.slots.values())for(let task of slot.tasks)ids.add(String(task.id));return ids}attach(){let entries=dedupeMachines(listMachines());if(entries.length===0)return;for(let entry of entries)this.slots.set(entry.alias,{entry,tasks:[]});this.publishStatuses(),this.localUnsubscribe=this.local.tasksSignal().subscribe(()=>this.republishTasks()),this.republishTasks();for(let entry of entries)this.connect(entry)}async connect(entry){let slot=this.slots.get(entry.alias);if(!slot||this.disposed)return;let found=await discoverMachine(entry.alias,entry);if(this.disposed)return;let sockets=found.ok?{daemon:found.status.socketPath,pty:found.status.ptySocketPath}:entry.sockets;if(!sockets){this.setStatus(entry.alias,{state:"offline",error:found.ok?void 0:found.message});return}if(found.ok)updateMachine(entry.alias,{sockets});let tunnel=startTunnel({alias:entry.alias,config:entry,remoteDaemonSocket:sockets.daemon,remotePtySocket:sockets.pty});if(slot.tunnel=tunnel,tunnel.state()==="unsupported"){this.setStatus(entry.alias,{state:"unsupported",error:"SSH unix-socket forwarding is not available on Windows"});return}if(tunnel.onState((state)=>{if(state==="online")this.openOrchestrator(entry,tunnel);else if(state==="offline")this.setStatus(entry.alias,{state:"offline"}),this.republishTasks()}),tunnel.state()==="online")await this.openOrchestrator(entry,tunnel)}async openOrchestrator(entry,tunnel){let slot=this.slots.get(entry.alias);if(!slot||this.disposed||slot.orchestrator)return;let client=new RoveDaemonClient(tunnel.daemonSocketPath),orchestrator=new RemoteOrchestrator(client,{role:"gui",expectForeignHome:!0,onPeerIdentity:(peer)=>this.onPeerIdentity(entry.alias,peer)});slot.orchestrator=orchestrator;try{await orchestrator.init()}catch(err){let message=err instanceof Error?err.message:String(err);this.setStatus(entry.alias,{state:classifyHandshakeFailure(message),error:message}),logClientError("machines",err);return}let stopTasks=orchestrator.tasksSignal().subscribe(()=>{slot.tasks=orchestrator.tasksSignal().get(),this.republishTasks()}),stopConnection=orchestrator.connectionStateSignal().subscribe(()=>{let connected=orchestrator.connectionStateSignal().get()==="online";if(this.slots.get(entry.alias)!==slot)return;this.setStatus(entry.alias,{state:connected?"online":"offline"}),this.republishTasks()});slot.unsubscribe=()=>{stopTasks(),stopConnection()},slot.tasks=orchestrator.tasksSignal().get(),this.setStatus(entry.alias,{state:"online",version:orchestrator.daemonVersionSignal().get()??void 0}),this.republishTasks(),logClient("machines",`${entry.alias} online (${slot.tasks.length} tasks)`)}onPeerIdentity(alias,peer){if(!peer.hostname&&!peer.homeDir)return;let identity={hostname:peer.hostname,homeDir:peer.homeDir,daemonPid:peer.daemonPid};setMachineIdentity(alias,identity);let duplicate=duplicateAliasOf(readMachines(loadStateFile()),alias,identity);this.setStatus(alias,{hostname:peer.hostname||void 0,version:peer.roveVersion||void 0,...duplicate?{duplicateOf:duplicate}:{}})}setStatus(alias,patch){let next=this.machinesAcc.get().map((row)=>row.alias===alias?{...row,...patch}:row);this.machinesAcc.set(next)}publishStatuses(){this.machinesAcc.set([...this.slots.values()].map((slot)=>({alias:slot.entry.alias,hostLabel:slot.entry.alias,state:"connecting"})))}republishTasks(){let merged=this.local.tasksSignal().get().map((task)=>stampOrigin(task,"local","local"));for(let slot of this.slots.values()){let status=this.machinesAcc.get().find((row)=>row.alias===slot.entry.alias);if(status?.duplicateOf)continue;let hostLabel=status?.hostLabel||slot.entry.alias,stale=status?.state!=="online";for(let task of slot.tasks)merged.push(stampOrigin(task,slot.entry.alias,hostLabel,stale))}this.mergedAcc.set(merged)}dispose(){this.disposed=!0,this.localUnsubscribe?.();for(let slot of this.slots.values())slot.unsubscribe?.(),slot.orchestrator?.dispose(),slot.tunnel?.stop();this.slots.clear()}}function classifyHandshakeFailure(message){return/protocol v\d/i.test(message)?"mismatch":"offline"}function stampOrigin(task,machineId,hostLabel,stale=!1){return{...task,origin:{machineId,hostLabel,...stale?{stale:!0}:{}}}}export{classifyHandshakeFailure,MachineHub};