aws-runtime-bridge 1.9.171 → 1.9.175

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 (74) hide show
  1. package/CHANGELOG.md +171 -0
  2. package/dist/adapter/AcodeSdkAdapter.d.ts +11 -13
  3. package/dist/adapter/AcodeSdkAdapter.js +3 -3
  4. package/dist/adapter/ClaudeSdkAdapter.d.ts +2 -0
  5. package/dist/adapter/ClaudeSdkAdapter.js +3 -3
  6. package/dist/adapter/types.d.ts +6 -7
  7. package/dist/index.js +1 -1
  8. package/dist/routes/bridge-temp-files.d.ts +1 -0
  9. package/dist/routes/bridge-temp-files.js +2 -0
  10. package/dist/routes/dashboard.js +35 -5
  11. package/dist/routes/file-browser.d.ts +4 -10
  12. package/dist/routes/file-browser.js +1 -1
  13. package/dist/routes/git.d.ts +42 -0
  14. package/dist/routes/git.js +34 -34
  15. package/dist/routes/sessions.js +1 -1
  16. package/dist/routes/tech-stack.d.ts +2 -0
  17. package/dist/routes/tech-stack.js +2 -0
  18. package/dist/routes/terminal.d.ts +8 -0
  19. package/dist/routes/terminal.js +27 -27
  20. package/dist/services/agent-pty-manager.d.ts +2 -0
  21. package/dist/services/agent-pty-manager.js +5 -4
  22. package/dist/services/agent-startup-memory.js +4 -4
  23. package/dist/services/bridge-temp-files.d.ts +36 -0
  24. package/dist/services/bridge-temp-files.js +2 -0
  25. package/dist/services/fileSnapshotStore.d.ts +9 -0
  26. package/dist/services/fileSnapshotStore.js +1 -1
  27. package/dist/services/gitignore.d.ts +12 -0
  28. package/dist/services/gitignore.js +2 -0
  29. package/dist/services/log-retention.js +1 -1
  30. package/dist/services/open-doc-events.d.ts +21 -0
  31. package/dist/services/open-doc-events.js +2 -0
  32. package/dist/services/open-doc-registry.d.ts +24 -0
  33. package/dist/services/open-doc-registry.js +2 -0
  34. package/dist/services/request-invalid-dump.js +1 -1
  35. package/dist/services/runtime-resources-snapshot.js +2 -2
  36. package/dist/services/session-output.d.ts +2 -0
  37. package/dist/services/session-output.js +2 -2
  38. package/dist/services/snapshot-retention.d.ts +7 -0
  39. package/dist/services/snapshot-retention.js +2 -0
  40. package/dist/services/tech-stack-scanner.d.ts +31 -0
  41. package/dist/services/tech-stack-scanner.js +2 -0
  42. package/dist/services/tunnel-client.d.ts +42 -0
  43. package/dist/services/tunnel-client.js +1 -1
  44. package/dist/services/workspaceTaskChangeWatcher.js +2 -2
  45. package/package/acode/dist/__tests__/summary-request-helper.d.ts +2 -0
  46. package/package/acode/dist/__tests__/summary-request-helper.js +2 -0
  47. package/package/acode/dist/ask-user-question-tool.d.ts +11 -1
  48. package/package/acode/dist/ask-user-question-tool.js +3 -3
  49. package/package/acode/dist/built-in-file-tools.js +2 -2
  50. package/package/acode/dist/cache-control.d.ts +26 -0
  51. package/package/acode/dist/cache-control.js +2 -0
  52. package/package/acode/dist/compaction.d.ts +174 -32
  53. package/package/acode/dist/compaction.js +22 -6
  54. package/package/acode/dist/microcompact.d.ts +57 -0
  55. package/package/acode/dist/microcompact.js +2 -0
  56. package/package/acode/dist/runtime-environment.js +1 -1
  57. package/package/acode/dist/runtime.d.ts +17 -4
  58. package/package/acode/dist/runtime.js +50 -40
  59. package/package/acode/dist/token-estimator.d.ts +15 -0
  60. package/package/acode/dist/token-estimator.js +2 -0
  61. package/package/acode/dist/types.d.ts +14 -1
  62. package/package/aws-client-agent-mcp/dist/ability-market-tools.js +1 -1
  63. package/package/aws-client-agent-mcp/dist/agent-client.d.ts +15 -2
  64. package/package/aws-client-agent-mcp/dist/agent-client.js +1 -1
  65. package/package/aws-client-agent-mcp/dist/mcp-server.d.ts +5 -0
  66. package/package/aws-client-agent-mcp/dist/mcp-server.js +25 -23
  67. package/package/aws-client-agent-mcp/dist/mcp-tools.js +7 -7
  68. package/package/aws-client-agent-mcp/dist/memory-store.d.ts +21 -2
  69. package/package/aws-client-agent-mcp/dist/memory-store.js +27 -2
  70. package/package/aws-client-agent-mcp/dist/memory-tools.js +13 -9
  71. package/package/aws-client-agent-mcp/dist/server-memory-store.d.ts +1 -1
  72. package/package/aws-client-agent-mcp/dist/server-memory-store.js +1 -1
  73. package/package/aws-client-agent-mcp/dist/types.d.ts +51 -0
  74. package/package.json +1 -1
@@ -1,37 +1,37 @@
1
- import{spawn as st}from"node:child_process";import{promises as b}from"node:fs";import ot from"node:os";import x from"node:path";import L from"axios";import{Router as it}from"express";import{validateToken as k}from"../middleware/auth.js";import{noteDashboardActivity as at}from"./dashboard.js";import{logger as m}from"../utils/logger.js";const S=it(),nt=1e4;function ct(){const r=Number(process.env.AWS_RUNTIME_GIT_COMMAND_TIMEOUT_MS);return Number.isFinite(r)&&r>=1e3?r:nt}async function ut(r){let t;try{t=await b.stat(r)}catch(e){const i=e;throw i.code==="ENOENT"?new Error(`workspace path does not exist: ${r}`):new Error(`workspace path is not accessible: ${r}${i.message?` (${i.message})`:""}`)}if(!t.isDirectory())throw new Error(`workspace path is not a directory: ${r}`)}const lt=/^[0-9a-f]{40}$/i;function ft(r,t,e,i){return r==="added"||r==="deleted"||r==="renamed"?!0:i||t>0||e>0}function ht(r){const t=String(r||"").toLowerCase();return t.includes("you do not have the initial commit yet")||t.includes("bad revision")&&t.includes("head")||t.includes("ambiguous argument")&&t.includes("head")}function dt(){return"\u5F53\u524D Git \u4ED3\u5E93\u8FD8\u6CA1\u6709\u521D\u59CB\u63D0\u4EA4\uFF0C\u65E0\u6CD5\u521B\u5EFA\u5FEB\u7167\u3002\u8BF7\u5148\u63D0\u4EA4\u4E00\u6B21\u521D\u59CB\u63D0\u4EA4\u540E\u518D\u521B\u5EFA\u5FEB\u7167\u3002"}function pt(r){const t=String(r||"").toLowerCase();return t.includes("no local changes to save")||t.includes("no changes to save")||t.includes("\u6CA1\u6709\u672C\u5730\u53D8\u66F4\u9700\u8981\u4FDD\u5B58")}function mt(r){const t=String(r||"").trim();return lt.test(t)?`refs/agentswork/stash-snapshots/${t.toLowerCase()}`:null}function _(r){const t=String(r||"").trim();if(!t)return null;const e=t.split("\0");if(e.length>=3){const a=e[0]?.trim(),o=e[1]?.trim(),c=e[2]?.trim()||o,u=e[3]?.trim()||null;if(!a||!o)return null;const l=O(c||"");return{ref:a,stashName:o,message:l.message,branch:l.branch,createdAt:u}}const i=t.match(/^(stash@\{\d+\})(?::\s*)?(.*)$/);if(!i)return null;const s=i[1],n=O(i[2]?.trim()||s);return{ref:s,stashName:s,message:n.message,branch:n.branch,createdAt:null}}function O(r){const t=String(r||"").trim(),e=t.match(/^(?:On|WIP on)\s+([^:]+):\s*(.*)$/);return e?{branch:e[1].trim(),message:e[2].trim()||t}:{branch:"",message:t}}function I(r){const t=String(r||"").split(`
2
- `).find(i=>i.trim());if(!t)return null;const e=_(t);return e?{ref:e.ref,stashName:e.stashName||e.ref,message:e.message||e.ref}:null}async function h(r,t,e){return new Promise(i=>{let s=!1,n=null;const a=f=>{s||(s=!0,n&&clearTimeout(n),i(f))},o=st("git",t,{cwd:r,stdio:["ignore","pipe","pipe"]});let c="",u="";o.stdout?.on("data",f=>{c+=String(f||"")}),o.stderr?.on("data",f=>{u+=String(f||"")});const l=e??ct();n=setTimeout(()=>{m.warn(`[runtime-bridge] git command timed out after ${l}ms: git ${t.join(" ")} (cwd=${r})`),o.kill("SIGKILL"),a({stdout:c,stderr:u||`git command timed out after ${l}ms`,exitCode:124})},l),o.on("error",f=>{a({stdout:"",stderr:f.message,exitCode:1})}),o.on("close",f=>{a({stdout:c,stderr:u,exitCode:f??1})})})}const gt=6e4;function yt(){const r=Number(process.env.AWS_RUNTIME_GIT_PUSH_TIMEOUT_MS);return Number.isFinite(r)&&r>=1e3?r:gt}async function z(r,t){const e=mt(t);if(!e)return null;const i=await h(r,["update-ref",e,t]);return i.exitCode!==0?(m.warn(`[Git] \u6301\u4E45\u5316 stash \u5FEB\u7167 ref \u5931\u8D25: ref=${e}, error=${i.stderr||i.stdout}`),null):e}function R(r){return String(r||"").replace(/\\/g,"/").replace(/^\.\//,"").trim()}async function j(r){const t=x.resolve(String(r).trim());await ut(t);const e=await h(t,["rev-parse","--show-toplevel"]);if(e.exitCode!==0)return{workspacePath:t,repositoryRootPath:null,hasRepository:!1,isGitRepo:!1,usingParentGitRepo:!1,relativeWorkspacePath:""};const i=x.resolve(e.stdout.trim()),s=i===t,n=s?"":R(x.relative(i,t));return{workspacePath:t,repositoryRootPath:i,hasRepository:!0,isGitRepo:s,usingParentGitRepo:!s,relativeWorkspacePath:n}}function A(r,t){if(!t.relativeWorkspacePath)return[...r];const e=!r.includes("status");return[...r,...e?[`--relative=${t.relativeWorkspacePath}`]:[],"--",t.relativeWorkspacePath]}function E(r,t){const e=R(r);return t.relativeWorkspacePath?R(x.posix.join(t.relativeWorkspacePath,e)):e}function M(r){const t=R(r);if(x.posix.isAbsolute(t)||t.split("/").some(e=>e===".."||e==="."))throw new Error(`unsafe repository relative path: ${r}`)}function Pt(r){const t=String(r||"").trim().toUpperCase();return t.startsWith("A")?"added":t.startsWith("D")?"deleted":t.startsWith("R")?"renamed":"modified"}function F(r){const t=R(r);if(!t.includes("=>"))return t;const e=t.match(/^(.*)\{([^{}]+)=>([^{}]+)\}(.*)$/);if(e){const s=e[1],n=e[3],a=e[4];return R(`${s}${n}${a}`)}const i=t.lastIndexOf("=>");return i>=0?R(t.slice(i+2)):t}function wt(r){const t=new Set,e=String(r||"").split(`
3
- `).filter(i=>i.trim());for(const i of e){const s=i[0]||" ";if(s===" "||s==="?")continue;const n=i.slice(3).trim(),a=n.split(" -> "),o=F(a[a.length-1]||n);o&&t.add(o)}return t}function H(r,t,e=new Set,i=!1){const s=new Map,n=r.split(`
4
- `).filter(c=>c.trim());for(const c of n){const u=c.split(" ");if(u.length<2)continue;const l=Pt(u[0]),f=l==="renamed"&&u.length>=3?2:1,d=F(u[f]);d&&s.set(d,l)}const a=[],o=t.split(`
5
- `).filter(c=>c.trim());for(const c of o){const u=c.split(" ");if(u.length<3)continue;const l=u[0]==="-"||u[1]==="-",f=u[0]==="-"?0:parseInt(u[0],10)||0,d=u[1]==="-"?0:parseInt(u[1],10)||0,p=F(u.slice(2).join(" ").trim()),y=s.get(p)||"modified";!p||!ft(y,f,d,l)||a.push({path:p,status:y,additions:f,deletions:d,staged:i||e.has(p)})}return a}function kt(r){const t=[],e=String(r||"").split(`
6
- `).filter(i=>i.trim());for(const i of e)if(i.startsWith("?? ")){const s=i.slice(3).trim();s&&t.push(s.replace(/\/$/,""))}return t}function St(r){if(!r||r.includes("\0"))return 0;let t=0;for(let e=0;e<r.length;e++)r.charCodeAt(e)===10&&t++;return r.endsWith(`
7
- `)?t:t+1}async function q(r){try{const t=await b.readFile(r,"utf8");return St(t)}catch{return 0}}async function Rt(r,t,e,i=10){const s=[];async function n(a,o){if(o>i)return;const c=x.join(r,a);let u;try{u=await b.readdir(c,{withFileTypes:!0})}catch{return}for(const l of u){if(l.name===".git"||l.name==="node_modules")continue;const f=a?`${a}/${l.name}`:l.name;if(l.isDirectory())await n(f,o+1);else if(l.isFile()){const d=R(f);d&&!e.has(d)&&s.push({path:d,status:"added",additions:await q(x.join(c,l.name)),deletions:0,staged:!1})}}}for(const a of t){const o=R(a);if(!o)continue;const c=x.join(r,a);let u;try{u=await b.stat(c)}catch{continue}u.isFile()?e.has(o)||s.push({path:o,status:"added",additions:await q(c),deletions:0,staged:!1}):u.isDirectory()&&await n(o,0)}return s}function xt(r,t){const e=new Map;for(const i of r)e.set(i.path,{...i});for(const i of t){const s=e.get(i.path);if(!s){e.set(i.path,{...i,staged:!0});continue}e.set(i.path,{...s,status:s.status==="modified"?i.status:s.status,additions:s.additions+i.additions,deletions:s.deletions+i.deletions,staged:!0})}return[...e.values()]}function B(r){return String(r||"").trim()}function jt(r){return String(r||"").trim()}function Ct(r){return["--",r.relativeWorkspacePath||"."]}function G(r,t){return["--",E(r,t)]}function bt(r){const t=r.split("\0"),e=[];for(let i=0;i+6<t.length;i+=7){const s=t[i]?.trim();if(!s)continue;const n=(t[i+5]?.trim()||"").split(/\s+/).filter(Boolean);e.push({hash:s,shortHash:t[i+1]?.trim()||s.slice(0,8),authorName:t[i+2]?.trim()||"unknown",authorDate:t[i+3]?.trim()||"",subject:t[i+4]?.trim()||s,body:t[i+6]?.trim()||"",parents:n})}return e}async function U(r){const t=new Set,e=await h(r,["for-each-ref","--format=%(refname)","refs/remotes"]);if(e.exitCode!==0)return t;const i=e.stdout.split(`
8
- `).map(n=>n.trim()).filter(Boolean);if(i.length===0)return t;const s=await h(r,["rev-list",...i]);if(s.exitCode!==0)return t;for(const n of s.stdout.split(`
9
- `)){const a=n.trim();a&&t.add(a)}return t}async function N(r){const t=await h(r,["rev-parse","--abbrev-ref","HEAD"]);return t.exitCode!==0||!t.stdout.trim()?"":t.stdout.trim()}async function J(r){const t=await h(r,["for-each-ref","--format=%(refname)%00%(objectname)%00%(HEAD)%00%(upstream:short)","refs/heads","refs/remotes"]);if(t.exitCode!==0)return[];const e=[];for(const i of t.stdout.split(`
10
- `)){const s=i.split("\0"),n=s[0]?.trim(),a=s[1]?.trim();if(!n||!a)continue;const o=n.startsWith("refs/remotes/"),c=o?n.slice(13):n.slice(11);e.push({name:c,ref:n,hash:a,isCurrent:s[2]?.trim()==="*",isRemote:o,upstream:s[3]?.trim()||""})}return e}function $t(r,t){return["diff",t?r:`${r}^!`]}async function vt(r){const t=await h(r,["rev-parse","--abbrev-ref","HEAD"]),e=t.stdout.trim();if(t.exitCode!==0||!e||e==="HEAD")throw new Error("\u5F53\u524D\u5904\u4E8E detached HEAD\uFF08\u5206\u79BB\u5934\u6307\u9488\uFF09\u72B6\u6001\uFF0C\u65E0\u6CD5\u63A8\u9001");const i=await h(r,["rev-parse","--abbrev-ref","--symbolic-full-name","@{u}"]);if(i.exitCode!==0||!i.stdout.trim())throw new Error(`\u5206\u652F ${e} \u672A\u914D\u7F6E\u4E0A\u6E38\u5206\u652F\uFF0C\u65E0\u6CD5\u63A8\u9001\u3002\u8BF7\u5148\u624B\u52A8\u6267\u884C\u4E00\u6B21 git push -u origin ${e} \u8BBE\u7F6E\u4E0A\u6E38\u5206\u652F`);const s=await h(r,["push"],yt());if(s.exitCode!==0)throw new Error(s.stderr||s.stdout||"git push failed");const n=`${s.stdout}
11
- ${s.stderr}`.trim(),a=/everything up-to-date|up to date/i.test(n);return{branch:e,pushed:!a,message:a?"\u6CA1\u6709\u9700\u8981\u63A8\u9001\u7684\u63D0\u4EA4":"\u63A8\u9001\u5B8C\u6210",output:n}}S.post("/git/check",k,async(r,t)=>{const{workspacePath:e}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const i=await j(String(e).trim());t.json({ok:!0,isGitRepo:i.isGitRepo,hasRepository:i.hasRepository,usingParentGitRepo:i.usingParentGitRepo,workspacePath:i.workspacePath,repositoryRootPath:i.repositoryRootPath,relativeWorkspacePath:i.relativeWorkspacePath});return}catch(i){const s=i;t.status(500).json({error:s.message||"check git repo failed"})}});async function At(r,t){const e=`\u5FEB\u7167\uFF08${new Date().toLocaleString("zh-CN",{year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit"})}\uFF09`,i=t&&String(t).trim()?`\u5FEB\u7167: ${String(t).trim()}`:e,s=await h(r,["stash","push","-m",i]);if(pt(`${s.stdout}
12
- ${s.stderr}`)){const u=await h(r,["stash","list","-n","1","--format=%H%x00%gd%x00%gs%x00%cr"]),l=u.exitCode===0?I(u.stdout):null;return{stashRef:(l?await z(r,l.ref):null)??l?.ref??null,stashName:l?.stashName??null,noChanges:!0,reusedLatestStash:!!l,message:l?`\u6CA1\u6709\u672C\u5730\u53D8\u66F4\u9700\u8981\u4FDD\u5B58\uFF0C\u5DF2\u590D\u7528\u6700\u8FD1\u5FEB\u7167 ${l.stashName}`:"\u6CA1\u6709\u672C\u5730\u53D8\u66F4\u9700\u8981\u4FDD\u5B58"}}if(s.exitCode!==0)throw ht(`${s.stdout}
13
- ${s.stderr}`)?new Error(dt()):new Error(s.stderr||"git stash push failed");const n=await h(r,["stash","list","-n","1","--format=%H%x00%gd%x00%gs%x00%cr"]),a=n.exitCode===0?I(n.stdout):null;return{stashRef:(a?await z(r,a.ref):null)??a?.ref??"stash@{0}",stashName:a?.stashName??null,noChanges:!1,reusedLatestStash:!1,message:i}}S.post("/git/stash/create",k,async(r,t)=>{const{workspacePath:e,message:i}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const s=x.resolve(String(e).trim()),n=await At(s,i?String(i):void 0);t.json({ok:!0,stashRef:n.stashRef,stashName:n.stashName,message:n.message,noChanges:n.noChanges,reusedLatestStash:n.reusedLatestStash,workspacePath:s})}catch(s){const n=s;t.status(400).json({error:n.message||"create stash failed"})}}),S.post("/git/stash/list",k,async(r,t)=>{const{workspacePath:e,limit:i=20}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const s=x.resolve(String(e).trim()),n=Math.min(Math.max(1,Number(i)||20),100),a=await h(s,["stash","list","-n",String(n),"--format=%H%x00%gd%x00%gs%x00%cr"]);if(a.exitCode!==0){t.status(400).json({error:a.stderr||"git stash list failed"});return}const o=a.stdout.split(`
14
- `).map(_).filter(c=>c!==null);t.json({ok:!0,stashes:o,count:o.length,workspacePath:s})}catch(s){const n=s;t.status(500).json({error:n.message||"list stash failed"})}});async function K(r,t,e){const{workspacePath:i,ref:s}=r.body||{};if(!i||!String(i).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const n=x.resolve(String(i).trim()),a=s&&String(s).trim()?String(s).trim():"stash@{0}";await h(n,["checkout","--","."]);const o=await h(n,["stash",e,a]);if(o.exitCode!==0){if(o.stdout.includes("CONFLICT")||o.stderr.includes("CONFLICT")){t.json({ok:!1,hasConflict:!0,message:"\u6062\u590D\u65F6\u53D1\u751F\u51B2\u7A81\uFF0C\u8BF7\u624B\u52A8\u89E3\u51B3",details:o.stdout||o.stderr,workspacePath:n});return}if(o.stderr.includes("is not a valid reference")){t.status(404).json({error:`\u5FEB\u7167 ${a} \u4E0D\u5B58\u5728`});return}t.status(400).json({error:o.stderr||`git stash ${e} failed`});return}t.json({ok:!0,stashRef:a,message:e==="apply"?`\u5DF2\u5E94\u7528 ${a}`:`\u5DF2\u6062\u590D ${a}`,preservedStash:e==="apply",workspacePath:n})}catch(n){const a=n;t.status(500).json({error:a.message||`${e} stash failed`})}}S.post("/git/stash/apply",k,async(r,t)=>{await K(r,t,"apply")}),S.post("/git/stash/pop",k,async(r,t)=>{await K(r,t,"pop")}),S.post("/git/stash/drop",k,async(r,t)=>{const{workspacePath:e,ref:i}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const s=x.resolve(String(e).trim()),n=i&&String(i).trim()?String(i).trim():"stash@{0}",a=await h(s,["stash","drop",n]);if(a.exitCode!==0){if(a.stderr.includes("is not a valid reference")){t.status(404).json({error:`\u5FEB\u7167 ${n} \u4E0D\u5B58\u5728`});return}t.status(400).json({error:a.stderr||"git stash drop failed"});return}t.json({ok:!0,stashRef:n,message:`\u5DF2\u5220\u9664 ${n}`,workspacePath:s})}catch(s){const n=s;t.status(500).json({error:n.message||"drop stash failed"})}}),S.post("/git/diff",k,async(r,t)=>{const{workspacePath:e,repositoryPath:i}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const s=await j(String(i||e).trim());if(!s.hasRepository||!s.repositoryRootPath){t.json({ok:!0,isGitRepo:s.isGitRepo,hasRepository:!1,usingParentGitRepo:!1,hasChanges:!1,files:[],workspacePath:s.workspacePath,repositoryRootPath:null,relativeWorkspacePath:""});return}const n=await h(s.repositoryRootPath,A(["diff","--name-status"],s));if(n.exitCode!==0){t.status(400).json({error:n.stderr||"git diff failed"});return}const a=await h(s.repositoryRootPath,A(["diff","--numstat"],s));if(a.exitCode!==0){t.status(400).json({error:a.stderr||"git diff failed"});return}const o=await h(s.repositoryRootPath,A(["diff","--cached","--name-status"],s));if(o.exitCode!==0){t.status(400).json({error:o.stderr||"git staged diff failed"});return}const c=await h(s.repositoryRootPath,A(["diff","--cached","--numstat"],s));if(c.exitCode!==0){t.status(400).json({error:c.stderr||"git staged diff failed"});return}const u=await h(s.repositoryRootPath,A(["status","--porcelain=v1"],s));if(u.exitCode!==0){t.status(400).json({error:u.stderr||"git status failed"});return}const l=wt(u.stdout),f=xt(H(n.stdout,a.stdout,l),H(o.stdout,c.stdout,l,!0)),d=kt(u.stdout);if(d.length>0){const p=new Set(f.map(g=>g.path)),y=await Rt(s.repositoryRootPath,d,p);f.push(...y)}t.json({ok:!0,isGitRepo:s.isGitRepo,hasRepository:!0,usingParentGitRepo:s.usingParentGitRepo,hasChanges:f.length>0,files:f,workspacePath:s.workspacePath,repositoryRootPath:s.repositoryRootPath,relativeWorkspacePath:s.relativeWorkspacePath})}catch(s){const n=s;t.status(500).json({error:n.message||"git diff failed"})}});async function Y(r){const t=[],e=await h(r,["config","user.name"]);(e.exitCode!==0||!e.stdout.trim())&&t.push("-c","user.name=AgentsWork");const i=await h(r,["config","user.email"]);return(i.exitCode!==0||!i.stdout.trim())&&t.push("-c","user.email=agent@agentswork.com"),t}async function Et(r,t,e){if(!t)throw new Error("commit message is required");if(e.length===0)throw new Error("filePaths must be valid workspace-relative paths");const i=e.map(c=>`:(literal)${c}`),s=await h(r,["add","--",...i]);if(s.exitCode!==0)throw new Error(s.stderr||"git add failed");const n=await h(r,["diff","--cached","--quiet","--",...i]);if(n.exitCode===0)throw new Error("\u6240\u9009\u6587\u4EF6\u6CA1\u6709\u53EF\u63D0\u4EA4\u7684\u53D8\u66F4");if(n.exitCode!==1)throw new Error(n.stderr||"git staged diff failed");const a=await Y(r),o=await h(r,[...a,"commit","-m",t,"--",...i]);if(o.exitCode!==0)throw new Error(o.stderr||o.stdout||"git commit failed");return{committedPaths:[...e],output:o.stdout}}S.post("/git/commit",k,async(r,t)=>{const{workspacePath:e,repositoryPath:i,message:s,filePaths:n}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}const a=jt(s);if(!a){t.status(400).json({error:"commit message is required"});return}try{const o=await j(String(i||e).trim());if(!o.hasRepository||!o.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}const c=Array.isArray(n)?n.map(y=>R(String(y||"").trim())).filter(Boolean):[];if(c.length>0){const y=c.map(w=>E(w,o)),g=[];for(const w of y)if(w)try{M(w),g.push(w)}catch{t.status(400).json({error:`unsafe file path: ${w}`});return}if(g.length===0){t.status(400).json({error:"filePaths must be valid workspace-relative paths"});return}try{const w=await Et(o.repositoryRootPath,a,g);t.json({ok:!0,message:"\u63D0\u4EA4\u5B8C\u6210",committedPaths:c,output:w.output,workspacePath:o.workspacePath,repositoryRootPath:o.repositoryRootPath,relativeWorkspacePath:o.relativeWorkspacePath})}catch(w){const P=w;t.status(400).json({error:P.message||"git commit failed"})}return}const u=await Y(o.repositoryRootPath),l=Ct(o),f=await h(o.repositoryRootPath,["add","--all",...l]);if(f.exitCode!==0){t.status(400).json({error:f.stderr||"git add failed"});return}const d=await h(o.repositoryRootPath,["diff","--cached","--quiet",...l]);if(d.exitCode===0){t.status(400).json({error:"\u6CA1\u6709\u53EF\u63D0\u4EA4\u7684\u53D8\u66F4"});return}if(d.exitCode!==1){t.status(400).json({error:d.stderr||"git staged diff failed"});return}const p=await h(o.repositoryRootPath,[...u,"commit","-m",a,...l]);if(p.exitCode!==0){t.status(400).json({error:p.stderr||p.stdout||"git commit failed"});return}t.json({ok:!0,message:"\u63D0\u4EA4\u5B8C\u6210",output:p.stdout,workspacePath:o.workspacePath,repositoryRootPath:o.repositoryRootPath,relativeWorkspacePath:o.relativeWorkspacePath})}catch(o){const c=o;t.status(500).json({error:c.message||"git commit failed"})}}),S.post("/git/add-file",k,async(r,t)=>{const{workspacePath:e,repositoryPath:i,filePath:s}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}const n=R(String(s||"").trim());if(!n){t.status(400).json({error:"filePath is required"});return}try{const a=await j(String(i||e).trim());if(!a.hasRepository||!a.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}const o=await h(a.repositoryRootPath,["add",...G(n,a)]);if(o.exitCode!==0){t.status(400).json({error:o.stderr||"git add file failed"});return}t.json({ok:!0,filePath:n,workspacePath:a.workspacePath,repositoryRootPath:a.repositoryRootPath,relativeWorkspacePath:a.relativeWorkspacePath})}catch(a){const o=a;t.status(500).json({error:o.message||"git add file failed"})}}),S.post("/git/add-files",k,async(r,t)=>{const{workspacePath:e,repositoryPath:i,filePaths:s}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}const a=(Array.isArray(s)?s:[]).map(o=>R(String(o||"").trim())).filter(o=>o.length>0);if(a.length===0){t.status(400).json({error:"filePaths is required and must be a non-empty array"});return}try{const o=await j(String(i||e).trim());if(!o.hasRepository||!o.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}const c=a.flatMap(d=>G(d,o)),u=["add"],l=[];for(let d=0;d<c.length;d++)c[d]!=="--"&&l.push(c[d]);u.push("--",...l);const f=await h(o.repositoryRootPath,u);if(f.exitCode!==0){t.status(400).json({error:f.stderr||"git add files failed"});return}t.json({ok:!0,filePaths:a,workspacePath:o.workspacePath,repositoryRootPath:o.repositoryRootPath,relativeWorkspacePath:o.relativeWorkspacePath})}catch(o){const c=o;t.status(500).json({error:c.message||"git add files failed"})}}),S.post("/git/commits",k,async(r,t)=>{const{workspacePath:e,repositoryPath:i,limit:s,branch:n}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const a=await j(String(i||e).trim());if(!a.hasRepository||!a.repositoryRootPath){t.json({ok:!0,commits:[],currentBranch:"",branchName:null,workspacePath:a.workspacePath,repositoryRootPath:null});return}const o=Number(s),c=!!r.body?.all,u=c?200:100,l=Number.isInteger(o)&&o>0&&o<=u?o:c?100:30,f=await N(a.repositoryRootPath),p=(n&&String(n).trim()?String(n).trim():null)||f||"HEAD",y="%H%x00%h%x00%an%x00%ad%x00%s%x00%P%x00%b%x00";let g;if(c?g=await h(a.repositoryRootPath,["log",`-${l}`,"--all","--topo-order","--date=iso-strict",`--pretty=format:${y}`]):g=await h(a.repositoryRootPath,["log",`-${l}`,"--date=iso-strict",`--pretty=format:${y}`,"--end-of-options",p,"--",a.relativeWorkspacePath||"."]),g.exitCode!==0){t.status(400).json({error:g.stderr||"git log failed"});return}const w=await U(a.repositoryRootPath),P=bt(g.stdout).map(v=>({...v,isRemote:w.has(v.hash)})),C=c?await J(a.repositoryRootPath):void 0;t.json({ok:!0,commits:P,currentBranch:f,branchName:c?null:p,...C?{branches:C}:{},workspacePath:a.workspacePath,repositoryRootPath:a.repositoryRootPath,relativeWorkspacePath:a.relativeWorkspacePath})}catch(a){const o=a;t.status(500).json({error:o.message||"git commits failed"})}}),S.post("/git/branches",k,async(r,t)=>{const{workspacePath:e,repositoryPath:i}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const s=await j(String(i||e).trim());if(!s.hasRepository||!s.repositoryRootPath){t.json({ok:!0,branches:[],currentBranch:"",workspacePath:s.workspacePath,repositoryRootPath:null});return}const n=await J(s.repositoryRootPath),a=await N(s.repositoryRootPath);t.json({ok:!0,branches:n,currentBranch:a,workspacePath:s.workspacePath,repositoryRootPath:s.repositoryRootPath,relativeWorkspacePath:s.relativeWorkspacePath})}catch(s){const n=s;t.status(500).json({error:n.message||"git branches failed"})}}),S.post("/git/push",k,async(r,t)=>{const{workspacePath:e,repositoryPath:i}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const s=await j(String(i||e).trim());if(!s.hasRepository||!s.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}const n=await vt(s.repositoryRootPath);t.json({ok:!0,branch:n.branch,pushed:n.pushed,message:n.message,output:n.output,workspacePath:s.workspacePath,repositoryRootPath:s.repositoryRootPath,relativeWorkspacePath:s.relativeWorkspacePath})}catch(s){const n=s;if(n.message&&/上游分支|detached HEAD|分离头指针|不存在|未配置/.test(n.message)){t.status(400).json({error:n.message});return}t.status(500).json({error:n.message||"git push failed"})}});const Gt=/^[0-9a-fA-F]{7,40}$/;function Tt(r){const t=String(r||"").trim();return Gt.test(t)?t:null}async function V(r,t){const e=await N(r);if(!e||e==="HEAD")throw new Error("\u5F53\u524D\u5904\u4E8E detached HEAD\uFF08\u5206\u79BB\u5934\u6307\u9488\uFF09\u72B6\u6001\uFF0C\u65E0\u6CD5\u56DE\u9000");const i=await h(r,["rev-parse","--verify","--quiet",`${t}^{commit}`]);if(i.exitCode!==0||!i.stdout.trim())throw new Error("\u76EE\u6807\u63D0\u4EA4\u4E0D\u5B58\u5728\uFF0C\u8BF7\u5237\u65B0\u63D0\u4EA4\u5217\u8868\u540E\u91CD\u8BD5");const s=i.stdout.trim();if((await h(r,["rev-parse","HEAD"])).stdout.trim()===s)throw new Error("\u76EE\u6807\u63D0\u4EA4\u5DF2\u662F\u5F53\u524D HEAD\uFF0C\u65E0\u9700\u56DE\u9000");if((await h(r,["merge-base","--is-ancestor",s,"HEAD"])).exitCode!==0)throw new Error("\u76EE\u6807\u63D0\u4EA4\u4E0D\u5728\u5F53\u524D\u5206\u652F\u5386\u53F2\u4E2D\uFF0C\u65E0\u6CD5\u56DE\u9000");const o=await h(r,["rev-list","--count",`${s}..HEAD`]),c=parseInt(o.stdout.trim()||"0",10);let u=!1;const l=await U(r);if(l.size>0){const f=await h(r,["rev-list",`${s}..HEAD`]);if(f.exitCode===0){for(const d of f.stdout.split(`
15
- `))if(l.has(d.trim())){u=!0;break}}}return{resolvedHash:s,branch:e,resetCount:c,affectsRemote:u}}async function X(r){const t=await h(r,["status","--porcelain"]);return t.exitCode===0&&!!t.stdout.trim()}async function Dt(r,t,e){const i=await V(r,t),s=await X(r);return{mode:e,commitHash:i.resolvedHash,branch:i.branch,resetCount:i.resetCount,hasLocalChanges:s,affectsRemote:i.affectsRemote}}async function Mt(r,t,e,i=""){const s=await V(r,t),n=await X(r),a=e==="hard"?["reset","--hard",s.resolvedHash]:["reset","--soft",s.resolvedHash],o=await h(r,a);if(o.exitCode!==0)throw new Error(o.stderr||o.stdout||"git reset \u6267\u884C\u5931\u8D25");if(e==="hard"){const c=["clean","-fd"];i&&c.push("--",i);const u=await h(r,c);u.exitCode!==0&&m.warn(`[Git] \u786C\u56DE\u9000\u540E\u6E05\u7406\u672A\u8DDF\u8E2A\u6587\u4EF6\u5931\u8D25: cwd=${r}, scope=${i||"(\u6574\u4ED3)"}, error=${u.stderr||u.stdout}`)}return{mode:e,commitHash:s.resolvedHash,branch:s.branch,resetCount:s.resetCount,hasLocalChanges:n,affectsRemote:s.affectsRemote}}S.post("/git/reset",k,async(r,t)=>{const{workspacePath:e,repositoryPath:i,commitHash:s,mode:n,preview:a}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}const o=Tt(s);if(!o){t.status(400).json({error:"commitHash \u683C\u5F0F\u975E\u6CD5\uFF0C\u65E0\u6CD5\u56DE\u9000"});return}const c=String(n||"").trim();if(c!=="soft"&&c!=="hard"){t.status(400).json({error:"mode must be soft or hard"});return}const u=a===!0||a==="true";try{const l=await j(String(i||e).trim());if(!l.hasRepository||!l.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}if(u){const y=await Dt(l.repositoryRootPath,o,c);t.json({ok:!0,preview:!0,...y,workspacePath:l.workspacePath,repositoryRootPath:l.repositoryRootPath,relativeWorkspacePath:l.relativeWorkspacePath});return}const f=await Mt(l.repositoryRootPath,o,c,l.relativeWorkspacePath),d=f.commitHash.slice(0,7),p=f.mode==="hard"?`\u5DF2\u786C\u56DE\u9000\u5230 ${d}\uFF0C\u5171\u56DE\u9000 ${f.resetCount} \u4E2A\u63D0\u4EA4\uFF0C\u5DE5\u4F5C\u533A\u5DF2\u5B8C\u5168\u6062\u590D\u5230\u8BE5\u63D0\u4EA4\u72B6\u6001`:`\u5DF2\u8F6F\u56DE\u9000\u5230 ${d}\uFF0C\u5171\u56DE\u9000 ${f.resetCount} \u4E2A\u63D0\u4EA4\uFF0C\u5DE5\u4F5C\u533A\u6587\u4EF6\u4FDD\u6301\u4E0D\u53D8`;t.json({ok:!0,...f,message:p,workspacePath:l.workspacePath,repositoryRootPath:l.repositoryRootPath,relativeWorkspacePath:l.relativeWorkspacePath})}catch(l){const f=l;if(f.message&&/回退|快照|detached HEAD|分离头指针|不存在|无需|HEAD/.test(f.message)){t.status(400).json({error:f.message});return}t.status(500).json({error:f.message||"git reset failed"})}}),S.post("/git/commit-diff",k,async(r,t)=>{const{workspacePath:e,repositoryPath:i,commitHash:s,aggregate:n}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}const a=B(s);if(!a){t.status(400).json({error:"commitHash is required"});return}const o=n===!0||n==="true";try{const c=await j(String(i||e).trim());if(!c.hasRepository||!c.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}const u=$t(a,o),l=await h(c.repositoryRootPath,A([...u,"--name-status"],c));if(l.exitCode!==0){t.status(400).json({error:l.stderr||"git commit diff failed"});return}const f=await h(c.repositoryRootPath,A([...u,"--numstat"],c));if(f.exitCode!==0){t.status(400).json({error:f.stderr||"git commit diff failed"});return}const d=H(l.stdout,f.stdout);t.json({ok:!0,commitHash:a,isGitRepo:c.isGitRepo,hasRepository:!0,usingParentGitRepo:c.usingParentGitRepo,hasChanges:d.length>0,files:d,workspacePath:c.workspacePath,repositoryRootPath:c.repositoryRootPath,relativeWorkspacePath:c.relativeWorkspacePath})}catch(c){const u=c;t.status(500).json({error:u.message||"git commit diff failed"})}}),S.post("/git/diff-file",k,async(r,t)=>{const{workspacePath:e,filePath:i,commitHash:s,aggregate:n}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}if(!i||!String(i).trim()){t.status(400).json({error:"filePath is required"});return}try{const a=await j(String(e).trim()),o=R(String(i).trim());if(!a.hasRepository||!a.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}const c=E(o,a),u=B(s),l=!!(u&&(n===!0||n==="true")),f=!u||l,d=u?l?["diff",u,"--",c]:["show","--format=","--no-ext-diff",u,"--",c]:["diff","HEAD","--",c];let p=await h(a.repositoryRootPath,d);if(f&&p.exitCode!==0&&(p=await h(a.repositoryRootPath,["diff","--",c])),p.exitCode!==0){t.status(400).json({error:p.stderr||"git diff file failed"});return}if(!u&&!p.stdout.trim()){const g=await h(a.repositoryRootPath,["diff","--cached","--",c]);if(g.exitCode!==0){t.status(400).json({error:g.stderr||"git staged diff file failed"});return}p=g}if(f&&!p.stdout.trim()){const g=x.join(a.repositoryRootPath,c);try{const w=await b.readFile(g,"utf-8");if(w){const P=w.split(`
16
- `).map(v=>`+${v}`).join(`
17
- `),C=w.split(`
18
- `).length;p={stdout:[`diff --git a/${c} b/${c}`,"new file mode 100644","--- /dev/null",`+++ b/${c}`,`@@ -0,0 +1,${C} @@`,P].join(`
19
- `),stderr:"",exitCode:0}}}catch{}}M(c);let y="";if(f){const g=x.join(a.repositoryRootPath,c);try{y=await b.readFile(g,"utf-8")}catch{}}else{const g=await h(a.repositoryRootPath,["show",`${u}:${c}`]);g.exitCode===0&&(y=g.stdout)}t.json({ok:!0,filePath:o,repositoryRelativeFilePath:c,commitHash:u||null,rangeFromCommit:l,diffContent:p.stdout,fileContent:y,workspacePath:a.workspacePath,repositoryRootPath:a.repositoryRootPath,usingParentGitRepo:a.usingParentGitRepo})}catch(a){const o=a;t.status(500).json({error:o.message||"git diff file failed"})}});const Z=["STRICTLY act as a Git commit message generator. NEVER explain, narrate, or describe your task.",'NEVER start your reply with phrases like "\u6211\u4EEC\u88AB\u95EE\u5230", "\u4EE5\u4E0B\u662F", "\u597D\u7684", "Sure", "Here is", "I will", "I can", "Let me", "\u6839\u636E", "\u4E0B\u9762".',"NEVER include any meta-language or thinking out loud. Output ONLY the commit message text.","","Format (Conventional Commits, 1 line, optional body):"," <type>(<scope>): <description>","","Allowed types: feat, fix, refactor, test, docs, style, chore, perf, ci, build, revert.","First line MUST be <= 72 characters. <description> MUST be in Chinese.","Add a body (separated by a blank line) ONLY when the change needs more context."].join(`
20
- `);function Q(r){return["\u8BF7\u76F4\u63A5\u8F93\u51FA\u4E0A\u8FF0\u683C\u5F0F\u7684\u63D0\u4EA4\u4FE1\u606F,\u4E0D\u8981\u4EFB\u4F55\u989D\u5916\u6587\u5B57\u3002","\u4EE5\u4E0B\u662F\u6682\u5B58\u533A diff:","",r].join(`
21
- `)}const tt=/\b(?:feat|fix|chore|docs|style|refactor|perf|test|build|ci|revert)(?:\s*\([^)]*\))?\s*:/i,Ft=/^(?:feat|fix|chore|docs|style|refactor|perf|test|build|ci|revert)\s*:/i;function Ht(r){const t=r.split(`
22
- `)[0]?.trim()??"";if(!t||t.length<3)return!1;const e=(t.match(/[\u4e00-\u9fa5]/g)||[]).length,i=(t.match(/[a-zA-Z]{2,}/g)||[]).length;if(e===0&&i===0)return!1;const s=[/^以下是/,/^上面是/,/^生成的/,/^这是/,/^下面/,/^(?:Here|This|This is|That is)/i];for(const n of s)if(n.test(t))return!1;return e>=2||i>=2}const Nt=[/^\s*(?:我们|我)\s*(?:被\s*)?(?:问[到过]?|需要|要|将|来|先)[^.:\n]{0,60}?(?:提交信息|commit\s*message|[::,,。])/i,/^\s*(?:好的|好)\s*[,,::]/i,/^\s*(?:好的|好)\s*(?:以下是|上面是|提供|生成|给出)[^.:\n]{0,20}?[::]?/i,/^\s*是\s*[::]/i,/^\s*(?:Sure|Here(?:'s|\s+is)|We(?:'re| are)|I\s+(?:will|can|'ll)|Let\s+me|Certainly|Of\s+course)[^.:\n]{0,40}?[,,::.]/i,/^\s*根据\s*(?:您|你|您所|你所|所)?\s*(?:提供|上述|给[我您你])?\s*(?:的)?\s*diff\s*[,,::]?/i,/^\s*(?:以下|下面)[^,,::\n]{0,30}?提交信息\s*[,,::]?/i,/^\s*(?:以下|下面)\s*是\s*[,,::]/i,/^\s*生成\s*(?:一条|一个|了)?\s*(?:符合|新的)?\s*(?:conventional\s*commit|git)?\s*提交信息\s*[,,::]?/i,/^\s*```(?:text|markdown|bash|shell)?\s*\n?/i];function T(r){if(!r)return m.info("[Git/sanitize] \u8F93\u5165\u4E3A\u7A7A, \u8FD4\u56DE fallback"),"chore: update staged changes";let t=r.replace(/\r\n/g,`
23
- `).trim();const e=JSON.stringify(r.substring(0,120)),i=t.match(tt);if(i&&i.index!==void 0&&i.index<=80)t=t.slice(i.index);else{for(let f=0;f<5;f+=1){const d=t;for(const p of Nt)t=t.replace(p,"");if(t=t.replace(/^[\s*\-`,,']+/,"").trim(),t===d)break}const a=t.split(`
24
- `)[0]?.trim()??"",o=!!t.match(tt),c=!!t.match(Ft),u=Ht(t),l=a.length>100;if(!o&&!c&&!u&&!l)return m.info(`[Git/sanitize] \u5265\u79BB\u540E\u65E0\u6709\u6548\u5185\u5BB9, \u8FD4\u56DE fallback. raw=${e}, afterStrip=${JSON.stringify(t.substring(0,100))}`),"chore: update staged changes";!o&&(c||u||l)&&(t=a||t)}const s=t.search(/\n\s*\n/);if(s>0&&(t=t.slice(0,s)),t=t.replace(/\n+\s*```\s*$/g,"").trim(),!t)return m.info(`[Git/sanitize] \u622A\u65AD\u540E\u4E3A\u7A7A, \u8FD4\u56DE fallback. raw=${e}`),"chore: update staged changes";const n=t.split(`
25
- `)[0]?.trim()??"";return n.length>100?(m.info(`[Git/sanitize] \u9996\u884C\u8D85\u8FC7 100 \u5B57\u7B26, \u4EC5\u4FDD\u7559\u9996\u884C. raw=${e}, firstLine=${JSON.stringify(n.substring(0,100))}`),n):(m.info(`[Git/sanitize] \u7ED3\u679C: ${JSON.stringify(t.substring(0,120))} (from raw=${e})`),t)}function et(r){const t=r.split(/\r?\n/);let e="";for(const i of t){const s=i.trim();if(!s.startsWith("data:"))continue;const n=s.slice(5).trim();if(!(!n||n==="[DONE]"))try{const o=JSON.parse(n)?.choices?.[0]?.delta?.content;typeof o=="string"&&o.length>0&&(e+=o)}catch{}}return e}function Wt(r,t){if(t>5)return;const e=r.split(/\r?\n/);for(const i of e){const s=i.trim();if(!s.startsWith("data:"))continue;const n=s.slice(5).trim();if(!(!n||n==="[DONE]"))try{const a=JSON.parse(n),o=Object.keys(a),c=a?.choices,u=Array.isArray(c)?c[0]:null,l=u?.delta?Object.keys(u.delta):null,f=u?.delta?.content;m.info(`[Git/extract] chunk#${t} structure: topKeys=${JSON.stringify(o)}, choicesLen=${Array.isArray(c)?c.length:typeof c}, deltaKeys=${JSON.stringify(l)}, content=${JSON.stringify(f?.substring(0,50))}, finishReason=${JSON.stringify(u?.finish_reason)}`)}catch{}}}async function Lt(r,t,e,i){const s=`${r.replace(/\/+$/,"")}/chat/completions`,o=(await L.post(s,{model:e,stream:!1,messages:[{role:"system",content:Z},{role:"user",content:Q(i)}],max_tokens:200,temperature:.2},{headers:{"Content-Type":"application/json",Authorization:`Bearer ${t}`},timeout:3e4})).data?.choices?.[0]?.message?.content?.trim()||"";return T(o)}async function _t(r,t,e,i,s,n){const a=`${r.replace(/\/+$/,"")}/chat/completions`,c=(await L.post(a,{model:e,stream:!0,messages:[{role:"system",content:Z},{role:"user",content:Q(i)}],max_tokens:200,temperature:.2},{headers:{"Content-Type":"application/json",Authorization:`Bearer ${t}`,Accept:"text/event-stream"},timeout:Number(process.env.AWS_AI_STREAM_TIMEOUT_MS)||6e4,responseType:"stream",signal:n})).data;let u="",l="",f="",d=0,p=0;return m.info(`[Git/streamAi] AI \u6D41\u5F00\u59CB: aiModel=${e}`),await new Promise((y,g)=>{const w=P=>{c.removeAllListeners("data"),c.removeAllListeners("end"),c.removeAllListeners("error"),c.removeAllListeners("aborted"),m.info(`[Git/streamAi] AI \u6D41\u7ED3\u675F: totalChunks=${d}, totalDeltas=${p}, bufferLength=${u.length}, sanitized=${JSON.stringify(P).substring(0,200)}`),y(P)};c.on("data",P=>{const C=typeof P=="string"?P:P.toString("utf8");d++,f+=C,d<=3&&m.info(`[Git/streamAi] chunk#${d}: length=${C.length}, preview=${JSON.stringify(C.substring(0,80))}`),Wt(C,d);const v=f.split(/\n\n/);f=v.pop()??"";for(const rt of v){const D=et(rt);D&&(p++,u+=D,l=T(u),s(D,l))}}),c.on("end",()=>{if(f.trim()){const P=et(f);P?(m.info(`[Git/streamAi] \u5C3E\u90E8\u6B8B\u7559 delta: length=${P.length}`),u+=P):m.info(`[Git/streamAi] \u5C3E\u90E8\u6B8B\u7559\u65E0 delta, pendingData=${JSON.stringify(f.substring(0,100))}`)}w(T(u))}),c.on("error",P=>{m.error(`[Git/streamAi] \u6D41\u9519\u8BEF: ${P.message}`),g(P)}),c.on("aborted",()=>{m.info(`[Git/streamAi] \u6D41\u88AB\u4E2D\u6B62, bufferLength=${u.length}`),w(T(u))})})}async function W(r,t,e){const i=await j(String(r||t).trim());if(!i.hasRepository||!i.repositoryRootPath)return m.warn(`[Git/collectDiff] \u4E0D\u5728 Git \u4ED3\u5E93\u4E2D: workspacePath=${t}`),{truncatedDiff:"",error:{status:400,message:"workspace is not inside a git repository"}};const s=e.map(u=>u.path).filter(Boolean);m.info(`[Git/collectDiff] \u5F00\u59CB\u6536\u96C6 ${s.length} \u4E2A\u6682\u5B58\u6587\u4EF6\u5DEE\u5F02: ${s.join(", ").substring(0,200)}`);const n=[];for(const u of s){const l=E(u,i),f=await h(i.repositoryRootPath,["diff","--cached","--",l]);f.exitCode===0&&f.stdout.trim()?n.push(`--- ${u} ---
26
- ${f.stdout.trim()}`):m.info(`[Git/collectDiff] \u6587\u4EF6\u65E0\u6682\u5B58\u5DEE\u5F02\u6216\u83B7\u53D6\u5931\u8D25: ${u}, exitCode=${f.exitCode}, stdout.length=${f.stdout.length}`)}if(n.length===0){m.info("[Git/collectDiff] \u9010\u6587\u4EF6 diff \u4E3A\u7A7A, \u56DE\u9000\u5230\u5168\u91CF diff --cached");const u=await h(i.repositoryRootPath,["diff","--cached"]);u.exitCode===0&&u.stdout.trim()?(n.push(u.stdout.trim()),m.info(`[Git/collectDiff] \u5168\u91CF diff \u83B7\u53D6\u6210\u529F: length=${u.stdout.trim().length}`)):m.info(`[Git/collectDiff] \u5168\u91CF diff \u4E5F\u4E3A\u7A7A, exitCode=${u.exitCode}`)}if(n.length===0)return m.info("[Git/collectDiff] \u6682\u5B58\u533A\u6CA1\u6709\u5DEE\u5F02\u5185\u5BB9 (diffParts \u4E3A\u7A7A)"),{truncatedDiff:""};const a=n.join(`
1
+ import{spawn as it}from"node:child_process";import{promises as j}from"node:fs";import at from"node:os";import x from"node:path";import I from"axios";import{Router as nt}from"express";import{validateToken as k}from"../middleware/auth.js";import{logger as m}from"../utils/logger.js";import{noteDashboardActivity as ct}from"./dashboard.js";const S=nt(),ut=1e4;function lt(){const e=Number(process.env.AWS_RUNTIME_GIT_COMMAND_TIMEOUT_MS);return Number.isFinite(e)&&e>=1e3?e:ut}async function ft(e){let t;try{t=await j.stat(e)}catch(r){const i=r;throw i.code==="ENOENT"?new Error(`workspace path does not exist: ${e}`):new Error(`workspace path is not accessible: ${e}${i.message?` (${i.message})`:""}`)}if(!t.isDirectory())throw new Error(`workspace path is not a directory: ${e}`)}const ht=/^[0-9a-f]{40}$/i,O="%H%x00%h%x00%an%x00%ad%x00%s%x00%P%x00%b%x00",dt=100,pt=200,mt=30,gt=100;function yt(e,t){const r=t?pt:dt,i=t?gt:mt,s=Number(e);return Number.isInteger(s)&&s>0&&s<=r?s:i}function Pt(e){const t=Number(e);return Number.isInteger(t)&&t>0?t:0}function wt(e,t,r){const i=e.length>t,s=i?e.slice(0,t):e;return{page:s,hasMore:i,nextSkip:r+s.length}}const kt=["--branches","--remotes","--tags"];function St(e){const t=["log",`-${e.limit}`,`--skip=${e.skip}`];if(e.graphMode){const r=e.graphHeadRef??null;return[...t,...kt,...r?[r]:[],"--topo-order","--date=iso-strict",`--pretty=format:${O}`]}return[...t,"--date=iso-strict",`--pretty=format:${O}`,"--end-of-options",e.focusedRef,"--",e.relativeWorkspacePath||"."]}function Rt(e,t,r,i){return e==="added"||e==="deleted"||e==="renamed"?!0:i||t>0||r>0}function xt(e){const t=String(e||"").toLowerCase();return t.includes("you do not have the initial commit yet")||t.includes("bad revision")&&t.includes("head")||t.includes("ambiguous argument")&&t.includes("head")}function Ct(){return"\u5F53\u524D Git \u4ED3\u5E93\u8FD8\u6CA1\u6709\u521D\u59CB\u63D0\u4EA4\uFF0C\u65E0\u6CD5\u521B\u5EFA\u5FEB\u7167\u3002\u8BF7\u5148\u63D0\u4EA4\u4E00\u6B21\u521D\u59CB\u63D0\u4EA4\u540E\u518D\u521B\u5EFA\u5FEB\u7167\u3002"}function jt(e){const t=String(e||"").toLowerCase();return t.includes("no local changes to save")||t.includes("no changes to save")||t.includes("\u6CA1\u6709\u672C\u5730\u53D8\u66F4\u9700\u8981\u4FDD\u5B58")}function bt(e){const t=String(e||"").trim();return ht.test(t)?`refs/agentswork/stash-snapshots/${t.toLowerCase()}`:null}function z(e){const t=String(e||"").trim();if(!t)return null;const r=t.split("\0");if(r.length>=3){const n=r[0]?.trim(),o=r[1]?.trim(),c=r[2]?.trim()||o,u=r[3]?.trim()||null;if(!n||!o)return null;const l=B(c||"");return{ref:n,stashName:o,message:l.message,branch:l.branch,createdAt:u}}const i=t.match(/^(stash@\{\d+\})(?::\s*)?(.*)$/);if(!i)return null;const s=i[1],a=B(i[2]?.trim()||s);return{ref:s,stashName:s,message:a.message,branch:a.branch,createdAt:null}}function B(e){const t=String(e||"").trim(),r=t.match(/^(?:On|WIP on)\s+([^:]+):\s*(.*)$/);return r?{branch:r[1].trim(),message:r[2].trim()||t}:{branch:"",message:t}}function q(e){const t=String(e||"").split(`
2
+ `).find(i=>i.trim());if(!t)return null;const r=z(t);return r?{ref:r.ref,stashName:r.stashName||r.ref,message:r.message||r.ref}:null}async function h(e,t,r){return new Promise(i=>{let s=!1,a=null;const n=f=>{s||(s=!0,a&&clearTimeout(a),i(f))},o=it("git",t,{cwd:e,stdio:["ignore","pipe","pipe"]});let c="",u="";o.stdout?.on("data",f=>{c+=String(f||"")}),o.stderr?.on("data",f=>{u+=String(f||"")});const l=r??lt();a=setTimeout(()=>{m.warn(`[runtime-bridge] git command timed out after ${l}ms: git ${t.join(" ")} (cwd=${e})`),o.kill("SIGKILL"),n({stdout:c,stderr:u||`git command timed out after ${l}ms`,exitCode:124})},l),o.on("error",f=>{n({stdout:"",stderr:f.message,exitCode:1})}),o.on("close",f=>{n({stdout:c,stderr:u,exitCode:f??1})})})}const $t=6e4;function vt(){const e=Number(process.env.AWS_RUNTIME_GIT_PUSH_TIMEOUT_MS);return Number.isFinite(e)&&e>=1e3?e:$t}async function U(e,t){const r=bt(t);if(!r)return null;const i=await h(e,["update-ref",r,t]);return i.exitCode!==0?(m.warn(`[Git] \u6301\u4E45\u5316 stash \u5FEB\u7167 ref \u5931\u8D25: ref=${r}, error=${i.stderr||i.stdout}`),null):r}function R(e){return String(e||"").replace(/\\/g,"/").replace(/^\.\//,"").trim()}async function C(e){const t=x.resolve(String(e).trim());await ft(t);const r=await h(t,["rev-parse","--show-toplevel"]);if(r.exitCode!==0)return{workspacePath:t,repositoryRootPath:null,hasRepository:!1,isGitRepo:!1,usingParentGitRepo:!1,relativeWorkspacePath:""};const i=x.resolve(r.stdout.trim()),s=i===t,a=s?"":R(x.relative(i,t));return{workspacePath:t,repositoryRootPath:i,hasRepository:!0,isGitRepo:s,usingParentGitRepo:!s,relativeWorkspacePath:a}}function v(e,t){if(!t.relativeWorkspacePath)return[...e];const r=!e.includes("status");return[...e,...r?[`--relative=${t.relativeWorkspacePath}`]:[],"--",t.relativeWorkspacePath]}function G(e,t){const r=R(e);return t.relativeWorkspacePath?R(x.posix.join(t.relativeWorkspacePath,r)):r}function H(e){const t=R(e);if(x.posix.isAbsolute(t)||t.split("/").some(r=>r===".."||r==="."))throw new Error(`unsafe repository relative path: ${e}`)}function At(e){const t=String(e||"").trim().toUpperCase();return t.startsWith("A")?"added":t.startsWith("D")?"deleted":t.startsWith("R")?"renamed":"modified"}function _(e){const t=R(e);if(!t.includes("=>"))return t;const r=t.match(/^(.*)\{([^{}]+)=>([^{}]+)\}(.*)$/);if(r){const s=r[1],a=r[3],n=r[4];return R(`${s}${a}${n}`)}const i=t.lastIndexOf("=>");return i>=0?R(t.slice(i+2)):t}function Et(e){const t=new Set,r=String(e||"").split(`
3
+ `).filter(i=>i.trim());for(const i of r){const s=i[0]||" ";if(s===" "||s==="?")continue;const a=i.slice(3).trim(),n=a.split(" -> "),o=_(n[n.length-1]||a);o&&t.add(o)}return t}function F(e,t,r=new Set,i=!1){const s=new Map,a=e.split(`
4
+ `).filter(c=>c.trim());for(const c of a){const u=c.split(" ");if(u.length<2)continue;const l=At(u[0]),f=l==="renamed"&&u.length>=3?2:1,d=_(u[f]);d&&s.set(d,l)}const n=[],o=t.split(`
5
+ `).filter(c=>c.trim());for(const c of o){const u=c.split(" ");if(u.length<3)continue;const l=u[0]==="-"||u[1]==="-",f=u[0]==="-"?0:parseInt(u[0],10)||0,d=u[1]==="-"?0:parseInt(u[1],10)||0,p=_(u.slice(2).join(" ").trim()),y=s.get(p)||"modified";!p||!Rt(y,f,d,l)||n.push({path:p,status:y,additions:f,deletions:d,staged:i||r.has(p)})}return n}function Gt(e){const t=[],r=String(e||"").split(`
6
+ `).filter(i=>i.trim());for(const i of r)if(i.startsWith("?? ")){const s=i.slice(3).trim();s&&t.push(s.replace(/\/$/,""))}return t}function Mt(e){if(!e||e.includes("\0"))return 0;let t=0;for(let r=0;r<e.length;r++)e.charCodeAt(r)===10&&t++;return e.endsWith(`
7
+ `)?t:t+1}async function J(e){try{const t=await j.readFile(e,"utf8");return Mt(t)}catch{return 0}}async function Tt(e,t,r,i=10){const s=[];async function a(n,o){if(o>i)return;const c=x.join(e,n);let u;try{u=await j.readdir(c,{withFileTypes:!0})}catch{return}for(const l of u){if(l.name===".git"||l.name==="node_modules")continue;const f=n?`${n}/${l.name}`:l.name;if(l.isDirectory())await a(f,o+1);else if(l.isFile()){const d=R(f);d&&!r.has(d)&&s.push({path:d,status:"added",additions:await J(x.join(c,l.name)),deletions:0,staged:!1})}}}for(const n of t){const o=R(n);if(!o)continue;const c=x.join(e,n);let u;try{u=await j.stat(c)}catch{continue}u.isFile()?r.has(o)||s.push({path:o,status:"added",additions:await J(c),deletions:0,staged:!1}):u.isDirectory()&&await a(o,0)}return s}function Dt(e,t){const r=new Map;for(const i of e)r.set(i.path,{...i});for(const i of t){const s=r.get(i.path);if(!s){r.set(i.path,{...i,staged:!0});continue}r.set(i.path,{...s,status:s.status==="modified"?i.status:s.status,additions:s.additions+i.additions,deletions:s.deletions+i.deletions,staged:!0})}return[...r.values()]}function K(e){return String(e||"").trim()}function Ht(e){return String(e||"").trim()}function _t(e){return["--",e.relativeWorkspacePath||"."]}function T(e,t){return["--",G(e,t)]}function Ft(e){const t=e.split("\0"),r=[];for(let i=0;i+6<t.length;i+=7){const s=t[i]?.trim();if(!s)continue;const a=(t[i+5]?.trim()||"").split(/\s+/).filter(Boolean);r.push({hash:s,shortHash:t[i+1]?.trim()||s.slice(0,8),authorName:t[i+2]?.trim()||"unknown",authorDate:t[i+3]?.trim()||"",subject:t[i+4]?.trim()||s,body:t[i+6]?.trim()||"",parents:a})}return r}async function Y(e){const t=new Set,r=await h(e,["for-each-ref","--format=%(refname)","refs/remotes"]);if(r.exitCode!==0)return t;const i=r.stdout.split(`
8
+ `).map(a=>a.trim()).filter(Boolean);if(i.length===0)return t;const s=await h(e,["rev-list",...i]);if(s.exitCode!==0)return t;for(const a of s.stdout.split(`
9
+ `)){const n=a.trim();n&&t.add(n)}return t}async function N(e){const t=await h(e,["rev-parse","--abbrev-ref","HEAD"]);return t.exitCode!==0||!t.stdout.trim()?"":t.stdout.trim()}async function V(e){const t=await h(e,["for-each-ref","--format=%(refname)%00%(objectname)%00%(HEAD)%00%(upstream:short)","refs/heads","refs/remotes"]);if(t.exitCode!==0)return[];const r=[];for(const i of t.stdout.split(`
10
+ `)){const s=i.split("\0"),a=s[0]?.trim(),n=s[1]?.trim();if(!a||!n)continue;const o=a.startsWith("refs/remotes/"),c=o?a.slice(13):a.slice(11);r.push({name:c,ref:a,hash:n,isCurrent:s[2]?.trim()==="*",isRemote:o,upstream:s[3]?.trim()||""})}return r}function Nt(e,t){return["diff",t?e:`${e}^!`]}async function Wt(e){const t=await h(e,["rev-parse","--abbrev-ref","HEAD"]),r=t.stdout.trim();if(t.exitCode!==0||!r||r==="HEAD")throw new Error("\u5F53\u524D\u5904\u4E8E detached HEAD\uFF08\u5206\u79BB\u5934\u6307\u9488\uFF09\u72B6\u6001\uFF0C\u65E0\u6CD5\u63A8\u9001");const i=await h(e,["rev-parse","--abbrev-ref","--symbolic-full-name","@{u}"]);if(i.exitCode!==0||!i.stdout.trim())throw new Error(`\u5206\u652F ${r} \u672A\u914D\u7F6E\u4E0A\u6E38\u5206\u652F\uFF0C\u65E0\u6CD5\u63A8\u9001\u3002\u8BF7\u5148\u624B\u52A8\u6267\u884C\u4E00\u6B21 git push -u origin ${r} \u8BBE\u7F6E\u4E0A\u6E38\u5206\u652F`);const s=await h(e,["push"],vt());if(s.exitCode!==0)throw new Error(s.stderr||s.stdout||"git push failed");const a=`${s.stdout}
11
+ ${s.stderr}`.trim(),n=/everything up-to-date|up to date/i.test(a);return{branch:r,pushed:!n,message:n?"\u6CA1\u6709\u9700\u8981\u63A8\u9001\u7684\u63D0\u4EA4":"\u63A8\u9001\u5B8C\u6210",output:a}}S.post("/git/check",k,async(e,t)=>{const{workspacePath:r}=e.body||{};if(!r||!String(r).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const i=await C(String(r).trim());t.json({ok:!0,isGitRepo:i.isGitRepo,hasRepository:i.hasRepository,usingParentGitRepo:i.usingParentGitRepo,workspacePath:i.workspacePath,repositoryRootPath:i.repositoryRootPath,relativeWorkspacePath:i.relativeWorkspacePath});return}catch(i){const s=i;t.status(500).json({error:s.message||"check git repo failed"})}});async function Lt(e,t){const r=`\u5FEB\u7167\uFF08${new Date().toISOString()}\uFF09`,i=t&&String(t).trim()?`\u5FEB\u7167: ${String(t).trim()}`:r,s=await h(e,["stash","push","-m",i]);if(jt(`${s.stdout}
12
+ ${s.stderr}`)){const u=await h(e,["stash","list","-n","1","--format=%H%x00%gd%x00%gs%x00%cr"]),l=u.exitCode===0?q(u.stdout):null;return{stashRef:(l?await U(e,l.ref):null)??l?.ref??null,stashName:l?.stashName??null,noChanges:!0,reusedLatestStash:!!l,message:l?`\u6CA1\u6709\u672C\u5730\u53D8\u66F4\u9700\u8981\u4FDD\u5B58\uFF0C\u5DF2\u590D\u7528\u6700\u8FD1\u5FEB\u7167 ${l.stashName}`:"\u6CA1\u6709\u672C\u5730\u53D8\u66F4\u9700\u8981\u4FDD\u5B58"}}if(s.exitCode!==0)throw xt(`${s.stdout}
13
+ ${s.stderr}`)?new Error(Ct()):new Error(s.stderr||"git stash push failed");const a=await h(e,["stash","list","-n","1","--format=%H%x00%gd%x00%gs%x00%cr"]),n=a.exitCode===0?q(a.stdout):null;return{stashRef:(n?await U(e,n.ref):null)??n?.ref??"stash@{0}",stashName:n?.stashName??null,noChanges:!1,reusedLatestStash:!1,message:i}}S.post("/git/stash/create",k,async(e,t)=>{const{workspacePath:r,message:i}=e.body||{};if(!r||!String(r).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const s=x.resolve(String(r).trim()),a=await Lt(s,i?String(i):void 0);t.json({ok:!0,stashRef:a.stashRef,stashName:a.stashName,message:a.message,noChanges:a.noChanges,reusedLatestStash:a.reusedLatestStash,workspacePath:s})}catch(s){const a=s;t.status(400).json({error:a.message||"create stash failed"})}}),S.post("/git/stash/list",k,async(e,t)=>{const{workspacePath:r,limit:i=20}=e.body||{};if(!r||!String(r).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const s=x.resolve(String(r).trim()),a=Math.min(Math.max(1,Number(i)||20),100),n=await h(s,["stash","list","-n",String(a),"--format=%H%x00%gd%x00%gs%x00%cr"]);if(n.exitCode!==0){t.status(400).json({error:n.stderr||"git stash list failed"});return}const o=n.stdout.split(`
14
+ `).map(z).filter(c=>c!==null);t.json({ok:!0,stashes:o,count:o.length,workspacePath:s})}catch(s){const a=s;t.status(500).json({error:a.message||"list stash failed"})}});async function Z(e,t,r){const{workspacePath:i,ref:s}=e.body||{};if(!i||!String(i).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const a=x.resolve(String(i).trim()),n=s&&String(s).trim()?String(s).trim():"stash@{0}";await h(a,["checkout","--","."]);const o=await h(a,["stash",r,n]);if(o.exitCode!==0){if(o.stdout.includes("CONFLICT")||o.stderr.includes("CONFLICT")){t.json({ok:!1,hasConflict:!0,message:"\u6062\u590D\u65F6\u53D1\u751F\u51B2\u7A81\uFF0C\u8BF7\u624B\u52A8\u89E3\u51B3",details:o.stdout||o.stderr,workspacePath:a});return}if(o.stderr.includes("is not a valid reference")){t.status(404).json({error:`\u5FEB\u7167 ${n} \u4E0D\u5B58\u5728`});return}t.status(400).json({error:o.stderr||`git stash ${r} failed`});return}t.json({ok:!0,stashRef:n,message:r==="apply"?`\u5DF2\u5E94\u7528 ${n}`:`\u5DF2\u6062\u590D ${n}`,preservedStash:r==="apply",workspacePath:a})}catch(a){const n=a;t.status(500).json({error:n.message||`${r} stash failed`})}}S.post("/git/stash/apply",k,async(e,t)=>{await Z(e,t,"apply")}),S.post("/git/stash/pop",k,async(e,t)=>{await Z(e,t,"pop")}),S.post("/git/stash/drop",k,async(e,t)=>{const{workspacePath:r,ref:i}=e.body||{};if(!r||!String(r).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const s=x.resolve(String(r).trim()),a=i&&String(i).trim()?String(i).trim():"stash@{0}",n=await h(s,["stash","drop",a]);if(n.exitCode!==0){if(n.stderr.includes("is not a valid reference")){t.status(404).json({error:`\u5FEB\u7167 ${a} \u4E0D\u5B58\u5728`});return}t.status(400).json({error:n.stderr||"git stash drop failed"});return}t.json({ok:!0,stashRef:a,message:`\u5DF2\u5220\u9664 ${a}`,workspacePath:s})}catch(s){const a=s;t.status(500).json({error:a.message||"drop stash failed"})}}),S.post("/git/diff",k,async(e,t)=>{const{workspacePath:r,repositoryPath:i}=e.body||{};if(!r||!String(r).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const s=await C(String(i||r).trim());if(!s.hasRepository||!s.repositoryRootPath){t.json({ok:!0,isGitRepo:s.isGitRepo,hasRepository:!1,usingParentGitRepo:!1,hasChanges:!1,files:[],workspacePath:s.workspacePath,repositoryRootPath:null,relativeWorkspacePath:""});return}const a=await h(s.repositoryRootPath,v(["diff","--name-status"],s));if(a.exitCode!==0){t.status(400).json({error:a.stderr||"git diff failed"});return}const n=await h(s.repositoryRootPath,v(["diff","--numstat"],s));if(n.exitCode!==0){t.status(400).json({error:n.stderr||"git diff failed"});return}const o=await h(s.repositoryRootPath,v(["diff","--cached","--name-status"],s));if(o.exitCode!==0){t.status(400).json({error:o.stderr||"git staged diff failed"});return}const c=await h(s.repositoryRootPath,v(["diff","--cached","--numstat"],s));if(c.exitCode!==0){t.status(400).json({error:c.stderr||"git staged diff failed"});return}const u=await h(s.repositoryRootPath,v(["status","--porcelain=v1"],s));if(u.exitCode!==0){t.status(400).json({error:u.stderr||"git status failed"});return}const l=Et(u.stdout),f=Dt(F(a.stdout,n.stdout,l),F(o.stdout,c.stdout,l,!0)),d=Gt(u.stdout);if(d.length>0){const p=new Set(f.map(g=>g.path)),y=await Tt(s.repositoryRootPath,d,p);f.push(...y)}t.json({ok:!0,isGitRepo:s.isGitRepo,hasRepository:!0,usingParentGitRepo:s.usingParentGitRepo,hasChanges:f.length>0,files:f,workspacePath:s.workspacePath,repositoryRootPath:s.repositoryRootPath,relativeWorkspacePath:s.relativeWorkspacePath})}catch(s){const a=s;t.status(500).json({error:a.message||"git diff failed"})}});async function X(e){const t=[],r=await h(e,["config","user.name"]);(r.exitCode!==0||!r.stdout.trim())&&t.push("-c","user.name=AgentsWork");const i=await h(e,["config","user.email"]);return(i.exitCode!==0||!i.stdout.trim())&&t.push("-c","user.email=agent@agentswork.com"),t}async function It(e,t,r){if(!t)throw new Error("commit message is required");if(r.length===0)throw new Error("filePaths must be valid workspace-relative paths");const i=r.map(c=>`:(literal)${c}`),s=await h(e,["add","--",...i]);if(s.exitCode!==0)throw new Error(s.stderr||"git add failed");const a=await h(e,["diff","--cached","--quiet","--",...i]);if(a.exitCode===0)throw new Error("\u6240\u9009\u6587\u4EF6\u6CA1\u6709\u53EF\u63D0\u4EA4\u7684\u53D8\u66F4");if(a.exitCode!==1)throw new Error(a.stderr||"git staged diff failed");const n=await X(e),o=await h(e,[...n,"commit","-m",t,"--",...i]);if(o.exitCode!==0)throw new Error(o.stderr||o.stdout||"git commit failed");return{committedPaths:[...r],output:o.stdout}}S.post("/git/commit",k,async(e,t)=>{const{workspacePath:r,repositoryPath:i,message:s,filePaths:a}=e.body||{};if(!r||!String(r).trim()){t.status(400).json({error:"workspacePath is required"});return}const n=Ht(s);if(!n){t.status(400).json({error:"commit message is required"});return}try{const o=await C(String(i||r).trim());if(!o.hasRepository||!o.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}const c=Array.isArray(a)?a.map(y=>R(String(y||"").trim())).filter(Boolean):[];if(c.length>0){const y=c.map(w=>G(w,o)),g=[];for(const w of y)if(w)try{H(w),g.push(w)}catch{t.status(400).json({error:`unsafe file path: ${w}`});return}if(g.length===0){t.status(400).json({error:"filePaths must be valid workspace-relative paths"});return}try{const w=await It(o.repositoryRootPath,n,g);t.json({ok:!0,message:"\u63D0\u4EA4\u5B8C\u6210",committedPaths:c,output:w.output,workspacePath:o.workspacePath,repositoryRootPath:o.repositoryRootPath,relativeWorkspacePath:o.relativeWorkspacePath})}catch(w){const P=w;t.status(400).json({error:P.message||"git commit failed"})}return}const u=await X(o.repositoryRootPath),l=_t(o),f=await h(o.repositoryRootPath,["add","--all",...l]);if(f.exitCode!==0){t.status(400).json({error:f.stderr||"git add failed"});return}const d=await h(o.repositoryRootPath,["diff","--cached","--quiet",...l]);if(d.exitCode===0){t.status(400).json({error:"\u6CA1\u6709\u53EF\u63D0\u4EA4\u7684\u53D8\u66F4"});return}if(d.exitCode!==1){t.status(400).json({error:d.stderr||"git staged diff failed"});return}const p=await h(o.repositoryRootPath,[...u,"commit","-m",n,...l]);if(p.exitCode!==0){t.status(400).json({error:p.stderr||p.stdout||"git commit failed"});return}t.json({ok:!0,message:"\u63D0\u4EA4\u5B8C\u6210",output:p.stdout,workspacePath:o.workspacePath,repositoryRootPath:o.repositoryRootPath,relativeWorkspacePath:o.relativeWorkspacePath})}catch(o){const c=o;t.status(500).json({error:c.message||"git commit failed"})}}),S.post("/git/add-file",k,async(e,t)=>{const{workspacePath:r,repositoryPath:i,filePath:s}=e.body||{};if(!r||!String(r).trim()){t.status(400).json({error:"workspacePath is required"});return}const a=R(String(s||"").trim());if(!a){t.status(400).json({error:"filePath is required"});return}try{const n=await C(String(i||r).trim());if(!n.hasRepository||!n.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}const o=await h(n.repositoryRootPath,["add",...T(a,n)]);if(o.exitCode!==0){t.status(400).json({error:o.stderr||"git add file failed"});return}t.json({ok:!0,filePath:a,workspacePath:n.workspacePath,repositoryRootPath:n.repositoryRootPath,relativeWorkspacePath:n.relativeWorkspacePath})}catch(n){const o=n;t.status(500).json({error:o.message||"git add file failed"})}}),S.post("/git/add-files",k,async(e,t)=>{const{workspacePath:r,repositoryPath:i,filePaths:s}=e.body||{};if(!r||!String(r).trim()){t.status(400).json({error:"workspacePath is required"});return}const n=(Array.isArray(s)?s:[]).map(o=>R(String(o||"").trim())).filter(o=>o.length>0);if(n.length===0){t.status(400).json({error:"filePaths is required and must be a non-empty array"});return}try{const o=await C(String(i||r).trim());if(!o.hasRepository||!o.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}const c=n.flatMap(d=>T(d,o)),u=["add"],l=[];for(let d=0;d<c.length;d++)c[d]!=="--"&&l.push(c[d]);u.push("--",...l);const f=await h(o.repositoryRootPath,u);if(f.exitCode!==0){t.status(400).json({error:f.stderr||"git add files failed"});return}t.json({ok:!0,filePaths:n,workspacePath:o.workspacePath,repositoryRootPath:o.repositoryRootPath,relativeWorkspacePath:o.relativeWorkspacePath})}catch(o){const c=o;t.status(500).json({error:c.message||"git add files failed"})}}),S.post("/git/commits",k,async(e,t)=>{const{workspacePath:r,repositoryPath:i,limit:s,skip:a,branch:n}=e.body||{};if(!r||!String(r).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const o=await C(String(i||r).trim());if(!o.hasRepository||!o.repositoryRootPath){t.json({ok:!0,commits:[],hasMore:!1,nextSkip:0,currentBranch:"",branchName:null,workspacePath:o.workspacePath,repositoryRootPath:null});return}const c=!!e.body?.all,u=yt(s,c),l=Pt(a),f=await N(o.repositoryRootPath),p=(n&&String(n).trim()?String(n).trim():null)||f||"HEAD",y=c&&f==="HEAD"?"HEAD":null,g=await h(o.repositoryRootPath,St({limit:u+1,skip:l,graphMode:c,focusedRef:p,graphHeadRef:y,relativeWorkspacePath:o.relativeWorkspacePath}));if(g.exitCode!==0){t.status(400).json({error:g.stderr||"git log failed"});return}const{page:w,hasMore:P,nextSkip:b}=wt(Ft(g.stdout),u,l),A=await Y(o.repositoryRootPath),M=w.map(L=>({...L,isRemote:A.has(L.hash)})),E=c?await V(o.repositoryRootPath):void 0;m.debug(`[Git/commits] page: graphMode=${c}, ref=${c?"branches+remotes+tags":p}, skip=${l}, limit=${u}, returned=${M.length}, hasMore=${P}`),t.json({ok:!0,commits:M,hasMore:P,nextSkip:b,currentBranch:f,branchName:c?null:p,...E?{branches:E}:{},workspacePath:o.workspacePath,repositoryRootPath:o.repositoryRootPath,relativeWorkspacePath:o.relativeWorkspacePath})}catch(o){const c=o;t.status(500).json({error:c.message||"git commits failed"})}}),S.post("/git/branches",k,async(e,t)=>{const{workspacePath:r,repositoryPath:i}=e.body||{};if(!r||!String(r).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const s=await C(String(i||r).trim());if(!s.hasRepository||!s.repositoryRootPath){t.json({ok:!0,branches:[],currentBranch:"",workspacePath:s.workspacePath,repositoryRootPath:null});return}const a=await V(s.repositoryRootPath),n=await N(s.repositoryRootPath);t.json({ok:!0,branches:a,currentBranch:n,workspacePath:s.workspacePath,repositoryRootPath:s.repositoryRootPath,relativeWorkspacePath:s.relativeWorkspacePath})}catch(s){const a=s;t.status(500).json({error:a.message||"git branches failed"})}}),S.post("/git/push",k,async(e,t)=>{const{workspacePath:r,repositoryPath:i}=e.body||{};if(!r||!String(r).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const s=await C(String(i||r).trim());if(!s.hasRepository||!s.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}const a=await Wt(s.repositoryRootPath);t.json({ok:!0,branch:a.branch,pushed:a.pushed,message:a.message,output:a.output,workspacePath:s.workspacePath,repositoryRootPath:s.repositoryRootPath,relativeWorkspacePath:s.relativeWorkspacePath})}catch(s){const a=s;if(a.message&&/上游分支|detached HEAD|分离头指针|不存在|未配置/.test(a.message)){t.status(400).json({error:a.message});return}t.status(500).json({error:a.message||"git push failed"})}});const Ot=/^[0-9a-fA-F]{7,40}$/;function zt(e){const t=String(e||"").trim();return Ot.test(t)?t:null}async function Q(e,t){const r=await N(e);if(!r||r==="HEAD")throw new Error("\u5F53\u524D\u5904\u4E8E detached HEAD\uFF08\u5206\u79BB\u5934\u6307\u9488\uFF09\u72B6\u6001\uFF0C\u65E0\u6CD5\u56DE\u9000");const i=await h(e,["rev-parse","--verify","--quiet",`${t}^{commit}`]);if(i.exitCode!==0||!i.stdout.trim())throw new Error("\u76EE\u6807\u63D0\u4EA4\u4E0D\u5B58\u5728\uFF0C\u8BF7\u5237\u65B0\u63D0\u4EA4\u5217\u8868\u540E\u91CD\u8BD5");const s=i.stdout.trim();if((await h(e,["rev-parse","HEAD"])).stdout.trim()===s)throw new Error("\u76EE\u6807\u63D0\u4EA4\u5DF2\u662F\u5F53\u524D HEAD\uFF0C\u65E0\u9700\u56DE\u9000");if((await h(e,["merge-base","--is-ancestor",s,"HEAD"])).exitCode!==0)throw new Error("\u76EE\u6807\u63D0\u4EA4\u4E0D\u5728\u5F53\u524D\u5206\u652F\u5386\u53F2\u4E2D\uFF0C\u65E0\u6CD5\u56DE\u9000");const o=await h(e,["rev-list","--count",`${s}..HEAD`]),c=parseInt(o.stdout.trim()||"0",10);let u=!1;const l=await Y(e);if(l.size>0){const f=await h(e,["rev-list",`${s}..HEAD`]);if(f.exitCode===0){for(const d of f.stdout.split(`
15
+ `))if(l.has(d.trim())){u=!0;break}}}return{resolvedHash:s,branch:r,resetCount:c,affectsRemote:u}}async function tt(e){const t=await h(e,["status","--porcelain"]);return t.exitCode===0&&!!t.stdout.trim()}async function Bt(e,t,r){const i=await Q(e,t),s=await tt(e);return{mode:r,commitHash:i.resolvedHash,branch:i.branch,resetCount:i.resetCount,hasLocalChanges:s,affectsRemote:i.affectsRemote}}async function qt(e,t,r,i=""){const s=await Q(e,t),a=await tt(e),n=r==="hard"?["reset","--hard",s.resolvedHash]:["reset","--soft",s.resolvedHash],o=await h(e,n);if(o.exitCode!==0)throw new Error(o.stderr||o.stdout||"git reset \u6267\u884C\u5931\u8D25");if(r==="hard"){const c=["clean","-fd"];i&&c.push("--",i);const u=await h(e,c);u.exitCode!==0&&m.warn(`[Git] \u786C\u56DE\u9000\u540E\u6E05\u7406\u672A\u8DDF\u8E2A\u6587\u4EF6\u5931\u8D25: cwd=${e}, scope=${i||"(\u6574\u4ED3)"}, error=${u.stderr||u.stdout}`)}return{mode:r,commitHash:s.resolvedHash,branch:s.branch,resetCount:s.resetCount,hasLocalChanges:a,affectsRemote:s.affectsRemote}}S.post("/git/reset",k,async(e,t)=>{const{workspacePath:r,repositoryPath:i,commitHash:s,mode:a,preview:n}=e.body||{};if(!r||!String(r).trim()){t.status(400).json({error:"workspacePath is required"});return}const o=zt(s);if(!o){t.status(400).json({error:"commitHash \u683C\u5F0F\u975E\u6CD5\uFF0C\u65E0\u6CD5\u56DE\u9000"});return}const c=String(a||"").trim();if(c!=="soft"&&c!=="hard"){t.status(400).json({error:"mode must be soft or hard"});return}const u=n===!0||n==="true";try{const l=await C(String(i||r).trim());if(!l.hasRepository||!l.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}if(u){const y=await Bt(l.repositoryRootPath,o,c);t.json({ok:!0,preview:!0,...y,workspacePath:l.workspacePath,repositoryRootPath:l.repositoryRootPath,relativeWorkspacePath:l.relativeWorkspacePath});return}const f=await qt(l.repositoryRootPath,o,c,l.relativeWorkspacePath),d=f.commitHash.slice(0,7),p=f.mode==="hard"?`\u5DF2\u786C\u56DE\u9000\u5230 ${d}\uFF0C\u5171\u56DE\u9000 ${f.resetCount} \u4E2A\u63D0\u4EA4\uFF0C\u5DE5\u4F5C\u533A\u5DF2\u5B8C\u5168\u6062\u590D\u5230\u8BE5\u63D0\u4EA4\u72B6\u6001`:`\u5DF2\u8F6F\u56DE\u9000\u5230 ${d}\uFF0C\u5171\u56DE\u9000 ${f.resetCount} \u4E2A\u63D0\u4EA4\uFF0C\u5DE5\u4F5C\u533A\u6587\u4EF6\u4FDD\u6301\u4E0D\u53D8`;t.json({ok:!0,...f,message:p,workspacePath:l.workspacePath,repositoryRootPath:l.repositoryRootPath,relativeWorkspacePath:l.relativeWorkspacePath})}catch(l){const f=l;if(f.message&&/回退|快照|detached HEAD|分离头指针|不存在|无需|HEAD/.test(f.message)){t.status(400).json({error:f.message});return}t.status(500).json({error:f.message||"git reset failed"})}}),S.post("/git/commit-diff",k,async(e,t)=>{const{workspacePath:r,repositoryPath:i,commitHash:s,aggregate:a}=e.body||{};if(!r||!String(r).trim()){t.status(400).json({error:"workspacePath is required"});return}const n=K(s);if(!n){t.status(400).json({error:"commitHash is required"});return}const o=a===!0||a==="true";try{const c=await C(String(i||r).trim());if(!c.hasRepository||!c.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}const u=Nt(n,o),l=await h(c.repositoryRootPath,v([...u,"--name-status"],c));if(l.exitCode!==0){t.status(400).json({error:l.stderr||"git commit diff failed"});return}const f=await h(c.repositoryRootPath,v([...u,"--numstat"],c));if(f.exitCode!==0){t.status(400).json({error:f.stderr||"git commit diff failed"});return}const d=F(l.stdout,f.stdout);t.json({ok:!0,commitHash:n,isGitRepo:c.isGitRepo,hasRepository:!0,usingParentGitRepo:c.usingParentGitRepo,hasChanges:d.length>0,files:d,workspacePath:c.workspacePath,repositoryRootPath:c.repositoryRootPath,relativeWorkspacePath:c.relativeWorkspacePath})}catch(c){const u=c;t.status(500).json({error:u.message||"git commit diff failed"})}}),S.post("/git/diff-file",k,async(e,t)=>{const{workspacePath:r,filePath:i,commitHash:s,aggregate:a}=e.body||{};if(!r||!String(r).trim()){t.status(400).json({error:"workspacePath is required"});return}if(!i||!String(i).trim()){t.status(400).json({error:"filePath is required"});return}try{const n=await C(String(r).trim()),o=R(String(i).trim());if(!n.hasRepository||!n.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}const c=G(o,n),u=K(s),l=!!(u&&(a===!0||a==="true")),f=!u||l,d=u?l?["diff",u,"--",c]:["show","--format=","--no-ext-diff",u,"--",c]:["diff","HEAD","--",c];let p=await h(n.repositoryRootPath,d);if(f&&p.exitCode!==0&&(p=await h(n.repositoryRootPath,["diff","--",c])),p.exitCode!==0){t.status(400).json({error:p.stderr||"git diff file failed"});return}if(!u&&!p.stdout.trim()){const g=await h(n.repositoryRootPath,["diff","--cached","--",c]);if(g.exitCode!==0){t.status(400).json({error:g.stderr||"git staged diff file failed"});return}p=g}if(f&&!p.stdout.trim()){const g=x.join(n.repositoryRootPath,c);try{const w=await j.readFile(g,"utf-8");if(w){const P=w.split(`
16
+ `).map(A=>`+${A}`).join(`
17
+ `),b=w.split(`
18
+ `).length;p={stdout:[`diff --git a/${c} b/${c}`,"new file mode 100644","--- /dev/null",`+++ b/${c}`,`@@ -0,0 +1,${b} @@`,P].join(`
19
+ `),stderr:"",exitCode:0}}}catch{}}H(c);let y="";if(f){const g=x.join(n.repositoryRootPath,c);try{y=await j.readFile(g,"utf-8")}catch{}}else{const g=await h(n.repositoryRootPath,["show",`${u}:${c}`]);g.exitCode===0&&(y=g.stdout)}t.json({ok:!0,filePath:o,repositoryRelativeFilePath:c,commitHash:u||null,rangeFromCommit:l,diffContent:p.stdout,fileContent:y,workspacePath:n.workspacePath,repositoryRootPath:n.repositoryRootPath,usingParentGitRepo:n.usingParentGitRepo})}catch(n){const o=n;t.status(500).json({error:o.message||"git diff file failed"})}});const et=["STRICTLY act as a Git commit message generator. NEVER explain, narrate, or describe your task.",'NEVER start your reply with phrases like "\u6211\u4EEC\u88AB\u95EE\u5230", "\u4EE5\u4E0B\u662F", "\u597D\u7684", "Sure", "Here is", "I will", "I can", "Let me", "\u6839\u636E", "\u4E0B\u9762".',"NEVER include any meta-language or thinking out loud. Output ONLY the commit message text.","","Format (Conventional Commits, 1 line, optional body):"," <type>(<scope>): <description>","","Allowed types: feat, fix, refactor, test, docs, style, chore, perf, ci, build, revert.","First line MUST be <= 72 characters. <description> MUST be in Chinese.","Add a body (separated by a blank line) ONLY when the change needs more context."].join(`
20
+ `);function rt(e){return["\u8BF7\u76F4\u63A5\u8F93\u51FA\u4E0A\u8FF0\u683C\u5F0F\u7684\u63D0\u4EA4\u4FE1\u606F,\u4E0D\u8981\u4EFB\u4F55\u989D\u5916\u6587\u5B57\u3002","\u4EE5\u4E0B\u662F\u6682\u5B58\u533A diff:","",e].join(`
21
+ `)}const st=/\b(?:feat|fix|chore|docs|style|refactor|perf|test|build|ci|revert)(?:\s*\([^)]*\))?\s*:/i,Ut=/^(?:feat|fix|chore|docs|style|refactor|perf|test|build|ci|revert)\s*:/i;function Jt(e){const t=e.split(`
22
+ `)[0]?.trim()??"";if(!t||t.length<3)return!1;const r=(t.match(/[\u4e00-\u9fa5]/g)||[]).length,i=(t.match(/[a-zA-Z]{2,}/g)||[]).length;if(r===0&&i===0)return!1;const s=[/^以下是/,/^上面是/,/^生成的/,/^这是/,/^下面/,/^(?:Here|This|This is|That is)/i];for(const a of s)if(a.test(t))return!1;return r>=2||i>=2}const Kt=[/^\s*(?:我们|我)\s*(?:被\s*)?(?:问[到过]?|需要|要|将|来|先)[^.:\n]{0,60}?(?:提交信息|commit\s*message|[::,,。])/i,/^\s*(?:好的|好)\s*[,,::]/i,/^\s*(?:好的|好)\s*(?:以下是|上面是|提供|生成|给出)[^.:\n]{0,20}?[::]?/i,/^\s*是\s*[::]/i,/^\s*(?:Sure|Here(?:'s|\s+is)|We(?:'re| are)|I\s+(?:will|can|'ll)|Let\s+me|Certainly|Of\s+course)[^.:\n]{0,40}?[,,::.]/i,/^\s*根据\s*(?:您|你|您所|你所|所)?\s*(?:提供|上述|给[我您你])?\s*(?:的)?\s*diff\s*[,,::]?/i,/^\s*(?:以下|下面)[^,,::\n]{0,30}?提交信息\s*[,,::]?/i,/^\s*(?:以下|下面)\s*是\s*[,,::]/i,/^\s*生成\s*(?:一条|一个|了)?\s*(?:符合|新的)?\s*(?:conventional\s*commit|git)?\s*提交信息\s*[,,::]?/i,/^\s*```(?:text|markdown|bash|shell)?\s*\n?/i];function D(e){if(!e)return m.info("[Git/sanitize] \u8F93\u5165\u4E3A\u7A7A, \u8FD4\u56DE fallback"),"chore: update staged changes";let t=e.replace(/\r\n/g,`
23
+ `).trim();const r=JSON.stringify(e.substring(0,120)),i=t.match(st);if(i&&i.index!==void 0&&i.index<=80)t=t.slice(i.index);else{for(let f=0;f<5;f+=1){const d=t;for(const p of Kt)t=t.replace(p,"");if(t=t.replace(/^[\s*\-`,,']+/,"").trim(),t===d)break}const n=t.split(`
24
+ `)[0]?.trim()??"",o=!!t.match(st),c=!!t.match(Ut),u=Jt(t),l=n.length>100;if(!o&&!c&&!u&&!l)return m.info(`[Git/sanitize] \u5265\u79BB\u540E\u65E0\u6709\u6548\u5185\u5BB9, \u8FD4\u56DE fallback. raw=${r}, afterStrip=${JSON.stringify(t.substring(0,100))}`),"chore: update staged changes";!o&&(c||u||l)&&(t=n||t)}const s=t.search(/\n\s*\n/);if(s>0&&(t=t.slice(0,s)),t=t.replace(/\n+\s*```\s*$/g,"").trim(),!t)return m.info(`[Git/sanitize] \u622A\u65AD\u540E\u4E3A\u7A7A, \u8FD4\u56DE fallback. raw=${r}`),"chore: update staged changes";const a=t.split(`
25
+ `)[0]?.trim()??"";return a.length>100?(m.info(`[Git/sanitize] \u9996\u884C\u8D85\u8FC7 100 \u5B57\u7B26, \u4EC5\u4FDD\u7559\u9996\u884C. raw=${r}, firstLine=${JSON.stringify(a.substring(0,100))}`),a):(m.info(`[Git/sanitize] \u7ED3\u679C: ${JSON.stringify(t.substring(0,120))} (from raw=${r})`),t)}function ot(e){const t=e.split(/\r?\n/);let r="";for(const i of t){const s=i.trim();if(!s.startsWith("data:"))continue;const a=s.slice(5).trim();if(!(!a||a==="[DONE]"))try{const o=JSON.parse(a)?.choices?.[0]?.delta?.content;typeof o=="string"&&o.length>0&&(r+=o)}catch{}}return r}function Yt(e,t){if(t>5)return;const r=e.split(/\r?\n/);for(const i of r){const s=i.trim();if(!s.startsWith("data:"))continue;const a=s.slice(5).trim();if(!(!a||a==="[DONE]"))try{const n=JSON.parse(a),o=Object.keys(n),c=n?.choices,u=Array.isArray(c)?c[0]:null,l=u?.delta?Object.keys(u.delta):null,f=u?.delta?.content;m.info(`[Git/extract] chunk#${t} structure: topKeys=${JSON.stringify(o)}, choicesLen=${Array.isArray(c)?c.length:typeof c}, deltaKeys=${JSON.stringify(l)}, content=${JSON.stringify(f?.substring(0,50))}, finishReason=${JSON.stringify(u?.finish_reason)}`)}catch{}}}async function Vt(e,t,r,i){const s=`${e.replace(/\/+$/,"")}/chat/completions`,o=(await I.post(s,{model:r,stream:!1,messages:[{role:"system",content:et},{role:"user",content:rt(i)}],max_tokens:200,temperature:.2},{headers:{"Content-Type":"application/json",Authorization:`Bearer ${t}`},timeout:3e4})).data?.choices?.[0]?.message?.content?.trim()||"";return D(o)}async function Zt(e,t,r,i,s,a){const n=`${e.replace(/\/+$/,"")}/chat/completions`,c=(await I.post(n,{model:r,stream:!0,messages:[{role:"system",content:et},{role:"user",content:rt(i)}],max_tokens:200,temperature:.2},{headers:{"Content-Type":"application/json",Authorization:`Bearer ${t}`,Accept:"text/event-stream"},timeout:Number(process.env.AWS_AI_STREAM_TIMEOUT_MS)||6e4,responseType:"stream",signal:a})).data;let u="",l="",f="",d=0,p=0;return m.info(`[Git/streamAi] AI \u6D41\u5F00\u59CB: aiModel=${r}`),await new Promise((y,g)=>{const w=P=>{c.removeAllListeners("data"),c.removeAllListeners("end"),c.removeAllListeners("error"),c.removeAllListeners("aborted"),m.info(`[Git/streamAi] AI \u6D41\u7ED3\u675F: totalChunks=${d}, totalDeltas=${p}, bufferLength=${u.length}, sanitized=${JSON.stringify(P).substring(0,200)}`),y(P)};c.on("data",P=>{const b=typeof P=="string"?P:P.toString("utf8");d++,f+=b,d<=3&&m.info(`[Git/streamAi] chunk#${d}: length=${b.length}, preview=${JSON.stringify(b.substring(0,80))}`),Yt(b,d);const A=f.split(/\n\n/);f=A.pop()??"";for(const M of A){const E=ot(M);E&&(p++,u+=E,l=D(u),s(E,l))}}),c.on("end",()=>{if(f.trim()){const P=ot(f);P?(m.info(`[Git/streamAi] \u5C3E\u90E8\u6B8B\u7559 delta: length=${P.length}`),u+=P):m.info(`[Git/streamAi] \u5C3E\u90E8\u6B8B\u7559\u65E0 delta, pendingData=${JSON.stringify(f.substring(0,100))}`)}w(D(u))}),c.on("error",P=>{m.error(`[Git/streamAi] \u6D41\u9519\u8BEF: ${P.message}`),g(P)}),c.on("aborted",()=>{m.info(`[Git/streamAi] \u6D41\u88AB\u4E2D\u6B62, bufferLength=${u.length}`),w(D(u))})})}async function W(e,t,r){const i=await C(String(e||t).trim());if(!i.hasRepository||!i.repositoryRootPath)return m.warn(`[Git/collectDiff] \u4E0D\u5728 Git \u4ED3\u5E93\u4E2D: workspacePath=${t}`),{truncatedDiff:"",error:{status:400,message:"workspace is not inside a git repository"}};const s=r.map(u=>u.path).filter(Boolean);m.info(`[Git/collectDiff] \u5F00\u59CB\u6536\u96C6 ${s.length} \u4E2A\u6682\u5B58\u6587\u4EF6\u5DEE\u5F02: ${s.join(", ").substring(0,200)}`);const a=[];for(const u of s){const l=G(u,i),f=await h(i.repositoryRootPath,["diff","--cached","--",l]);f.exitCode===0&&f.stdout.trim()?a.push(`--- ${u} ---
26
+ ${f.stdout.trim()}`):m.info(`[Git/collectDiff] \u6587\u4EF6\u65E0\u6682\u5B58\u5DEE\u5F02\u6216\u83B7\u53D6\u5931\u8D25: ${u}, exitCode=${f.exitCode}, stdout.length=${f.stdout.length}`)}if(a.length===0){m.info("[Git/collectDiff] \u9010\u6587\u4EF6 diff \u4E3A\u7A7A, \u56DE\u9000\u5230\u5168\u91CF diff --cached");const u=await h(i.repositoryRootPath,["diff","--cached"]);u.exitCode===0&&u.stdout.trim()?(a.push(u.stdout.trim()),m.info(`[Git/collectDiff] \u5168\u91CF diff \u83B7\u53D6\u6210\u529F: length=${u.stdout.trim().length}`)):m.info(`[Git/collectDiff] \u5168\u91CF diff \u4E5F\u4E3A\u7A7A, exitCode=${u.exitCode}`)}if(a.length===0)return m.info("[Git/collectDiff] \u6682\u5B58\u533A\u6CA1\u6709\u5DEE\u5F02\u5185\u5BB9 (diffParts \u4E3A\u7A7A)"),{truncatedDiff:""};const n=a.join(`
27
27
 
28
- `),o=15e3,c=a.length>o?`${a.slice(0,o)}
29
- ...(diff truncated due to length)`:a;return m.info(`[Git/collectDiff] \u6536\u96C6\u5B8C\u6210: diffContent.length=${a.length}, truncated=${c.length}`),{truncatedDiff:c}}function $(r,t,e){try{r.write(`event: ${t}
30
- `),r.write(`data: ${JSON.stringify(e)}
28
+ `),o=15e3,c=n.length>o?`${n.slice(0,o)}
29
+ ...(diff truncated due to length)`:n;return m.info(`[Git/collectDiff] \u6536\u96C6\u5B8C\u6210: diffContent.length=${n.length}, truncated=${c.length}`),{truncatedDiff:c}}function $(e,t,r){try{e.write(`event: ${t}
30
+ `),e.write(`data: ${JSON.stringify(r)}
31
31
 
32
- `)}catch{}}S.post("/git/generate-commit-message",k,async(r,t)=>{const{workspacePath:e,repositoryPath:i,stagedFiles:s,aiBaseUrl:n,aiApiKey:a,aiModel:o}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}if(!Array.isArray(s)||s.length===0){t.status(400).json({error:"stagedFiles is required and must be a non-empty array"});return}if(!n||!String(n).trim()||!a||!String(a).trim()||!o||!String(o).trim()){t.status(400).json({error:"aiBaseUrl, aiApiKey and aiModel are required"});return}try{const{truncatedDiff:c,error:u}=await W(String(i||""),String(e),s);if(u){t.status(u.status).json({error:u.message});return}if(!c){t.json({message:"\u6682\u5B58\u533A\u6CA1\u6709\u53D8\u66F4\u5185\u5BB9"});return}const l=await Lt(n,a,o,c);t.json({message:l})}catch(c){const u=c;m.error(`[Git] generate commit message failed: ${u.message}`),t.status(500).json({error:u.message||"generate commit message failed"})}}),S.post("/git/generate-commit-message-stream",k,async(r,t)=>{at();const{workspacePath:e,repositoryPath:i,stagedFiles:s,aiBaseUrl:n,aiApiKey:a,aiModel:o}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}if(!Array.isArray(s)||s.length===0){t.status(400).json({error:"stagedFiles is required and must be a non-empty array"});return}if(!n||!String(n).trim()||!a||!String(a).trim()||!o||!String(o).trim()){t.status(400).json({error:"aiBaseUrl, aiApiKey and aiModel are required"});return}m.info(`[Git/SSE] \u6536\u5230 generate-commit-message-stream \u8BF7\u6C42: workspacePath=${String(e||"").substring(0,40)}, stagedFiles=${s?.length??0}, aiModel=${o}, aiBaseUrl=${n}`),t.status(200),t.setHeader("Content-Type","text/event-stream; charset=utf-8"),t.setHeader("Cache-Control","no-cache, no-transform"),t.setHeader("Connection","keep-alive"),t.setHeader("X-Accel-Buffering","no"),r.socket?.setNoDelay(!0),t.flushHeaders?.();let c=!1,u=null;const l={delta:0},f=`${r.ip}:${r.socket?.remotePort??"?"}`;r.on("close",()=>{c||(c=!0,m.info(`[Git/SSE] \u5BA2\u6237\u7AEF\u65AD\u5F00\u8FDE\u63A5: workspacePath=${String(e||"").substring(0,40)}, deltaEventsSent=${l.delta}, peer=${f}`))}),$(t,"init",{status:"generating"});try{const{truncatedDiff:d,error:p}=await W(String(i||""),String(e),s);if(p){m.warn(`[Git/SSE] collectStagedDiffForAi \u8FD4\u56DE\u9519\u8BEF: ${p.message}`),$(t,"error",{message:p.message}),t.end();return}if(!d){m.info("[Git/SSE] \u6682\u5B58\u533A\u6CA1\u6709\u53D8\u66F4\u5185\u5BB9, \u76F4\u63A5\u8FD4\u56DE done"),$(t,"delta",{contentDelta:"",message:"\u6682\u5B58\u533A\u6CA1\u6709\u53D8\u66F4\u5185\u5BB9"}),$(t,"done",{message:"\u6682\u5B58\u533A\u6CA1\u6709\u53D8\u66F4\u5185\u5BB9"}),t.end();return}m.info(`[Git/SSE] \u6536\u96C6\u5230 diff: truncatedDiff.length=${d.length}`),c&&m.info("[Git/SSE] \u5BA2\u6237\u7AEF\u5728 collectDiff \u671F\u95F4\u65AD\u5F00, \u4F46\u4ECD\u5C1D\u8BD5 AI \u8C03\u7528"),m.info(`[Git/SSE] \u5F00\u59CB\u8C03\u7528 AI streamAiForCommitMessage: aiModel=${o}`),u=new AbortController;let y="";const g=await _t(n,a,o,d,(w,P)=>{l.delta++,P!==y?(y=P,$(t,"delta",{contentDelta:w,message:P})):$(t,"delta",{contentDelta:w,message:P})},u.signal);m.info(`[Git/SSE] AI streamAiForCommitMessage \u5B8C\u6210: finalMessage=${JSON.stringify(g).substring(0,200)}, deltaEventsSent=${l.delta}`),c||$(t,"done",{message:g}),t.end()}catch(d){const p=d;m.error(`[Git/SSE] stream commit message failed: ${p.message}${p.stack?`
33
- `+p.stack:""}`),c||$(t,"error",{message:p.message||"generate commit message failed"}),t.end()}}),S.post("/git/unstage-files",k,async(r,t)=>{const{workspacePath:e,repositoryPath:i,filePaths:s}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}const a=(Array.isArray(s)?s:[]).map(o=>R(String(o||"").trim())).filter(o=>o.length>0);if(a.length===0){t.status(400).json({error:"filePaths is required and must be a non-empty array"});return}try{const o=await j(String(i||e).trim());if(!o.hasRepository||!o.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}let c="";for(const u of a){const l=G(u,o),f=await h(o.repositoryRootPath,["restore","--staged",...l]);f.exitCode!==0&&(c=f.stderr||`git restore --staged failed for ${u}`)}if(c){t.status(400).json({error:c});return}t.json({ok:!0,filePaths:a,workspacePath:o.workspacePath,repositoryRootPath:o.repositoryRootPath,relativeWorkspacePath:o.relativeWorkspacePath})}catch(o){const c=o;t.status(500).json({error:c.message||"git unstage files failed"})}}),S.post("/git/restore-files",k,async(r,t)=>{const{workspacePath:e,repositoryPath:i,filePaths:s}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}const a=(Array.isArray(s)?s:[]).map(o=>R(String(o||"").trim())).filter(o=>o.length>0);if(a.length===0){t.status(400).json({error:"filePaths is required and must be a non-empty array"});return}try{const o=await j(String(i||e).trim());if(!o.hasRepository||!o.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}let c="";for(const u of a){const l=E(u,o),f=G(u,o),p=(await h(o.repositoryRootPath,["ls-files","--error-unmatch","--",l])).exitCode===0,y=p?await h(o.repositoryRootPath,["restore",...f]):await h(o.repositoryRootPath,["clean","-f",...f]);y.exitCode!==0&&(c=y.stderr||`${p?"git restore":"git clean -f"} failed for ${u}`)}if(c){t.status(400).json({error:c});return}t.json({ok:!0,filePaths:a,workspacePath:o.workspacePath,repositoryRootPath:o.repositoryRootPath,relativeWorkspacePath:o.relativeWorkspacePath})}catch(o){const c=o;t.status(500).json({error:c.message||"git restore files failed"})}});function Ot(r){const t=r.split(`
34
- `),e=[];for(let i=0;i<t.length;i++){const s=t[i],n=s.endsWith("\r")?"\r":"",o=(n?s.slice(0,-1):s).match(/^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/);if(!o){e.push(s);continue}let c=0,u=0;for(let l=i+1;l<t.length;l++){const f=t[l].replace(/\r$/,"");if(/^diff --git /.test(f)||/^@@ /.test(f))break;f.startsWith("+")?u++:f.startsWith("-")?c++:f.startsWith(" ")&&(c++,u++)}e.push(`@@ -${o[1]},${c} +${o[2]},${u} @@${n}`)}return e.join(`
35
- `)}S.post("/git/revert-hunk",k,async(r,t)=>{const{workspacePath:e,repositoryPath:i,filePath:s,patch:n}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}if(!s||!String(s).trim()){t.status(400).json({error:"filePath is required"});return}const a=String(n||"").replace(/^\uFEFF/,"").trim();if(!a){t.status(400).json({error:"patch is required"});return}try{const o=await j(String(i||e).trim());if(!o.hasRepository||!o.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}const c=R(String(s).trim()),u=E(c,o);if(M(u),(a.match(/^diff --git /gm)||[]).length!==1){t.status(400).json({error:"patch must contain exactly one file diff"});return}const f=a.match(/^\+\+\+ b\/(.+)$/m),d=f?R(f[1].trim()):"";if(!d||d!==u){t.status(400).json({error:"patch target path does not match filePath"});return}let p=null;try{p=await b.mkdtemp(x.join(ot.tmpdir(),"agentswork-git-revert-"));const y=x.join(p,"revert.patch");await b.writeFile(y,Ot(a)+`
36
- `,"utf-8");const g=await h(o.repositoryRootPath,["apply","--reverse",y]);if(g.exitCode!==0){t.status(400).json({error:g.stderr||"git apply --reverse failed; \u8BE5\u5904\u5185\u5BB9\u53EF\u80FD\u5DF2\u53D8\u5316\uFF0C\u8BF7\u5237\u65B0\u5DEE\u5F02\u540E\u91CD\u8BD5"});return}t.json({ok:!0,filePath:c,repositoryRelativeFilePath:u,workspacePath:o.workspacePath,repositoryRootPath:o.repositoryRootPath})}finally{p&&await b.rm(p,{recursive:!0,force:!0}).catch(()=>{})}}catch(o){const c=o;t.status(500).json({error:c.message||"git revert hunk failed"})}}),S.post("/git/diff-content",k,async(r,t)=>{const{workspacePath:e,repositoryPath:i,stagedFiles:s}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}if(!Array.isArray(s)||s.length===0){t.status(400).json({error:"stagedFiles is required and must be a non-empty array"});return}try{const{truncatedDiff:n,error:a}=await W(String(i||""),String(e),s);if(a){t.status(a.status).json({error:a.message});return}t.json({ok:!0,diff:n||""})}catch(n){const a=n;m.error(`[Git/diff-content] \u83B7\u53D6\u6682\u5B58\u533A\u5DEE\u5F02\u5931\u8D25: ${a.message}`),t.status(500).json({error:a.message||"get diff content failed"})}});export{ut as assertGitWorkspacePathAccessible,$t as buildCommitDiffBaseArgs,Lt as callAiForCommitMessage,Rt as collectUntrackedFiles,Et as commitSelectedFiles,St as countUntrackedFileAdditions,mt as createAgentsWorkStashSnapshotRef,At as createGitStashSnapshot,dt as createMissingInitialCommitSnapshotError,G as createScopedFilePathspecArgs,Ct as createScopedGitPathspecArgs,Wt as debugAiStreamChunk,et as extractOpenAiStreamDelta,S as gitRouter,ht as isGitStashMissingInitialCommitError,pt as isGitStashNoChangesOutput,J as listGitBranches,xt as mergeGitDiffSummaryFiles,jt as normalizeGitCommitMessage,H as parseGitDiffSummary,_ as parseGitStashListLine,wt as parseGitStatusStagedPaths,kt as parseGitStatusUntrackedPaths,I as parseLatestGitStash,z as persistAgentsWorkStashSnapshotRef,Dt as previewRepositoryReset,vt as pushBranchToRemote,Mt as resetRepositoryTo,N as resolveCurrentBranchName,ct as resolveGitCommandTimeoutMs,yt as resolveGitPushTimeoutMs,U as resolveRemoteCommitHashes,T as sanitizeCommitMessage,ft as shouldIncludeGitDiffSummaryFile,_t as streamAiForCommitMessage};
32
+ `)}catch{}}S.post("/git/generate-commit-message",k,async(e,t)=>{const{workspacePath:r,repositoryPath:i,stagedFiles:s,aiBaseUrl:a,aiApiKey:n,aiModel:o}=e.body||{};if(!r||!String(r).trim()){t.status(400).json({error:"workspacePath is required"});return}if(!Array.isArray(s)||s.length===0){t.status(400).json({error:"stagedFiles is required and must be a non-empty array"});return}if(!a||!String(a).trim()||!n||!String(n).trim()||!o||!String(o).trim()){t.status(400).json({error:"aiBaseUrl, aiApiKey and aiModel are required"});return}try{const{truncatedDiff:c,error:u}=await W(String(i||""),String(r),s);if(u){t.status(u.status).json({error:u.message});return}if(!c){t.json({message:"\u6682\u5B58\u533A\u6CA1\u6709\u53D8\u66F4\u5185\u5BB9"});return}const l=await Vt(a,n,o,c);t.json({message:l})}catch(c){const u=c;m.error(`[Git] generate commit message failed: ${u.message}`),t.status(500).json({error:u.message||"generate commit message failed"})}}),S.post("/git/generate-commit-message-stream",k,async(e,t)=>{ct();const{workspacePath:r,repositoryPath:i,stagedFiles:s,aiBaseUrl:a,aiApiKey:n,aiModel:o}=e.body||{};if(!r||!String(r).trim()){t.status(400).json({error:"workspacePath is required"});return}if(!Array.isArray(s)||s.length===0){t.status(400).json({error:"stagedFiles is required and must be a non-empty array"});return}if(!a||!String(a).trim()||!n||!String(n).trim()||!o||!String(o).trim()){t.status(400).json({error:"aiBaseUrl, aiApiKey and aiModel are required"});return}m.info(`[Git/SSE] \u6536\u5230 generate-commit-message-stream \u8BF7\u6C42: workspacePath=${String(r||"").substring(0,40)}, stagedFiles=${s?.length??0}, aiModel=${o}, aiBaseUrl=${a}`),t.status(200),t.setHeader("Content-Type","text/event-stream; charset=utf-8"),t.setHeader("Cache-Control","no-cache, no-transform"),t.setHeader("Connection","keep-alive"),t.setHeader("X-Accel-Buffering","no"),e.socket?.setNoDelay(!0),t.flushHeaders?.();let c=!1,u=null;const l={delta:0},f=`${e.ip}:${e.socket?.remotePort??"?"}`;e.on("close",()=>{c||(c=!0,m.info(`[Git/SSE] \u5BA2\u6237\u7AEF\u65AD\u5F00\u8FDE\u63A5: workspacePath=${String(r||"").substring(0,40)}, deltaEventsSent=${l.delta}, peer=${f}`))}),$(t,"init",{status:"generating"});try{const{truncatedDiff:d,error:p}=await W(String(i||""),String(r),s);if(p){m.warn(`[Git/SSE] collectStagedDiffForAi \u8FD4\u56DE\u9519\u8BEF: ${p.message}`),$(t,"error",{message:p.message}),t.end();return}if(!d){m.info("[Git/SSE] \u6682\u5B58\u533A\u6CA1\u6709\u53D8\u66F4\u5185\u5BB9, \u76F4\u63A5\u8FD4\u56DE done"),$(t,"delta",{contentDelta:"",message:"\u6682\u5B58\u533A\u6CA1\u6709\u53D8\u66F4\u5185\u5BB9"}),$(t,"done",{message:"\u6682\u5B58\u533A\u6CA1\u6709\u53D8\u66F4\u5185\u5BB9"}),t.end();return}m.info(`[Git/SSE] \u6536\u96C6\u5230 diff: truncatedDiff.length=${d.length}`),c&&m.info("[Git/SSE] \u5BA2\u6237\u7AEF\u5728 collectDiff \u671F\u95F4\u65AD\u5F00, \u4F46\u4ECD\u5C1D\u8BD5 AI \u8C03\u7528"),m.info(`[Git/SSE] \u5F00\u59CB\u8C03\u7528 AI streamAiForCommitMessage: aiModel=${o}`),u=new AbortController;let y="";const g=await Zt(a,n,o,d,(w,P)=>{l.delta++,P!==y?(y=P,$(t,"delta",{contentDelta:w,message:P})):$(t,"delta",{contentDelta:w,message:P})},u.signal);m.info(`[Git/SSE] AI streamAiForCommitMessage \u5B8C\u6210: finalMessage=${JSON.stringify(g).substring(0,200)}, deltaEventsSent=${l.delta}`),c||$(t,"done",{message:g}),t.end()}catch(d){const p=d;m.error(`[Git/SSE] stream commit message failed: ${p.message}${p.stack?`
33
+ `+p.stack:""}`),c||$(t,"error",{message:p.message||"generate commit message failed"}),t.end()}}),S.post("/git/unstage-files",k,async(e,t)=>{const{workspacePath:r,repositoryPath:i,filePaths:s}=e.body||{};if(!r||!String(r).trim()){t.status(400).json({error:"workspacePath is required"});return}const n=(Array.isArray(s)?s:[]).map(o=>R(String(o||"").trim())).filter(o=>o.length>0);if(n.length===0){t.status(400).json({error:"filePaths is required and must be a non-empty array"});return}try{const o=await C(String(i||r).trim());if(!o.hasRepository||!o.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}let c="";for(const u of n){const l=T(u,o),f=await h(o.repositoryRootPath,["restore","--staged",...l]);f.exitCode!==0&&(c=f.stderr||`git restore --staged failed for ${u}`)}if(c){t.status(400).json({error:c});return}t.json({ok:!0,filePaths:n,workspacePath:o.workspacePath,repositoryRootPath:o.repositoryRootPath,relativeWorkspacePath:o.relativeWorkspacePath})}catch(o){const c=o;t.status(500).json({error:c.message||"git unstage files failed"})}}),S.post("/git/restore-files",k,async(e,t)=>{const{workspacePath:r,repositoryPath:i,filePaths:s}=e.body||{};if(!r||!String(r).trim()){t.status(400).json({error:"workspacePath is required"});return}const n=(Array.isArray(s)?s:[]).map(o=>R(String(o||"").trim())).filter(o=>o.length>0);if(n.length===0){t.status(400).json({error:"filePaths is required and must be a non-empty array"});return}try{const o=await C(String(i||r).trim());if(!o.hasRepository||!o.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}let c="";for(const u of n){const l=G(u,o),f=T(u,o),p=(await h(o.repositoryRootPath,["ls-files","--error-unmatch","--",l])).exitCode===0,y=p?await h(o.repositoryRootPath,["restore",...f]):await h(o.repositoryRootPath,["clean","-f",...f]);y.exitCode!==0&&(c=y.stderr||`${p?"git restore":"git clean -f"} failed for ${u}`)}if(c){t.status(400).json({error:c});return}t.json({ok:!0,filePaths:n,workspacePath:o.workspacePath,repositoryRootPath:o.repositoryRootPath,relativeWorkspacePath:o.relativeWorkspacePath})}catch(o){const c=o;t.status(500).json({error:c.message||"git restore files failed"})}});function Xt(e){const t=e.split(`
34
+ `),r=[];for(let i=0;i<t.length;i++){const s=t[i],a=s.endsWith("\r")?"\r":"",o=(a?s.slice(0,-1):s).match(/^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/);if(!o){r.push(s);continue}let c=0,u=0;for(let l=i+1;l<t.length;l++){const f=t[l].replace(/\r$/,"");if(/^diff --git /.test(f)||/^@@ /.test(f))break;f.startsWith("+")?u++:f.startsWith("-")?c++:f.startsWith(" ")&&(c++,u++)}r.push(`@@ -${o[1]},${c} +${o[2]},${u} @@${a}`)}return r.join(`
35
+ `)}S.post("/git/revert-hunk",k,async(e,t)=>{const{workspacePath:r,repositoryPath:i,filePath:s,patch:a}=e.body||{};if(!r||!String(r).trim()){t.status(400).json({error:"workspacePath is required"});return}if(!s||!String(s).trim()){t.status(400).json({error:"filePath is required"});return}const n=String(a||"").replace(/^\uFEFF/,"").trim();if(!n){t.status(400).json({error:"patch is required"});return}try{const o=await C(String(i||r).trim());if(!o.hasRepository||!o.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}const c=R(String(s).trim()),u=G(c,o);if(H(u),(n.match(/^diff --git /gm)||[]).length!==1){t.status(400).json({error:"patch must contain exactly one file diff"});return}const f=n.match(/^\+\+\+ b\/(.+)$/m),d=f?R(f[1].trim()):"";if(!d||d!==u){t.status(400).json({error:"patch target path does not match filePath"});return}let p=null;try{p=await j.mkdtemp(x.join(at.tmpdir(),"agentswork-git-revert-"));const y=x.join(p,"revert.patch");await j.writeFile(y,Xt(n)+`
36
+ `,"utf-8");const g=await h(o.repositoryRootPath,["apply","--reverse",y]);if(g.exitCode!==0){t.status(400).json({error:g.stderr||"git apply --reverse failed; \u8BE5\u5904\u5185\u5BB9\u53EF\u80FD\u5DF2\u53D8\u5316\uFF0C\u8BF7\u5237\u65B0\u5DEE\u5F02\u540E\u91CD\u8BD5"});return}t.json({ok:!0,filePath:c,repositoryRelativeFilePath:u,workspacePath:o.workspacePath,repositoryRootPath:o.repositoryRootPath})}finally{p&&await j.rm(p,{recursive:!0,force:!0}).catch(()=>{})}}catch(o){const c=o;t.status(500).json({error:c.message||"git revert hunk failed"})}}),S.post("/git/diff-content",k,async(e,t)=>{const{workspacePath:r,repositoryPath:i,stagedFiles:s}=e.body||{};if(!r||!String(r).trim()){t.status(400).json({error:"workspacePath is required"});return}if(!Array.isArray(s)||s.length===0){t.status(400).json({error:"stagedFiles is required and must be a non-empty array"});return}try{const{truncatedDiff:a,error:n}=await W(String(i||""),String(r),s);if(n){t.status(n.status).json({error:n.message});return}t.json({ok:!0,diff:a||""})}catch(a){const n=a;m.error(`[Git/diff-content] \u83B7\u53D6\u6682\u5B58\u533A\u5DEE\u5F02\u5931\u8D25: ${n.message}`),t.status(500).json({error:n.message||"get diff content failed"})}});export{ft as assertGitWorkspacePathAccessible,Nt as buildCommitDiffBaseArgs,St as buildGitCommitLogArgs,Vt as callAiForCommitMessage,Tt as collectUntrackedFiles,It as commitSelectedFiles,Mt as countUntrackedFileAdditions,bt as createAgentsWorkStashSnapshotRef,Lt as createGitStashSnapshot,Ct as createMissingInitialCommitSnapshotError,T as createScopedFilePathspecArgs,_t as createScopedGitPathspecArgs,Yt as debugAiStreamChunk,ot as extractOpenAiStreamDelta,S as gitRouter,xt as isGitStashMissingInitialCommitError,jt as isGitStashNoChangesOutput,V as listGitBranches,Dt as mergeGitDiffSummaryFiles,Ht as normalizeGitCommitMessage,Ft as parseGitCommitLog,F as parseGitDiffSummary,z as parseGitStashListLine,Et as parseGitStatusStagedPaths,Gt as parseGitStatusUntrackedPaths,q as parseLatestGitStash,U as persistAgentsWorkStashSnapshotRef,Bt as previewRepositoryReset,Wt as pushBranchToRemote,qt as resetRepositoryTo,yt as resolveCommitPageLimit,Pt as resolveCommitPageSkip,N as resolveCurrentBranchName,lt as resolveGitCommandTimeoutMs,vt as resolveGitPushTimeoutMs,Y as resolveRemoteCommitHashes,D as sanitizeCommitMessage,Rt as shouldIncludeGitDiffSummaryFile,wt as sliceCommitPage,Zt as streamAiForCommitMessage};
37
37
 
@@ -1,2 +1,2 @@
1
- import{execSync as R}from"node:child_process";import{Router as C}from"express";import{adapterRegistry as y}from"../adapter/index.js";import{validateToken as I,validateTokenOrLoopback as P}from"../middleware/auth.js";import{isProcessRunning as w,findOrphanProcessByAgentId as b,findOrphanProcessByWorkspace as F}from"../services/process-detector.js";import{findPersistedSessionByAgentId as S,loadPersistedSessions as E,removePersistedSession as A}from"../services/terminal-persistence.js";import{createLogger as K}from"../utils/logger.js";import{sdkSessions as m,cleanupSdkSessionByAgentId as L}from"./terminal.js";const d=K("sessions"),h=C();h.post("/sessions/:agentId/notify-force-message",P,(i,r)=>{const e=String(i.params.agentId??"").trim();if(!e){r.status(400).json({ok:!1,error:"agentId is required"});return}const n=Array.from(m.entries()).find(([,a])=>a.agentId===e);if(!n){r.json({ok:!0,notified:!1,message:"No active SDK session for this agentId"});return}const[o,s]=n,t=y.get(s.providerId||"claude-code");if(typeof t.notifyForceMessage=="function"){const a=t.notifyForceMessage(o);r.json({ok:!0,notified:a})}else r.json({ok:!0,notified:!1,message:"Adapter does not support force message notification"})}),h.post("/sessions/:agentId/capability-tools",P,async(i,r)=>{const e=String(i.params.agentId??"").trim(),n=()=>{const s=Array.from(m.entries()).find(([,t])=>t.agentId===e);return s?{agentId:e,providerId:s[1].providerId||""}:void 0},o=await x({agentId:e,capability:String(i.body?.capability??""),action:i.body?.action,resolveSession:n,resolveAdapter:s=>y.get(s||"claude-code")});r.status(o.status).json(o.body)}),h.post("/sessions/:agentId/capability-tools/call",P,async(i,r)=>{const e=String(i.params.agentId??"").trim(),n=()=>{const s=Array.from(m.entries()).find(([,t])=>t.agentId===e);return s?{agentId:e,providerId:s[1].providerId||""}:void 0},o=await B({agentId:e,capability:String(i.body?.capability??""),tool:String(i.body?.tool??""),args:i.body?.args??{},resolveSession:n,resolveAdapter:s=>y.get(s||"claude-code")});r.status(o.status).json(o.body)});const D=new Set(["browser","desktop"]);async function x(i){const r=String(i.agentId??"").trim();if(!r)return{status:400,body:{ok:!1,error:"agentId is required"}};const e=String(i.capability??"").trim();if(!D.has(e))return{status:400,body:{ok:!1,error:'capability must be "browser" or "desktop"'}};const n=i.action;if(n!=="expand"&&n!=="collapse")return{status:400,body:{ok:!1,error:`action must be "expand" or "collapse", got: ${String(n)}`}};const o=i.resolveSession();if(!o)return{status:200,body:{ok:!0,changed:{sessions:0,tools:0}}};const s=i.resolveAdapter?.(o.providerId);if(!s||typeof s.setBuiltInCapabilityTools!="function")return{status:200,body:{ok:!0,changed:{sessions:0,tools:0},message:`Adapter for provider "${o.providerId}" does not support setBuiltInCapabilityTools`}};try{const t=await s.setBuiltInCapabilityTools(r,e,n),a={ok:!0,changed:{sessions:t.sessions,tools:t.tools}};return n==="expand"&&Array.isArray(t.manifests)&&(a.manifests=t.manifests),{status:200,body:a}}catch(t){const a=t instanceof Error?t.message:String(t);return d.error(`capability-tools ${n} failed for agent ${r}: ${a}`),{status:500,body:{ok:!1,error:a}}}}async function B(i){const r=String(i.agentId??"").trim();if(!r)return{status:400,body:{ok:!1,error:"agentId is required"}};const e=String(i.capability??"").trim();if(!D.has(e))return{status:400,body:{ok:!1,error:'capability must be "browser" or "desktop"'}};const n=String(i.tool??"").trim();if(!n)return{status:400,body:{ok:!1,error:"tool is required"}};const o=i.resolveSession();if(!o)return{status:200,body:{ok:!1,error:`No active SDK session for agentId "${r}"`}};const s=i.resolveAdapter?.(o.providerId);if(!s||typeof s.callBuiltInCapabilityTool!="function")return{status:200,body:{ok:!1,error:`Adapter for provider "${o.providerId}" does not support callBuiltInCapabilityTool`}};try{return{status:200,body:await s.callBuiltInCapabilityTool(r,e,n,i.args??{})}}catch(t){const a=t instanceof Error?t.message:String(t);return d.error(`capability-tools/call ${n} failed for agent ${r}: ${a}`),{status:500,body:{ok:!1,error:a}}}}function j(i,r,e){const n=String(i||"").trim();if(n){const s=String(r||"").trim(),t=String(e||"").trim().toLowerCase();return n.toLowerCase()==="tool_using"&&s.toLowerCase()==="waiting_input"&&t==="idle"?s:n}return String(r||"").trim()||"running"}h.get("/sessions",I,async(i,r)=>{try{const n=Array.from(m.entries()).map(([p,u])=>{const l=y.get(u.providerId||"claude-code"),c=j(l.getSessionStatus(p),u.currentRuntimeStatus,u.currentRuntimeActionType);return{sessionId:p,agentId:u.agentId,workspacePath:u.workspacePath,status:c,runtimeStatus:c,runtimeActionType:u.currentRuntimeActionType,runtimeActionLabel:u.currentRuntimeActionLabel,runtimeActionDetail:u.currentRuntimeActionDetail,mode:"sdk",startedAt:null}}),o=await E(),s=new Set(n.map(p=>p.sessionId)),t=o.filter(p=>!s.has(p.sessionId)),a={ok:!0,active:n,persisted:t};r.json(a)}catch(e){const n=e;r.status(500).json({error:n.message})}}),h.post("/sessions/recover",I,async(i,r)=>{const{agentId:e,workspacePath:n,command:o}=i.body;if(!e){r.status(400).json({error:"agentId is required"});return}try{const s=Array.from(m.entries()).find(([,g])=>g.agentId===e);if(s){const g=y.get(s[1].providerId||"claude-code"),k=j(g.getSessionStatus(s[0]),s[1].currentRuntimeStatus,s[1].currentRuntimeActionType);r.json({ok:!0,sessionId:s[0],agentId:e,workspacePath:s[1].workspacePath,command:s[1].config.command,status:k,runtimeStatus:k,runtimeActionType:s[1].currentRuntimeActionType,runtimeActionLabel:s[1].currentRuntimeActionLabel,runtimeActionDetail:s[1].currentRuntimeActionDetail,mode:"sdk",recovered:!1,message:"SDK session already active"});return}const t=await S(e);let a=!1,p;if(t?.pid&&await w(t.pid))a=!0,p=t.pid,d.info(`[Recover] Detected orphan process for agentId=${e}, pid=${t.pid}`);else if(t?.mode==="sdk"||!t?.pid){const g=await b(e);g&&(a=!0,p=g.pid,d.info(`[Recover] Found orphan process by agentId search: agentId=${e}, pid=${g.pid}`))}const u=n||t?.workspacePath,l=o||t?.command||"acode",c=t?.mode||"sdk";if(!u){r.status(400).json({error:"workspacePath is required (not found in persisted session)"});return}if(a&&p){d.info(`[Recover] Returning orphan process info for agentId=${e}`),r.json({ok:!0,sessionId:t?.sessionId||"",agentId:e,workspacePath:u,command:l,status:"orphan_detected",runtimeStatus:t?.runtimeStatus,runtimeActionType:t?.runtimeActionType,runtimeActionLabel:t?.runtimeActionLabel,runtimeActionDetail:t?.runtimeActionDetail,recovered:!1,orphan:{pid:p,message:"Found orphan process still running. Manual intervention may be required."},mode:c});return}await A(t?.sessionId||"");const f={ok:!1,sessionId:t?.sessionId||"",agentId:e,workspacePath:u,command:l,status:"stopped",recovered:!1,message:"No active SDK session found; start a new SDK runtime session instead."};r.json(f)}catch(s){const t=s;d.error("Error:",t),r.status(500).json({error:t.message})}}),h.post("/search-orphan",I,async(i,r)=>{const{agentId:e,workspacePath:n}=i.body;if(!e){r.status(400).json({error:"agentId is required"});return}try{const o=Array.from(m.entries()).find(([,c])=>c.agentId===e);let s=!1,t,a;if(o){const[c,f]=o,g=y.get(f.providerId||"claude-code");t=g.getSessionPid?.(c),a=f.config?.command;const k=t?await w(t):!1,v=g.getSessionStatus(c),T=v==="completed"||v==="terminated"||v==="error";if(k)s=!0;else if(t&&!k)d.info(`[search-orphan] Cleaning up stale SDK session (process dead): agentId=${e}, sessionId=${c}, pid=${t}`),m.delete(c),await A(c);else if(T){d.info(`[search-orphan] Cleaning up stale in-process SDK session (status=${v}): agentId=${e}, sessionId=${c}`);try{await g.terminateSession(c)}catch($){d.warn(`[search-orphan] Failed to terminate stale in-process session: ${$}`)}m.delete(c),await A(c)}else{d.info(`[search-orphan] Terminating unreachable in-process SDK session: agentId=${e}, sessionId=${c}, status=${v||"unknown"}`);try{await g.abortCurrentTurn(c).catch(()=>{}),await g.terminateSession(c)}catch($){d.warn(`[search-orphan] Failed to terminate in-process session: ${$}`)}m.delete(c),await A(c)}}const p=await b(e);if(p){d.info(`[search-orphan] Found orphan process for agentId=${e}, pid=${p.pid}`),r.json({ok:!0,agentId:e,hasActiveSession:s,found:!0,orphan:{pid:p.pid,command:p.command,cwd:p.cwd,startedAt:p.startedAt,message:"Found orphan process. Click terminate to stop it."}});return}if(s&&t){d.info(`[search-orphan] Active session process still running, reporting as terminable orphan: agentId=${e}, pid=${t}`),r.json({ok:!0,agentId:e,hasActiveSession:!0,found:!0,orphan:{pid:t,command:a||"(active SDK session)",message:"Found active SDK session process. Click terminate to stop it."}});return}const u=await S(e);if(u?.pid&&await w(u.pid)){d.info(`[search-orphan] Found orphan by persisted PID: agentId=${e}, pid=${u.pid}`),r.json({ok:!0,agentId:e,hasActiveSession:s,found:!0,orphan:{pid:u.pid,command:u.command||"(persisted session)",message:"Found orphan process by persisted PID. Click terminate to stop it."}});return}const l=n||u?.workspacePath;if(l){const c=await F(l);if(c){d.info(`[search-orphan] Found orphan by workspace: agentId=${e}, pid=${c.pid}, workspace=${l}`),r.json({ok:!0,agentId:e,hasActiveSession:s,found:!0,orphan:{pid:c.pid,command:c.command,message:"Found orphan process by workspace path. Click terminate to stop it."}});return}}d.info(`[search-orphan] No orphan process found for agentId=${e}, hasActiveSession=${!!s}`),r.json({ok:!0,agentId:e,hasActiveSession:!!s,found:!1,orphan:null,message:s?"Agent has active session in runtime-bridge":"No orphan process and no active session found"})}catch(o){const s=o;d.error("[search-orphan] Error:",s),r.status(500).json({error:s.message})}}),h.post("/terminate-orphan",I,async(i,r)=>{const{agentId:e,pid:n}=i.body;if(!e||!n){r.status(400).json({error:"agentId and pid are required"});return}try{const o=Array.from(m.entries()).find(([,a])=>a.agentId===e);if(o){const[a,p]=o,u=y.get(p.providerId||"claude-code");try{await u.terminateSession(a)}catch(l){d.warn(`[terminate-orphan] Failed to terminate SDK session for agentId=${e}:`,l)}m.delete(a),await A(a),d.info(`[terminate-orphan] Cleaned up SDK session for agentId=${e}, sessionId=${a}`)}if(!await w(n)){r.json({ok:!0,agentId:e,pid:n,terminated:!1,message:"Process already terminated"});return}const s=process.platform;let t=!1;if(s==="win32")try{R(`taskkill /PID ${n} /T /F`,{encoding:"utf8",timeout:5e3}),t=!0}catch(a){d.warn(`[terminate-orphan] taskkill failed for pid=${n}:`,a)}else try{process.kill(n,"SIGTERM"),t=!0}catch(a){d.warn(`[terminate-orphan] kill failed for pid=${n}:`,a)}t?(d.info(`[terminate-orphan] Terminated orphan process for agentId=${e}, pid=${n}`),r.json({ok:!0,agentId:e,pid:n,terminated:!0,message:"Orphan process terminated successfully"})):r.json({ok:!0,agentId:e,pid:n,terminated:!1,message:"Failed to terminate process"})}catch(o){const s=o;d.error("[terminate-orphan] Error:",s),r.status(500).json({error:s.message})}}),h.get("/sessions/by-agent/:agentId",I,async(i,r)=>{const e=i.params.agentId,n=Array.isArray(e)?e[0]:e;if(!n){r.status(400).json({error:"agentId is required"});return}try{const o=Array.from(m.entries()).find(([,t])=>t.agentId===n);if(o){const t=y.get(o[1].providerId||"claude-code"),a=t.getSessionPid?.(o[0]),p=j(t.getSessionStatus(o[0]),o[1].currentRuntimeStatus,o[1].currentRuntimeActionType);r.json({ok:!0,sessionId:o[0],agentId:n,workspacePath:o[1].workspacePath,status:p,runtimeStatus:p,isActive:!0,mode:"sdk",pid:a,isManaged:!0,providerId:o[1].providerId});return}const s=await S(n);if(s){const{isProcessRunning:t}=await import("../services/process-detector.js"),a=s.pid?await t(s.pid):!1;r.json({ok:!0,sessionId:s.sessionId,agentId:n,workspacePath:s.workspacePath,command:s.command,status:a?"orphan":"stopped",runtimeStatus:s.runtimeStatus,runtimeActionType:s.runtimeActionType,runtimeActionLabel:s.runtimeActionLabel,runtimeActionDetail:s.runtimeActionDetail,startedAt:s.startedAt,isActive:!1,mode:s.mode,pid:s.pid,isManaged:!1,isProcessAlive:a,isOrphan:a});return}r.json({ok:!0,agentId:n,status:"none",isActive:!1,isManaged:!1})}catch(o){const s=o;d.error("[sessions/by-agent] Error:",s),r.status(500).json({error:s.message})}}),h.post("/takeover-session",I,async(i,r)=>{const{agentId:e}=i.body;if(!e){r.status(400).json({error:"agentId is required"});return}try{const n=Array.from(m.entries()).find(([,t])=>t.agentId===e);if(n){r.json({ok:!0,agentId:e,sessionId:n[0],mode:"sdk",status:"running",isActive:!0,recovered:!1,message:"SDK session already active"});return}const o=await S(e);if(!o){r.status(404).json({error:"No persisted session found for this agentId",agentId:e});return}let s=!1;if(o.mode==="sdk"&&(s=!!await b(e),!s&&o.pid&&(s=await w(o.pid))),!s){d.info(`[takeover-session] Process not alive for agentId=${e}, mode=${o.mode}`),r.json({ok:!1,agentId:e,status:"process_not_alive",isActive:!1,message:"Agent process is no longer running"});return}if(d.info(`[takeover-session] Taking over session for agentId=${e}, mode=${o.mode}, pid=${o.pid}`),o.mode==="sdk"){const t=await b(e);t?(d.info(`[takeover-session] Found SDK process for agentId=${e}, pid=${t.pid}`),r.json({ok:!0,agentId:e,sessionId:o.sessionId,mode:"sdk",status:"running",isActive:!0,recovered:!0,pid:t.pid||o.pid,message:"SDK session process found, can be taken over by aws-mcp-server"})):r.json({ok:!1,agentId:e,status:"process_not_alive",isActive:!1,message:"SDK process not found"})}else r.status(410).json({error:"Session takeover is no longer supported; start a new SDK runtime session.",agentId:e,mode:"sdk"})}catch(n){const o=n;d.error("[takeover-session] Error:",o),r.status(500).json({error:o.message})}}),h.post("/terminate-tree",I,async(i,r)=>{const{agentId:e,sessionId:n}=i.body;if(!e){r.status(400).json({error:"agentId is required"});return}try{let o=n;if(!o){const f=await S(e);if(!f){r.status(404).json({error:"Session not found for this agentId"});return}o=f.sessionId}const s=m.get(o);let t,a;if(s)t=y.get(s.providerId||"claude-code").getSessionPid?.(o),a="sdk";else{const f=await S(e);if(!f?.pid){r.status(404).json({error:"Session PID not found"});return}t=f.pid,a=f.mode||"sdk"}const{terminateProcessTree:p,waitForProcessExit:u}=await import("../services/process-detector.js");if(!t){r.status(404).json({error:"PID not found"});return}const l=await p(t),c=await u(t,5e3);L(e),await import("../services/terminal-persistence.js").then(f=>f.removePersistedSession(o)),d.info(`[terminate-tree] Terminated process tree for agentId=${e}, pid=${t}, mode=${a}, terminated=${l.terminated}, failed=${l.failed}, exited=${c}`),r.json({ok:!0,agentId:e,pid:t,mode:a,terminated:l.terminated,failed:l.failed,exited:c,message:c?`Successfully terminated ${l.terminated} processes`:`Terminated ${l.terminated} processes, but process ${t} still running`})}catch(o){const s=o;d.error("[terminate-tree] Error:",s),r.status(500).json({error:s.message})}}),h.get("/health/:agentId",I,async(i,r)=>{const e=i.params.agentId,n=Array.isArray(e)?e[0]:e;if(!n){r.status(400).json({error:"agentId is required"});return}try{const o=await S(n);if(!o){r.status(404).json({error:"Session not found"});return}const s=m.get(o.sessionId),t=!!s,{checkProcessHealth:a}=await import("../services/process-detector.js"),p={sessionId:o.sessionId,mode:o.mode,workspacePath:o.workspacePath,isManaged:t,isActive:!!s};if(o.pid){const u=await a(o.pid,n);Object.assign(p,u)}else Object.assign(p,{pid:0,alive:!1,cpu:0,memoryMB:0,status:"dead",lastUpdate:new Date});r.json({agentId:n,...p})}catch(o){const s=o;d.error("[health] Error:",s),r.status(500).json({error:s.message})}});export{B as handleCapabilityToolCallRequest,x as handleCapabilityToolsRequest,j as resolveActiveSdkSessionSnapshotStatus,h as sessionsRouter};
1
+ import{execSync as R}from"node:child_process";import{Router as C}from"express";import{adapterRegistry as I}from"../adapter/index.js";import{validateToken as h,validateTokenOrLoopback as b}from"../middleware/auth.js";import{isProcessRunning as A,findOrphanProcessByAgentId as $,findOrphanProcessByWorkspace as L}from"../services/process-detector.js";import{findPersistedSessionByAgentId as S,loadPersistedSessions as F,removePersistedSession as w}from"../services/terminal-persistence.js";import{createLogger as E}from"../utils/logger.js";import{sdkSessions as m,cleanupSdkSessionByAgentId as K}from"./terminal.js";const d=E("sessions"),y=C();y.post("/sessions/:agentId/notify-force-message",b,(i,r)=>{const e=String(i.params.agentId??"").trim();if(!e){r.status(400).json({ok:!1,error:"agentId is required"});return}const n=Array.from(m.entries()).find(([,a])=>a.agentId===e);if(!n){r.json({ok:!0,notified:!1,message:"No active SDK session for this agentId"});return}const[o,t]=n,s=I.get(t.providerId||"claude-code");if(typeof s.notifyForceMessage=="function"){const a=s.notifyForceMessage(o);r.json({ok:!0,notified:a})}else r.json({ok:!0,notified:!1,message:"Adapter does not support force message notification"})}),y.post("/sessions/:agentId/capability-tools",b,async(i,r)=>{const e=String(i.params.agentId??"").trim(),n=()=>{const t=Array.from(m.entries()).find(([,s])=>s.agentId===e);return t?{agentId:e,providerId:t[1].providerId||""}:void 0},o=await B({agentId:e,capability:String(i.body?.capability??""),action:i.body?.action,resolveSession:n,resolveAdapter:t=>I.get(t||"claude-code")});r.status(o.status).json(o.body)}),y.post("/sessions/:agentId/capability-tools/call",b,async(i,r)=>{const e=String(i.params.agentId??"").trim(),n=()=>{const t=Array.from(m.entries()).find(([,s])=>s.agentId===e);return t?{agentId:e,providerId:t[1].providerId||""}:void 0},o=await x({agentId:e,capability:String(i.body?.capability??""),tool:String(i.body?.tool??""),args:i.body?.args??{},resolveSession:n,resolveAdapter:t=>I.get(t||"claude-code")});r.status(o.status).json(o.body)});const T=new Set(["browser","desktop"]);async function B(i){const r=String(i.agentId??"").trim();if(!r)return{status:400,body:{ok:!1,error:"agentId is required"}};const e=String(i.capability??"").trim();if(!T.has(e))return{status:400,body:{ok:!1,error:'capability must be "browser" or "desktop"'}};const n=i.action;if(n!=="expand"&&n!=="collapse")return{status:400,body:{ok:!1,error:`action must be "expand" or "collapse", got: ${String(n)}`}};const o=i.resolveSession();if(!o)return{status:200,body:{ok:!0,changed:{sessions:0,tools:0}}};const t=i.resolveAdapter?.(o.providerId);if(!t||typeof t.setBuiltInCapabilityTools!="function")return{status:200,body:{ok:!0,changed:{sessions:0,tools:0},message:`Adapter for provider "${o.providerId}" does not support setBuiltInCapabilityTools`}};try{const s=await t.setBuiltInCapabilityTools(r,e,n),a={ok:!0,changed:{sessions:s.sessions,tools:s.tools}};return n==="expand"&&Array.isArray(s.manifests)&&(a.manifests=s.manifests),{status:200,body:a}}catch(s){const a=s instanceof Error?s.message:String(s);return d.error(`capability-tools ${n} failed for agent ${r}: ${a}`),{status:500,body:{ok:!1,error:a}}}}async function x(i){const r=String(i.agentId??"").trim();if(!r)return{status:400,body:{ok:!1,error:"agentId is required"}};const e=String(i.capability??"").trim();if(!T.has(e))return{status:400,body:{ok:!1,error:'capability must be "browser" or "desktop"'}};const n=String(i.tool??"").trim();if(!n)return{status:400,body:{ok:!1,error:"tool is required"}};const o=i.resolveSession();if(!o)return{status:200,body:{ok:!1,error:`No active SDK session for agentId "${r}"`}};const t=i.resolveAdapter?.(o.providerId);if(!t||typeof t.callBuiltInCapabilityTool!="function")return{status:200,body:{ok:!1,error:`Adapter for provider "${o.providerId}" does not support callBuiltInCapabilityTool`}};try{return{status:200,body:await t.callBuiltInCapabilityTool(r,e,n,i.args??{})}}catch(s){const a=s instanceof Error?s.message:String(s);return d.error(`capability-tools/call ${n} failed for agent ${r}: ${a}`),{status:500,body:{ok:!1,error:a}}}}function P(i,r,e){const n=String(i||"").trim();if(n){const t=String(r||"").trim(),s=String(e||"").trim().toLowerCase();return n.toLowerCase()==="tool_using"&&t.toLowerCase()==="waiting_input"&&s==="idle"?t:n}return String(r||"").trim()||"running"}y.get("/sessions",h,async(i,r)=>{try{const n=Array.from(m.entries()).map(([u,p])=>{const l=I.get(p.providerId||"claude-code"),c=P(l.getSessionStatus(u),p.currentRuntimeStatus,p.currentRuntimeActionType);return{sessionId:u,agentId:p.agentId,workspacePath:p.workspacePath,status:c,runtimeStatus:c,runtimeActionType:p.currentRuntimeActionType,runtimeActionLabel:p.currentRuntimeActionLabel,runtimeActionDetail:p.currentRuntimeActionDetail,mode:"sdk",startedAt:null}}),o=await F(),t=new Set(n.map(u=>u.sessionId)),s=o.filter(u=>!t.has(u.sessionId)),a={ok:!0,active:n,persisted:s};r.json(a)}catch(e){const n=e;r.status(500).json({error:n.message})}}),y.post("/sessions/recover",h,async(i,r)=>{const{agentId:e,workspacePath:n,command:o}=i.body;if(!e){r.status(400).json({error:"agentId is required"});return}try{const t=Array.from(m.entries()).find(([,g])=>g.agentId===e);if(t){const g=I.get(t[1].providerId||"claude-code"),k=P(g.getSessionStatus(t[0]),t[1].currentRuntimeStatus,t[1].currentRuntimeActionType);r.json({ok:!0,sessionId:t[0],agentId:e,workspacePath:t[1].workspacePath,command:t[1].config.command,status:k,runtimeStatus:k,runtimeActionType:t[1].currentRuntimeActionType,runtimeActionLabel:t[1].currentRuntimeActionLabel,runtimeActionDetail:t[1].currentRuntimeActionDetail,mode:"sdk",recovered:!1,message:"SDK session already active"});return}const s=await S(e);let a=!1,u;if(s?.pid&&await A(s.pid))a=!0,u=s.pid,d.info(`[Recover] Detected orphan process for agentId=${e}, pid=${s.pid}`);else if(s?.mode==="sdk"||!s?.pid){const g=await $(e);g&&(a=!0,u=g.pid,d.info(`[Recover] Found orphan process by agentId search: agentId=${e}, pid=${g.pid}`))}const p=n||s?.workspacePath,l=o||s?.command||"acode",c=s?.mode||"sdk";if(!p){r.status(400).json({error:"workspacePath is required (not found in persisted session)"});return}if(a&&u){d.info(`[Recover] Returning orphan process info for agentId=${e}`),r.json({ok:!0,sessionId:s?.sessionId||"",agentId:e,workspacePath:p,command:l,status:"orphan_detected",runtimeStatus:s?.runtimeStatus,runtimeActionType:s?.runtimeActionType,runtimeActionLabel:s?.runtimeActionLabel,runtimeActionDetail:s?.runtimeActionDetail,recovered:!1,orphan:{pid:u,message:"Found orphan process still running. Manual intervention may be required."},mode:c});return}await w(s?.sessionId||"");const f={ok:!1,sessionId:s?.sessionId||"",agentId:e,workspacePath:p,command:l,status:"stopped",recovered:!1,message:"No active SDK session found; start a new SDK runtime session instead."};r.json(f)}catch(t){const s=t;d.error("Error:",s),r.status(500).json({error:s.message})}}),y.post("/search-orphan",h,async(i,r)=>{const{agentId:e,workspacePath:n}=i.body;if(!e){r.status(400).json({error:"agentId is required"});return}try{const o=Array.from(m.entries()).find(([,c])=>c.agentId===e);let t=!1,s,a;if(o){const[c,f]=o,g=I.get(f.providerId||"claude-code");s=g.getSessionPid?.(c),a=f.config?.command;const k=s?await A(s):!1,v=g.getSessionStatus(c),D=v==="completed"||v==="terminated"||v==="error";if(k)t=!0;else if(s&&!k)d.info(`[search-orphan] Cleaning up stale SDK session (process dead): agentId=${e}, sessionId=${c}, pid=${s}`),m.delete(c),await w(c);else if(D){d.info(`[search-orphan] Cleaning up stale in-process SDK session (status=${v}): agentId=${e}, sessionId=${c}`);try{await g.terminateSession(c)}catch(j){d.warn(`[search-orphan] Failed to terminate stale in-process session: ${j}`)}m.delete(c),await w(c)}else{d.info(`[search-orphan] Terminating unreachable in-process SDK session: agentId=${e}, sessionId=${c}, status=${v||"unknown"}`);try{await g.abortCurrentTurn(c).catch(()=>{}),await g.terminateSession(c)}catch(j){d.warn(`[search-orphan] Failed to terminate in-process session: ${j}`)}m.delete(c),await w(c)}}const u=await $(e);if(u){d.info(`[search-orphan] Found orphan process for agentId=${e}, pid=${u.pid}`),r.json({ok:!0,agentId:e,hasActiveSession:t,found:!0,orphan:{pid:u.pid,command:u.command,cwd:u.cwd,startedAt:u.startedAt,message:"Found orphan process. Click terminate to stop it."}});return}if(t&&s){d.info(`[search-orphan] Active session process still running, reporting as terminable orphan: agentId=${e}, pid=${s}`),r.json({ok:!0,agentId:e,hasActiveSession:!0,found:!0,orphan:{pid:s,command:a||"(active SDK session)",message:"Found active SDK session process. Click terminate to stop it."}});return}const p=await S(e);if(p?.pid&&await A(p.pid)){d.info(`[search-orphan] Found orphan by persisted PID: agentId=${e}, pid=${p.pid}`),r.json({ok:!0,agentId:e,hasActiveSession:t,found:!0,orphan:{pid:p.pid,command:p.command||"(persisted session)",message:"Found orphan process by persisted PID. Click terminate to stop it."}});return}const l=n||p?.workspacePath;if(l){const c=await L(l);if(c){d.info(`[search-orphan] Found orphan by workspace: agentId=${e}, pid=${c.pid}, workspace=${l}`),r.json({ok:!0,agentId:e,hasActiveSession:t,found:!0,orphan:{pid:c.pid,command:c.command,message:"Found orphan process by workspace path. Click terminate to stop it."}});return}}d.info(`[search-orphan] No orphan process found for agentId=${e}, hasActiveSession=${!!t}`),r.json({ok:!0,agentId:e,hasActiveSession:!!t,found:!1,orphan:null,message:t?"Agent has active session in runtime-bridge":"No orphan process and no active session found"})}catch(o){const t=o;d.error("[search-orphan] Error:",t),r.status(500).json({error:t.message})}}),y.post("/terminate-orphan",h,async(i,r)=>{const{agentId:e,pid:n}=i.body;if(!e||!n){r.status(400).json({error:"agentId and pid are required"});return}try{const o=Array.from(m.entries()).find(([,a])=>a.agentId===e);if(o){const[a,u]=o,p=I.get(u.providerId||"claude-code");try{await p.terminateSession(a)}catch(l){d.warn(`[terminate-orphan] Failed to terminate SDK session for agentId=${e}:`,l)}m.delete(a),await w(a),d.info(`[terminate-orphan] Cleaned up SDK session for agentId=${e}, sessionId=${a}`)}if(!await A(n)){r.json({ok:!0,agentId:e,pid:n,terminated:!1,message:"Process already terminated"});return}const t=process.platform;let s=!1;if(t==="win32")try{R(`taskkill /PID ${n} /T /F`,{encoding:"utf8",timeout:5e3}),s=!0}catch(a){d.warn(`[terminate-orphan] taskkill failed for pid=${n}:`,a)}else try{process.kill(n,"SIGTERM"),s=!0}catch(a){d.warn(`[terminate-orphan] kill failed for pid=${n}:`,a)}s?(d.info(`[terminate-orphan] Terminated orphan process for agentId=${e}, pid=${n}`),r.json({ok:!0,agentId:e,pid:n,terminated:!0,message:"Orphan process terminated successfully"})):r.json({ok:!0,agentId:e,pid:n,terminated:!1,message:"Failed to terminate process"})}catch(o){const t=o;d.error("[terminate-orphan] Error:",t),r.status(500).json({error:t.message})}}),y.get("/sessions/:agentId/current-action",b,(i,r)=>{const e=i.params.agentId,n=Array.isArray(e)?e[0]:e;if(!n){r.status(400).json({ok:!1,error:"agentId is required"});return}const o=Array.from(m.entries()).find(([,a])=>a.agentId===n)?.[1];if(!o){r.json({ok:!0,found:!1});return}const t=I.get(o.providerId||"claude-code"),s=typeof t.hasActiveBlockingToolCall=="function"?t.hasActiveBlockingToolCall(o.sessionId)===!0:!1;r.json({ok:!0,found:!0,hasActiveBlockingToolCall:s,runtimeStatus:o.currentRuntimeStatus,runtimeActionType:o.currentRuntimeActionType,runtimeActionLabel:o.currentRuntimeActionLabel,runtimeActionDetail:o.currentRuntimeActionDetail})}),y.get("/sessions/by-agent/:agentId",h,async(i,r)=>{const e=i.params.agentId,n=Array.isArray(e)?e[0]:e;if(!n){r.status(400).json({error:"agentId is required"});return}try{const o=Array.from(m.entries()).find(([,s])=>s.agentId===n);if(o){const s=I.get(o[1].providerId||"claude-code"),a=s.getSessionPid?.(o[0]),u=P(s.getSessionStatus(o[0]),o[1].currentRuntimeStatus,o[1].currentRuntimeActionType);r.json({ok:!0,sessionId:o[0],agentId:n,workspacePath:o[1].workspacePath,status:u,runtimeStatus:u,isActive:!0,mode:"sdk",pid:a,isManaged:!0,providerId:o[1].providerId});return}const t=await S(n);if(t){const{isProcessRunning:s}=await import("../services/process-detector.js"),a=t.pid?await s(t.pid):!1;r.json({ok:!0,sessionId:t.sessionId,agentId:n,workspacePath:t.workspacePath,command:t.command,status:a?"orphan":"stopped",runtimeStatus:t.runtimeStatus,runtimeActionType:t.runtimeActionType,runtimeActionLabel:t.runtimeActionLabel,runtimeActionDetail:t.runtimeActionDetail,startedAt:t.startedAt,isActive:!1,mode:t.mode,pid:t.pid,isManaged:!1,isProcessAlive:a,isOrphan:a});return}r.json({ok:!0,agentId:n,status:"none",isActive:!1,isManaged:!1})}catch(o){const t=o;d.error("[sessions/by-agent] Error:",t),r.status(500).json({error:t.message})}}),y.post("/takeover-session",h,async(i,r)=>{const{agentId:e}=i.body;if(!e){r.status(400).json({error:"agentId is required"});return}try{const n=Array.from(m.entries()).find(([,s])=>s.agentId===e);if(n){r.json({ok:!0,agentId:e,sessionId:n[0],mode:"sdk",status:"running",isActive:!0,recovered:!1,message:"SDK session already active"});return}const o=await S(e);if(!o){r.status(404).json({error:"No persisted session found for this agentId",agentId:e});return}let t=!1;if(o.mode==="sdk"&&(t=!!await $(e),!t&&o.pid&&(t=await A(o.pid))),!t){d.info(`[takeover-session] Process not alive for agentId=${e}, mode=${o.mode}`),r.json({ok:!1,agentId:e,status:"process_not_alive",isActive:!1,message:"Agent process is no longer running"});return}if(d.info(`[takeover-session] Taking over session for agentId=${e}, mode=${o.mode}, pid=${o.pid}`),o.mode==="sdk"){const s=await $(e);s?(d.info(`[takeover-session] Found SDK process for agentId=${e}, pid=${s.pid}`),r.json({ok:!0,agentId:e,sessionId:o.sessionId,mode:"sdk",status:"running",isActive:!0,recovered:!0,pid:s.pid||o.pid,message:"SDK session process found, can be taken over by aws-mcp-server"})):r.json({ok:!1,agentId:e,status:"process_not_alive",isActive:!1,message:"SDK process not found"})}else r.status(410).json({error:"Session takeover is no longer supported; start a new SDK runtime session.",agentId:e,mode:"sdk"})}catch(n){const o=n;d.error("[takeover-session] Error:",o),r.status(500).json({error:o.message})}}),y.post("/terminate-tree",h,async(i,r)=>{const{agentId:e,sessionId:n}=i.body;if(!e){r.status(400).json({error:"agentId is required"});return}try{let o=n;if(!o){const f=await S(e);if(!f){r.status(404).json({error:"Session not found for this agentId"});return}o=f.sessionId}const t=m.get(o);let s,a;if(t)s=I.get(t.providerId||"claude-code").getSessionPid?.(o),a="sdk";else{const f=await S(e);if(!f?.pid){r.status(404).json({error:"Session PID not found"});return}s=f.pid,a=f.mode||"sdk"}const{terminateProcessTree:u,waitForProcessExit:p}=await import("../services/process-detector.js");if(!s){r.status(404).json({error:"PID not found"});return}const l=await u(s),c=await p(s,5e3);K(e),await import("../services/terminal-persistence.js").then(f=>f.removePersistedSession(o)),d.info(`[terminate-tree] Terminated process tree for agentId=${e}, pid=${s}, mode=${a}, terminated=${l.terminated}, failed=${l.failed}, exited=${c}`),r.json({ok:!0,agentId:e,pid:s,mode:a,terminated:l.terminated,failed:l.failed,exited:c,message:c?`Successfully terminated ${l.terminated} processes`:`Terminated ${l.terminated} processes, but process ${s} still running`})}catch(o){const t=o;d.error("[terminate-tree] Error:",t),r.status(500).json({error:t.message})}}),y.get("/health/:agentId",h,async(i,r)=>{const e=i.params.agentId,n=Array.isArray(e)?e[0]:e;if(!n){r.status(400).json({error:"agentId is required"});return}try{const o=await S(n);if(!o){r.status(404).json({error:"Session not found"});return}const t=m.get(o.sessionId),s=!!t,{checkProcessHealth:a}=await import("../services/process-detector.js"),u={sessionId:o.sessionId,mode:o.mode,workspacePath:o.workspacePath,isManaged:s,isActive:!!t};if(o.pid){const p=await a(o.pid,n);Object.assign(u,p)}else Object.assign(u,{pid:0,alive:!1,cpu:0,memoryMB:0,status:"dead",lastUpdate:new Date});r.json({agentId:n,...u})}catch(o){const t=o;d.error("[health] Error:",t),r.status(500).json({error:t.message})}});export{x as handleCapabilityToolCallRequest,B as handleCapabilityToolsRequest,P as resolveActiveSdkSessionSnapshotStatus,y as sessionsRouter};
2
2
 
@@ -0,0 +1,2 @@
1
+
2
+ export declare const techStackRouter: import("express-serve-static-core").Router;
@@ -0,0 +1,2 @@
1
+ import{Router as s}from"express";import{validateToken as c}from"../middleware/auth.js";import{scanTechStack as n}from"../services/tech-stack-scanner.js";import{createLogger as k}from"../utils/logger.js";const m=k("tech-stack"),p=s();p.post("/tech-stack/scan",c,async(e,r)=>{const t=e.body?.workspacePath;if(typeof t!="string"||!t.trim()){r.status(400).json({error:"workspacePath \u4E0D\u80FD\u4E3A\u7A7A"});return}try{const o=await n(t);r.json({ok:!0,...o})}catch(o){const a=o;m.warn(`[tech-stack/scan] workspacePath=${t} error=${a.message}`),r.status(400).json({error:a.message})}});export{p as techStackRouter};
2
+
@@ -72,6 +72,11 @@ export declare function isIdleStatusChangeRetryable(params: {
72
72
  currentRuntimeActionType?: string;
73
73
  shouldPreservePrevAction: boolean;
74
74
  }): boolean;
75
+
76
+ export declare function isUsageOnlyStatusChange(params: {
77
+ actionInfo?: RuntimeStatusActionInfo;
78
+ usage?: ReturnType<typeof resolveStatusChangeUsage>;
79
+ }): boolean;
75
80
  export declare function buildAcodeCommandOutputPath(agentId: string, now?: Date): string;
76
81
  export declare function countTerminalOutputLines(output: string): number;
77
82
  export declare function formatCommandOutputSpilloverNotice(outputPath: string, lineLimit?: number): string;
@@ -125,6 +130,9 @@ export declare function resolveRuntimeStatusFromProviderEvent(event: ProviderEve
125
130
  } | undefined;
126
131
 
127
132
  export declare function raceSendOutputWithRetry(factory: (signal: AbortSignal) => Promise<void>, timeoutMs?: number, maxRetries?: number): Promise<void>;
133
+ export declare function enqueueCompactionSummaryChunk(entry: SdkSessionEntry, sessionId: string, output: string, seq: number): void;
134
+
135
+ export declare function flushCompactionSummaryAccumulator(sessionId: string): void;
128
136
  export declare function resolveSdkProviderId(command?: string): string;
129
137
 
130
138
  export declare function withSenderSignature(input: unknown, sender: unknown): unknown;