aws-runtime-bridge 1.9.139 → 1.9.140
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter/AcodeSdkAdapter.d.ts +2 -1
- package/dist/adapter/AcodeSdkAdapter.js +2 -2
- package/dist/config.d.ts +22 -6
- package/dist/config.js +2 -2
- package/dist/index.js +1 -1
- package/dist/remote-desktop/computer-control.d.ts +1 -1
- package/dist/remote-desktop/index.js +2 -2
- package/dist/remote-desktop/screen-capture.js +1 -1
- package/dist/remote-desktop/session-manager.js +1 -1
- package/dist/remote-desktop/types.d.ts +1 -1
- package/dist/remote-desktop/ui-automation.d.ts +2 -2
- package/dist/remote-desktop/ui-automation.js +5 -5
- package/dist/routes/ai-sources.js +1 -1
- package/dist/routes/background-tasks.js +5 -5
- package/dist/routes/dashboard.js +1 -1
- package/dist/routes/git.js +20 -20
- package/dist/routes/terminal.js +27 -27
- package/dist/services/agent-capability-sync.js +1 -1
- package/dist/services/ai-config-service.js +2 -2
- package/dist/services/auto-register.js +1 -1
- package/dist/services/aws-client-agent-mcp.d.ts +5 -0
- package/dist/services/aws-client-agent-mcp.js +2 -2
- package/dist/services/browser-session-manager.js +3 -3
- package/dist/services/capability-config.js +2 -2
- package/dist/services/event-delta-pusher.d.ts +1 -1
- package/dist/services/fileSnapshotStore.js +1 -1
- package/dist/services/instance-ai-config-service.js +1 -1
- package/dist/services/instance-init-service.js +1 -1
- package/dist/services/instance-state.d.ts +1 -1
- package/dist/services/instance-state.js +1 -1
- package/dist/services/legacy-path-migration.d.ts +0 -2
- package/dist/services/legacy-path-migration.js +1 -1
- package/dist/services/mcp-deploy.js +2 -2
- package/dist/services/plugin-manager.d.ts +1 -1
- package/dist/services/plugin-manager.js +9 -9
- package/dist/services/skill-manager.js +2 -2
- package/package/acode/dist/cli.js +2 -2
- package/package/acode/dist/runtime.d.ts +2 -0
- package/package/acode/dist/runtime.js +12 -12
- package/package/acode/dist/types.d.ts +2 -0
- package/package.json +1 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{randomUUID as a}from"node:crypto";import{ComputerControl as c}from"./computer-control.js";import{InputDispatcher as h}from"./input-dispatcher.js";import{ScreenCapture as u}from"./screen-capture.js";import{SystemBackend as p}from"./system-backend.js";class o{constructor(s,t,e){this.inputDispatcher=null,this.state="active",this.ws=null,this.onFrame=null,this.focusPollTimer=null,this.lastFocusState=null,this.inputChain=Promise.resolve(),this.id=s,this.agentId=t,this.permissions=e,this.screenCapture=new u,this.inputBackend=new p,this.computerControl=new c(this.screenCapture,this.inputBackend)}async init(){const s=await this.screenCapture.listScreens(),t=s[0];return t&&(this.inputDispatcher=new h(this.inputBackend,this.permissions,t),this.inputBackend.setScreenSize(t.width,t.height),this.inputBackend.setActiveScreen(t)),{screens:s}}async switchScreen(s){this.screenCapture.setActiveScreen(s);const t=this.screenCapture.getScreens(),e=s?t.find(i=>i.id===s):t[0];e&&this.inputDispatcher&&(this.inputDispatcher.setScreen(e),this.inputBackend.setScreenSize(e.width,e.height),this.inputBackend.setActiveScreen(e))}startInputBackend(){this.inputBackend.startPersistentProcess()}async enableControl(){this.inputBackend.startPersistentProcess(),this.startFocusPolling(),await this.inputBackend.centerMouse()}stopInputBackend(){this.stopFocusPolling(),this.inputBackend.stopPersistentProcess()}startFocusPolling(){this.stopFocusPolling(),this.focusPollTimer=setInterval(()=>{const s=this.inputBackend.isInputFocused();s!==this.lastFocusState&&(this.lastFocusState=s,this.sendFocusState(s))},o.FOCUS_POLL_INTERVAL_MS)}stopFocusPolling(){this.focusPollTimer&&(clearInterval(this.focusPollTimer),this.focusPollTimer=null),this.lastFocusState=null}sendFocusState(s){!this.ws||this.ws.readyState!==this.ws.OPEN||this.ws.send(JSON.stringify({type:"remote-desktop:focus-state",sessionId:this.id,data:{hasInputFocus:s}}))}attachWebSocket(s){this.ws=s}startFrameLoop(s){this.onFrame=s,this.screenCapture.startLoop(s)}stopFrameLoop(){this.screenCapture.stopLoop(),this.onFrame=null}handleInput(s){if(!
|
|
1
|
+
import{randomUUID as a}from"node:crypto";import{ComputerControl as c}from"./computer-control.js";import{InputDispatcher as h}from"./input-dispatcher.js";import{ScreenCapture as u}from"./screen-capture.js";import{SystemBackend as p}from"./system-backend.js";class o{constructor(s,t,e){this.inputDispatcher=null,this.state="active",this.ws=null,this.onFrame=null,this.focusPollTimer=null,this.lastFocusState=null,this.inputChain=Promise.resolve(),this.id=s,this.agentId=t,this.permissions=e,this.screenCapture=new u,this.inputBackend=new p,this.computerControl=new c(this.screenCapture,this.inputBackend)}async init(){const s=await this.screenCapture.listScreens(),t=s[0];return t&&(this.inputDispatcher=new h(this.inputBackend,this.permissions,t),this.inputBackend.setScreenSize(t.width,t.height),this.inputBackend.setActiveScreen(t)),{screens:s}}async switchScreen(s){this.screenCapture.setActiveScreen(s);const t=this.screenCapture.getScreens(),e=s?t.find(i=>i.id===s):t[0];e&&this.inputDispatcher&&(this.inputDispatcher.setScreen(e),this.inputBackend.setScreenSize(e.width,e.height),this.inputBackend.setActiveScreen(e))}startInputBackend(){this.inputBackend.startPersistentProcess()}async enableControl(){this.inputBackend.startPersistentProcess(),this.startFocusPolling(),await this.inputBackend.centerMouse()}stopInputBackend(){this.stopFocusPolling(),this.inputBackend.stopPersistentProcess()}startFocusPolling(){this.stopFocusPolling(),this.focusPollTimer=setInterval(()=>{const s=this.inputBackend.isInputFocused();s!==this.lastFocusState&&(this.lastFocusState=s,this.sendFocusState(s))},o.FOCUS_POLL_INTERVAL_MS)}stopFocusPolling(){this.focusPollTimer&&(clearInterval(this.focusPollTimer),this.focusPollTimer=null),this.lastFocusState=null}sendFocusState(s){!this.ws||this.ws.readyState!==this.ws.OPEN||this.ws.send(JSON.stringify({type:"remote-desktop:focus-state",sessionId:this.id,data:{hasInputFocus:s}}))}attachWebSocket(s){this.ws=s}startFrameLoop(s){this.onFrame=s,this.screenCapture.startLoop(s)}stopFrameLoop(){this.screenCapture.stopLoop(),this.onFrame=null}handleInput(s){const t=this.inputDispatcher;if(!t)return Promise.resolve();this.screenCapture.requestFrame();const e=this.inputChain.then(async()=>{await t.handle(s),this.screenCapture.requestFrame()});return this.inputChain=e.catch(()=>{}),e}close(){this.state="closed",this.stopFocusPolling(),this.stopFrameLoop(),this.inputBackend.stopPersistentProcess(),this.ws=null}}o.FOCUS_POLL_INTERVAL_MS=1e3;class P{constructor(s){this.sessions=new Map,this.agentSessions=new Map,this.permissionManager=s}async createSession(s,t){if(!this.permissionManager.validateToken(t))throw new Error("Invalid token");const e=this.agentSessions.get(s);e&&this.closeSession(e);const i=this.permissionManager.resolvePermissions(t),n=a(),r=new o(n,s,i);return await r.init(),this.sessions.set(n,r),this.agentSessions.set(s,n),{sessionId:n,screens:r.screenCapture.getScreens(),permissions:Array.from(i)}}getSession(s){return this.sessions.get(s)}async handleInput(s,t){const e=this.sessions.get(s);!e||e.state!=="active"||await e.handleInput(t)}closeSession(s){const t=this.sessions.get(s);if(t){t.close(),this.sessions.delete(s);for(const[e,i]of this.agentSessions)if(i===s){this.agentSessions.delete(e);break}}}closeAll(){for(const s of this.sessions.values())s.close();this.sessions.clear(),this.agentSessions.clear()}}export{o as RemoteDesktopSession,P as SessionManager};
|
|
2
2
|
|
|
@@ -32,9 +32,9 @@ export declare class UIAutomation {
|
|
|
32
32
|
|
|
33
33
|
getUIElements(pid: number): Promise<UIElement[]>;
|
|
34
34
|
|
|
35
|
-
clickElement(
|
|
35
|
+
clickElement(_elementId: string): Promise<void>;
|
|
36
36
|
|
|
37
|
-
setElementValue(
|
|
37
|
+
setElementValue(_elementId: string, _value: string): Promise<void>;
|
|
38
38
|
private listAppsWindows;
|
|
39
39
|
private parseWindowsApps;
|
|
40
40
|
private getUIElementsWindows;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import{exec as
|
|
2
|
-
`).filter(Boolean).map(o=>{const e=o.split(" "),[s,
|
|
3
|
-
`).filter(Boolean).map((o,e)=>{const s=o.split(" "),[
|
|
1
|
+
import{exec as c}from"node:child_process";import{promisify as y}from"node:util";const p=y(c);function r(l){return typeof l=="number"?l:void 0}class h{async listApplications(){return process.platform==="win32"?this.listAppsWindows():process.platform==="darwin"?this.listAppsMacOS():this.listAppsLinux()}async getUIElements(t){return process.platform==="win32"?this.getUIElementsWindows(t):process.platform==="darwin"?this.getUIElementsMacOS(t):this.getUIElementsLinux(t)}async clickElement(t){throw new Error(`clickElement not yet implemented for ${process.platform}. Use click(x, y) with element bounds.`)}async setElementValue(t,n){throw new Error(`setElementValue not yet implemented for ${process.platform}. Use type(text) with element focus.`)}async listAppsWindows(){const t='powershell.exe -NoProfile -Command "Add-Type -AssemblyName UIAutomationClient, UIAutomationTypes; $root = [System.Windows.Automation.AutomationElement]::RootElement; $cond = New-Object System.Windows.Automation.PropertyCondition([System.Windows.Automation.AutomationElement]::ControlTypeProperty, [System.Windows.Automation.ControlType]::Window); $wins = $root.FindAll([System.Windows.Automation.TreeScope]::Children, $cond); $result = $wins | ForEach-Object { $r = $_.Current.BoundingRectangle; [PSCustomObject]@{ pid=$_.Current.ProcessId; name=$_.Current.ProcessName; title=$_.Current.Name; x=$r.X; y=$r.Y; w=$r.Width; h=$r.Height } }; $result | ConvertTo-Json -Compress"';try{const{stdout:n}=await p(t,{timeout:1e4,maxBuffer:1048576});return this.parseWindowsApps(n)}catch{return[]}}parseWindowsApps(t){try{const n=JSON.parse(t.trim());return(Array.isArray(n)?n:n?[n]:[]).map((e,s)=>({pid:typeof e.pid=="number"?e.pid:0,name:typeof e.name=="string"?e.name:`Process ${String(e.pid??"")}`,isForeground:!1,windowTitle:typeof e.title=="string"?e.title:void 0,windowBounds:typeof e.w=="number"?{x:r(e.x)??0,y:r(e.y)??0,width:e.w,height:r(e.h)??0}:void 0}))}catch{return[]}}async getUIElementsWindows(t){const n=`powershell.exe -NoProfile -Command "Add-Type -AssemblyName UIAutomationClient, UIAutomationTypes; $root = [System.Windows.Automation.AutomationElement]::RootElement; $pidCond = New-Object System.Windows.Automation.PropertyCondition([System.Windows.Automation.AutomationElement]::ProcessIdProperty, ${t}); $app = $root.FindFirst([System.Windows.Automation.TreeScope]::Children, $pidCond); if ($app) { $children = $app.FindAll([System.Windows.Automation.TreeScope]::Descendants, [System.Windows.Automation.Condition]::TrueCondition); $children | ForEach-Object { $r = $_.Current.BoundingRectangle; [PSCustomObject]@{ id=$_.GetRuntimeId(); role=$_.Current.ControlType.ProgrammaticName; name=$_.Current.Name; value=$_.Current.HelpText; x=$r.X; y=$r.Y; w=$r.Width; h=$r.Height } } | ConvertTo-Json -Compress }"`;try{const{stdout:o}=await p(n,{timeout:1e4,maxBuffer:1048576});return this.parseWindowsElements(o)}catch{return[]}}parseWindowsElements(t){try{const n=JSON.parse(t.trim());return(Array.isArray(n)?n:n?[n]:[]).map(e=>({id:String(e.id??""),role:(typeof e.role=="string"?e.role:"").replace("ControlType.",""),name:typeof e.name=="string"?e.name:void 0,value:typeof e.value=="string"?e.value:void 0,bounds:typeof e.w=="number"?{x:r(e.x)??0,y:r(e.y)??0,width:e.w,height:r(e.h)??0}:void 0}))}catch{return[]}}async listAppsMacOS(){const t=`osascript -e 'tell application "System Events" to set appList to (every process whose background only is false)' -e 'set output to ""' -e 'repeat with proc in appList' -e 'set procName to name of proc' -e 'set procPid to unix id of proc' -e 'set winList to every window of proc' -e 'repeat with win in winList' -e 'try' -e 'set winName to name of win' -e 'set {winX, winY, winW, winH} to {position, size} of win' -e 'set output to output & procPid & "\\t" & procName & "\\t" & winName & "\\t" & winX & "," & winY & "," & winW & "," & winH & "\\n"' -e 'end try' -e 'end repeat' -e 'end repeat' -e 'output'`;try{const{stdout:n}=await p(t,{timeout:1e4});return this.parseMacOSApps(n)}catch{return[]}}parseMacOSApps(t){return t.trim().split(`
|
|
2
|
+
`).filter(Boolean).map(o=>{const e=o.split(" "),[s,u,m,d]=e,i=d?d.split(",").map(Number):[];return{pid:Number(s)||0,name:u||"",isForeground:!1,windowTitle:m||void 0,windowBounds:i.length===4?{x:i[0],y:i[1],width:i[2],height:i[3]}:void 0}})}async getUIElementsMacOS(t){const n=`osascript -e 'tell application "System Events"' -e 'set proc to first process whose unix id is ${t}' -e 'set output to ""' -e 'repeat with win in (every window of proc)' -e 'set winName to name of win' -e 'set elemList to every UI element of win' -e 'repeat with elem in elemList' -e 'set eRole to role of elem' -e 'set eName to name of elem' -e 'set eDesc to description of elem' -e 'set {eX, eY} to position of elem' -e 'set {eW, eH} to size of elem' -e 'set output to output & eRole & "\\t" & eName & "\\t" & eDesc & "\\t" & eX & "," & eY & "," & eW & "," & eH & "\\n"' -e 'end repeat' -e 'end repeat' -e 'end tell' -e 'output'`;try{const{stdout:o}=await p(n,{timeout:1e4});return this.parseMacOSElements(o)}catch{return[]}}parseMacOSElements(t){return t.trim().split(`
|
|
3
|
+
`).filter(Boolean).map((o,e)=>{const s=o.split(" "),[u,m,d,i]=s,a=i?i.split(",").map(Number):[];return{id:`mac-${e}`,role:(u||"").replace("AX","").toLowerCase(),name:m||void 0,value:d||void 0,bounds:a.length===4?{x:a[0],y:a[1],width:a[2],height:a[3]}:void 0}})}async listAppsLinux(){const t=`python3 -c "
|
|
4
4
|
import pyatspi, json
|
|
5
5
|
desktop = pyatspi.Registry.getDesktop(0)
|
|
6
6
|
apps = []
|
|
@@ -9,7 +9,7 @@ for i in range(desktop.childCount):
|
|
|
9
9
|
if app:
|
|
10
10
|
apps.append({'pid': app.getApplicationID(), 'name': app.name, 'role': app.getRoleName()})
|
|
11
11
|
print(json.dumps(apps))
|
|
12
|
-
" 2>/dev/null`;try{const{stdout:n}=await
|
|
12
|
+
" 2>/dev/null`;try{const{stdout:n}=await p(t,{timeout:1e4}),o=JSON.parse(n.trim());return(Array.isArray(o)?o:o?[o]:[]).map(e=>({pid:typeof e.pid=="number"?e.pid:0,name:typeof e.name=="string"?e.name:"",isForeground:!1,windowTitle:typeof e.name=="string"?e.name:void 0}))}catch{return[]}}async getUIElementsLinux(t){const n=`python3 -c "
|
|
13
13
|
import pyatspi, json
|
|
14
14
|
desktop = pyatspi.Registry.getDesktop(0)
|
|
15
15
|
elements = []
|
|
@@ -27,5 +27,5 @@ for i in range(desktop.childCount):
|
|
|
27
27
|
'x': b.x, 'y': b.y, 'w': b.width, 'h': b.height
|
|
28
28
|
})
|
|
29
29
|
print(json.dumps(elements))
|
|
30
|
-
" 2>/dev/null`;try{const{stdout:o}=await
|
|
30
|
+
" 2>/dev/null`;try{const{stdout:o}=await p(n,{timeout:1e4}),e=JSON.parse(o.trim());return(Array.isArray(e)?e:e?[e]:[]).map(s=>({id:String(s.id??""),role:typeof s.role=="string"?s.role:"",name:typeof s.name=="string"?s.name:void 0,bounds:typeof s.w=="number"?{x:r(s.x)??0,y:r(s.y)??0,width:s.w,height:r(s.h)??0}:void 0}))}catch{return[]}}}export{h as UIAutomation};
|
|
31
31
|
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import m from"axios";import{Router as U}from"express";import{validateToken as f}from"../middleware/auth.js";import{createAiConfigService as $}from"../services/ai-config-service.js";import{applyCurrentAiConfigToInstalledTools as k}from"../services/ai-source-apply.js";import{detectToolStatuses as z}from"../services/tool-installer.js";import{createLogger as C}from"../utils/logger.js";import{ensureEnabledTools as F}from"../utils/validation.js";const u=C("ai-sources"),g=U();function S(r){if(!r||typeof r!="object"||Array.isArray(r))return[];const o=r,d=Array.isArray(o.data)?o.data:[],e=new Set;for(const a of d){if(!a||typeof a!="object"||Array.isArray(a))continue;const t=String(a.id||"").trim();t&&e.add(t)}return[...e]}async function R(r,o,d){const e=r.replace(/\/+$/,"");if(d==="anthropic-compatible"){const t=`${e.endsWith("/v1")?e.replace(/\/v1$/,""):e}/v1/models`,
|
|
1
|
+
import m from"axios";import{Router as U}from"express";import{validateToken as f}from"../middleware/auth.js";import{createAiConfigService as $}from"../services/ai-config-service.js";import{applyCurrentAiConfigToInstalledTools as k}from"../services/ai-source-apply.js";import{detectToolStatuses as z}from"../services/tool-installer.js";import{createLogger as C}from"../utils/logger.js";import{ensureEnabledTools as F}from"../utils/validation.js";const u=C("ai-sources"),g=U();function S(r){if(!r||typeof r!="object"||Array.isArray(r))return[];const o=r,d=Array.isArray(o.data)?o.data:[],e=new Set;for(const a of d){if(!a||typeof a!="object"||Array.isArray(a))continue;const t=String(a.id||"").trim();t&&e.add(t)}return[...e]}async function R(r,o,d){const e=r.replace(/\/+$/,"");if(d==="anthropic-compatible"){const t=`${e.endsWith("/v1")?e.replace(/\/v1$/,""):e}/v1/models`,s=await m.get(t,{headers:{"x-api-key":o,"anthropic-version":"2023-06-01"},timeout:1e4});return S(s.data)}const a=e.endsWith("/v1")?[`${e}/models`]:[`${e}/models`,`${e}/v1/models`];for(const t of a)try{const s=await m.get(t,{headers:{Authorization:`Bearer ${o}`},timeout:1e4}),i=S(s.data);if(i.length>0)return i}catch{}return[]}g.post("/cc-switch/apply-ai-sources",f,async(r,o)=>{const{agentId:d,template:e,ccSwitchEnabledTools:a,acodeTemplateId:t,claudecodeTemplateId:s,codexTemplateId:i,opencodeTemplateId:n,acodeModelEffort:y,claudecodeModelEffort:T,codexModelEffort:A,opencodeModelEffort:I,acodeSubAgentTemplateId:b,acodeSubAgentModel:h,acodeSubAgentModelEffort:M,acode:E,claudecode:v,codex:w,opencode:j}=r.body||{};if(!d){o.status(400).json({error:"agentId is required"});return}try{const l=$(),c=F(a),x=await z(c),p=await k({template:e,requestedSources:{acode:E,claudecode:v,codex:w,opencode:j},toolStatus:x,aiConfigService:l});o.json({ok:!0,agentId:String(d),acodeTemplateId:String(t||""),claudecodeTemplateId:String(s||""),codexTemplateId:String(i||""),opencodeTemplateId:String(n||""),acodeModelEffort:String(y||""),claudecodeModelEffort:String(T||""),codexModelEffort:String(A||""),opencodeModelEffort:String(I||""),acodeSubAgentTemplateId:String(b||""),acodeSubAgentModel:String(h||""),acodeSubAgentModelEffort:String(M||""),generatedFromUnifiedTemplate:p.generatedFromUnifiedTemplate,applied:p.applied,skippedTools:p.skippedTools})}catch(l){const c=l;u.error("Failed to apply AI sources",{error:c?.message,agentId:String(d||"")}),o.status(400).json({error:c?.message||"apply ai sources failed"})}}),g.post("/cc-switch/list-ai-models",f,async(r,o)=>{const{baseUrl:d,apiKey:e,protocolType:a}=r.body||{},t=String(d||"").trim(),s=String(e||"").trim();if(!t||!s){o.status(400).json({error:"baseUrl and apiKey are required"});return}try{const i=await R(t,s,a);o.json({ok:!0,models:i})}catch(i){const n=i;u.error("Failed to list remote AI models",{error:n?.message,baseUrl:t}),o.status(400).json({error:n?.message||"list ai models failed"})}});export{g as aiSourcesRouter};
|
|
2
2
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import{promises as
|
|
2
|
-
`);return
|
|
3
|
-
`),t.write(`data: ${JSON.stringify(
|
|
1
|
+
import{promises as h}from"node:fs";import y from"node:path";import{pathToFileURL as b}from"node:url";import{Router as j}from"express";import{getAcodeDataHome as B}from"../config.js";import{validateToken as k}from"../middleware/auth.js";import{getAcodeBgManager as I}from"../services/acode-bg-manager.js";import{getReleasedAcodeEntryPath as T}from"../services/acode-package.js";import{createLogger as E}from"../utils/logger.js";import{listPtySessions as _}from"./pty.js";let m=null;function S(){return m||(m=(async()=>{try{const a=T(),t=y.join(y.dirname(a),"background-command-store.js"),r=await import(b(t).href);return r.BackgroundCommandStore?new r.BackgroundCommandStore:(g.warn("[acode-bg-tasks] BackgroundCommandStore \u4E0D\u53EF\u7528,\u78C1\u76D8\u515C\u5E95\u7981\u7528"),null)}catch(a){return g.warn("[acode-bg-tasks] \u52A0\u8F7D BackgroundCommandStore \u5931\u8D25: %s",a?.message),null}})().then(a=>(a||(m=null),a)),m)}const g=E("acode-bg-tasks"),p=j();function L(a){return String(a).replace(/[^a-zA-Z0-9_-]/g,"_")}function v(a){return y.join(B(),"bg-tasks",L(a),"state.json")}p.get("/acode-bg-tasks/:agentId",k,async(a,t)=>{const r=a.params.agentId,i=Array.isArray(r)?r[0]:r;if(!i||!String(i).trim()){t.status(400).json({ok:!1,error:"agentId is required"});return}const o=v(String(i));try{const s=await h.readFile(o,"utf-8"),d=JSON.parse(s),u=(Array.isArray(d.tasks)?d.tasks:[]).map(l=>{const e=l;return{taskId:e.taskId,status:e.status,command:e.commandPreview??e.command,commandPreview:e.commandPreview,cwd:e.cwd,startedAt:e.startedAt,finishedAt:e.finishedAt,exitCode:e.exitCode,signal:e.signal,outputLineCount:e.outputLineCount,outputBytes:e.outputBytes}});t.json({ok:!0,agentId:String(i),updatedAt:typeof d.updatedAt=="number"?d.updatedAt:null,tasks:u})}catch(s){if((typeof s=="object"&&s!==null?Reflect.get(s,"code"):void 0)==="ENOENT"){t.json({ok:!0,agentId:String(i),updatedAt:null,tasks:[]});return}if(s instanceof SyntaxError){g.warn("[acode-bg-tasks] \u6587\u4EF6\u635F\u574F\uFF08JSON \u89E3\u6790\u5931\u8D25\uFF09: %s",o),t.json({ok:!0,agentId:String(i),updatedAt:null,tasks:[]});return}const n=s;g.error("[acode-bg-tasks] \u8BFB\u53D6\u5931\u8D25: %s",n.message),t.status(500).json({ok:!1,error:n.message})}}),p.get("/acode-agent-pty-sessions/:agentId",k,(a,t)=>{const r=a.params.agentId,i=Array.isArray(r)?r[0]:r;if(!i||!String(i).trim()){t.status(400).json({ok:!1,error:"agentId is required"});return}const o=String(i),d=_().filter(n=>n.ownerUserId===o);t.json({ok:!0,agentId:o,sessions:d})});const P=15e3;function x(a){return{taskId:a.taskId,status:a.status,exitCode:a.exitCode,signal:a.signal,startedAt:a.startedAt,finishedAt:a.finishedAt,command:a.command,lines:a.lines,nextSeq:a.nextSeq,totalLines:a.totalLines,totalBytes:a.totalBytes,truncated:a.truncated}}async function A(a,t){let i=null;try{i=await h.open(a,"r")}catch(o){if(o?.code==="ENOENT")return{text:[],fileExists:!1,truncated:!1};throw o}try{const s=(await i.stat()).size;if(s===0)return{text:[],fileExists:!0,truncated:!1};const d=Math.min(s,2097152),n=s-d,u=Buffer.alloc(d);await i.read(u,0,d,n);const e=u.toString("utf-8").split(`
|
|
2
|
+
`);return n>0&&e.shift(),{text:e.slice(-t),fileExists:!0,truncated:d<s}}finally{await i.close()}}p.get("/acode-bg-tasks/:agentId/:taskId/output",k,(a,t)=>{(async()=>{const r=String(a.params.agentId),i=String(a.params.taskId),o=Math.min(2e3,Math.max(100,Number(a.query.tailLines)||500));try{const s=await I();if(s){const d=s.getTaskOutputSnapshot(r,i);if(d){t.json({ok:!0,source:"memory",...x(d)});return}}}catch(s){g.warn("[acode-bg-tasks] \u5185\u5B58\u5FEB\u7167\u8BFB\u53D6\u5931\u8D25,\u964D\u7EA7\u8D70\u78C1\u76D8: %s",s?.message)}try{const s=await S();if(!s){t.status(503).json({ok:!1,error:"store unavailable"});return}const d=await s.readSession(r);if(!d){t.status(404).json({ok:!1,error:"task_not_found"});return}const n=d.tasks.find(f=>f.taskId===i);if(!n){t.status(404).json({ok:!1,error:"task_not_found"});return}const{text:u,fileExists:l,truncated:e}=await A(n.outputPath,o);t.json({ok:!0,source:"file",taskId:n.taskId,status:n.status,exitCode:n.exitCode,signal:n.signal,startedAt:n.startedAt,finishedAt:n.finishedAt,command:n.command,lines:u,nextSeq:0,totalLines:n.outputLineCount,totalBytes:n.outputBytes,truncated:e||!l})}catch(s){g.error("[acode-bg-tasks] \u78C1\u76D8\u515C\u5E95\u8BFB\u53D6\u5931\u8D25: %s",s?.message),t.status(500).json({ok:!1,error:"internal_error"})}})().catch(r=>{g.error("[acode-bg-tasks] output \u8BFB\u53D6\u5931\u8D25: %s",r?.message),t.status(500).json({ok:!1,error:"internal_error"})})}),p.get("/acode-bg-tasks/:agentId/:taskId/stream",k,(a,t)=>{(async()=>{const r=String(a.params.agentId),i=String(a.params.taskId);t.status(200),t.set({"Content-Type":"text/event-stream","Cache-Control":"no-cache, no-transform",Connection:"keep-alive","X-Accel-Buffering":"no"}),t.flushHeaders?.();const o=(u,l)=>{t.writableEnded||t.destroyed||(l!==void 0&&t.write(`id: ${l}
|
|
3
|
+
`),t.write(`data: ${JSON.stringify(u)}
|
|
4
4
|
|
|
5
|
-
`))};let
|
|
5
|
+
`))};let s,d=null,n=null;try{s=await I(),s&&(d=s,n=s.getTaskOutputSnapshot(r,i))}catch(u){g.warn("[acode-bg-tasks] \u5185\u5B58\u8BFB\u53D6\u5931\u8D25,\u964D\u7EA7\u8D70\u78C1\u76D8: %s",u?.message)}if(n){if(!d){o({type:"done",status:"killed",exitCode:null,signal:null}),t.end();return}if(o({type:"snapshot",source:"memory",...x(n)}),n.status!=="running"){o({type:"done",status:n.status,exitCode:n.exitCode,signal:n.signal}),t.end();return}let u=null,l=!1;const e=()=>{l||(l=!0,u?.(),clearInterval(f))},f=setInterval(()=>{if(t.writableEnded||t.destroyed){e();return}try{t.write(`: ping
|
|
6
6
|
|
|
7
|
-
`)}catch{
|
|
7
|
+
`)}catch{e()}},P);if(f.unref?.(),u=d.subscribeTaskOutput(r,i,{onLine:c=>o({type:"output",stream:c.stream,text:c.text,seq:c.seq},c.seq),onDone:c=>{o({type:"done",status:c.status,exitCode:c.exitCode,signal:c.signal}),e(),t.end()}}),!u){o({type:"done",status:n.status,exitCode:n.exitCode,signal:n.signal}),e(),t.end();return}a.on("close",()=>{e(),t.writableEnded||t.end()}),a.on("aborted",()=>e());return}try{const u=await S();if(!u){o({type:"done",status:"killed",exitCode:null,signal:null}),t.end();return}const l=await u.readSession(r);if(!l){o({type:"done",status:"killed",exitCode:null,signal:null}),t.end();return}const e=l.tasks.find(C=>C.taskId===i);if(!e){o({type:"done",status:"killed",exitCode:null,signal:null}),t.end();return}const{text:f,fileExists:c,truncated:w}=await A(e.outputPath,500);o({type:"snapshot",source:"file",taskId:e.taskId,status:e.status,exitCode:e.exitCode,signal:e.signal,startedAt:e.startedAt,finishedAt:e.finishedAt,command:e.command,lines:f,nextSeq:0,totalLines:e.outputLineCount,totalBytes:e.outputBytes,truncated:w||!c}),o({type:"done",status:e.status,exitCode:e.exitCode,signal:e.signal}),t.end()}catch(u){g.error("[acode-bg-tasks] \u78C1\u76D8\u515C\u5E95 stream \u5931\u8D25: %s",u?.message),t.headersSent?t.end():t.status(500).json({ok:!1,error:"internal_error"})}})().catch(r=>{g.error("[acode-bg-tasks] stream \u5EFA\u7ACB\u5931\u8D25: %s",r?.message),t.headersSent?t.end():t.status(500).json({ok:!1,error:"internal_error"})})});export{p as backgroundTasksRouter};
|
|
8
8
|
|
package/dist/routes/dashboard.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{execFile as $}from"node:child_process";import v from"node:fs";import I from"node:os";import K from"node:path";import{Router as Y}from"express";import{getBridgeVersion as Q,getRuntimeHomeDir as X,getUserHomeDir as Z}from"../config.js";import{getAccessControlEntries as ee,isValidIpEntry as te,parseAllowIps as ne,refreshAccessControlCache as se}from"../services/access-control.js";import{getAgentProcessManager as C}from"../services/agent-process-manager.js";import{autoRegister as re,getRegistrationState as H,getAutoRegisterConfigFilePath as B}from"../services/auto-register.js";import{createSession as ae,getPanelCredentials as N,validateSession as U,destroySession as O,validateCredentials as z,hasConfiguredCredentials as E,isDefaultCredentials as oe,setPanelCredentials as ie,getAllSessions as de,getActiveSessionCount as le,checkLoginAllowed as ce,recordLoginFailure as ue,recordLoginSuccess as ge}from"../services/panel-auth.js";import{getRuntimeBindingPublicState as F,getRuntimePairingCode as pe,normalizeSchedulerBaseUrl as fe}from"../services/runtime-binding.js";import{collectSystemMetrics as me}from"../services/system-metrics.js";import{loadPersistedSessions as be}from"../services/terminal-persistence.js";import{runExclusiveConfigWrite as _}from"../utils/config-write-queue.js";import{createLogger as he,getRecentLogs as ve}from"../utils/logger.js";import{sdkSessions as ye}from"./terminal.js";const x=he("dashboard");let T=null;const xe=1e4;let L=null,P=null;const we=3e4;async function D(){me().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=Q();async function Se(){const t=await import("node:fs/promises"),n=(await import("node:path")).join(X(),".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=Y(),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=ce(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(!z(String(i),String(r))){ue(n),e.status(401).json({ok:!1,error:"invalid credentials"});return}ge(n);const a=ae(),o=!E();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&&O(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:oe(),configured:E(),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(E()){if(!r){e.status(400).json({ok:!1,error:"old password required"});return}const o=N();if(!z(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 ie(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 fe(t)??String(t||"").trim()}g.get("/runtime/server-config",(t,e)=>{if(!m(t,e))return;const n=B();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=B();await _(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:ee(),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=ne(n),i=s.filter(r=>!te(r));if(i.length>0){e.status(400).json({ok:!1,error:`invalid IP/CIDR entries: ${i.join(", ")}`});return}try{const r=B();await _(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"),se()}),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=F(),s=H();e.json({ok:!0,hostname:I.hostname(),platform:I.platform(),arch:I.arch(),uptime:Math.floor(process.uptime()),nodeVersion:process.version,bridgeVersion:V,configPath:B(),homeDir:Z(),bindingStatus:n.status,paired:n.paired,instanceId:n.instanceId||s.instanceId,schedulerBaseUrl:n.schedulerBaseUrl,registered:s.registered,pairingCode:n.pairingCode,activeSessions:le()})}),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=ve(s,r);e.json({ok:!0,logs:a})}),g.post("/runtime/re-register",async(t,e)=>{if(m(t,e))try{const n=await re();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)&&(O(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)];$(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(F()),Promise.resolve(H()),be().catch(()=>[])]),l=C().getAllManagedProcesses(),f=Array.from(ye.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:pe()},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(!E()){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>
|
|
1
|
+
import{execFile as $}from"node:child_process";import v from"node:fs";import I from"node:os";import K from"node:path";import{Router as Y}from"express";import{getBridgeVersion as Q,getRuntimeHomeDir as X,getUserHomeDir as Z}from"../config.js";import{getAccessControlEntries as ee,isValidIpEntry as te,parseAllowIps as ne,refreshAccessControlCache as se}from"../services/access-control.js";import{getAgentProcessManager as C}from"../services/agent-process-manager.js";import{autoRegister as re,getRegistrationState as H,getAutoRegisterConfigFilePath as B}from"../services/auto-register.js";import{createSession as ae,getPanelCredentials as N,validateSession as U,destroySession as O,validateCredentials as z,hasConfiguredCredentials as E,isDefaultCredentials as oe,setPanelCredentials as ie,getAllSessions as de,getActiveSessionCount as le,checkLoginAllowed as ce,recordLoginFailure as ue,recordLoginSuccess as ge}from"../services/panel-auth.js";import{getRuntimeBindingPublicState as F,getRuntimePairingCode as pe,normalizeSchedulerBaseUrl as fe}from"../services/runtime-binding.js";import{collectSystemMetrics as me}from"../services/system-metrics.js";import{loadPersistedSessions as be}from"../services/terminal-persistence.js";import{runExclusiveConfigWrite as _}from"../utils/config-write-queue.js";import{createLogger as he,getRecentLogs as ve}from"../utils/logger.js";import{sdkSessions as ye}from"./terminal.js";const x=he("dashboard");let T=null;const xe=1e4;let L=null,P=null;const we=3e4;async function D(){me().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=Q();async function Se(){const t=await import("node:fs/promises"),n=(await import("node:path")).join(X(),".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=Y(),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=ce(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(!z(String(i),String(r))){ue(n),e.status(401).json({ok:!1,error:"invalid credentials"});return}ge(n);const a=ae(),o=!E();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&&O(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:oe(),configured:E(),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(E()){if(!r){e.status(400).json({ok:!1,error:"old password required"});return}const o=N();if(!z(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 ie(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 fe(t)??String(t||"").trim()}g.get("/runtime/server-config",(t,e)=>{if(!m(t,e))return;const n=B();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 p=o.tunnel;let f;if(p==="auto"||p===""||p===null||p===void 0)f=void 0;else if(p===!0||p==="true")f=!0;else if(p===!1||p==="false")f=!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:f})}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=B();await _(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),p=l.length>0&&l[0]===a,f=new Map(l.map(d=>[R(String(d.serverUrl??d.schedulerBaseUrl??"").trim()),p?Ce(d):d])),h=[];for(const d of s){let b;if(d.originalServerUrl){const w=R(d.originalServerUrl);b=f.get(w),b&&f.delete(w)}if(!b){const w=R(d.serverUrl);b=f.get(w),b&&f.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:ee(),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=ne(n),i=s.filter(r=>!te(r));if(i.length>0){e.status(400).json({ok:!1,error:`invalid IP/CIDR entries: ${i.join(", ")}`});return}try{const r=B();await _(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"),se()}),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=F(),s=H();e.json({ok:!0,hostname:I.hostname(),platform:I.platform(),arch:I.arch(),uptime:Math.floor(process.uptime()),nodeVersion:process.version,bridgeVersion:V,configPath:B(),homeDir:Z(),bindingStatus:n.status,paired:n.paired,instanceId:n.instanceId||s.instanceId,schedulerBaseUrl:n.schedulerBaseUrl,registered:s.registered,pairingCode:n.pairingCode,activeSessions:le()})}),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=ve(s,r);e.json({ok:!0,logs:a})}),g.post("/runtime/re-register",async(t,e)=>{if(m(t,e))try{const n=await re();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)&&(O(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)];$(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(F()),Promise.resolve(H()),be().catch(()=>[])]),l=C().getAllManagedProcesses(),p=Array.from(ye.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 f={managed:0,orphan:0,unknown:0},h=[];if(s){f={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(p.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:pe()},registration:{registered:r.registered,instanceId:r.instanceId,lastAttempt:r.lastAttempt?.toISOString(),error:r.error},metrics:n,sessions:{active:p,activeCount:p.length,persisted:b,persistedCount:b.length,total:p.length+b.length},processes:{...f,total:f.managed+f.orphan+f.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(!E()){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">
|
package/dist/routes/git.js
CHANGED
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
import{spawn as st}from"node:child_process";import{promises as b}from"node:fs";import ot from"node:os";import
|
|
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=
|
|
1
|
+
import{spawn as st}from"node:child_process";import{promises as b}from"node:fs";import ot from"node:os";import x from"node:path";import L from"axios";import{Router as it}from"express";import{validateToken as k}from"../middleware/auth.js";import{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 l=O(c||"");return{ref:a,stashName:o,message:l.message,branch:l.branch,createdAt:u}}const i=t.match(/^(stash@\{\d+\})(?::\s*)?(.*)$/);if(!i)return null;const s=i[1],n=O(i[2]?.trim()||s);return{ref:s,stashName:s,message:n.message,branch:n.branch,createdAt:null}}function O(r){const t=String(r||"").trim(),e=t.match(/^(?:On|WIP on)\s+([^:]+):\s*(.*)$/);return e?{branch:e[1].trim(),message:e[2].trim()||t}:{branch:"",message:t}}function I(r){const t=String(r||"").split(`
|
|
2
|
+
`).find(i=>i.trim());if(!t)return null;const e=_(t);return e?{ref:e.ref,stashName:e.stashName||e.ref,message:e.message||e.ref}:null}async function h(r,t,e){return new Promise(i=>{let s=!1,n=null;const a=f=>{s||(s=!0,n&&clearTimeout(n),i(f))},o=st("git",t,{cwd:r,stdio:["ignore","pipe","pipe"]});let c="",u="";o.stdout?.on("data",f=>{c+=String(f||"")}),o.stderr?.on("data",f=>{u+=String(f||"")});const l=e??nt();n=setTimeout(()=>{m.warn(`[runtime-bridge] git command timed out after ${l}ms: git ${t.join(" ")} (cwd=${r})`),o.kill("SIGKILL"),a({stdout:c,stderr:u||`git command timed out after ${l}ms`,exitCode:124})},l),o.on("error",f=>{a({stdout:"",stderr:f.message,exitCode:1})}),o.on("close",f=>{a({stdout:c,stderr:u,exitCode:f??1})})})}const 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 R(r){return String(r||"").replace(/\\/g,"/").replace(/^\.\//,"").trim()}async function j(r){const t=x.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=x.resolve(e.stdout.trim()),s=i===t,n=s?"":R(x.relative(i,t));return{workspacePath:t,repositoryRootPath:i,hasRepository:!0,isGitRepo:s,usingParentGitRepo:!s,relativeWorkspacePath:n}}function A(r,t){if(!t.relativeWorkspacePath)return[...r];const e=!r.includes("status");return[...r,...e?[`--relative=${t.relativeWorkspacePath}`]:[],"--",t.relativeWorkspacePath]}function E(r,t){const e=R(r);return t.relativeWorkspacePath?R(x.posix.join(t.relativeWorkspacePath,e)):e}function D(r){const t=R(r);if(x.posix.isAbsolute(t)||t.split("/").some(e=>e===".."||e==="."))throw new Error(`unsafe repository relative path: ${r}`)}function 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=R(r);if(!t.includes("=>"))return t;const e=t.match(/^(.*)\{([^{}]+)=>([^{}]+)\}(.*)$/);if(e){const s=e[1],n=e[3],a=e[4];return R(`${s}${n}${a}`)}const i=t.lastIndexOf("=>");return i>=0?R(t.slice(i+2)):t}function Pt(r){const t=new Set,e=String(r||"").split(`
|
|
3
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
|
|
5
|
-
`).filter(c=>c.trim());for(const c of o){const u=c.split(" ");if(u.length<3)continue;const
|
|
4
|
+
`).filter(c=>c.trim());for(const c of n){const u=c.split(" ");if(u.length<2)continue;const l=yt(u[0]),f=l==="renamed"&&u.length>=3?2:1,d=F(u[f]);d&&s.set(d,l)}const a=[],o=t.split(`
|
|
5
|
+
`).filter(c=>c.trim());for(const c of o){const u=c.split(" ");if(u.length<3)continue;const l=u[0]==="-"||u[1]==="-",f=u[0]==="-"?0:parseInt(u[0],10)||0,d=u[1]==="-"?0:parseInt(u[1],10)||0,p=F(u.slice(2).join(" ").trim()),y=s.get(p)||"modified";!p||!lt(y,f,d,l)||a.push({path:p,status:y,additions:f,deletions:d,staged:i||e.has(p)})}return a}function wt(r){const t=[],e=String(r||"").split(`
|
|
6
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=
|
|
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=x.join(r,a);let u;try{u=await b.readdir(c,{withFileTypes:!0})}catch{return}for(const l of u){if(l.name===".git"||l.name==="node_modules")continue;const f=a?`${a}/${l.name}`:l.name;if(l.isDirectory())await n(f,o+1);else if(l.isFile()){const d=R(f);d&&!e.has(d)&&s.push({path:d,status:"added",additions:await q(x.join(c,l.name)),deletions:0,staged:!1})}}}for(const a of t){const o=R(a);if(!o)continue;const c=x.join(r,a);let u;try{u=await b.stat(c)}catch{continue}u.isFile()?e.has(o)||s.push({path:o,status:"added",additions:await q(c),deletions:0,staged:!1}):u.isDirectory()&&await n(o,0)}return s}function 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
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
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
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
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"]),
|
|
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=
|
|
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(
|
|
12
|
+
${s.stderr}`)){const u=await h(r,["stash","list","-n","1","--format=%H%x00%gd%x00%gs%x00%cr"]),l=u.exitCode===0?I(u.stdout):null;return{stashRef:(l?await z(r,l.ref):null)??l?.ref??null,stashName:l?.stashName??null,noChanges:!0,reusedLatestStash:!!l,message:l?`\u6CA1\u6709\u672C\u5730\u53D8\u66F4\u9700\u8981\u4FDD\u5B58\uFF0C\u5DF2\u590D\u7528\u6700\u8FD1\u5FEB\u7167 ${l.stashName}`:"\u6CA1\u6709\u672C\u5730\u53D8\u66F4\u9700\u8981\u4FDD\u5B58"}}if(s.exitCode!==0)throw 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=x.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=x.resolve(String(e).trim()),n=Math.min(Math.max(1,Number(i)||20),100),a=await h(s,["stash","list","-n",String(n),"--format=%H%x00%gd%x00%gs%x00%cr"]);if(a.exitCode!==0){t.status(400).json({error:a.stderr||"git stash list failed"});return}const o=a.stdout.split(`
|
|
14
|
+
`).map(_).filter(c=>c!==null);t.json({ok:!0,stashes:o,count:o.length,workspacePath:s})}catch(s){const n=s;t.status(500).json({error:n.message||"list stash failed"})}});async function K(r,t,e){const{workspacePath:i,ref:s}=r.body||{};if(!i||!String(i).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const n=x.resolve(String(i).trim()),a=s&&String(s).trim()?String(s).trim():"stash@{0}";await h(n,["checkout","--","."]);const o=await h(n,["stash",e,a]);if(o.exitCode!==0){if(o.stdout.includes("CONFLICT")||o.stderr.includes("CONFLICT")){t.json({ok:!1,hasConflict:!0,message:"\u6062\u590D\u65F6\u53D1\u751F\u51B2\u7A81\uFF0C\u8BF7\u624B\u52A8\u89E3\u51B3",details:o.stdout||o.stderr,workspacePath:n});return}if(o.stderr.includes("is not a valid reference")){t.status(404).json({error:`\u5FEB\u7167 ${a} \u4E0D\u5B58\u5728`});return}t.status(400).json({error:o.stderr||`git stash ${e} failed`});return}t.json({ok:!0,stashRef:a,message:e==="apply"?`\u5DF2\u5E94\u7528 ${a}`:`\u5DF2\u6062\u590D ${a}`,preservedStash:e==="apply",workspacePath:n})}catch(n){const a=n;t.status(500).json({error:a.message||`${e} stash failed`})}}S.post("/git/stash/apply",k,async(r,t)=>{await K(r,t,"apply")}),S.post("/git/stash/pop",k,async(r,t)=>{await K(r,t,"pop")}),S.post("/git/stash/drop",k,async(r,t)=>{const{workspacePath:e,ref:i}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const s=x.resolve(String(e).trim()),n=i&&String(i).trim()?String(i).trim():"stash@{0}",a=await h(s,["stash","drop",n]);if(a.exitCode!==0){if(a.stderr.includes("is not a valid reference")){t.status(404).json({error:`\u5FEB\u7167 ${n} \u4E0D\u5B58\u5728`});return}t.status(400).json({error:a.stderr||"git stash drop failed"});return}t.json({ok:!0,stashRef:n,message:`\u5DF2\u5220\u9664 ${n}`,workspacePath:s})}catch(s){const n=s;t.status(500).json({error:n.message||"drop stash failed"})}}),S.post("/git/diff",k,async(r,t)=>{const{workspacePath:e,repositoryPath:i}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const s=await j(String(i||e).trim());if(!s.hasRepository||!s.repositoryRootPath){t.json({ok:!0,isGitRepo:s.isGitRepo,hasRepository:!1,usingParentGitRepo:!1,hasChanges:!1,files:[],workspacePath:s.workspacePath,repositoryRootPath:null,relativeWorkspacePath:""});return}const n=await h(s.repositoryRootPath,A(["diff","--name-status"],s));if(n.exitCode!==0){t.status(400).json({error:n.stderr||"git diff failed"});return}const a=await h(s.repositoryRootPath,A(["diff","--numstat"],s));if(a.exitCode!==0){t.status(400).json({error:a.stderr||"git diff failed"});return}const o=await h(s.repositoryRootPath,A(["diff","--cached","--name-status"],s));if(o.exitCode!==0){t.status(400).json({error:o.stderr||"git staged diff failed"});return}const c=await h(s.repositoryRootPath,A(["diff","--cached","--numstat"],s));if(c.exitCode!==0){t.status(400).json({error:c.stderr||"git staged diff failed"});return}const u=await h(s.repositoryRootPath,A(["status","--porcelain=v1"],s));if(u.exitCode!==0){t.status(400).json({error:u.stderr||"git status failed"});return}const l=Pt(u.stdout),f=Rt(H(n.stdout,a.stdout,l),H(o.stdout,c.stdout,l,!0)),d=wt(u.stdout);if(d.length>0){const p=new Set(f.map(g=>g.path)),y=await St(s.repositoryRootPath,d,p);f.push(...y)}t.json({ok:!0,isGitRepo:s.isGitRepo,hasRepository:!0,usingParentGitRepo:s.usingParentGitRepo,hasChanges:f.length>0,files:f,workspacePath:s.workspacePath,repositoryRootPath:s.repositoryRootPath,relativeWorkspacePath:s.relativeWorkspacePath})}catch(s){const n=s;t.status(500).json({error:n.message||"git diff failed"})}});async function Y(r){const t=[],e=await h(r,["config","user.name"]);(e.exitCode!==0||!e.stdout.trim())&&t.push("-c","user.name=AgentsWork");const i=await h(r,["config","user.email"]);return(i.exitCode!==0||!i.stdout.trim())&&t.push("-c","user.email=agent@agentswork.com"),t}async function 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(y=>R(String(y||"").trim())).filter(Boolean):[];if(c.length>0){const y=c.map(w=>E(w,o)),g=[];for(const w of y)if(w)try{D(w),g.push(w)}catch{t.status(400).json({error:`unsafe file path: ${w}`});return}if(g.length===0){t.status(400).json({error:"filePaths must be valid workspace-relative paths"});return}try{const w=await At(o.repositoryRootPath,a,g);t.json({ok:!0,message:"\u63D0\u4EA4\u5B8C\u6210",committedPaths:c,output:w.output,workspacePath:o.workspacePath,repositoryRootPath:o.repositoryRootPath,relativeWorkspacePath:o.relativeWorkspacePath})}catch(w){const P=w;t.status(400).json({error:P.message||"git commit failed"})}return}const u=await Y(o.repositoryRootPath),l=jt(o),f=await h(o.repositoryRootPath,["add","--all",...l]);if(f.exitCode!==0){t.status(400).json({error:f.stderr||"git add failed"});return}const d=await h(o.repositoryRootPath,["diff","--cached","--quiet",...l]);if(d.exitCode===0){t.status(400).json({error:"\u6CA1\u6709\u53EF\u63D0\u4EA4\u7684\u53D8\u66F4"});return}if(d.exitCode!==1){t.status(400).json({error:d.stderr||"git staged diff failed"});return}const p=await h(o.repositoryRootPath,[...u,"commit","-m",a,...l]);if(p.exitCode!==0){t.status(400).json({error:p.stderr||p.stdout||"git commit failed"});return}t.json({ok:!0,message:"\u63D0\u4EA4\u5B8C\u6210",output:p.stdout,workspacePath:o.workspacePath,repositoryRootPath:o.repositoryRootPath,relativeWorkspacePath:o.relativeWorkspacePath})}catch(o){const c=o;t.status(500).json({error:c.message||"git commit failed"})}}),S.post("/git/add-file",k,async(r,t)=>{const{workspacePath:e,repositoryPath:i,filePath:s}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}const n=R(String(s||"").trim());if(!n){t.status(400).json({error:"filePath is required"});return}try{const a=await j(String(i||e).trim());if(!a.hasRepository||!a.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}const o=await h(a.repositoryRootPath,["add",...G(n,a)]);if(o.exitCode!==0){t.status(400).json({error:o.stderr||"git add file failed"});return}t.json({ok:!0,filePath:n,workspacePath:a.workspacePath,repositoryRootPath:a.repositoryRootPath,relativeWorkspacePath:a.relativeWorkspacePath})}catch(a){const o=a;t.status(500).json({error:o.message||"git add file failed"})}}),S.post("/git/add-files",k,async(r,t)=>{const{workspacePath:e,repositoryPath:i,filePaths:s}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}const a=(Array.isArray(s)?s:[]).map(o=>R(String(o||"").trim())).filter(o=>o.length>0);if(a.length===0){t.status(400).json({error:"filePaths is required and must be a non-empty array"});return}try{const o=await j(String(i||e).trim());if(!o.hasRepository||!o.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}const c=a.flatMap(d=>G(d,o)),u=["add"],l=[];for(let d=0;d<c.length;d++)c[d]!=="--"&&l.push(c[d]);u.push("--",...l);const f=await h(o.repositoryRootPath,u);if(f.exitCode!==0){t.status(400).json({error:f.stderr||"git add files failed"});return}t.json({ok:!0,filePaths:a,workspacePath:o.workspacePath,repositoryRootPath:o.repositoryRootPath,relativeWorkspacePath:o.relativeWorkspacePath})}catch(o){const c=o;t.status(500).json({error:c.message||"git add files failed"})}}),S.post("/git/commits",k,async(r,t)=>{const{workspacePath:e,repositoryPath:i,limit:s,branch:n}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const a=await j(String(i||e).trim());if(!a.hasRepository||!a.repositoryRootPath){t.json({ok:!0,commits:[],currentBranch:"",branchName:null,workspacePath:a.workspacePath,repositoryRootPath:null});return}const o=Number(s),c=!!r.body?.all,u=c?200:100,l=Number.isInteger(o)&&o>0&&o<=u?o:c?100:30,f=await N(a.repositoryRootPath),p=(n&&String(n).trim()?String(n).trim():null)||f||"HEAD",y="%H%x00%h%x00%an%x00%ad%x00%s%x00%P%x00%b%x00";let g;if(c?g=await h(a.repositoryRootPath,["log",`-${l}`,"--all","--topo-order","--date=iso-strict",`--pretty=format:${y}`]):g=await h(a.repositoryRootPath,["log",`-${l}`,"--date=iso-strict",`--pretty=format:${y}`,"--end-of-options",p,"--",a.relativeWorkspacePath||"."]),g.exitCode!==0){t.status(400).json({error:g.stderr||"git log failed"});return}const w=await U(a.repositoryRootPath),P=Ct(g.stdout).map(v=>({...v,isRemote:w.has(v.hash)})),C=c?await J(a.repositoryRootPath):void 0;t.json({ok:!0,commits:P,currentBranch:f,branchName:c?null:p,...C?{branches:C}:{},workspacePath:a.workspacePath,repositoryRootPath:a.repositoryRootPath,relativeWorkspacePath:a.relativeWorkspacePath})}catch(a){const o=a;t.status(500).json({error:o.message||"git commits failed"})}}),S.post("/git/branches",k,async(r,t)=>{const{workspacePath:e,repositoryPath:i}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const s=await j(String(i||e).trim());if(!s.hasRepository||!s.repositoryRootPath){t.json({ok:!0,branches:[],currentBranch:"",workspacePath:s.workspacePath,repositoryRootPath:null});return}const n=await J(s.repositoryRootPath),a=await N(s.repositoryRootPath);t.json({ok:!0,branches:n,currentBranch:a,workspacePath:s.workspacePath,repositoryRootPath:s.repositoryRootPath,relativeWorkspacePath:s.relativeWorkspacePath})}catch(s){const n=s;t.status(500).json({error:n.message||"git branches failed"})}}),S.post("/git/push",k,async(r,t)=>{const{workspacePath:e,repositoryPath:i}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const s=await j(String(i||e).trim());if(!s.hasRepository||!s.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}const n=await $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 l=await U(r);if(l.size>0){const f=await h(r,["rev-list",`${s}..HEAD`]);if(f.exitCode===0){for(const d of f.stdout.split(`
|
|
15
|
+
`))if(l.has(d.trim())){u=!0;break}}}return{resolvedHash:s,branch:e,resetCount:c,affectsRemote:u}}async function X(r){const t=await h(r,["status","--porcelain"]);return t.exitCode===0&&!!t.stdout.trim()}async function 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 Mt(r,t,e,i=""){const s=await V(r,t),n=await X(r),a=e==="hard"?["reset","--hard",s.resolvedHash]:["reset","--soft",s.resolvedHash],o=await h(r,a);if(o.exitCode!==0)throw new Error(o.stderr||o.stdout||"git reset \u6267\u884C\u5931\u8D25");if(e==="hard"){const c=["clean","-fd"];i&&c.push("--",i);const u=await h(r,c);u.exitCode!==0&&m.warn(`[Git] \u786C\u56DE\u9000\u540E\u6E05\u7406\u672A\u8DDF\u8E2A\u6587\u4EF6\u5931\u8D25: cwd=${r}, scope=${i||"(\u6574\u4ED3)"}, error=${u.stderr||u.stdout}`)}return{mode:e,commitHash:s.resolvedHash,branch:s.branch,resetCount:s.resetCount,hasLocalChanges:n,affectsRemote:s.affectsRemote}}S.post("/git/reset",k,async(r,t)=>{const{workspacePath:e,repositoryPath:i,commitHash:s,mode:n,preview:a}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}const o=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 l=await j(String(i||e).trim());if(!l.hasRepository||!l.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}if(u){const y=await Tt(l.repositoryRootPath,o,c);t.json({ok:!0,preview:!0,...y,workspacePath:l.workspacePath,repositoryRootPath:l.repositoryRootPath,relativeWorkspacePath:l.relativeWorkspacePath});return}const f=await Mt(l.repositoryRootPath,o,c,l.relativeWorkspacePath),d=f.commitHash.slice(0,7),p=f.mode==="hard"?`\u5DF2\u786C\u56DE\u9000\u5230 ${d}\uFF0C\u5171\u56DE\u9000 ${f.resetCount} \u4E2A\u63D0\u4EA4\uFF0C\u5DE5\u4F5C\u533A\u5DF2\u5B8C\u5168\u6062\u590D\u5230\u8BE5\u63D0\u4EA4\u72B6\u6001`:`\u5DF2\u8F6F\u56DE\u9000\u5230 ${d}\uFF0C\u5171\u56DE\u9000 ${f.resetCount} \u4E2A\u63D0\u4EA4\uFF0C\u5DE5\u4F5C\u533A\u6587\u4EF6\u4FDD\u6301\u4E0D\u53D8`;t.json({ok:!0,...f,message:p,workspacePath:l.workspacePath,repositoryRootPath:l.repositoryRootPath,relativeWorkspacePath:l.relativeWorkspacePath})}catch(l){const f=l;if(f.message&&/回退|快照|detached HEAD|分离头指针|不存在|无需|HEAD/.test(f.message)){t.status(400).json({error:f.message});return}t.status(500).json({error:f.message||"git reset failed"})}}),S.post("/git/commit-diff",k,async(r,t)=>{const{workspacePath:e,repositoryPath:i,commitHash:s,aggregate:n}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}const a=B(s);if(!a){t.status(400).json({error:"commitHash is required"});return}const o=n===!0||n==="true";try{const c=await j(String(i||e).trim());if(!c.hasRepository||!c.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}const u=bt(a,o),l=await h(c.repositoryRootPath,A([...u,"--name-status"],c));if(l.exitCode!==0){t.status(400).json({error:l.stderr||"git commit diff failed"});return}const f=await h(c.repositoryRootPath,A([...u,"--numstat"],c));if(f.exitCode!==0){t.status(400).json({error:f.stderr||"git commit diff failed"});return}const d=H(l.stdout,f.stdout);t.json({ok:!0,commitHash:a,isGitRepo:c.isGitRepo,hasRepository:!0,usingParentGitRepo:c.usingParentGitRepo,hasChanges:d.length>0,files:d,workspacePath:c.workspacePath,repositoryRootPath:c.repositoryRootPath,relativeWorkspacePath:c.relativeWorkspacePath})}catch(c){const u=c;t.status(500).json({error:u.message||"git commit diff failed"})}}),S.post("/git/diff-file",k,async(r,t)=>{const{workspacePath:e,filePath:i,commitHash:s,aggregate:n}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}if(!i||!String(i).trim()){t.status(400).json({error:"filePath is required"});return}try{const a=await j(String(e).trim()),o=R(String(i).trim());if(!a.hasRepository||!a.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}const c=E(o,a),u=B(s),l=!!(u&&(n===!0||n==="true")),f=!u||l,d=u?l?["diff",u,"--",c]:["show","--format=","--no-ext-diff",u,"--",c]:["diff","HEAD","--",c];let p=await h(a.repositoryRootPath,d);if(f&&p.exitCode!==0&&(p=await h(a.repositoryRootPath,["diff","--",c])),p.exitCode!==0){t.status(400).json({error:p.stderr||"git diff file failed"});return}if(!u&&!p.stdout.trim()){const g=await h(a.repositoryRootPath,["diff","--cached","--",c]);if(g.exitCode!==0){t.status(400).json({error:g.stderr||"git staged diff file failed"});return}p=g}if(f&&!p.stdout.trim()){const g=x.join(a.repositoryRootPath,c);try{const w=await b.readFile(g,"utf-8");if(w){const P=w.split(`
|
|
16
16
|
`).map(v=>`+${v}`).join(`
|
|
17
17
|
`),C=w.split(`
|
|
18
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{}}
|
|
19
|
+
`),stderr:"",exitCode:0}}}catch{}}D(c);let y="";if(f){const g=x.join(a.repositoryRootPath,c);try{y=await b.readFile(g,"utf-8")}catch{}}else{const g=await h(a.repositoryRootPath,["show",`${u}:${c}`]);g.exitCode===0&&(y=g.stdout)}t.json({ok:!0,filePath:o,repositoryRelativeFilePath:c,commitHash:u||null,rangeFromCommit:l,diffContent:p.stdout,fileContent:y,workspacePath:a.workspacePath,repositoryRootPath:a.repositoryRootPath,usingParentGitRepo:a.usingParentGitRepo})}catch(a){const o=a;t.status(500).json({error:o.message||"git diff file failed"})}});const Z=["STRICTLY act as a Git commit message generator. NEVER explain, narrate, or describe your task.",'NEVER start your reply with phrases like "\u6211\u4EEC\u88AB\u95EE\u5230", "\u4EE5\u4E0B\u662F", "\u597D\u7684", "Sure", "Here is", "I will", "I can", "Let me", "\u6839\u636E", "\u4E0B\u9762".',"NEVER include any meta-language or thinking out loud. Output ONLY the commit message text.","","Format (Conventional Commits, 1 line, optional body):"," <type>(<scope>): <description>","","Allowed types: feat, fix, refactor, test, docs, style, chore, perf, ci, build, revert.","First line MUST be <= 72 characters. <description> MUST be in Chinese.","Add a body (separated by a blank line) ONLY when the change needs more context."].join(`
|
|
20
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,
|
|
21
|
+
`)}const tt=/\b(?:feat|fix|chore|docs|style|refactor|perf|test|build|ci|revert)(?:\s*\([^)]*\))?\s*:/i,Dt=/^(?:feat|fix|chore|docs|style|refactor|perf|test|build|ci|revert)\s*:/i;function Ft(r){const t=r.split(`
|
|
22
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
|
|
24
|
-
`)[0]?.trim()??"",o=!!t.match(tt),c=!!t.match(
|
|
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,
|
|
26
|
-
${
|
|
23
|
+
`).trim();const e=JSON.stringify(r.substring(0,120)),i=t.match(tt);if(i&&i.index!==void 0&&i.index<=80)t=t.slice(i.index);else{for(let f=0;f<5;f+=1){const d=t;for(const p of 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(Dt),u=Ft(t),l=a.length>100;if(!o&&!c&&!u&&!l)return m.info(`[Git/sanitize] \u5265\u79BB\u540E\u65E0\u6709\u6548\u5185\u5BB9, \u8FD4\u56DE fallback. raw=${e}, afterStrip=${JSON.stringify(t.substring(0,100))}`),"chore: update staged changes";!o&&(c||u||l)&&(t=a||t)}const s=t.search(/\n\s*\n/);if(s>0&&(t=t.slice(0,s)),t=t.replace(/\n+\s*```\s*$/g,"").trim(),!t)return m.info(`[Git/sanitize] \u622A\u65AD\u540E\u4E3A\u7A7A, \u8FD4\u56DE fallback. raw=${e}`),"chore: update staged changes";const n=t.split(`
|
|
25
|
+
`)[0]?.trim()??"";return n.length>100?(m.info(`[Git/sanitize] \u9996\u884C\u8D85\u8FC7 100 \u5B57\u7B26, \u4EC5\u4FDD\u7559\u9996\u884C. raw=${e}, firstLine=${JSON.stringify(n.substring(0,100))}`),n):(m.info(`[Git/sanitize] \u7ED3\u679C: ${JSON.stringify(t.substring(0,120))} (from raw=${e})`),t)}function et(r){const t=r.split(/\r?\n/);let e="";for(const i of t){const s=i.trim();if(!s.startsWith("data:"))continue;const n=s.slice(5).trim();if(!(!n||n==="[DONE]"))try{const o=JSON.parse(n)?.choices?.[0]?.delta?.content;typeof o=="string"&&o.length>0&&(e+=o)}catch{}}return e}function 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,l=u?.delta?Object.keys(u.delta):null,f=u?.delta?.content;m.info(`[Git/extract] chunk#${t} structure: topKeys=${JSON.stringify(o)}, choicesLen=${Array.isArray(c)?c.length:typeof c}, deltaKeys=${JSON.stringify(l)}, content=${JSON.stringify(f?.substring(0,50))}, finishReason=${JSON.stringify(u?.finish_reason)}`)}catch{}}}async function 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="",l="",f="",d=0,p=0;return m.info(`[Git/streamAi] AI \u6D41\u5F00\u59CB: aiModel=${e}`),await new Promise((y,g)=>{const w=P=>{c.removeAllListeners("data"),c.removeAllListeners("end"),c.removeAllListeners("error"),c.removeAllListeners("aborted"),m.info(`[Git/streamAi] AI \u6D41\u7ED3\u675F: totalChunks=${d}, totalDeltas=${p}, bufferLength=${u.length}, sanitized=${JSON.stringify(P).substring(0,200)}`),y(P)};c.on("data",P=>{const C=typeof P=="string"?P:P.toString("utf8");d++,f+=C,d<=3&&m.info(`[Git/streamAi] chunk#${d}: length=${C.length}, preview=${JSON.stringify(C.substring(0,80))}`),Nt(C,d);const v=f.split(/\n\n/);f=v.pop()??"";for(const rt of v){const M=et(rt);M&&(p++,u+=M,l=T(u),s(M,l))}}),c.on("end",()=>{if(f.trim()){const P=et(f);P?(m.info(`[Git/streamAi] \u5C3E\u90E8\u6B8B\u7559 delta: length=${P.length}`),u+=P):m.info(`[Git/streamAi] \u5C3E\u90E8\u6B8B\u7559\u65E0 delta, pendingData=${JSON.stringify(f.substring(0,100))}`)}w(T(u))}),c.on("error",P=>{m.error(`[Git/streamAi] \u6D41\u9519\u8BEF: ${P.message}`),g(P)}),c.on("aborted",()=>{m.info(`[Git/streamAi] \u6D41\u88AB\u4E2D\u6B62, bufferLength=${u.length}`),w(T(u))})})}async function W(r,t,e){const i=await j(String(r||t).trim());if(!i.hasRepository||!i.repositoryRootPath)return m.warn(`[Git/collectDiff] \u4E0D\u5728 Git \u4ED3\u5E93\u4E2D: workspacePath=${t}`),{truncatedDiff:"",error:{status:400,message:"workspace is not inside a git repository"}};const s=e.map(u=>u.path).filter(Boolean);m.info(`[Git/collectDiff] \u5F00\u59CB\u6536\u96C6 ${s.length} \u4E2A\u6682\u5B58\u6587\u4EF6\u5DEE\u5F02: ${s.join(", ").substring(0,200)}`);const n=[];for(const u of s){const l=E(u,i),f=await h(i.repositoryRootPath,["diff","--cached","--",l]);f.exitCode===0&&f.stdout.trim()?n.push(`--- ${u} ---
|
|
26
|
+
${f.stdout.trim()}`):m.info(`[Git/collectDiff] \u6587\u4EF6\u65E0\u6682\u5B58\u5DEE\u5F02\u6216\u83B7\u53D6\u5931\u8D25: ${u}, exitCode=${f.exitCode}, stdout.length=${f.stdout.length}`)}if(n.length===0){m.info("[Git/collectDiff] \u9010\u6587\u4EF6 diff \u4E3A\u7A7A, \u56DE\u9000\u5230\u5168\u91CF diff --cached");const u=await h(i.repositoryRootPath,["diff","--cached"]);u.exitCode===0&&u.stdout.trim()?(n.push(u.stdout.trim()),m.info(`[Git/collectDiff] \u5168\u91CF diff \u83B7\u53D6\u6210\u529F: length=${u.stdout.trim().length}`)):m.info(`[Git/collectDiff] \u5168\u91CF diff \u4E5F\u4E3A\u7A7A, exitCode=${u.exitCode}`)}if(n.length===0)return m.info("[Git/collectDiff] \u6682\u5B58\u533A\u6CA1\u6709\u5DEE\u5F02\u5185\u5BB9 (diffParts \u4E3A\u7A7A)"),{truncatedDiff:""};const a=n.join(`
|
|
27
27
|
|
|
28
28
|
`),o=15e3,c=a.length>o?`${a.slice(0,o)}
|
|
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
30
|
`),r.write(`data: ${JSON.stringify(e)}
|
|
31
31
|
|
|
32
|
-
`)}catch{}}S.post("/git/generate-commit-message",k,async(r,t)=>{const{workspacePath:e,repositoryPath:i,stagedFiles:s,aiBaseUrl:n,aiApiKey:a,aiModel:o}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}if(!Array.isArray(s)||s.length===0){t.status(400).json({error:"stagedFiles is required and must be a non-empty array"});return}if(!n||!String(n).trim()||!a||!String(a).trim()||!o||!String(o).trim()){t.status(400).json({error:"aiBaseUrl, aiApiKey and aiModel are required"});return}try{const{truncatedDiff:c,error:u}=await W(String(i||""),String(e),s);if(u){t.status(u.status).json({error:u.message});return}if(!c){t.json({message:"\u6682\u5B58\u533A\u6CA1\u6709\u53D8\u66F4\u5185\u5BB9"});return}const
|
|
33
|
-
`+
|
|
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
|
|
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=
|
|
36
|
-
`,"utf-8");const
|
|
32
|
+
`)}catch{}}S.post("/git/generate-commit-message",k,async(r,t)=>{const{workspacePath:e,repositoryPath:i,stagedFiles:s,aiBaseUrl:n,aiApiKey:a,aiModel:o}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}if(!Array.isArray(s)||s.length===0){t.status(400).json({error:"stagedFiles is required and must be a non-empty array"});return}if(!n||!String(n).trim()||!a||!String(a).trim()||!o||!String(o).trim()){t.status(400).json({error:"aiBaseUrl, aiApiKey and aiModel are required"});return}try{const{truncatedDiff:c,error:u}=await W(String(i||""),String(e),s);if(u){t.status(u.status).json({error:u.message});return}if(!c){t.json({message:"\u6682\u5B58\u533A\u6CA1\u6709\u53D8\u66F4\u5185\u5BB9"});return}const l=await Wt(n,a,o,c);t.json({message:l})}catch(c){const u=c;m.error(`[Git] generate commit message failed: ${u.message}`),t.status(500).json({error:u.message||"generate commit message failed"})}}),S.post("/git/generate-commit-message-stream",k,async(r,t)=>{const{workspacePath:e,repositoryPath:i,stagedFiles:s,aiBaseUrl:n,aiApiKey:a,aiModel:o}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}if(!Array.isArray(s)||s.length===0){t.status(400).json({error:"stagedFiles is required and must be a non-empty array"});return}if(!n||!String(n).trim()||!a||!String(a).trim()||!o||!String(o).trim()){t.status(400).json({error:"aiBaseUrl, aiApiKey and aiModel are required"});return}m.info(`[Git/SSE] \u6536\u5230 generate-commit-message-stream \u8BF7\u6C42: workspacePath=${String(e||"").substring(0,40)}, stagedFiles=${s?.length??0}, aiModel=${o}, aiBaseUrl=${n}`),t.status(200),t.setHeader("Content-Type","text/event-stream; charset=utf-8"),t.setHeader("Cache-Control","no-cache, no-transform"),t.setHeader("Connection","keep-alive"),t.setHeader("X-Accel-Buffering","no"),r.socket?.setNoDelay(!0),t.flushHeaders?.();let c=!1,u=null;const l={delta:0},f=`${r.ip}:${r.socket?.remotePort??"?"}`;r.on("close",()=>{c||(c=!0,m.info(`[Git/SSE] \u5BA2\u6237\u7AEF\u65AD\u5F00\u8FDE\u63A5: workspacePath=${String(e||"").substring(0,40)}, deltaEventsSent=${l.delta}, peer=${f}`))}),$(t,"init",{status:"generating"});try{const{truncatedDiff:d,error:p}=await W(String(i||""),String(e),s);if(p){m.warn(`[Git/SSE] collectStagedDiffForAi \u8FD4\u56DE\u9519\u8BEF: ${p.message}`),$(t,"error",{message:p.message}),t.end();return}if(!d){m.info("[Git/SSE] \u6682\u5B58\u533A\u6CA1\u6709\u53D8\u66F4\u5185\u5BB9, \u76F4\u63A5\u8FD4\u56DE done"),$(t,"delta",{contentDelta:"",message:"\u6682\u5B58\u533A\u6CA1\u6709\u53D8\u66F4\u5185\u5BB9"}),$(t,"done",{message:"\u6682\u5B58\u533A\u6CA1\u6709\u53D8\u66F4\u5185\u5BB9"}),t.end();return}m.info(`[Git/SSE] \u6536\u96C6\u5230 diff: truncatedDiff.length=${d.length}`),c&&m.info("[Git/SSE] \u5BA2\u6237\u7AEF\u5728 collectDiff \u671F\u95F4\u65AD\u5F00, \u4F46\u4ECD\u5C1D\u8BD5 AI \u8C03\u7528"),m.info(`[Git/SSE] \u5F00\u59CB\u8C03\u7528 AI streamAiForCommitMessage: aiModel=${o}`),u=new AbortController;let y="";const g=await Lt(n,a,o,d,(w,P)=>{l.delta++,P!==y?(y=P,$(t,"delta",{contentDelta:w,message:P})):$(t,"delta",{contentDelta:w,message:P})},u.signal);m.info(`[Git/SSE] AI streamAiForCommitMessage \u5B8C\u6210: finalMessage=${JSON.stringify(g).substring(0,200)}, deltaEventsSent=${l.delta}`),c||$(t,"done",{message:g}),t.end()}catch(d){const p=d;m.error(`[Git/SSE] stream commit message failed: ${p.message}${p.stack?`
|
|
33
|
+
`+p.stack:""}`),c||$(t,"error",{message:p.message||"generate commit message failed"}),t.end()}}),S.post("/git/unstage-files",k,async(r,t)=>{const{workspacePath:e,repositoryPath:i,filePaths:s}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}const a=(Array.isArray(s)?s:[]).map(o=>R(String(o||"").trim())).filter(o=>o.length>0);if(a.length===0){t.status(400).json({error:"filePaths is required and must be a non-empty array"});return}try{const o=await j(String(i||e).trim());if(!o.hasRepository||!o.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}let c="";for(const u of a){const l=G(u,o),f=await h(o.repositoryRootPath,["restore","--staged",...l]);f.exitCode!==0&&(c=f.stderr||`git restore --staged failed for ${u}`)}if(c){t.status(400).json({error:c});return}t.json({ok:!0,filePaths:a,workspacePath:o.workspacePath,repositoryRootPath:o.repositoryRootPath,relativeWorkspacePath:o.relativeWorkspacePath})}catch(o){const c=o;t.status(500).json({error:c.message||"git unstage files failed"})}}),S.post("/git/restore-files",k,async(r,t)=>{const{workspacePath:e,repositoryPath:i,filePaths:s}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}const a=(Array.isArray(s)?s:[]).map(o=>R(String(o||"").trim())).filter(o=>o.length>0);if(a.length===0){t.status(400).json({error:"filePaths is required and must be a non-empty array"});return}try{const o=await j(String(i||e).trim());if(!o.hasRepository||!o.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}let c="";for(const u of a){const l=E(u,o),f=G(u,o),p=(await h(o.repositoryRootPath,["ls-files","--error-unmatch","--",l])).exitCode===0,y=p?await h(o.repositoryRootPath,["restore",...f]):await h(o.repositoryRootPath,["clean","-f",...f]);y.exitCode!==0&&(c=y.stderr||`${p?"git restore":"git clean -f"} failed for ${u}`)}if(c){t.status(400).json({error:c});return}t.json({ok:!0,filePaths:a,workspacePath:o.workspacePath,repositoryRootPath:o.repositoryRootPath,relativeWorkspacePath:o.relativeWorkspacePath})}catch(o){const c=o;t.status(500).json({error:c.message||"git restore files failed"})}});function _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 l=i+1;l<t.length;l++){const f=t[l].replace(/\r$/,"");if(/^diff --git /.test(f)||/^@@ /.test(f))break;f.startsWith("+")?u++:f.startsWith("-")?c++:f.startsWith(" ")&&(c++,u++)}e.push(`@@ -${o[1]},${c} +${o[2]},${u} @@${n}`)}return e.join(`
|
|
35
|
+
`)}S.post("/git/revert-hunk",k,async(r,t)=>{const{workspacePath:e,repositoryPath:i,filePath:s,patch:n}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}if(!s||!String(s).trim()){t.status(400).json({error:"filePath is required"});return}const a=String(n||"").replace(/^\uFEFF/,"").trim();if(!a){t.status(400).json({error:"patch is required"});return}try{const o=await j(String(i||e).trim());if(!o.hasRepository||!o.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}const c=R(String(s).trim()),u=E(c,o);if(D(u),(a.match(/^diff --git /gm)||[]).length!==1){t.status(400).json({error:"patch must contain exactly one file diff"});return}const f=a.match(/^\+\+\+ b\/(.+)$/m),d=f?R(f[1].trim()):"";if(!d||d!==u){t.status(400).json({error:"patch target path does not match filePath"});return}let p=null;try{p=await b.mkdtemp(x.join(ot.tmpdir(),"agentswork-git-revert-"));const y=x.join(p,"revert.patch");await b.writeFile(y,_t(a)+`
|
|
36
|
+
`,"utf-8");const g=await h(o.repositoryRootPath,["apply","--reverse",y]);if(g.exitCode!==0){t.status(400).json({error:g.stderr||"git apply --reverse failed; \u8BE5\u5904\u5185\u5BB9\u53EF\u80FD\u5DF2\u53D8\u5316\uFF0C\u8BF7\u5237\u65B0\u5DEE\u5F02\u540E\u91CD\u8BD5"});return}t.json({ok:!0,filePath:c,repositoryRelativeFilePath:u,workspacePath:o.workspacePath,repositoryRootPath:o.repositoryRootPath})}finally{p&&await b.rm(p,{recursive:!0,force:!0}).catch(()=>{})}}catch(o){const c=o;t.status(500).json({error:c.message||"git revert hunk failed"})}}),S.post("/git/diff-content",k,async(r,t)=>{const{workspacePath:e,repositoryPath:i,stagedFiles:s}=r.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}if(!Array.isArray(s)||s.length===0){t.status(400).json({error:"stagedFiles is required and must be a non-empty array"});return}try{const{truncatedDiff:n,error:a}=await W(String(i||""),String(e),s);if(a){t.status(a.status).json({error:a.message});return}t.json({ok:!0,diff:n||""})}catch(n){const a=n;m.error(`[Git/diff-content] \u83B7\u53D6\u6682\u5B58\u533A\u5DEE\u5F02\u5931\u8D25: ${a.message}`),t.status(500).json({error:a.message||"get diff content failed"})}});export{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,Pt as parseGitStatusStagedPaths,wt as parseGitStatusUntrackedPaths,I as parseLatestGitStash,z as persistAgentsWorkStashSnapshotRef,Tt as previewRepositoryReset,$t as pushBranchToRemote,Mt as resetRepositoryTo,N as resolveCurrentBranchName,nt as resolveGitCommandTimeoutMs,gt as resolveGitPushTimeoutMs,U as resolveRemoteCommitHashes,T as sanitizeCommitMessage,lt as shouldIncludeGitDiffSummaryFile,Lt as streamAiForCommitMessage};
|
|
37
37
|
|