aws-runtime-bridge 1.9.172 → 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.
- package/CHANGELOG.md +126 -0
- package/dist/adapter/AcodeSdkAdapter.d.ts +5 -1
- package/dist/adapter/AcodeSdkAdapter.js +3 -3
- package/dist/adapter/types.d.ts +2 -0
- package/dist/index.js +1 -1
- package/dist/routes/bridge-temp-files.d.ts +1 -0
- package/dist/routes/bridge-temp-files.js +2 -0
- package/dist/routes/dashboard.js +35 -5
- package/dist/routes/file-browser.d.ts +4 -10
- package/dist/routes/file-browser.js +1 -1
- package/dist/routes/git.d.ts +42 -0
- package/dist/routes/git.js +34 -34
- package/dist/routes/sessions.js +1 -1
- package/dist/routes/tech-stack.d.ts +2 -0
- package/dist/routes/tech-stack.js +2 -0
- package/dist/routes/terminal.js +19 -19
- package/dist/services/agent-pty-manager.d.ts +2 -0
- package/dist/services/agent-pty-manager.js +5 -4
- package/dist/services/agent-startup-memory.js +4 -4
- package/dist/services/bridge-temp-files.d.ts +36 -0
- package/dist/services/bridge-temp-files.js +2 -0
- package/dist/services/fileSnapshotStore.d.ts +9 -0
- package/dist/services/fileSnapshotStore.js +1 -1
- package/dist/services/gitignore.d.ts +12 -0
- package/dist/services/gitignore.js +2 -0
- package/dist/services/log-retention.js +1 -1
- package/dist/services/open-doc-events.d.ts +21 -0
- package/dist/services/open-doc-events.js +2 -0
- package/dist/services/open-doc-registry.d.ts +24 -0
- package/dist/services/open-doc-registry.js +2 -0
- package/dist/services/request-invalid-dump.js +1 -1
- package/dist/services/runtime-resources-snapshot.js +2 -2
- package/dist/services/snapshot-retention.d.ts +7 -0
- package/dist/services/snapshot-retention.js +2 -0
- package/dist/services/tech-stack-scanner.d.ts +31 -0
- package/dist/services/tech-stack-scanner.js +2 -0
- package/dist/services/tunnel-client.d.ts +42 -0
- package/dist/services/tunnel-client.js +1 -1
- package/dist/services/workspaceTaskChangeWatcher.js +2 -2
- package/package/acode/dist/__tests__/summary-request-helper.d.ts +2 -0
- package/package/acode/dist/__tests__/summary-request-helper.js +2 -0
- package/package/acode/dist/cache-control.d.ts +26 -0
- package/package/acode/dist/cache-control.js +2 -0
- package/package/acode/dist/compaction.d.ts +35 -5
- package/package/acode/dist/compaction.js +19 -17
- package/package/acode/dist/microcompact.d.ts +57 -0
- package/package/acode/dist/microcompact.js +2 -0
- package/package/acode/dist/runtime.d.ts +4 -0
- package/package/acode/dist/runtime.js +40 -33
- package/package/acode/dist/token-estimator.d.ts +15 -0
- package/package/acode/dist/token-estimator.js +2 -0
- package/package/acode/dist/types.d.ts +13 -0
- package/package/aws-client-agent-mcp/dist/ability-market-tools.js +1 -1
- package/package/aws-client-agent-mcp/dist/agent-client.d.ts +15 -2
- package/package/aws-client-agent-mcp/dist/agent-client.js +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.d.ts +5 -0
- package/package/aws-client-agent-mcp/dist/mcp-server.js +25 -23
- package/package/aws-client-agent-mcp/dist/mcp-tools.js +7 -7
- package/package/aws-client-agent-mcp/dist/memory-store.d.ts +21 -2
- package/package/aws-client-agent-mcp/dist/memory-store.js +27 -2
- package/package/aws-client-agent-mcp/dist/memory-tools.js +13 -9
- package/package/aws-client-agent-mcp/dist/server-memory-store.d.ts +1 -1
- package/package/aws-client-agent-mcp/dist/server-memory-store.js +1 -1
- package/package/aws-client-agent-mcp/dist/types.d.ts +51 -0
- package/package.json +1 -1
package/dist/routes/git.js
CHANGED
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
import{spawn as
|
|
2
|
-
`).find(i=>i.trim());if(!t)return null;const
|
|
3
|
-
`).filter(i=>i.trim());for(const i of
|
|
4
|
-
`).filter(c=>c.trim());for(const c of
|
|
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=
|
|
6
|
-
`).filter(i=>i.trim());for(const i of
|
|
7
|
-
`)?t:t+1}async function
|
|
8
|
-
`).map(
|
|
9
|
-
`)){const a
|
|
10
|
-
`)){const s=i.split("\0"),
|
|
11
|
-
${s.stderr}`.trim(),
|
|
12
|
-
${s.stderr}`)){const u=await h(
|
|
13
|
-
${s.stderr}`)?new Error(
|
|
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:
|
|
16
|
-
`).map(
|
|
17
|
-
`),
|
|
18
|
-
`).length;p={stdout:[`diff --git a/${c} b/${c}`,"new file mode 100644","--- /dev/null",`+++ b/${c}`,`@@ -0,0 +1,${
|
|
19
|
-
`),stderr:"",exitCode:0}}}catch{}}
|
|
20
|
-
`);function
|
|
21
|
-
`)}const
|
|
22
|
-
`)[0]?.trim()??"";if(!t||t.length<3)return!1;const
|
|
23
|
-
`).trim();const
|
|
24
|
-
`)[0]?.trim()??"",o=!!t.match(
|
|
25
|
-
`)[0]?.trim()??"";return
|
|
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(
|
|
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=
|
|
29
|
-
...(diff truncated due to length)`:
|
|
30
|
-
`),
|
|
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(
|
|
33
|
-
`+p.stack:""}`),c||$(t,"error",{message:p.message||"generate commit message failed"}),t.end()}}),S.post("/git/unstage-files",k,async(
|
|
34
|
-
`),
|
|
35
|
-
`)}S.post("/git/revert-hunk",k,async(
|
|
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
|
|
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
|
|
package/dist/routes/sessions.js
CHANGED
|
@@ -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
|
+
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
|
+
|