aws-runtime-bridge 1.9.124 → 1.9.125
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/README.md +23 -2
- package/dist/adapter/AcodeSdkAdapter.js +1 -1
- package/dist/adapter/types.js +1 -1
- package/dist/config.d.ts +22 -2
- package/dist/config.js +1 -1
- package/dist/index.js +1 -1
- package/dist/routes/dashboard.js +58 -3
- package/dist/routes/git.d.ts +28 -0
- package/dist/routes/git.js +32 -31
- package/dist/routes/terminal.js +24 -24
- package/dist/services/auto-register.d.ts +2 -2
- package/dist/services/auto-register.js +1 -1
- package/dist/services/aws-client-agent-mcp.d.ts +2 -1
- package/dist/services/aws-client-agent-mcp.js +2 -2
- package/dist/services/cc-switch-sdk.js +1 -1
- package/dist/services/cli-commands.js +5 -5
- package/dist/services/fileSnapshotStore.js +1 -1
- package/dist/services/instance-ai-config-service.js +1 -1
- package/dist/services/instance-features.d.ts +2 -0
- package/dist/services/instance-features.js +1 -1
- package/dist/services/panel-auth.d.ts +2 -0
- package/dist/services/panel-auth.js +1 -1
- package/dist/services/startup-config-wizard.js +6 -6
- package/dist/services/tunnel-client.js +1 -1
- package/dist/services/update-notifier.js +2 -2
- package/package/acode/dist/ask-user-question-tool.js +2 -2
- package/package/acode/dist/mcpToolNames.d.ts +2 -0
- package/package/acode/dist/mcpToolNames.js +1 -1
- package/package/acode/dist/meeting-debug-log.d.ts +1 -0
- package/package/acode/dist/meeting-debug-log.js +3 -0
- package/package/acode/dist/runtime.d.ts +2 -0
- package/package/acode/dist/runtime.js +12 -12
- package/package/aws-client-agent-mcp/dist/agent-client.d.ts +12 -0
- package/package/aws-client-agent-mcp/dist/agent-client.js +1 -1
- package/package/aws-client-agent-mcp/dist/agent-client.test.js +179 -1
- package/package/aws-client-agent-mcp/dist/config.js +1 -1
- package/package/aws-client-agent-mcp/dist/http-client.d.ts +2 -0
- package/package/aws-client-agent-mcp/dist/http-client.js +1 -1
- package/package/aws-client-agent-mcp/dist/launch-config-tools.test.js +6 -2
- package/package/aws-client-agent-mcp/dist/mcp-server.d.ts +21 -0
- package/package/aws-client-agent-mcp/dist/mcp-server.js +28 -28
- package/package/aws-client-agent-mcp/dist/mcp-server.test.js +604 -1
- package/package/aws-client-agent-mcp/dist/mcp-tools.d.ts +10 -0
- package/package/aws-client-agent-mcp/dist/mcp-tools.js +12 -10
- package/package/aws-client-agent-mcp/dist/mcp-tools.test.js +38 -0
- package/package/aws-client-agent-mcp/dist/meeting-debug-log.d.ts +13 -0
- package/package/aws-client-agent-mcp/dist/meeting-debug-log.js +3 -0
- package/package/aws-client-agent-mcp/dist/message-buffer.d.ts +2 -0
- package/package/aws-client-agent-mcp/dist/message-buffer.js +1 -1
- package/package/aws-client-agent-mcp/dist/message-buffer.test.js +60 -0
- package/package/aws-client-agent-mcp/dist/types.d.ts +11 -1
- package/package/aws-client-agent-mcp/dist/websocket-client.js +1 -1
- package/package.json +1 -1
package/dist/routes/dashboard.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{Router as $}from"express";import v from"node:fs";import K from"node:path";import w from"node:os";import{execFile as Y}from"node:child_process";import{getRuntimeBindingPublicState as H,getRuntimePairingCode as Q,normalizeSchedulerBaseUrl as X}from"../services/runtime-binding.js";import{getAgentProcessManager as C}from"../services/agent-process-manager.js";import{collectSystemMetrics as Z}from"../services/system-metrics.js";import{loadPersistedSessions as ee}from"../services/terminal-persistence.js";import{createLogger as te}from"../utils/logger.js";import{getBridgeVersion as ne}from"../config.js";import{runExclusiveConfigWrite as O}from"../utils/config-write-queue.js";import{sdkSessions as se}from"./terminal.js";import{createSession as re,getPanelCredentials as N,validateSession as U,destroySession as z,validateCredentials as F,hasConfiguredCredentials as B,isDefaultCredentials as ae,setPanelCredentials as oe,getAllSessions as de,getActiveSessionCount as ie,checkLoginAllowed as le,recordLoginFailure as ce,recordLoginSuccess as ue}from"../services/panel-auth.js";import{getRecentLogs as ge}from"../utils/logger.js";import{autoRegister as pe,getRegistrationState as _,getAutoRegisterConfigFilePath as E}from"../services/auto-register.js";import{getAccessControlEntries as fe,isValidIpEntry as me,parseAllowIps as he,refreshAccessControlCache as be}from"../services/access-control.js";const x=te("dashboard");let T=null;const ve=1e4;let L=null,P=null;const ye=3e4;async function D(){Z().then(t=>{L={data:t,ts:Date.now()}}).catch(()=>{}),C().discoverAllProcesses().then(t=>{P={data:t,ts:Date.now()}}).catch(()=>{}),C().cleanupStaleRegistry().catch(()=>{})}D(),setInterval(D,ye);const V=ne();async function xe(){const t=await import("node:fs/promises"),e=await import("node:path"),n=await import("node:os"),s=e.join(String(process.env.AWS_RUNTIME_HOME_DIR||n.homedir()).trim()||n.homedir(),".aws-bridge","lifecycle-state.json");try{const d=await t.readFile(s,"utf8"),r=JSON.parse(d),a=Array.isArray(r.operations)?r.operations:[];return a.length===0?null:(a.sort((o,l)=>new Date(l.updatedAt).getTime()-new Date(o.updatedAt).getTime()),a[0]||null)}catch{return null}}const g=$(),J="panel_sid";function we(t){const e={};if(!t)return e;for(const n of t.split(";")){const s=n.indexOf("=");if(s===-1)continue;const d=n.slice(0,s).trim(),r=n.slice(s+1).trim();d&&(e[d]=r)}return e}function A(t){return we(t.headers.cookie)[J]}function W(t,e,n){const s=t.secure||t.headers["x-forwarded-proto"]==="https";return`${J}=${e}; HttpOnly; SameSite=Lax; Path=/${s?"; Secure":""}; Max-Age=${n}`}function m(t,e){const n=A(t);return n&&U(n)?!0:t.xhr||t.headers.accept?.includes("application/json")?(e.status(401).json({ok:!1,error:"unauthorized",needLogin:!0}),!1):(e.redirect("/login"),!1)}g.post("/runtime/login",(t,e)=>{const n=t.socket.remoteAddress||"unknown",s=le(n);if(!s.allowed){e.status(429).set("Retry-After",String(s.retryAfterSec)).json({ok:!1,error:"too many failed login attempts, try again later",retryAfterSec:s.retryAfterSec});return}const{username:d,password:r}=t.body||{};if(!d||!r){e.status(400).json({ok:!1,error:"username and password required"});return}if(!F(String(d),String(r))){ce(n),e.status(401).json({ok:!1,error:"invalid credentials"});return}ue(n);const a=re(),o=!B();e.setHeader("Set-Cookie",W(t,a,86400)),e.json({ok:!0,needSetup:o})}),g.post("/runtime/logout",(t,e)=>{const n=A(t);n&&z(n),e.setHeader("Set-Cookie",W(t,"",0)),e.json({ok:!0})}),g.get("/runtime/credentials-status",(t,e)=>{if(!m(t,e))return;const n=N();e.json({ok:!0,isDefault:ae(),configured:B(),username:n.username})}),g.post("/runtime/change-credentials",async(t,e)=>{if(!m(t,e))return;const{username:n,password:s,passwordConfirm:d,oldPassword:r}=t.body||{};if(s&&String(s).trim()){if(String(s)!==String(d)){e.status(400).json({ok:!1,error:"passwords do not match"});return}if(B()){if(!r){e.status(400).json({ok:!1,error:"old password required"});return}const o=N();if(!F(o.username,String(r))){e.status(403).json({ok:!1,error:"old password is incorrect"});return}}}if(!n||!String(n).trim()){e.status(400).json({ok:!1,error:"username required"});return}const a=s&&String(s).trim()?String(s).trim():N().password;try{await oe(String(n).trim(),a),e.json({ok:!0})}catch(o){const l=o;x.error("Failed to change panel credentials:",l.message),e.status(500).json({ok:!1,error:l.message})}});function q(t){const e=typeof t=="string"?t.trim():"";if(e)return e.length<8?"\u2022\u2022\u2022\u2022":"\u2022\u2022\u2022\u2022"+e.slice(-4)}function G(t){const e=Array.isArray(t.autoRegisterTargets)?t.autoRegisterTargets.filter(s=>!!(s&&typeof s=="object"&&!Array.isArray(s))):[];return e.length>0?e:["serverUrl","schedulerBaseUrl","userKey"].some(s=>!!String(t[s]??"").trim())?[t]:[]}const Se=["enabled","serverUrl","schedulerBaseUrl","tenantId","userKey","connectionKey","password","instanceName","instanceId","projectId","roleName","workspacePath","registerAddr","registerIp","virtualIp","tunnel","preferSameLanIp","maxRetries","retryInterval"];function Ie(t){const e={};for(const n of Se)t[n]!==void 0&&(e[n]=t[n]);return e}function ke(t,e){const n=String(t.connectionKey??"").trim()||String(e??"").trim();return{serverUrl:String(t.serverUrl??t.schedulerBaseUrl??"").trim(),tenantId:String(t.tenantId??"").trim(),instanceName:String(t.instanceName??"").trim(),instanceId:String(t.instanceId??"").trim()||void 0,registerAddr:String(t.registerAddr??"").trim(),tunnel:typeof t.tunnel=="boolean"?t.tunnel:void 0,userKeyMasked:q(t.userKey),userKeySet:!!String(t.userKey??"").trim(),connectionKeyMasked:q(n),connectionKeySet:!!n}}function R(t){return X(t)??String(t||"").trim()}g.get("/runtime/server-config",(t,e)=>{if(!m(t,e))return;const n=E();let s={};if(v.existsSync(n))try{s=JSON.parse(v.readFileSync(n,"utf-8"))}catch{e.status(500).json({ok:!1,error:"config.json is not valid JSON; fix it manually first"});return}const d=String(s.connectionKey??"").trim()||void 0,r=G(s).map(a=>ke(a,d));e.json({ok:!0,targets:r})});function M(t,e){const n=new Error(e);return n.status=t,n}g.post("/runtime/server-config",async(t,e)=>{if(!m(t,e))return;const n=(t.body||{}).targets;if(!Array.isArray(n)){e.status(400).json({ok:!1,error:"targets array is required"});return}const s=[];for(let r=0;r<n.length;r++){const a=n[r];if(!a||typeof a!="object"||Array.isArray(a)){e.status(400).json({ok:!1,error:`targets[${r}] must be an object`});return}const o=a,l=String(o.serverUrl??o.schedulerBaseUrl??"").trim();if(!l){e.status(400).json({ok:!1,error:`targets[${r}].serverUrl is required`});return}try{const i=new URL(l);if(!["http:","https:"].includes(i.protocol))throw new Error("protocol must be http or https")}catch{e.status(400).json({ok:!1,error:`targets[${r}].serverUrl must be a valid http(s) URL`});return}const f=o.tunnel;let p;if(f==="auto"||f===""||f==null)p=void 0;else if(f===!0||f==="true")p=!0;else if(f===!1||f==="false")p=!1;else{e.status(400).json({ok:!1,error:`targets[${r}].tunnel must be auto, true or false`});return}const b=String(o.originalServerUrl??"").trim();s.push({serverUrl:l,originalServerUrl:b&&b!==l?b:"",userKey:String(o.userKey??"").trim(),tenantId:String(o.tenantId??"").trim(),instanceName:String(o.instanceName??"").trim(),connectionKey:String(o.connectionKey??"").trim(),registerAddr:String(o.registerAddr??"").trim(),tunnel:p})}const d=new Set;for(const r of s){const a=R(r.serverUrl);if(d.has(a)){e.status(400).json({ok:!1,error:"duplicate serverUrl in targets"});return}d.add(a)}try{const r=E();await O(r,()=>{let a={};if(v.existsSync(r))try{a=JSON.parse(v.readFileSync(r,"utf-8"))}catch{throw M(500,"config.json is not valid JSON; fix it manually first")}const o=String(a.connectionKey??"").trim(),l=G(a),f=l.length>0&&l[0]===a,p=new Map(l.map(i=>[R(String(i.serverUrl??i.schedulerBaseUrl??"").trim()),f?Ie(i):i])),b=[];for(const i of s){let h;if(i.originalServerUrl){const S=R(i.originalServerUrl);h=p.get(S),h&&p.delete(S)}if(!h){const S=R(i.serverUrl);h=p.get(S),h&&p.delete(S)}const c=h?{...h}:{};if(c.serverUrl=i.serverUrl,c.tenantId=i.tenantId,c.instanceName=i.instanceName,c.registerAddr=i.registerAddr,i.tunnel===void 0?delete c.tunnel:c.tunnel=i.tunnel,i.userKey)c.userKey=i.userKey;else if(!String(c.userKey??"").trim())throw M(400,`userKey is required for ${i.serverUrl} (new target without inheritable userKey)`);i.connectionKey?c.connectionKey=i.connectionKey:!String(c.connectionKey??"").trim()&&o&&(c.connectionKey=o),b.push(c)}a.autoRegisterTargets=b;for(const i of["enabled","serverUrl","schedulerBaseUrl","tenantId","userKey","connectionKey","password","instanceName","instanceId","registerAddr","registerIp","virtualIp","tunnel"])delete a[i];v.mkdirSync(K.dirname(r),{recursive:!0}),v.writeFileSync(r,JSON.stringify(a,null,2),"utf-8")}),x.info("Server connection config updated via panel",{configPath:r,targetCount:s.length}),e.json({ok:!0})}catch(r){const a=r,o=Number(a.status)||500;o>=500&&x.error("Failed to save server connection config:",a.message),e.status(o).json({ok:!1,error:a.message})}}),g.get("/runtime/access-control",(t,e)=>{m(t,e)&&e.json({ok:!0,allowIps:fe(),defaultAllowIps:["0.0.0.0"]})}),g.post("/runtime/access-control",async(t,e)=>{if(!m(t,e))return;const n=(t.body||{}).allowIps;if(typeof n!="string"&&!Array.isArray(n)){e.status(400).json({ok:!1,error:"allowIps must be a string or an array"});return}const s=he(n),d=s.filter(r=>!me(r));if(d.length>0){e.status(400).json({ok:!1,error:`invalid IP/CIDR entries: ${d.join(", ")}`});return}try{const r=E();await O(r,()=>{let a={};if(v.existsSync(r))try{a=JSON.parse(v.readFileSync(r,"utf-8"))}catch{throw M(500,"config.json is not valid JSON; fix it manually first")}const o=a.accessControl&&typeof a.accessControl=="object"&&!Array.isArray(a.accessControl)?{...a.accessControl}:{};o.allowIps=s,a.accessControl=o,v.mkdirSync(K.dirname(r),{recursive:!0}),v.writeFileSync(r,JSON.stringify(a,null,2),"utf-8"),be()}),x.info("Access control config updated via panel",{configPath:r,entryCount:s.length}),e.json({ok:!0,allowIps:s})}catch(r){const a=r,o=Number(a.status)||500;o>=500&&x.error("Failed to save access control config:",a.message),e.status(o).json({ok:!1,error:a.message})}}),g.get("/runtime/node-info",(t,e)=>{if(!m(t,e))return;const n=H(),s=_();e.json({ok:!0,hostname:w.hostname(),platform:w.platform(),arch:w.arch(),uptime:Math.floor(process.uptime()),nodeVersion:process.version,bridgeVersion:V,configPath:E(),homeDir:process.env.AWS_RUNTIME_HOME_DIR||w.homedir(),bindingStatus:n.status,paired:n.paired,instanceId:n.instanceId||s.instanceId,schedulerBaseUrl:n.schedulerBaseUrl,registered:s.registered,pairingCode:n.pairingCode,activeSessions:ie()})}),g.get("/runtime/logs",(t,e)=>{if(!m(t,e))return;const n=t.query.level||void 0,s=Math.min(Number(t.query.count)||200,500),r=n&&["debug","info","warn","error"].includes(n)?n:void 0,a=ge(s,r);e.json({ok:!0,logs:a})}),g.post("/runtime/re-register",async(t,e)=>{if(m(t,e))try{const n=await pe();e.json({ok:n})}catch(n){const s=n;e.status(500).json({ok:!1,error:s.message})}}),g.get("/runtime/admin/sessions",(t,e)=>{if(!m(t,e))return;const n=de().map(s=>({token:s.token.slice(0,8)+"...",createdAt:new Date(s.createdAt).toISOString(),lastUsedAt:new Date(s.lastUsedAt).toISOString(),age:Math.floor((Date.now()-s.createdAt)/1e3)}));e.json({ok:!0,sessions:n})}),g.post("/runtime/admin/sessions/:token/destroy",(t,e)=>{m(t,e)&&(z(t.params.token),e.json({ok:!0}))});function Ce(t){return new Promise((e,n)=>{const s=w.platform()==="win32",d=s?"taskkill":"kill",r=s?["/PID",String(t),"/F"]:["-9",String(t)];Y(d,r,{timeout:5e3},a=>a?n(a):e())})}g.post("/runtime/kill-process",async(t,e)=>{if(!m(t,e))return;const n=Number(t.body.pid);if(!n||!Number.isInteger(n)||n<=0){e.status(400).json({ok:!1,error:"Invalid PID"});return}const s=C(),d=()=>{const a=new Set,o=P?.data??[];for(const l of o)typeof l.pid=="number"&&a.add(l.pid);for(const l of s.getAllManagedProcesses())typeof l.pid=="number"&&a.add(l.pid);return a};let r=d();if(r.size===0)try{P={data:await s.discoverAllProcesses(),ts:Date.now()},r=d()}catch{}if(!r.has(n)){x.warn("Rejected kill-process for unrecognized PID",{pid:n}),e.status(404).json({ok:!1,error:"PID is not a bridge-discovered or registered agent process"});return}try{await Ce(n),x.info("Killed process",{pid:n}),e.json({ok:!0})}catch(a){const o=a;x.warn("Failed to kill process",{pid:n,error:o.message}),e.status(500).json({ok:!1,error:o.message})}}),g.get("/runtime/dashboard",async(t,e)=>{if(m(t,e)){if(T&&Date.now()-T.ts<ve)return void e.json(T.data);try{const n=L?.data??null,s=P?.data??null;L||D();const[d,r,a]=await Promise.all([Promise.resolve(H()),Promise.resolve(_()),ee().catch(()=>[])]),l=C().getAllManagedProcesses(),f=Array.from(se.entries()).map(([I,y])=>({sessionId:I,agentId:y.agentId,workspacePath:y.workspacePath,runtimeStatus:y.currentRuntimeStatus||"running",runtimeActionType:y.currentRuntimeActionType,runtimeActionLabel:y.currentRuntimeActionLabel,providerId:y.providerId}));let p={managed:0,orphan:0,unknown:0},b=[];if(s){p={managed:s.filter(u=>u.managementStatus==="managed").length,orphan:s.filter(u=>u.managementStatus==="orphan").length,unknown:s.filter(u=>u.managementStatus==="unknown").length};const I=new Map(l.map(u=>[u.pid,u]));b=s.map(u=>{const k=I.get(u.pid);return{agentId:u.agentId||k?.agentId,pid:u.pid,managementStatus:u.managementStatus,sessionId:k?.sessionId,state:k?.state,command:u.command,cwd:u.cwd,startedAt:u.startedAt}});const y={managed:0,orphan:1,unknown:2};b.sort((u,k)=>(y[u.managementStatus]??9)-(y[k.managementStatus]??9))}const i=new Set(f.map(I=>I.sessionId)),h=a.filter(I=>!i.has(I.sessionId)),c=d,S=await xe(),j={ok:!0,collectedAt:new Date().toISOString(),bridge:{version:V,hostname:w.hostname(),platform:w.platform(),arch:w.arch(),uptime:Math.floor(process.uptime()),nodeVersion:process.version},binding:{status:c.status,paired:c.paired,instanceId:c.instanceId,userId:c.userId,schedulerBaseUrl:c.schedulerBaseUrl,pairingCode:Q()},registration:{registered:r.registered,instanceId:r.instanceId,lastAttempt:r.lastAttempt?.toISOString(),error:r.error},metrics:n,sessions:{active:f,activeCount:f.length,persisted:h,persistedCount:h.length,total:f.length+h.length},processes:{...p,total:p.managed+p.orphan+p.unknown,list:b,registryCount:l.length},lifecycleOperation:S};T={data:j,ts:Date.now()},e.json(j)}catch(n){const s=n;x.error("Dashboard aggregation failed:",s.message),e.status(500).json({ok:!1,error:s.message})}}}),g.get("/login",(t,e)=>{e.type("text/html; charset=utf-8"),e.send(Ee)}),g.get("/",(t,e)=>{const n=A(t);if(!n||!U(n)){e.redirect("/login");return}if(!B()){e.redirect("/setup");return}const s=t.query.lang==="en"?"en":"zh";e.type("text/html; charset=utf-8"),e.send(Be.replace("{{LANG}}",s))}),g.get("/setup",(t,e)=>{const n=A(t);if(!n||!U(n)){e.redirect("/login");return}e.type("text/html; charset=utf-8"),e.send(Te)});const Be=`<!DOCTYPE html>
|
|
1
|
+
import{Router as $}from"express";import v from"node:fs";import K from"node:path";import I from"node:os";import{execFile as Y}from"node:child_process";import{getRuntimeBindingPublicState as H,getRuntimePairingCode as Q,normalizeSchedulerBaseUrl as X}from"../services/runtime-binding.js";import{getAgentProcessManager as C}from"../services/agent-process-manager.js";import{collectSystemMetrics as Z}from"../services/system-metrics.js";import{loadPersistedSessions as ee}from"../services/terminal-persistence.js";import{createLogger as te}from"../utils/logger.js";import{getBridgeVersion as ne,getRuntimeHomeDir as se,getUserHomeDir as re}from"../config.js";import{runExclusiveConfigWrite as O}from"../utils/config-write-queue.js";import{sdkSessions as ae}from"./terminal.js";import{createSession as oe,getPanelCredentials as N,validateSession as U,destroySession as z,validateCredentials as F,hasConfiguredCredentials as B,isDefaultCredentials as ie,setPanelCredentials as de,getAllSessions as le,getActiveSessionCount as ce,checkLoginAllowed as ue,recordLoginFailure as ge,recordLoginSuccess as pe}from"../services/panel-auth.js";import{getRecentLogs as fe}from"../utils/logger.js";import{autoRegister as me,getRegistrationState as _,getAutoRegisterConfigFilePath as E}from"../services/auto-register.js";import{getAccessControlEntries as be,isValidIpEntry as he,parseAllowIps as ve,refreshAccessControlCache as ye}from"../services/access-control.js";const x=te("dashboard");let T=null;const xe=1e4;let L=null,P=null;const we=3e4;async function D(){Z().then(t=>{L={data:t,ts:Date.now()}}).catch(()=>{}),C().discoverAllProcesses().then(t=>{P={data:t,ts:Date.now()}}).catch(()=>{}),C().cleanupStaleRegistry().catch(()=>{})}D(),setInterval(D,we);const V=ne();async function Se(){const t=await import("node:fs/promises"),n=(await import("node:path")).join(se(),".aws-bridge","lifecycle-state.json");try{const s=await t.readFile(n,"utf8"),i=JSON.parse(s),r=Array.isArray(i.operations)?i.operations:[];return r.length===0?null:(r.sort((a,o)=>new Date(o.updatedAt).getTime()-new Date(a.updatedAt).getTime()),r[0]||null)}catch{return null}}const g=$(),J="panel_sid";function Ie(t){const e={};if(!t)return e;for(const n of t.split(";")){const s=n.indexOf("=");if(s===-1)continue;const i=n.slice(0,s).trim(),r=n.slice(s+1).trim();i&&(e[i]=r)}return e}function A(t){return Ie(t.headers.cookie)[J]}function q(t,e,n){const s=t.secure||t.headers["x-forwarded-proto"]==="https";return`${J}=${e}; HttpOnly; SameSite=Lax; Path=/${s?"; Secure":""}; Max-Age=${n}`}function m(t,e){const n=A(t);return n&&U(n)?!0:t.xhr||t.headers.accept?.includes("application/json")?(e.status(401).json({ok:!1,error:"unauthorized",needLogin:!0}),!1):(e.redirect("/login"),!1)}g.post("/runtime/login",(t,e)=>{const n=t.socket.remoteAddress||"unknown",s=ue(n);if(!s.allowed){e.status(429).set("Retry-After",String(s.retryAfterSec)).json({ok:!1,error:"too many failed login attempts, try again later",retryAfterSec:s.retryAfterSec});return}const{username:i,password:r}=t.body||{};if(!i||!r){e.status(400).json({ok:!1,error:"username and password required"});return}if(!F(String(i),String(r))){ge(n),e.status(401).json({ok:!1,error:"invalid credentials"});return}pe(n);const a=oe(),o=!B();e.setHeader("Set-Cookie",q(t,a,86400)),e.json({ok:!0,needSetup:o})}),g.post("/runtime/logout",(t,e)=>{const n=A(t);n&&z(n),e.setHeader("Set-Cookie",q(t,"",0)),e.json({ok:!0})}),g.get("/runtime/credentials-status",(t,e)=>{if(!m(t,e))return;const n=N();e.json({ok:!0,isDefault:ie(),configured:B(),username:n.username})}),g.post("/runtime/change-credentials",async(t,e)=>{if(!m(t,e))return;const{username:n,password:s,passwordConfirm:i,oldPassword:r}=t.body||{};if(s&&String(s).trim()){if(String(s)!==String(i)){e.status(400).json({ok:!1,error:"passwords do not match"});return}if(B()){if(!r){e.status(400).json({ok:!1,error:"old password required"});return}const o=N();if(!F(o.username,String(r))){e.status(403).json({ok:!1,error:"old password is incorrect"});return}}}if(!n||!String(n).trim()){e.status(400).json({ok:!1,error:"username required"});return}const a=s&&String(s).trim()?String(s).trim():N().password;try{await de(String(n).trim(),a),e.json({ok:!0})}catch(o){const l=o;x.error("Failed to change panel credentials:",l.message),e.status(500).json({ok:!1,error:l.message})}});function W(t){const e=typeof t=="string"?t.trim():"";if(e)return e.length<8?"\u2022\u2022\u2022\u2022":"\u2022\u2022\u2022\u2022"+e.slice(-4)}function G(t){const e=Array.isArray(t.autoRegisterTargets)?t.autoRegisterTargets.filter(s=>!!(s&&typeof s=="object"&&!Array.isArray(s))):[];return e.length>0?e:["serverUrl","schedulerBaseUrl","userKey"].some(s=>!!String(t[s]??"").trim())?[t]:[]}const ke=["enabled","serverUrl","schedulerBaseUrl","tenantId","userKey","connectionKey","password","instanceName","instanceId","projectId","roleName","workspacePath","registerAddr","registerIp","virtualIp","tunnel","preferSameLanIp","maxRetries","retryInterval"];function Ce(t){const e={};for(const n of ke)t[n]!==void 0&&(e[n]=t[n]);return e}function Be(t,e){const n=String(t.connectionKey??"").trim()||String(e??"").trim();return{serverUrl:String(t.serverUrl??t.schedulerBaseUrl??"").trim(),tenantId:String(t.tenantId??"").trim(),instanceName:String(t.instanceName??"").trim(),instanceId:String(t.instanceId??"").trim()||void 0,registerAddr:String(t.registerAddr??"").trim(),tunnel:typeof t.tunnel=="boolean"?t.tunnel:void 0,userKeyMasked:W(t.userKey),userKeySet:!!String(t.userKey??"").trim(),connectionKeyMasked:W(n),connectionKeySet:!!n}}function R(t){return X(t)??String(t||"").trim()}g.get("/runtime/server-config",(t,e)=>{if(!m(t,e))return;const n=E();let s={};if(v.existsSync(n))try{s=JSON.parse(v.readFileSync(n,"utf-8"))}catch{e.status(500).json({ok:!1,error:"config.json is not valid JSON; fix it manually first"});return}const i=String(s.connectionKey??"").trim()||void 0,r=G(s).map(a=>Be(a,i));e.json({ok:!0,targets:r})});function M(t,e){const n=new Error(e);return n.status=t,n}g.post("/runtime/server-config",async(t,e)=>{if(!m(t,e))return;const n=(t.body||{}).targets;if(!Array.isArray(n)){e.status(400).json({ok:!1,error:"targets array is required"});return}const s=[];for(let r=0;r<n.length;r++){const a=n[r];if(!a||typeof a!="object"||Array.isArray(a)){e.status(400).json({ok:!1,error:`targets[${r}] must be an object`});return}const o=a,l=String(o.serverUrl??o.schedulerBaseUrl??"").trim();if(!l){e.status(400).json({ok:!1,error:`targets[${r}].serverUrl is required`});return}try{const d=new URL(l);if(!["http:","https:"].includes(d.protocol))throw new Error("protocol must be http or https")}catch{e.status(400).json({ok:!1,error:`targets[${r}].serverUrl must be a valid http(s) URL`});return}const f=o.tunnel;let p;if(f==="auto"||f===""||f==null)p=void 0;else if(f===!0||f==="true")p=!0;else if(f===!1||f==="false")p=!1;else{e.status(400).json({ok:!1,error:`targets[${r}].tunnel must be auto, true or false`});return}const h=String(o.originalServerUrl??"").trim();s.push({serverUrl:l,originalServerUrl:h&&h!==l?h:"",userKey:String(o.userKey??"").trim(),tenantId:String(o.tenantId??"").trim(),instanceName:String(o.instanceName??"").trim(),connectionKey:String(o.connectionKey??"").trim(),registerAddr:String(o.registerAddr??"").trim(),tunnel:p})}const i=new Set;for(const r of s){const a=R(r.serverUrl);if(i.has(a)){e.status(400).json({ok:!1,error:"duplicate serverUrl in targets"});return}i.add(a)}try{const r=E();await O(r,()=>{let a={};if(v.existsSync(r))try{a=JSON.parse(v.readFileSync(r,"utf-8"))}catch{throw M(500,"config.json is not valid JSON; fix it manually first")}const o=String(a.connectionKey??"").trim(),l=G(a),f=l.length>0&&l[0]===a,p=new Map(l.map(d=>[R(String(d.serverUrl??d.schedulerBaseUrl??"").trim()),f?Ce(d):d])),h=[];for(const d of s){let b;if(d.originalServerUrl){const w=R(d.originalServerUrl);b=p.get(w),b&&p.delete(w)}if(!b){const w=R(d.serverUrl);b=p.get(w),b&&p.delete(w)}const c=b?{...b}:{};if(c.serverUrl=d.serverUrl,c.tenantId=d.tenantId,c.instanceName=d.instanceName,c.registerAddr=d.registerAddr,d.tunnel===void 0?delete c.tunnel:c.tunnel=d.tunnel,d.userKey)c.userKey=d.userKey;else if(!String(c.userKey??"").trim())throw M(400,`userKey is required for ${d.serverUrl} (new target without inheritable userKey)`);d.connectionKey?c.connectionKey=d.connectionKey:!String(c.connectionKey??"").trim()&&o&&(c.connectionKey=o),h.push(c)}a.autoRegisterTargets=h;for(const d of["enabled","serverUrl","schedulerBaseUrl","tenantId","userKey","connectionKey","password","instanceName","instanceId","registerAddr","registerIp","virtualIp","tunnel"])delete a[d];v.mkdirSync(K.dirname(r),{recursive:!0}),v.writeFileSync(r,JSON.stringify(a,null,2),"utf-8")}),x.info("Server connection config updated via panel",{configPath:r,targetCount:s.length}),e.json({ok:!0})}catch(r){const a=r,o=Number(a.status)||500;o>=500&&x.error("Failed to save server connection config:",a.message),e.status(o).json({ok:!1,error:a.message})}}),g.get("/runtime/access-control",(t,e)=>{m(t,e)&&e.json({ok:!0,allowIps:be(),defaultAllowIps:["0.0.0.0"]})}),g.post("/runtime/access-control",async(t,e)=>{if(!m(t,e))return;const n=(t.body||{}).allowIps;if(typeof n!="string"&&!Array.isArray(n)){e.status(400).json({ok:!1,error:"allowIps must be a string or an array"});return}const s=ve(n),i=s.filter(r=>!he(r));if(i.length>0){e.status(400).json({ok:!1,error:`invalid IP/CIDR entries: ${i.join(", ")}`});return}try{const r=E();await O(r,()=>{let a={};if(v.existsSync(r))try{a=JSON.parse(v.readFileSync(r,"utf-8"))}catch{throw M(500,"config.json is not valid JSON; fix it manually first")}const o=a.accessControl&&typeof a.accessControl=="object"&&!Array.isArray(a.accessControl)?{...a.accessControl}:{};o.allowIps=s,a.accessControl=o,v.mkdirSync(K.dirname(r),{recursive:!0}),v.writeFileSync(r,JSON.stringify(a,null,2),"utf-8"),ye()}),x.info("Access control config updated via panel",{configPath:r,entryCount:s.length}),e.json({ok:!0,allowIps:s})}catch(r){const a=r,o=Number(a.status)||500;o>=500&&x.error("Failed to save access control config:",a.message),e.status(o).json({ok:!1,error:a.message})}}),g.get("/runtime/node-info",(t,e)=>{if(!m(t,e))return;const n=H(),s=_();e.json({ok:!0,hostname:I.hostname(),platform:I.platform(),arch:I.arch(),uptime:Math.floor(process.uptime()),nodeVersion:process.version,bridgeVersion:V,configPath:E(),homeDir:re(),bindingStatus:n.status,paired:n.paired,instanceId:n.instanceId||s.instanceId,schedulerBaseUrl:n.schedulerBaseUrl,registered:s.registered,pairingCode:n.pairingCode,activeSessions:ce()})}),g.get("/runtime/logs",(t,e)=>{if(!m(t,e))return;const n=t.query.level||void 0,s=Math.min(Number(t.query.count)||200,500),r=n&&["debug","info","warn","error"].includes(n)?n:void 0,a=fe(s,r);e.json({ok:!0,logs:a})}),g.post("/runtime/re-register",async(t,e)=>{if(m(t,e))try{const n=await me();e.json({ok:n})}catch(n){const s=n;e.status(500).json({ok:!1,error:s.message})}}),g.get("/runtime/admin/sessions",(t,e)=>{if(!m(t,e))return;const n=le().map(s=>({token:s.token.slice(0,8)+"...",createdAt:new Date(s.createdAt).toISOString(),lastUsedAt:new Date(s.lastUsedAt).toISOString(),age:Math.floor((Date.now()-s.createdAt)/1e3)}));e.json({ok:!0,sessions:n})}),g.post("/runtime/admin/sessions/:token/destroy",(t,e)=>{m(t,e)&&(z(t.params.token),e.json({ok:!0}))});function Ee(t){return new Promise((e,n)=>{const s=I.platform()==="win32",i=s?"taskkill":"kill",r=s?["/PID",String(t),"/F"]:["-9",String(t)];Y(i,r,{timeout:5e3},a=>a?n(a):e())})}g.post("/runtime/kill-process",async(t,e)=>{if(!m(t,e))return;const n=Number(t.body.pid);if(!n||!Number.isInteger(n)||n<=0){e.status(400).json({ok:!1,error:"Invalid PID"});return}const s=C(),i=()=>{const a=new Set,o=P?.data??[];for(const l of o)typeof l.pid=="number"&&a.add(l.pid);for(const l of s.getAllManagedProcesses())typeof l.pid=="number"&&a.add(l.pid);return a};let r=i();if(r.size===0)try{P={data:await s.discoverAllProcesses(),ts:Date.now()},r=i()}catch{}if(!r.has(n)){x.warn("Rejected kill-process for unrecognized PID",{pid:n}),e.status(404).json({ok:!1,error:"PID is not a bridge-discovered or registered agent process"});return}try{await Ee(n),x.info("Killed process",{pid:n}),e.json({ok:!0})}catch(a){const o=a;x.warn("Failed to kill process",{pid:n,error:o.message}),e.status(500).json({ok:!1,error:o.message})}}),g.get("/runtime/dashboard",async(t,e)=>{if(m(t,e)){if(T&&Date.now()-T.ts<xe)return void e.json(T.data);try{const n=L?.data??null,s=P?.data??null;L||D();const[i,r,a]=await Promise.all([Promise.resolve(H()),Promise.resolve(_()),ee().catch(()=>[])]),l=C().getAllManagedProcesses(),f=Array.from(ae.entries()).map(([S,y])=>({sessionId:S,agentId:y.agentId,workspacePath:y.workspacePath,runtimeStatus:y.currentRuntimeStatus||"running",runtimeActionType:y.currentRuntimeActionType,runtimeActionLabel:y.currentRuntimeActionLabel,providerId:y.providerId}));let p={managed:0,orphan:0,unknown:0},h=[];if(s){p={managed:s.filter(u=>u.managementStatus==="managed").length,orphan:s.filter(u=>u.managementStatus==="orphan").length,unknown:s.filter(u=>u.managementStatus==="unknown").length};const S=new Map(l.map(u=>[u.pid,u]));h=s.map(u=>{const k=S.get(u.pid);return{agentId:u.agentId||k?.agentId,pid:u.pid,managementStatus:u.managementStatus,sessionId:k?.sessionId,state:k?.state,command:u.command,cwd:u.cwd,startedAt:u.startedAt}});const y={managed:0,orphan:1,unknown:2};h.sort((u,k)=>(y[u.managementStatus]??9)-(y[k.managementStatus]??9))}const d=new Set(f.map(S=>S.sessionId)),b=a.filter(S=>!d.has(S.sessionId)),c=i,w=await Se(),j={ok:!0,collectedAt:new Date().toISOString(),bridge:{version:V,hostname:I.hostname(),platform:I.platform(),arch:I.arch(),uptime:Math.floor(process.uptime()),nodeVersion:process.version},binding:{status:c.status,paired:c.paired,instanceId:c.instanceId,userId:c.userId,schedulerBaseUrl:c.schedulerBaseUrl,pairingCode:Q()},registration:{registered:r.registered,instanceId:r.instanceId,lastAttempt:r.lastAttempt?.toISOString(),error:r.error},metrics:n,sessions:{active:f,activeCount:f.length,persisted:b,persistedCount:b.length,total:f.length+b.length},processes:{...p,total:p.managed+p.orphan+p.unknown,list:h,registryCount:l.length},lifecycleOperation:w};T={data:j,ts:Date.now()},e.json(j)}catch(n){const s=n;x.error("Dashboard aggregation failed:",s.message),e.status(500).json({ok:!1,error:s.message})}}}),g.get("/login",(t,e)=>{e.type("text/html; charset=utf-8"),e.send(Pe)}),g.get("/",(t,e)=>{const n=A(t);if(!n||!U(n)){e.redirect("/login");return}if(!B()){e.redirect("/setup");return}const s=t.query.lang==="en"?"en":"zh";e.type("text/html; charset=utf-8"),e.send(Te.replace("{{LANG}}",s))}),g.get("/setup",(t,e)=>{const n=A(t);if(!n||!U(n)){e.redirect("/login");return}e.type("text/html; charset=utf-8"),e.send(Ae)});const Te=`<!DOCTYPE html>
|
|
2
2
|
<html lang="{{LANG}}">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
@@ -280,6 +280,61 @@ import{Router as $}from"express";import v from"node:fs";import K from"node:path"
|
|
|
280
280
|
th, td { padding: 7px 10px; }
|
|
281
281
|
.header h1 { font-size: 18px; }
|
|
282
282
|
}
|
|
283
|
+
|
|
284
|
+
/* ===== Scrollbar (\u4E3B\u9898\u9002\u914D) ===== */
|
|
285
|
+
/* \u9875\u9762\u7EA7\u6EDA\u52A8\u6761\uFF1Apill \u5F62\u6001\uFF08\u8FB9\u6846 + background-clip\uFF09\uFF0C\u4E0E\u6DF1\u8272\u80CC\u666F\u878D\u4E3A\u4E00\u4F53 */
|
|
286
|
+
html {
|
|
287
|
+
scrollbar-width: thin;
|
|
288
|
+
scrollbar-color: color-mix(in srgb, var(--accent) 30%, transparent) var(--bg);
|
|
289
|
+
}
|
|
290
|
+
html::-webkit-scrollbar {
|
|
291
|
+
width: 10px;
|
|
292
|
+
background: var(--bg);
|
|
293
|
+
}
|
|
294
|
+
html::-webkit-scrollbar-thumb {
|
|
295
|
+
background: color-mix(in srgb, var(--accent) 30%, transparent);
|
|
296
|
+
border: 2px solid var(--bg);
|
|
297
|
+
border-radius: 5px;
|
|
298
|
+
background-clip: content-box;
|
|
299
|
+
}
|
|
300
|
+
html::-webkit-scrollbar-thumb:hover {
|
|
301
|
+
background: color-mix(in srgb, var(--accent) 50%, transparent);
|
|
302
|
+
background-clip: content-box;
|
|
303
|
+
}
|
|
304
|
+
/* \u5185\u90E8\u6EDA\u52A8\u5BB9\u5668\uFF08\u8868\u683C\u3001\u8BBE\u7F6E\u9762\u677F\u3001\u65E5\u5FD7\u5217\u8868\uFF09\uFF1A6px \u900F\u660E\u8F68\u9053 + accent \u80F6\u56CA\u6ED1\u5757 */
|
|
305
|
+
.table-wrap,
|
|
306
|
+
.settings-content,
|
|
307
|
+
.s-log-list,
|
|
308
|
+
.s-table-wrap {
|
|
309
|
+
scrollbar-width: thin;
|
|
310
|
+
scrollbar-color: color-mix(in srgb, var(--accent) 30%, transparent) transparent;
|
|
311
|
+
}
|
|
312
|
+
.table-wrap::-webkit-scrollbar,
|
|
313
|
+
.settings-content::-webkit-scrollbar,
|
|
314
|
+
.s-log-list::-webkit-scrollbar,
|
|
315
|
+
.s-table-wrap::-webkit-scrollbar {
|
|
316
|
+
width: 6px;
|
|
317
|
+
height: 6px;
|
|
318
|
+
}
|
|
319
|
+
.table-wrap::-webkit-scrollbar-track,
|
|
320
|
+
.settings-content::-webkit-scrollbar-track,
|
|
321
|
+
.s-log-list::-webkit-scrollbar-track,
|
|
322
|
+
.s-table-wrap::-webkit-scrollbar-track {
|
|
323
|
+
background: transparent;
|
|
324
|
+
}
|
|
325
|
+
.table-wrap::-webkit-scrollbar-thumb,
|
|
326
|
+
.settings-content::-webkit-scrollbar-thumb,
|
|
327
|
+
.s-log-list::-webkit-scrollbar-thumb,
|
|
328
|
+
.s-table-wrap::-webkit-scrollbar-thumb {
|
|
329
|
+
background: color-mix(in srgb, var(--accent) 30%, transparent);
|
|
330
|
+
border-radius: 3px;
|
|
331
|
+
}
|
|
332
|
+
.table-wrap::-webkit-scrollbar-thumb:hover,
|
|
333
|
+
.settings-content::-webkit-scrollbar-thumb:hover,
|
|
334
|
+
.s-log-list::-webkit-scrollbar-thumb:hover,
|
|
335
|
+
.s-table-wrap::-webkit-scrollbar-thumb:hover {
|
|
336
|
+
background: color-mix(in srgb, var(--accent) 50%, transparent);
|
|
337
|
+
}
|
|
283
338
|
</style>
|
|
284
339
|
</head>
|
|
285
340
|
<body>
|
|
@@ -2006,7 +2061,7 @@ switchSettingsPage = function(page) {
|
|
|
2006
2061
|
};
|
|
2007
2062
|
</script>
|
|
2008
2063
|
</body>
|
|
2009
|
-
</html>`,
|
|
2064
|
+
</html>`,Pe=`<!DOCTYPE html>
|
|
2010
2065
|
<html lang="zh-CN">
|
|
2011
2066
|
<head>
|
|
2012
2067
|
<meta charset="UTF-8">
|
|
@@ -2177,7 +2232,7 @@ document.getElementById("loginForm").addEventListener("keydown", function(e) {
|
|
|
2177
2232
|
});
|
|
2178
2233
|
</script>
|
|
2179
2234
|
</body>
|
|
2180
|
-
</html>`,
|
|
2235
|
+
</html>`,Ae=`<!DOCTYPE html>
|
|
2181
2236
|
<html lang="zh-CN">
|
|
2182
2237
|
<head>
|
|
2183
2238
|
<meta charset="UTF-8">
|
package/dist/routes/git.d.ts
CHANGED
|
@@ -90,12 +90,40 @@ export interface GitPushResult {
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
export declare function pushBranchToRemote(repoRootPath: string): Promise<GitPushResult>;
|
|
93
|
+
export interface GitStashSnapshotResult {
|
|
94
|
+
|
|
95
|
+
stashRef: string | null;
|
|
96
|
+
stashName: string | null;
|
|
97
|
+
|
|
98
|
+
noChanges: boolean;
|
|
99
|
+
reusedLatestStash: boolean;
|
|
100
|
+
message: string;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export declare function createGitStashSnapshot(workspacePath: string, message?: string): Promise<GitStashSnapshotResult>;
|
|
93
104
|
export interface GitCommitOutcome {
|
|
94
105
|
committedPaths: string[];
|
|
95
106
|
output: string;
|
|
96
107
|
}
|
|
97
108
|
|
|
98
109
|
export declare function commitSelectedFiles(repoRootPath: string, message: string, pathspecs: string[]): Promise<GitCommitOutcome>;
|
|
110
|
+
export type GitResetMode = 'soft' | 'hard';
|
|
111
|
+
export interface GitResetPreview {
|
|
112
|
+
mode: GitResetMode;
|
|
113
|
+
|
|
114
|
+
commitHash: string;
|
|
115
|
+
branch: string;
|
|
116
|
+
|
|
117
|
+
resetCount: number;
|
|
118
|
+
|
|
119
|
+
hasLocalChanges: boolean;
|
|
120
|
+
|
|
121
|
+
affectsRemote: boolean;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export declare function previewRepositoryReset(repoRootPath: string, commitHash: string, mode: GitResetMode): Promise<GitResetPreview>;
|
|
125
|
+
|
|
126
|
+
export declare function resetRepositoryTo(repoRootPath: string, commitHash: string, mode: GitResetMode, cleanScopePath?: string): Promise<GitResetPreview>;
|
|
99
127
|
|
|
100
128
|
export declare function sanitizeCommitMessage(raw: string): string;
|
|
101
129
|
|
package/dist/routes/git.js
CHANGED
|
@@ -1,36 +1,37 @@
|
|
|
1
|
-
import{spawn as
|
|
2
|
-
`).find(i=>i.trim());if(!t)return null;const e=
|
|
3
|
-
`).filter(i=>i.trim());for(const i of e){const
|
|
4
|
-
`).filter(c=>c.trim());for(const c of n){const u=c.split(" ");if(u.length<2)continue;const f=
|
|
5
|
-
`).filter(c=>c.trim());for(const c of o){const u=c.split(" ");if(u.length<3)continue;const f=u[0]==="-"||u[1]==="-",l=u[0]==="-"?0:parseInt(u[0],10)||0,d=u[1]==="-"?0:parseInt(u[1],10)||0,
|
|
6
|
-
`).filter(i=>i.trim());for(const i of e)if(i.startsWith("?? ")){const
|
|
7
|
-
`)?t:t+1}async function
|
|
8
|
-
`).map(n=>n.trim()).filter(Boolean);if(i.length===0)return t;const
|
|
9
|
-
`)){const a=n.trim();a&&t.add(a)}return t}async function
|
|
10
|
-
`)){const
|
|
11
|
-
${
|
|
12
|
-
${
|
|
13
|
-
${
|
|
14
|
-
`).map(O).filter(c=>c!==null);t.json({ok:!0,stashes:o,count:o.length,workspacePath:r})}catch(r){const n=r;t.status(500).json({error:n.message||"list stash failed"})}});async function J(s,t,e){const{workspacePath:i,ref:r}=s.body||{};if(!i||!String(i).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const n=R.resolve(String(i).trim()),a=r&&String(r).trim()?String(r).trim():"stash@{0}";await p(n,["checkout","--","."]);const o=await p(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(s,t)=>{await J(s,t,"apply")}),S.post("/git/stash/pop",k,async(s,t)=>{await J(s,t,"pop")}),S.post("/git/stash/drop",k,async(s,t)=>{const{workspacePath:e,ref:i}=s.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const r=R.resolve(String(e).trim()),n=i&&String(i).trim()?String(i).trim():"stash@{0}",a=await p(r,["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:r})}catch(r){const n=r;t.status(500).json({error:n.message||"drop stash failed"})}}),S.post("/git/diff",k,async(s,t)=>{const{workspacePath:e,repositoryPath:i}=s.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const r=await b(String(i||e).trim());if(!r.hasRepository||!r.repositoryRootPath){t.json({ok:!0,isGitRepo:r.isGitRepo,hasRepository:!1,usingParentGitRepo:!1,hasChanges:!1,files:[],workspacePath:r.workspacePath,repositoryRootPath:null,relativeWorkspacePath:""});return}const n=await p(r.repositoryRootPath,A(["diff","--name-status"],r));if(n.exitCode!==0){t.status(400).json({error:n.stderr||"git diff failed"});return}const a=await p(r.repositoryRootPath,A(["diff","--numstat"],r));if(a.exitCode!==0){t.status(400).json({error:a.stderr||"git diff failed"});return}const o=await p(r.repositoryRootPath,A(["diff","--cached","--name-status"],r));if(o.exitCode!==0){t.status(400).json({error:o.stderr||"git staged diff failed"});return}const c=await p(r.repositoryRootPath,A(["diff","--cached","--numstat"],r));if(c.exitCode!==0){t.status(400).json({error:c.stderr||"git staged diff failed"});return}const u=await p(r.repositoryRootPath,A(["status","--porcelain=v1"],r));if(u.exitCode!==0){t.status(400).json({error:u.stderr||"git status failed"});return}const f=mt(u.stdout),l=wt(N(n.stdout,a.stdout,f),N(o.stdout,c.stdout,f,!0)),d=gt(u.stdout);if(d.length>0){const h=new Set(l.map(y=>y.path)),m=await Pt(r.repositoryRootPath,d,h);l.push(...m)}t.json({ok:!0,isGitRepo:r.isGitRepo,hasRepository:!0,usingParentGitRepo:r.usingParentGitRepo,hasChanges:l.length>0,files:l,workspacePath:r.workspacePath,repositoryRootPath:r.repositoryRootPath,relativeWorkspacePath:r.relativeWorkspacePath})}catch(r){const n=r;t.status(500).json({error:n.message||"git diff failed"})}});async function K(s){const t=[],e=await p(s,["config","user.name"]);(e.exitCode!==0||!e.stdout.trim())&&t.push("-c","user.name=AgentsWork");const i=await p(s,["config","user.email"]);return(i.exitCode!==0||!i.stdout.trim())&&t.push("-c","user.email=agent@agentswork.com"),t}async function Ct(s,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}`),r=await p(s,["add","--",...i]);if(r.exitCode!==0)throw new Error(r.stderr||"git add failed");const n=await p(s,["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 K(s),o=await p(s,[...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(s,t)=>{const{workspacePath:e,repositoryPath:i,message:r,filePaths:n}=s.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}const a=kt(r);if(!a){t.status(400).json({error:"commit message is required"});return}try{const o=await b(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(m=>x(String(m||"").trim())).filter(Boolean):[];if(c.length>0){const m=c.map(P=>G(P,o)),y=[];for(const P of m)if(P)try{F(P),y.push(P)}catch{t.status(400).json({error:`unsafe file path: ${P}`});return}if(y.length===0){t.status(400).json({error:"filePaths must be valid workspace-relative paths"});return}try{const P=await Ct(o.repositoryRootPath,a,y);t.json({ok:!0,message:"\u63D0\u4EA4\u5B8C\u6210",committedPaths:c,output:P.output,workspacePath:o.workspacePath,repositoryRootPath:o.repositoryRootPath,relativeWorkspacePath:o.relativeWorkspacePath})}catch(P){const w=P;t.status(400).json({error:w.message||"git commit failed"})}return}const u=await K(o.repositoryRootPath),f=St(o),l=await p(o.repositoryRootPath,["add","--all",...f]);if(l.exitCode!==0){t.status(400).json({error:l.stderr||"git add failed"});return}const d=await p(o.repositoryRootPath,["diff","--cached","--quiet",...f]);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 h=await p(o.repositoryRootPath,[...u,"commit","-m",a,...f]);if(h.exitCode!==0){t.status(400).json({error:h.stderr||h.stdout||"git commit failed"});return}t.json({ok:!0,message:"\u63D0\u4EA4\u5B8C\u6210",output:h.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(s,t)=>{const{workspacePath:e,repositoryPath:i,filePath:r}=s.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}const n=x(String(r||"").trim());if(!n){t.status(400).json({error:"filePath is required"});return}try{const a=await b(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 p(a.repositoryRootPath,["add",...E(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(s,t)=>{const{workspacePath:e,repositoryPath:i,filePaths:r}=s.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}const a=(Array.isArray(r)?r:[]).map(o=>x(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 b(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=>E(d,o)),u=["add"],f=[];for(let d=0;d<c.length;d++)c[d]!=="--"&&f.push(c[d]);u.push("--",...f);const l=await p(o.repositoryRootPath,u);if(l.exitCode!==0){t.status(400).json({error:l.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(s,t)=>{const{workspacePath:e,repositoryPath:i,limit:r,branch:n}=s.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const a=await b(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(r),c=!!s.body?.all,u=c?200:100,f=Number.isInteger(o)&&o>0&&o<=u?o:c?100:30,l=await B(a.repositoryRootPath),h=(n&&String(n).trim()?String(n).trim():null)||l||"HEAD",m="%H%x00%h%x00%an%x00%ad%x00%s%x00%P%x00%b%x00";let y;if(c?y=await p(a.repositoryRootPath,["log",`-${f}`,"--all","--topo-order","--date=iso-strict",`--pretty=format:${m}`]):y=await p(a.repositoryRootPath,["log",`-${f}`,"--date=iso-strict",`--pretty=format:${m}`,"--end-of-options",h,"--",a.relativeWorkspacePath||"."]),y.exitCode!==0){t.status(400).json({error:y.stderr||"git log failed"});return}const P=await xt(a.repositoryRootPath),w=Rt(y.stdout).map(v=>({...v,isRemote:P.has(v.hash)})),j=c?await U(a.repositoryRootPath):void 0;t.json({ok:!0,commits:w,currentBranch:l,branchName:c?null:h,...j?{branches:j}:{},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(s,t)=>{const{workspacePath:e,repositoryPath:i}=s.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const r=await b(String(i||e).trim());if(!r.hasRepository||!r.repositoryRootPath){t.json({ok:!0,branches:[],currentBranch:"",workspacePath:r.workspacePath,repositoryRootPath:null});return}const n=await U(r.repositoryRootPath),a=await B(r.repositoryRootPath);t.json({ok:!0,branches:n,currentBranch:a,workspacePath:r.workspacePath,repositoryRootPath:r.repositoryRootPath,relativeWorkspacePath:r.relativeWorkspacePath})}catch(r){const n=r;t.status(500).json({error:n.message||"git branches failed"})}}),S.post("/git/push",k,async(s,t)=>{const{workspacePath:e,repositoryPath:i}=s.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const r=await b(String(i||e).trim());if(!r.hasRepository||!r.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}const n=await bt(r.repositoryRootPath);t.json({ok:!0,branch:n.branch,pushed:n.pushed,message:n.message,output:n.output,workspacePath:r.workspacePath,repositoryRootPath:r.repositoryRootPath,relativeWorkspacePath:r.relativeWorkspacePath})}catch(r){const n=r;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"})}}),S.post("/git/commit-diff",k,async(s,t)=>{const{workspacePath:e,repositoryPath:i,commitHash:r,aggregate:n}=s.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}const a=q(r);if(!a){t.status(400).json({error:"commitHash is required"});return}const o=n===!0||n==="true";try{const c=await b(String(i||e).trim());if(!c.hasRepository||!c.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}const u=jt(a,o),f=await p(c.repositoryRootPath,A([...u,"--name-status"],c));if(f.exitCode!==0){t.status(400).json({error:f.stderr||"git commit diff failed"});return}const l=await p(c.repositoryRootPath,A([...u,"--numstat"],c));if(l.exitCode!==0){t.status(400).json({error:l.stderr||"git commit diff failed"});return}const d=N(f.stdout,l.stdout);t.json({ok:!0,commitHash:a,isGitRepo:c.isGitRepo,hasRepository:!0,usingParentGitRepo:c.usingParentGitRepo,hasChanges:d.length>0,files:d,workspacePath:c.workspacePath,repositoryRootPath:c.repositoryRootPath,relativeWorkspacePath:c.relativeWorkspacePath})}catch(c){const u=c;t.status(500).json({error:u.message||"git commit diff failed"})}}),S.post("/git/diff-file",k,async(s,t)=>{const{workspacePath:e,filePath:i,commitHash:r,aggregate:n}=s.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}if(!i||!String(i).trim()){t.status(400).json({error:"filePath is required"});return}try{const a=await b(String(e).trim()),o=x(String(i).trim());if(!a.hasRepository||!a.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}const c=G(o,a),u=q(r),f=!!(u&&(n===!0||n==="true")),l=!u||f,d=u?f?["diff",u,"--",c]:["show","--format=","--no-ext-diff",u,"--",c]:["diff","HEAD","--",c];let h=await p(a.repositoryRootPath,d);if(l&&h.exitCode!==0&&(h=await p(a.repositoryRootPath,["diff","--",c])),h.exitCode!==0){t.status(400).json({error:h.stderr||"git diff file failed"});return}if(!u&&!h.stdout.trim()){const y=await p(a.repositoryRootPath,["diff","--cached","--",c]);if(y.exitCode!==0){t.status(400).json({error:y.stderr||"git staged diff file failed"});return}h=y}if(l&&!h.stdout.trim()){const y=R.join(a.repositoryRootPath,c);try{const P=await C.readFile(y,"utf-8");if(P){const w=P.split(`
|
|
1
|
+
import{spawn as st}from"node:child_process";import{promises as b}from"node:fs";import ot from"node:os";import R from"node:path";import L from"axios";import{Router as it}from"express";import{validateToken as k}from"../middleware/auth.js";import{logger as m}from"../utils/logger.js";const S=it(),at=1e4;function nt(){const r=Number(process.env.AWS_RUNTIME_GIT_COMMAND_TIMEOUT_MS);return Number.isFinite(r)&&r>=1e3?r:at}async function ct(r){let t;try{t=await b.stat(r)}catch(e){const i=e;throw i.code==="ENOENT"?new Error(`workspace path does not exist: ${r}`):new Error(`workspace path is not accessible: ${r}${i.message?` (${i.message})`:""}`)}if(!t.isDirectory())throw new Error(`workspace path is not a directory: ${r}`)}const ut=/^[0-9a-f]{40}$/i;function lt(r,t,e,i){return r==="added"||r==="deleted"||r==="renamed"?!0:i||t>0||e>0}function ft(r){const t=String(r||"").toLowerCase();return t.includes("you do not have the initial commit yet")||t.includes("bad revision")&&t.includes("head")||t.includes("ambiguous argument")&&t.includes("head")}function ht(){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 dt(r){const t=String(r||"").toLowerCase();return t.includes("no local changes to save")||t.includes("no changes to save")||t.includes("\u6CA1\u6709\u672C\u5730\u53D8\u66F4\u9700\u8981\u4FDD\u5B58")}function pt(r){const t=String(r||"").trim();return ut.test(t)?`refs/agentswork/stash-snapshots/${t.toLowerCase()}`:null}function _(r){const t=String(r||"").trim();if(!t)return null;const e=t.split("\0");if(e.length>=3){const a=e[0]?.trim(),o=e[1]?.trim(),c=e[2]?.trim()||o,u=e[3]?.trim()||null;if(!a||!o)return null;const f=O(c||"");return{ref:a,stashName:o,message:f.message,branch:f.branch,createdAt:u}}const i=t.match(/^(stash@\{\d+\})(?::\s*)?(.*)$/);if(!i)return null;const s=i[1],n=O(i[2]?.trim()||s);return{ref:s,stashName:s,message:n.message,branch:n.branch,createdAt:null}}function O(r){const t=String(r||"").trim(),e=t.match(/^(?:On|WIP on)\s+([^:]+):\s*(.*)$/);return e?{branch:e[1].trim(),message:e[2].trim()||t}:{branch:"",message:t}}function I(r){const t=String(r||"").split(`
|
|
2
|
+
`).find(i=>i.trim());if(!t)return null;const e=_(t);return e?{ref:e.ref,stashName:e.stashName||e.ref,message:e.message||e.ref}:null}async function h(r,t,e){return new Promise(i=>{let s=!1,n=null;const a=l=>{s||(s=!0,n&&clearTimeout(n),i(l))},o=st("git",t,{cwd:r,stdio:["ignore","pipe","pipe"]});let c="",u="";o.stdout?.on("data",l=>{c+=String(l||"")}),o.stderr?.on("data",l=>{u+=String(l||"")});const f=e??nt();n=setTimeout(()=>{m.warn(`[runtime-bridge] git command timed out after ${f}ms: git ${t.join(" ")} (cwd=${r})`),o.kill("SIGKILL"),a({stdout:c,stderr:u||`git command timed out after ${f}ms`,exitCode:124})},f),o.on("error",l=>{a({stdout:"",stderr:l.message,exitCode:1})}),o.on("close",l=>{a({stdout:c,stderr:u,exitCode:l??1})})})}const mt=6e4;function gt(){const r=Number(process.env.AWS_RUNTIME_GIT_PUSH_TIMEOUT_MS);return Number.isFinite(r)&&r>=1e3?r:mt}async function z(r,t){const e=pt(t);if(!e)return null;const i=await h(r,["update-ref",e,t]);return i.exitCode!==0?(m.warn(`[Git] \u6301\u4E45\u5316 stash \u5FEB\u7167 ref \u5931\u8D25: ref=${e}, error=${i.stderr||i.stdout}`),null):e}function x(r){return String(r||"").replace(/\\/g,"/").replace(/^\.\//,"").trim()}async function Yt(r){const t=R.join(r,".git");try{const e=await b.stat(t);return e.isDirectory()||e.isFile()}catch{return!1}}async function j(r){const t=R.resolve(String(r).trim());await ct(t);const e=await h(t,["rev-parse","--show-toplevel"]);if(e.exitCode!==0)return{workspacePath:t,repositoryRootPath:null,hasRepository:!1,isGitRepo:!1,usingParentGitRepo:!1,relativeWorkspacePath:""};const i=R.resolve(e.stdout.trim()),s=i===t,n=s?"":x(R.relative(i,t));return{workspacePath:t,repositoryRootPath:i,hasRepository:!0,isGitRepo:s,usingParentGitRepo:!s,relativeWorkspacePath:n}}function A(r,t){if(!t.relativeWorkspacePath)return[...r];const e=!r.includes("status");return[...r,...e?[`--relative=${t.relativeWorkspacePath}`]:[],"--",t.relativeWorkspacePath]}function E(r,t){const e=x(r);return t.relativeWorkspacePath?x(R.posix.join(t.relativeWorkspacePath,e)):e}function M(r){const t=x(r);if(R.posix.isAbsolute(t)||t.split("/").some(e=>e===".."||e==="."))throw new Error(`unsafe repository relative path: ${r}`)}function yt(r){const t=String(r||"").trim().toUpperCase();return t.startsWith("A")?"added":t.startsWith("D")?"deleted":t.startsWith("R")?"renamed":"modified"}function F(r){const t=x(r);if(!t.includes("=>"))return t;const e=t.match(/^(.*)\{([^{}]+)=>([^{}]+)\}(.*)$/);if(e){const s=e[1],n=e[3],a=e[4];return x(`${s}${n}${a}`)}const i=t.lastIndexOf("=>");return i>=0?x(t.slice(i+2)):t}function wt(r){const t=new Set,e=String(r||"").split(`
|
|
3
|
+
`).filter(i=>i.trim());for(const i of e){const s=i[0]||" ";if(s===" "||s==="?")continue;const n=i.slice(3).trim(),a=n.split(" -> "),o=F(a[a.length-1]||n);o&&t.add(o)}return t}function H(r,t,e=new Set,i=!1){const s=new Map,n=r.split(`
|
|
4
|
+
`).filter(c=>c.trim());for(const c of n){const u=c.split(" ");if(u.length<2)continue;const f=yt(u[0]),l=f==="renamed"&&u.length>=3?2:1,d=F(u[l]);d&&s.set(d,f)}const a=[],o=t.split(`
|
|
5
|
+
`).filter(c=>c.trim());for(const c of o){const u=c.split(" ");if(u.length<3)continue;const f=u[0]==="-"||u[1]==="-",l=u[0]==="-"?0:parseInt(u[0],10)||0,d=u[1]==="-"?0:parseInt(u[1],10)||0,p=F(u.slice(2).join(" ").trim()),g=s.get(p)||"modified";!p||!lt(g,l,d,f)||a.push({path:p,status:g,additions:l,deletions:d,staged:i||e.has(p)})}return a}function Pt(r){const t=[],e=String(r||"").split(`
|
|
6
|
+
`).filter(i=>i.trim());for(const i of e)if(i.startsWith("?? ")){const s=i.slice(3).trim();s&&t.push(s.replace(/\/$/,""))}return t}function kt(r){if(!r||r.includes("\0"))return 0;let t=0;for(let e=0;e<r.length;e++)r.charCodeAt(e)===10&&t++;return r.endsWith(`
|
|
7
|
+
`)?t:t+1}async function q(r){try{const t=await b.readFile(r,"utf8");return kt(t)}catch{return 0}}async function St(r,t,e,i=10){const s=[];async function n(a,o){if(o>i)return;const c=R.join(r,a);let u;try{u=await b.readdir(c,{withFileTypes:!0})}catch{return}for(const f of u){if(f.name===".git"||f.name==="node_modules")continue;const l=a?`${a}/${f.name}`:f.name;if(f.isDirectory())await n(l,o+1);else if(f.isFile()){const d=x(l);d&&!e.has(d)&&s.push({path:d,status:"added",additions:await q(R.join(c,f.name)),deletions:0,staged:!1})}}}for(const a of t){const o=x(a);if(!o)continue;const c=R.join(r,a);let u;try{u=await b.stat(c)}catch{continue}u.isFile()?e.has(o)||s.push({path:o,status:"added",additions:await q(c),deletions:0,staged:!1}):u.isDirectory()&&await n(o,0)}return s}function Rt(r,t){const e=new Map;for(const i of r)e.set(i.path,{...i});for(const i of t){const s=e.get(i.path);if(!s){e.set(i.path,{...i,staged:!0});continue}e.set(i.path,{...s,status:s.status==="modified"?i.status:s.status,additions:s.additions+i.additions,deletions:s.deletions+i.deletions,staged:!0})}return[...e.values()]}function B(r){return String(r||"").trim()}function xt(r){return String(r||"").trim()}function jt(r){return["--",r.relativeWorkspacePath||"."]}function G(r,t){return["--",E(r,t)]}function Ct(r){const t=r.split("\0"),e=[];for(let i=0;i+6<t.length;i+=7){const s=t[i]?.trim();if(!s)continue;const n=(t[i+5]?.trim()||"").split(/\s+/).filter(Boolean);e.push({hash:s,shortHash:t[i+1]?.trim()||s.slice(0,8),authorName:t[i+2]?.trim()||"unknown",authorDate:t[i+3]?.trim()||"",subject:t[i+4]?.trim()||s,body:t[i+6]?.trim()||"",parents:n})}return e}async function U(r){const t=new Set,e=await h(r,["for-each-ref","--format=%(refname)","refs/remotes"]);if(e.exitCode!==0)return t;const i=e.stdout.split(`
|
|
8
|
+
`).map(n=>n.trim()).filter(Boolean);if(i.length===0)return t;const s=await h(r,["rev-list",...i]);if(s.exitCode!==0)return t;for(const n of s.stdout.split(`
|
|
9
|
+
`)){const a=n.trim();a&&t.add(a)}return t}async function N(r){const t=await h(r,["rev-parse","--abbrev-ref","HEAD"]);return t.exitCode!==0||!t.stdout.trim()?"":t.stdout.trim()}async function J(r){const t=await h(r,["for-each-ref","--format=%(refname)%00%(objectname)%00%(HEAD)%00%(upstream:short)","refs/heads","refs/remotes"]);if(t.exitCode!==0)return[];const e=[];for(const i of t.stdout.split(`
|
|
10
|
+
`)){const s=i.split("\0"),n=s[0]?.trim(),a=s[1]?.trim();if(!n||!a)continue;const o=n.startsWith("refs/remotes/"),c=o?n.slice(13):n.slice(11);e.push({name:c,ref:n,hash:a,isCurrent:s[2]?.trim()==="*",isRemote:o,upstream:s[3]?.trim()||""})}return e}function bt(r,t){return["diff",t?r:`${r}^!`]}async function $t(r){const t=await h(r,["rev-parse","--abbrev-ref","HEAD"]),e=t.stdout.trim();if(t.exitCode!==0||!e||e==="HEAD")throw new Error("\u5F53\u524D\u5904\u4E8E detached HEAD\uFF08\u5206\u79BB\u5934\u6307\u9488\uFF09\u72B6\u6001\uFF0C\u65E0\u6CD5\u63A8\u9001");const i=await h(r,["rev-parse","--abbrev-ref","--symbolic-full-name","@{u}"]);if(i.exitCode!==0||!i.stdout.trim())throw new Error(`\u5206\u652F ${e} \u672A\u914D\u7F6E\u4E0A\u6E38\u5206\u652F\uFF0C\u65E0\u6CD5\u63A8\u9001\u3002\u8BF7\u5148\u624B\u52A8\u6267\u884C\u4E00\u6B21 git push -u origin ${e} \u8BBE\u7F6E\u4E0A\u6E38\u5206\u652F`);const s=await h(r,["push"],gt());if(s.exitCode!==0)throw new Error(s.stderr||s.stdout||"git push failed");const n=`${s.stdout}
|
|
11
|
+
${s.stderr}`.trim(),a=/everything up-to-date|up to date/i.test(n);return{branch:e,pushed:!a,message:a?"\u6CA1\u6709\u9700\u8981\u63A8\u9001\u7684\u63D0\u4EA4":"\u63A8\u9001\u5B8C\u6210",output:n}}S.post("/git/check",k,async(r,t)=>{const{workspacePath:e}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const i=await j(String(e).trim());t.json({ok:!0,isGitRepo:i.isGitRepo,hasRepository:i.hasRepository,usingParentGitRepo:i.usingParentGitRepo,workspacePath:i.workspacePath,repositoryRootPath:i.repositoryRootPath,relativeWorkspacePath:i.relativeWorkspacePath});return}catch(i){const s=i;t.status(500).json({error:s.message||"check git repo failed"})}});async function vt(r,t){const e=`\u5FEB\u7167\uFF08${new Date().toLocaleString("zh-CN",{year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit"})}\uFF09`,i=t&&String(t).trim()?`\u5FEB\u7167: ${String(t).trim()}`:e,s=await h(r,["stash","push","-m",i]);if(dt(`${s.stdout}
|
|
12
|
+
${s.stderr}`)){const u=await h(r,["stash","list","-n","1","--format=%H%x00%gd%x00%gs%x00%cr"]),f=u.exitCode===0?I(u.stdout):null;return{stashRef:(f?await z(r,f.ref):null)??f?.ref??null,stashName:f?.stashName??null,noChanges:!0,reusedLatestStash:!!f,message:f?`\u6CA1\u6709\u672C\u5730\u53D8\u66F4\u9700\u8981\u4FDD\u5B58\uFF0C\u5DF2\u590D\u7528\u6700\u8FD1\u5FEB\u7167 ${f.stashName}`:"\u6CA1\u6709\u672C\u5730\u53D8\u66F4\u9700\u8981\u4FDD\u5B58"}}if(s.exitCode!==0)throw ft(`${s.stdout}
|
|
13
|
+
${s.stderr}`)?new Error(ht()):new Error(s.stderr||"git stash push failed");const n=await h(r,["stash","list","-n","1","--format=%H%x00%gd%x00%gs%x00%cr"]),a=n.exitCode===0?I(n.stdout):null;return{stashRef:(a?await z(r,a.ref):null)??a?.ref??"stash@{0}",stashName:a?.stashName??null,noChanges:!1,reusedLatestStash:!1,message:i}}S.post("/git/stash/create",k,async(r,t)=>{const{workspacePath:e,message:i}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const s=R.resolve(String(e).trim()),n=await vt(s,i?String(i):void 0);t.json({ok:!0,stashRef:n.stashRef,stashName:n.stashName,message:n.message,noChanges:n.noChanges,reusedLatestStash:n.reusedLatestStash,workspacePath:s})}catch(s){const n=s;t.status(400).json({error:n.message||"create stash failed"})}}),S.post("/git/stash/list",k,async(r,t)=>{const{workspacePath:e,limit:i=20}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const s=R.resolve(String(e).trim()),n=Math.min(Math.max(1,Number(i)||20),100),a=await h(s,["stash","list","-n",String(n),"--format=%H%x00%gd%x00%gs%x00%cr"]);if(a.exitCode!==0){t.status(400).json({error:a.stderr||"git stash list failed"});return}const o=a.stdout.split(`
|
|
14
|
+
`).map(_).filter(c=>c!==null);t.json({ok:!0,stashes:o,count:o.length,workspacePath:s})}catch(s){const n=s;t.status(500).json({error:n.message||"list stash failed"})}});async function K(r,t,e){const{workspacePath:i,ref:s}=r.body||{};if(!i||!String(i).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const n=R.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=R.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 f=wt(u.stdout),l=Rt(H(n.stdout,a.stdout,f),H(o.stdout,c.stdout,f,!0)),d=Pt(u.stdout);if(d.length>0){const p=new Set(l.map(y=>y.path)),g=await St(s.repositoryRootPath,d,p);l.push(...g)}t.json({ok:!0,isGitRepo:s.isGitRepo,hasRepository:!0,usingParentGitRepo:s.usingParentGitRepo,hasChanges:l.length>0,files:l,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 At(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=xt(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(g=>x(String(g||"").trim())).filter(Boolean):[];if(c.length>0){const g=c.map(w=>E(w,o)),y=[];for(const w of g)if(w)try{M(w),y.push(w)}catch{t.status(400).json({error:`unsafe file path: ${w}`});return}if(y.length===0){t.status(400).json({error:"filePaths must be valid workspace-relative paths"});return}try{const w=await At(o.repositoryRootPath,a,y);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),f=jt(o),l=await h(o.repositoryRootPath,["add","--all",...f]);if(l.exitCode!==0){t.status(400).json({error:l.stderr||"git add failed"});return}const d=await h(o.repositoryRootPath,["diff","--cached","--quiet",...f]);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,...f]);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=x(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=>x(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"],f=[];for(let d=0;d<c.length;d++)c[d]!=="--"&&f.push(c[d]);u.push("--",...f);const l=await h(o.repositoryRootPath,u);if(l.exitCode!==0){t.status(400).json({error:l.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,f=Number.isInteger(o)&&o>0&&o<=u?o:c?100:30,l=await N(a.repositoryRootPath),p=(n&&String(n).trim()?String(n).trim():null)||l||"HEAD",g="%H%x00%h%x00%an%x00%ad%x00%s%x00%P%x00%b%x00";let y;if(c?y=await h(a.repositoryRootPath,["log",`-${f}`,"--all","--topo-order","--date=iso-strict",`--pretty=format:${g}`]):y=await h(a.repositoryRootPath,["log",`-${f}`,"--date=iso-strict",`--pretty=format:${g}`,"--end-of-options",p,"--",a.relativeWorkspacePath||"."]),y.exitCode!==0){t.status(400).json({error:y.stderr||"git log failed"});return}const w=await U(a.repositoryRootPath),P=Ct(y.stdout).map(v=>({...v,isRemote:w.has(v.hash)})),C=c?await J(a.repositoryRootPath):void 0;t.json({ok:!0,commits:P,currentBranch:l,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 $t(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 Et=/^[0-9a-fA-F]{7,40}$/;function Gt(r){const t=String(r||"").trim();return Et.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 f=await U(r);if(f.size>0){const l=await h(r,["rev-list",`${s}..HEAD`]);if(l.exitCode===0){for(const d of l.stdout.split(`
|
|
15
|
+
`))if(f.has(d.trim())){u=!0;break}}}return{resolvedHash:s,branch:e,resetCount:c,affectsRemote:u}}async function X(r){const t=await h(r,["status","--porcelain"]);return t.exitCode===0&&!!t.stdout.trim()}async function Tt(r,t,e){const i=await V(r,t),s=await X(r);return{mode:e,commitHash:i.resolvedHash,branch:i.branch,resetCount:i.resetCount,hasLocalChanges:s,affectsRemote:i.affectsRemote}}async function Dt(r,t,e,i=""){const s=await V(r,t),n=await X(r),a=e==="hard"?["reset","--hard",s.resolvedHash]:["reset","--soft",s.resolvedHash],o=await h(r,a);if(o.exitCode!==0)throw new Error(o.stderr||o.stdout||"git reset \u6267\u884C\u5931\u8D25");if(e==="hard"){const c=["clean","-fd"];i&&c.push("--",i);const u=await h(r,c);u.exitCode!==0&&m.warn(`[Git] \u786C\u56DE\u9000\u540E\u6E05\u7406\u672A\u8DDF\u8E2A\u6587\u4EF6\u5931\u8D25: cwd=${r}, scope=${i||"(\u6574\u4ED3)"}, error=${u.stderr||u.stdout}`)}return{mode:e,commitHash:s.resolvedHash,branch:s.branch,resetCount:s.resetCount,hasLocalChanges:n,affectsRemote:s.affectsRemote}}S.post("/git/reset",k,async(r,t)=>{const{workspacePath:e,repositoryPath:i,commitHash:s,mode:n,preview:a}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}const o=Gt(s);if(!o){t.status(400).json({error:"commitHash \u683C\u5F0F\u975E\u6CD5\uFF0C\u65E0\u6CD5\u56DE\u9000"});return}const c=String(n||"").trim();if(c!=="soft"&&c!=="hard"){t.status(400).json({error:"mode must be soft or hard"});return}const u=a===!0||a==="true";try{const f=await j(String(i||e).trim());if(!f.hasRepository||!f.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}if(u){const g=await Tt(f.repositoryRootPath,o,c);t.json({ok:!0,preview:!0,...g,workspacePath:f.workspacePath,repositoryRootPath:f.repositoryRootPath,relativeWorkspacePath:f.relativeWorkspacePath});return}const l=await Dt(f.repositoryRootPath,o,c,f.relativeWorkspacePath),d=l.commitHash.slice(0,7),p=l.mode==="hard"?`\u5DF2\u786C\u56DE\u9000\u5230 ${d}\uFF0C\u5171\u56DE\u9000 ${l.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 ${l.resetCount} \u4E2A\u63D0\u4EA4\uFF0C\u5DE5\u4F5C\u533A\u6587\u4EF6\u4FDD\u6301\u4E0D\u53D8`;t.json({ok:!0,...l,message:p,workspacePath:f.workspacePath,repositoryRootPath:f.repositoryRootPath,relativeWorkspacePath:f.relativeWorkspacePath})}catch(f){const l=f;if(l.message&&/回退|快照|detached HEAD|分离头指针|不存在|无需|HEAD/.test(l.message)){t.status(400).json({error:l.message});return}t.status(500).json({error:l.message||"git reset failed"})}}),S.post("/git/commit-diff",k,async(r,t)=>{const{workspacePath:e,repositoryPath:i,commitHash:s,aggregate:n}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}const a=B(s);if(!a){t.status(400).json({error:"commitHash is required"});return}const o=n===!0||n==="true";try{const c=await j(String(i||e).trim());if(!c.hasRepository||!c.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}const u=bt(a,o),f=await h(c.repositoryRootPath,A([...u,"--name-status"],c));if(f.exitCode!==0){t.status(400).json({error:f.stderr||"git commit diff failed"});return}const l=await h(c.repositoryRootPath,A([...u,"--numstat"],c));if(l.exitCode!==0){t.status(400).json({error:l.stderr||"git commit diff failed"});return}const d=H(f.stdout,l.stdout);t.json({ok:!0,commitHash:a,isGitRepo:c.isGitRepo,hasRepository:!0,usingParentGitRepo:c.usingParentGitRepo,hasChanges:d.length>0,files:d,workspacePath:c.workspacePath,repositoryRootPath:c.repositoryRootPath,relativeWorkspacePath:c.relativeWorkspacePath})}catch(c){const u=c;t.status(500).json({error:u.message||"git commit diff failed"})}}),S.post("/git/diff-file",k,async(r,t)=>{const{workspacePath:e,filePath:i,commitHash:s,aggregate:n}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}if(!i||!String(i).trim()){t.status(400).json({error:"filePath is required"});return}try{const a=await j(String(e).trim()),o=x(String(i).trim());if(!a.hasRepository||!a.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}const c=E(o,a),u=B(s),f=!!(u&&(n===!0||n==="true")),l=!u||f,d=u?f?["diff",u,"--",c]:["show","--format=","--no-ext-diff",u,"--",c]:["diff","HEAD","--",c];let p=await h(a.repositoryRootPath,d);if(l&&p.exitCode!==0&&(p=await h(a.repositoryRootPath,["diff","--",c])),p.exitCode!==0){t.status(400).json({error:p.stderr||"git diff file failed"});return}if(!u&&!p.stdout.trim()){const y=await h(a.repositoryRootPath,["diff","--cached","--",c]);if(y.exitCode!==0){t.status(400).json({error:y.stderr||"git staged diff file failed"});return}p=y}if(l&&!p.stdout.trim()){const y=R.join(a.repositoryRootPath,c);try{const w=await b.readFile(y,"utf-8");if(w){const P=w.split(`
|
|
15
16
|
`).map(v=>`+${v}`).join(`
|
|
16
|
-
`),
|
|
17
|
-
`).length;
|
|
18
|
-
`),stderr:"",exitCode:0}}}catch{}}
|
|
19
|
-
`);function
|
|
20
|
-
`)}const
|
|
21
|
-
`)[0]?.trim()??"";if(!t||t.length<3)return!1;const e=(t.match(/[\u4e00-\u9fa5]/g)||[]).length,i=(t.match(/[a-zA-Z]{2,}/g)||[]).length;if(e===0&&i===0)return!1;const
|
|
22
|
-
`).trim();const e=JSON.stringify(
|
|
23
|
-
`)[0]?.trim()??"",o=!!t.match(
|
|
24
|
-
`)[0]?.trim()??"";return n.length>100?(
|
|
25
|
-
${l.stdout.trim()}`):
|
|
17
|
+
`),C=w.split(`
|
|
18
|
+
`).length;p={stdout:[`diff --git a/${c} b/${c}`,"new file mode 100644","--- /dev/null",`+++ b/${c}`,`@@ -0,0 +1,${C} @@`,P].join(`
|
|
19
|
+
`),stderr:"",exitCode:0}}}catch{}}M(c);let g="";if(l){const y=R.join(a.repositoryRootPath,c);try{g=await b.readFile(y,"utf-8")}catch{}}else{const y=await h(a.repositoryRootPath,["show",`${u}:${c}`]);y.exitCode===0&&(g=y.stdout)}t.json({ok:!0,filePath:o,repositoryRelativeFilePath:c,commitHash:u||null,rangeFromCommit:f,diffContent:p.stdout,fileContent:g,workspacePath:a.workspacePath,repositoryRootPath:a.repositoryRootPath,usingParentGitRepo:a.usingParentGitRepo})}catch(a){const o=a;t.status(500).json({error:o.message||"git diff file failed"})}});const Z=["STRICTLY act as a Git commit message generator. NEVER explain, narrate, or describe your task.",'NEVER start your reply with phrases like "\u6211\u4EEC\u88AB\u95EE\u5230", "\u4EE5\u4E0B\u662F", "\u597D\u7684", "Sure", "Here is", "I will", "I can", "Let me", "\u6839\u636E", "\u4E0B\u9762".',"NEVER include any meta-language or thinking out loud. Output ONLY the commit message text.","","Format (Conventional Commits, 1 line, optional body):"," <type>(<scope>): <description>","","Allowed types: feat, fix, refactor, test, docs, style, chore, perf, ci, build, revert.","First line MUST be <= 72 characters. <description> MUST be in Chinese.","Add a body (separated by a blank line) ONLY when the change needs more context."].join(`
|
|
20
|
+
`);function Q(r){return["\u8BF7\u76F4\u63A5\u8F93\u51FA\u4E0A\u8FF0\u683C\u5F0F\u7684\u63D0\u4EA4\u4FE1\u606F,\u4E0D\u8981\u4EFB\u4F55\u989D\u5916\u6587\u5B57\u3002","\u4EE5\u4E0B\u662F\u6682\u5B58\u533A diff:","",r].join(`
|
|
21
|
+
`)}const tt=/\b(?:feat|fix|chore|docs|style|refactor|perf|test|build|ci|revert)(?:\s*\([^)]*\))?\s*:/i,Mt=/^(?:feat|fix|chore|docs|style|refactor|perf|test|build|ci|revert)\s*:/i;function Ft(r){const t=r.split(`
|
|
22
|
+
`)[0]?.trim()??"";if(!t||t.length<3)return!1;const e=(t.match(/[\u4e00-\u9fa5]/g)||[]).length,i=(t.match(/[a-zA-Z]{2,}/g)||[]).length;if(e===0&&i===0)return!1;const s=[/^以下是/,/^上面是/,/^生成的/,/^这是/,/^下面/,/^(?:Here|This|This is|That is)/i];for(const n of s)if(n.test(t))return!1;return e>=2||i>=2}const Ht=[/^\s*(?:我们|我)\s*(?:被\s*)?(?:问[到过]?|需要|要|将|来|先)[^.:\n]{0,60}?(?:提交信息|commit\s*message|[::,,。])/i,/^\s*(?:好的|好)\s*[,,::]/i,/^\s*(?:好的|好)\s*(?:以下是|上面是|提供|生成|给出)[^.:\n]{0,20}?[::]?/i,/^\s*是\s*[::]/i,/^\s*(?:Sure|Here(?:'s|\s+is)|We(?:'re| are)|I\s+(?:will|can|'ll)|Let\s+me|Certainly|Of\s+course)[^.:\n]{0,40}?[,,::.]/i,/^\s*根据\s*(?:您|你|您所|你所|所)?\s*(?:提供|上述|给[我您你])?\s*(?:的)?\s*diff\s*[,,::]?/i,/^\s*(?:以下|下面)[^,,::\n]{0,30}?提交信息\s*[,,::]?/i,/^\s*(?:以下|下面)\s*是\s*[,,::]/i,/^\s*生成\s*(?:一条|一个|了)?\s*(?:符合|新的)?\s*(?:conventional\s*commit|git)?\s*提交信息\s*[,,::]?/i,/^\s*```(?:text|markdown|bash|shell)?\s*\n?/i];function T(r){if(!r)return m.info("[Git/sanitize] \u8F93\u5165\u4E3A\u7A7A, \u8FD4\u56DE fallback"),"chore: update staged changes";let t=r.replace(/\r\n/g,`
|
|
23
|
+
`).trim();const e=JSON.stringify(r.substring(0,120)),i=t.match(tt);if(i&&i.index!==void 0&&i.index<=80)t=t.slice(i.index);else{for(let l=0;l<5;l+=1){const d=t;for(const p of Ht)t=t.replace(p,"");if(t=t.replace(/^[\s*\-`,,']+/,"").trim(),t===d)break}const a=t.split(`
|
|
24
|
+
`)[0]?.trim()??"",o=!!t.match(tt),c=!!t.match(Mt),u=Ft(t),f=a.length>100;if(!o&&!c&&!u&&!f)return m.info(`[Git/sanitize] \u5265\u79BB\u540E\u65E0\u6709\u6548\u5185\u5BB9, \u8FD4\u56DE fallback. raw=${e}, afterStrip=${JSON.stringify(t.substring(0,100))}`),"chore: update staged changes";!o&&(c||u||f)&&(t=a||t)}const s=t.search(/\n\s*\n/);if(s>0&&(t=t.slice(0,s)),t=t.replace(/\n+\s*```\s*$/g,"").trim(),!t)return m.info(`[Git/sanitize] \u622A\u65AD\u540E\u4E3A\u7A7A, \u8FD4\u56DE fallback. raw=${e}`),"chore: update staged changes";const n=t.split(`
|
|
25
|
+
`)[0]?.trim()??"";return n.length>100?(m.info(`[Git/sanitize] \u9996\u884C\u8D85\u8FC7 100 \u5B57\u7B26, \u4EC5\u4FDD\u7559\u9996\u884C. raw=${e}, firstLine=${JSON.stringify(n.substring(0,100))}`),n):(m.info(`[Git/sanitize] \u7ED3\u679C: ${JSON.stringify(t.substring(0,120))} (from raw=${e})`),t)}function et(r){const t=r.split(/\r?\n/);let e="";for(const i of t){const s=i.trim();if(!s.startsWith("data:"))continue;const n=s.slice(5).trim();if(!(!n||n==="[DONE]"))try{const o=JSON.parse(n)?.choices?.[0]?.delta?.content;typeof o=="string"&&o.length>0&&(e+=o)}catch{}}return e}function Nt(r,t){if(t>5)return;const e=r.split(/\r?\n/);for(const i of e){const s=i.trim();if(!s.startsWith("data:"))continue;const n=s.slice(5).trim();if(!(!n||n==="[DONE]"))try{const a=JSON.parse(n),o=Object.keys(a),c=a?.choices,u=Array.isArray(c)?c[0]:null,f=u?.delta?Object.keys(u.delta):null,l=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(f)}, content=${JSON.stringify(l?.substring(0,50))}, finishReason=${JSON.stringify(u?.finish_reason)}`)}catch{}}}async function Wt(r,t,e,i){const s=`${r.replace(/\/+$/,"")}/chat/completions`,o=(await L.post(s,{model:e,stream:!1,messages:[{role:"system",content:Z},{role:"user",content:Q(i)}],max_tokens:200,temperature:.2},{headers:{"Content-Type":"application/json",Authorization:`Bearer ${t}`},timeout:3e4})).data?.choices?.[0]?.message?.content?.trim()||"";return T(o)}async function Lt(r,t,e,i,s,n){const a=`${r.replace(/\/+$/,"")}/chat/completions`,c=(await L.post(a,{model:e,stream:!0,messages:[{role:"system",content:Z},{role:"user",content:Q(i)}],max_tokens:200,temperature:.2},{headers:{"Content-Type":"application/json",Authorization:`Bearer ${t}`,Accept:"text/event-stream"},timeout:Number(process.env.AWS_AI_STREAM_TIMEOUT_MS)||6e4,responseType:"stream",signal:n})).data;let u="",f="",l="",d=0,p=0;return m.info(`[Git/streamAi] AI \u6D41\u5F00\u59CB: aiModel=${e}`),await new Promise((g,y)=>{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)}`),g(P)};c.on("data",P=>{const C=typeof P=="string"?P:P.toString("utf8");d++,l+=C,d<=3&&m.info(`[Git/streamAi] chunk#${d}: length=${C.length}, preview=${JSON.stringify(C.substring(0,80))}`),Nt(C,d);const v=l.split(/\n\n/);l=v.pop()??"";for(const rt of v){const D=et(rt);D&&(p++,u+=D,f=T(u),s(D,f))}}),c.on("end",()=>{if(l.trim()){const P=et(l);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(l.substring(0,100))}`)}w(T(u))}),c.on("error",P=>{m.error(`[Git/streamAi] \u6D41\u9519\u8BEF: ${P.message}`),y(P)}),c.on("aborted",()=>{m.info(`[Git/streamAi] \u6D41\u88AB\u4E2D\u6B62, bufferLength=${u.length}`),w(T(u))})})}async function W(r,t,e){const i=await j(String(r||t).trim());if(!i.hasRepository||!i.repositoryRootPath)return m.warn(`[Git/collectDiff] \u4E0D\u5728 Git \u4ED3\u5E93\u4E2D: workspacePath=${t}`),{truncatedDiff:"",error:{status:400,message:"workspace is not inside a git repository"}};const s=e.map(u=>u.path).filter(Boolean);m.info(`[Git/collectDiff] \u5F00\u59CB\u6536\u96C6 ${s.length} \u4E2A\u6682\u5B58\u6587\u4EF6\u5DEE\u5F02: ${s.join(", ").substring(0,200)}`);const n=[];for(const u of s){const f=E(u,i),l=await h(i.repositoryRootPath,["diff","--cached","--",f]);l.exitCode===0&&l.stdout.trim()?n.push(`--- ${u} ---
|
|
26
|
+
${l.stdout.trim()}`):m.info(`[Git/collectDiff] \u6587\u4EF6\u65E0\u6682\u5B58\u5DEE\u5F02\u6216\u83B7\u53D6\u5931\u8D25: ${u}, exitCode=${l.exitCode}, stdout.length=${l.stdout.length}`)}if(n.length===0){m.info("[Git/collectDiff] \u9010\u6587\u4EF6 diff \u4E3A\u7A7A, \u56DE\u9000\u5230\u5168\u91CF diff --cached");const u=await h(i.repositoryRootPath,["diff","--cached"]);u.exitCode===0&&u.stdout.trim()?(n.push(u.stdout.trim()),m.info(`[Git/collectDiff] \u5168\u91CF diff \u83B7\u53D6\u6210\u529F: length=${u.stdout.trim().length}`)):m.info(`[Git/collectDiff] \u5168\u91CF diff \u4E5F\u4E3A\u7A7A, exitCode=${u.exitCode}`)}if(n.length===0)return m.info("[Git/collectDiff] \u6682\u5B58\u533A\u6CA1\u6709\u5DEE\u5F02\u5185\u5BB9 (diffParts \u4E3A\u7A7A)"),{truncatedDiff:""};const a=n.join(`
|
|
26
27
|
|
|
27
28
|
`),o=15e3,c=a.length>o?`${a.slice(0,o)}
|
|
28
|
-
...(diff truncated due to length)`:a;return
|
|
29
|
-
`),
|
|
29
|
+
...(diff truncated due to length)`:a;return m.info(`[Git/collectDiff] \u6536\u96C6\u5B8C\u6210: diffContent.length=${a.length}, truncated=${c.length}`),{truncatedDiff:c}}function $(r,t,e){try{r.write(`event: ${t}
|
|
30
|
+
`),r.write(`data: ${JSON.stringify(e)}
|
|
30
31
|
|
|
31
|
-
`)}catch{}}S.post("/git/generate-commit-message",k,async(
|
|
32
|
-
`+
|
|
33
|
-
`),e=[];for(let i=0;i<t.length;i++){const
|
|
34
|
-
`)}S.post("/git/revert-hunk",k,async(
|
|
35
|
-
`,"utf-8");const y=await
|
|
32
|
+
`)}catch{}}S.post("/git/generate-commit-message",k,async(r,t)=>{const{workspacePath:e,repositoryPath:i,stagedFiles:s,aiBaseUrl:n,aiApiKey:a,aiModel:o}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}if(!Array.isArray(s)||s.length===0){t.status(400).json({error:"stagedFiles is required and must be a non-empty array"});return}if(!n||!String(n).trim()||!a||!String(a).trim()||!o||!String(o).trim()){t.status(400).json({error:"aiBaseUrl, aiApiKey and aiModel are required"});return}try{const{truncatedDiff:c,error:u}=await W(String(i||""),String(e),s);if(u){t.status(u.status).json({error:u.message});return}if(!c){t.json({message:"\u6682\u5B58\u533A\u6CA1\u6709\u53D8\u66F4\u5185\u5BB9"});return}const f=await Wt(n,a,o,c);t.json({message:f})}catch(c){const u=c;m.error(`[Git] generate commit message failed: ${u.message}`),t.status(500).json({error:u.message||"generate commit message failed"})}}),S.post("/git/generate-commit-message-stream",k,async(r,t)=>{const{workspacePath:e,repositoryPath:i,stagedFiles:s,aiBaseUrl:n,aiApiKey:a,aiModel:o}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}if(!Array.isArray(s)||s.length===0){t.status(400).json({error:"stagedFiles is required and must be a non-empty array"});return}if(!n||!String(n).trim()||!a||!String(a).trim()||!o||!String(o).trim()){t.status(400).json({error:"aiBaseUrl, aiApiKey and aiModel are required"});return}m.info(`[Git/SSE] \u6536\u5230 generate-commit-message-stream \u8BF7\u6C42: workspacePath=${String(e||"").substring(0,40)}, stagedFiles=${s?.length??0}, aiModel=${o}, aiBaseUrl=${n}`),t.status(200),t.setHeader("Content-Type","text/event-stream; charset=utf-8"),t.setHeader("Cache-Control","no-cache, no-transform"),t.setHeader("Connection","keep-alive"),t.setHeader("X-Accel-Buffering","no"),r.socket?.setNoDelay(!0),t.flushHeaders?.();let c=!1,u=null,f=!1;const l={delta:0},d=`${r.ip}:${r.socket?.remotePort??"?"}`;r.on("close",()=>{c||(c=!0,m.info(`[Git/SSE] \u5BA2\u6237\u7AEF\u65AD\u5F00\u8FDE\u63A5: workspacePath=${String(e||"").substring(0,40)}, deltaEventsSent=${l.delta}, peer=${d}`))}),$(t,"init",{status:"generating"});try{const{truncatedDiff:p,error:g}=await W(String(i||""),String(e),s);if(g){m.warn(`[Git/SSE] collectStagedDiffForAi \u8FD4\u56DE\u9519\u8BEF: ${g.message}`),$(t,"error",{message:g.message}),t.end();return}if(!p){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=${p.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 w=await Lt(n,a,o,p,(P,C)=>{l.delta++,C!==y?(y=C,$(t,"delta",{contentDelta:P,message:C})):$(t,"delta",{contentDelta:P,message:C})},u.signal);m.info(`[Git/SSE] AI streamAiForCommitMessage \u5B8C\u6210: finalMessage=${JSON.stringify(w).substring(0,200)}, deltaEventsSent=${l.delta}`),c||$(t,"done",{message:w}),t.end()}catch(p){const g=p;m.error(`[Git/SSE] stream commit message failed: ${g.message}${g.stack?`
|
|
33
|
+
`+g.stack:""}`),c||$(t,"error",{message:g.message||"generate commit message failed"}),t.end()}}),S.post("/git/unstage-files",k,async(r,t)=>{const{workspacePath:e,repositoryPath:i,filePaths:s}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}const a=(Array.isArray(s)?s:[]).map(o=>x(String(o||"").trim())).filter(o=>o.length>0);if(a.length===0){t.status(400).json({error:"filePaths is required and must be a non-empty array"});return}try{const o=await j(String(i||e).trim());if(!o.hasRepository||!o.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}let c="";for(const u of a){const f=G(u,o),l=await h(o.repositoryRootPath,["restore","--staged",...f]);l.exitCode!==0&&(c=l.stderr||`git restore --staged failed for ${u}`)}if(c){t.status(400).json({error:c});return}t.json({ok:!0,filePaths:a,workspacePath:o.workspacePath,repositoryRootPath:o.repositoryRootPath,relativeWorkspacePath:o.relativeWorkspacePath})}catch(o){const c=o;t.status(500).json({error:c.message||"git unstage files failed"})}}),S.post("/git/restore-files",k,async(r,t)=>{const{workspacePath:e,repositoryPath:i,filePaths:s}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}const a=(Array.isArray(s)?s:[]).map(o=>x(String(o||"").trim())).filter(o=>o.length>0);if(a.length===0){t.status(400).json({error:"filePaths is required and must be a non-empty array"});return}try{const o=await j(String(i||e).trim());if(!o.hasRepository||!o.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}let c="";for(const u of a){const f=E(u,o),l=G(u,o),p=(await h(o.repositoryRootPath,["ls-files","--error-unmatch","--",f])).exitCode===0,g=p?await h(o.repositoryRootPath,["restore",...l]):await h(o.repositoryRootPath,["clean","-f",...l]);g.exitCode!==0&&(c=g.stderr||`${p?"git restore":"git clean -f"} failed for ${u}`)}if(c){t.status(400).json({error:c});return}t.json({ok:!0,filePaths:a,workspacePath:o.workspacePath,repositoryRootPath:o.repositoryRootPath,relativeWorkspacePath:o.relativeWorkspacePath})}catch(o){const c=o;t.status(500).json({error:c.message||"git restore files failed"})}});function _t(r){const t=r.split(`
|
|
34
|
+
`),e=[];for(let i=0;i<t.length;i++){const s=t[i],n=s.endsWith("\r")?"\r":"",o=(n?s.slice(0,-1):s).match(/^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/);if(!o){e.push(s);continue}let c=0,u=0;for(let f=i+1;f<t.length;f++){const l=t[f].replace(/\r$/,"");if(/^diff --git /.test(l)||/^@@ /.test(l))break;l.startsWith("+")?u++:l.startsWith("-")?c++:l.startsWith(" ")&&(c++,u++)}e.push(`@@ -${o[1]},${c} +${o[2]},${u} @@${n}`)}return e.join(`
|
|
35
|
+
`)}S.post("/git/revert-hunk",k,async(r,t)=>{const{workspacePath:e,repositoryPath:i,filePath:s,patch:n}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}if(!s||!String(s).trim()){t.status(400).json({error:"filePath is required"});return}const a=String(n||"").replace(/^\uFEFF/,"").trim();if(!a){t.status(400).json({error:"patch is required"});return}try{const o=await j(String(i||e).trim());if(!o.hasRepository||!o.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}const c=x(String(s).trim()),u=E(c,o);if(M(u),(a.match(/^diff --git /gm)||[]).length!==1){t.status(400).json({error:"patch must contain exactly one file diff"});return}const l=a.match(/^\+\+\+ b\/(.+)$/m),d=l?x(l[1].trim()):"";if(!d||d!==u){t.status(400).json({error:"patch target path does not match filePath"});return}let p=null;try{p=await b.mkdtemp(R.join(ot.tmpdir(),"agentswork-git-revert-"));const g=R.join(p,"revert.patch");await b.writeFile(g,_t(a)+`
|
|
36
|
+
`,"utf-8");const y=await h(o.repositoryRootPath,["apply","--reverse",g]);if(y.exitCode!==0){t.status(400).json({error:y.stderr||"git apply --reverse failed; \u8BE5\u5904\u5185\u5BB9\u53EF\u80FD\u5DF2\u53D8\u5316\uFF0C\u8BF7\u5237\u65B0\u5DEE\u5F02\u540E\u91CD\u8BD5"});return}t.json({ok:!0,filePath:c,repositoryRelativeFilePath:u,workspacePath:o.workspacePath,repositoryRootPath:o.repositoryRootPath})}finally{p&&await b.rm(p,{recursive:!0,force:!0}).catch(()=>{})}}catch(o){const c=o;t.status(500).json({error:c.message||"git revert hunk failed"})}}),S.post("/git/diff-content",k,async(r,t)=>{const{workspacePath:e,repositoryPath:i,stagedFiles:s}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}if(!Array.isArray(s)||s.length===0){t.status(400).json({error:"stagedFiles is required and must be a non-empty array"});return}try{const{truncatedDiff:n,error:a}=await W(String(i||""),String(e),s);if(a){t.status(a.status).json({error:a.message});return}t.json({ok:!0,diff:n||""})}catch(n){const a=n;m.error(`[Git/diff-content] \u83B7\u53D6\u6682\u5B58\u533A\u5DEE\u5F02\u5931\u8D25: ${a.message}`),t.status(500).json({error:a.message||"get diff content failed"})}});export{ct as assertGitWorkspacePathAccessible,bt as buildCommitDiffBaseArgs,Wt as callAiForCommitMessage,St as collectUntrackedFiles,At as commitSelectedFiles,kt as countUntrackedFileAdditions,pt as createAgentsWorkStashSnapshotRef,vt as createGitStashSnapshot,ht as createMissingInitialCommitSnapshotError,G as createScopedFilePathspecArgs,jt as createScopedGitPathspecArgs,Nt as debugAiStreamChunk,et as extractOpenAiStreamDelta,S as gitRouter,ft as isGitStashMissingInitialCommitError,dt as isGitStashNoChangesOutput,J as listGitBranches,Rt as mergeGitDiffSummaryFiles,xt as normalizeGitCommitMessage,H as parseGitDiffSummary,_ as parseGitStashListLine,wt as parseGitStatusStagedPaths,Pt as parseGitStatusUntrackedPaths,I as parseLatestGitStash,z as persistAgentsWorkStashSnapshotRef,Tt as previewRepositoryReset,$t as pushBranchToRemote,Dt as resetRepositoryTo,N as resolveCurrentBranchName,nt as resolveGitCommandTimeoutMs,gt as resolveGitPushTimeoutMs,U as resolveRemoteCommitHashes,T as sanitizeCommitMessage,lt as shouldIncludeGitDiffSummaryFile,Lt as streamAiForCommitMessage};
|
|
36
37
|
|