aws-runtime-bridge 1.9.120 → 1.9.122

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/dist/adapter/AcodeSdkAdapter.d.ts +18 -0
  2. package/dist/adapter/AcodeSdkAdapter.js +5 -5
  3. package/dist/adapter/types.d.ts +4 -0
  4. package/dist/adapter/types.js +1 -1
  5. package/dist/browser/built-in-browser-tools.js +1 -1
  6. package/dist/desktop/built-in-desktop-tools.d.ts +49 -0
  7. package/dist/desktop/built-in-desktop-tools.js +2 -0
  8. package/dist/desktop/desktop-mcp-client.d.ts +47 -0
  9. package/dist/desktop/desktop-mcp-client.js +3 -0
  10. package/dist/index.js +1 -1
  11. package/dist/routes/dashboard.js +4 -4
  12. package/dist/routes/git.d.ts +39 -0
  13. package/dist/routes/git.js +33 -25
  14. package/dist/routes/instance.js +1 -1
  15. package/dist/routes/jdks.d.ts +2 -0
  16. package/dist/routes/jdks.js +2 -0
  17. package/dist/routes/launch.d.ts +6 -0
  18. package/dist/routes/launch.js +30 -0
  19. package/dist/routes/terminal.js +30 -30
  20. package/dist/services/change-cache-retention.d.ts +5 -0
  21. package/dist/services/change-cache-retention.js +2 -0
  22. package/dist/services/desktop-mcp-manager.d.ts +47 -0
  23. package/dist/services/desktop-mcp-manager.js +2 -0
  24. package/dist/services/instance-features.d.ts +6 -0
  25. package/dist/services/instance-features.js +1 -1
  26. package/dist/services/jdk-scanner.d.ts +20 -0
  27. package/dist/services/jdk-scanner.js +2 -0
  28. package/dist/services/launch-manager.d.ts +181 -0
  29. package/dist/services/launch-manager.js +12 -0
  30. package/dist/services/launch-metrics.d.ts +89 -0
  31. package/dist/services/launch-metrics.js +4 -0
  32. package/dist/services/launch-streams.d.ts +24 -0
  33. package/dist/services/launch-streams.js +11 -0
  34. package/dist/services/mcp-launch-binding-queue.js +1 -1
  35. package/dist/services/panel-auth.d.ts +10 -0
  36. package/dist/services/panel-auth.js +1 -1
  37. package/dist/services/panel-env.d.ts +5 -0
  38. package/dist/services/panel-env.js +2 -0
  39. package/dist/services/port-detector.d.ts +3 -0
  40. package/dist/services/port-detector.js +6 -0
  41. package/dist/services/runtime-lifecycle-policy.d.ts +1 -0
  42. package/dist/services/runtime-lifecycle-policy.js +1 -1
  43. package/dist/services/tunnel-client.js +1 -1
  44. package/dist/services/workspace-files.d.ts +3 -0
  45. package/dist/services/workspace-files.js +1 -1
  46. package/dist/services/workspaceTaskChangeWatcher.d.ts +2 -0
  47. package/dist/services/workspaceTaskChangeWatcher.js +3 -3
  48. package/package/acode/dist/built-in-file-tools.d.ts +2 -0
  49. package/package/acode/dist/runtime.d.ts +20 -0
  50. package/package/acode/dist/runtime.js +27 -27
  51. package/package/acode/dist/sub-agent-manager.d.ts +4 -0
  52. package/package/acode/dist/sub-agent-manager.js +1 -1
  53. package/package/acode/dist/sub-agent-tools.d.ts +6 -0
  54. package/package/acode/dist/sub-agent-tools.js +39 -10
  55. package/package/acode/dist/sub-agent-types.d.ts +2 -0
  56. package/package/acode/dist/tool-allocation.d.ts +55 -0
  57. package/package/acode/dist/tool-allocation.js +2 -0
  58. package/package/acode/dist/types.d.ts +6 -0
  59. package/package/aws-client-agent-mcp/dist/agent-client.d.ts +24 -0
  60. package/package/aws-client-agent-mcp/dist/agent-client.js +1 -1
  61. package/package/aws-client-agent-mcp/dist/agent-client.test.js +19 -5
  62. package/package/aws-client-agent-mcp/dist/context-manager.test.js +6 -6
  63. package/package/aws-client-agent-mcp/dist/http-client.d.ts +2 -0
  64. package/package/aws-client-agent-mcp/dist/http-client.js +1 -1
  65. package/package/aws-client-agent-mcp/dist/index.js +1 -1
  66. package/package/aws-client-agent-mcp/dist/launch-config-tools.test.d.ts +1 -0
  67. package/package/aws-client-agent-mcp/dist/launch-config-tools.test.js +106 -0
  68. package/package/aws-client-agent-mcp/dist/logger.d.ts +4 -0
  69. package/package/aws-client-agent-mcp/dist/logger.js +2 -2
  70. package/package/aws-client-agent-mcp/dist/mcp-server.d.ts +21 -0
  71. package/package/aws-client-agent-mcp/dist/mcp-server.js +25 -25
  72. package/package/aws-client-agent-mcp/dist/mcp-server.test.js +24 -16
  73. package/package/aws-client-agent-mcp/dist/mcp-tools.d.ts +20 -0
  74. package/package/aws-client-agent-mcp/dist/mcp-tools.js +8 -8
  75. package/package/aws-client-agent-mcp/dist/mcp-tools.test.js +55 -1
  76. package/package/aws-client-agent-mcp/dist/memory-store.test.js +5 -1
  77. package/package/aws-client-agent-mcp/dist/parent-lifecycle.d.ts +10 -0
  78. package/package/aws-client-agent-mcp/dist/parent-lifecycle.js +2 -0
  79. package/package/aws-client-agent-mcp/dist/parent-lifecycle.test.d.ts +1 -0
  80. package/package/aws-client-agent-mcp/dist/parent-lifecycle.test.js +81 -0
  81. package/package/aws-client-agent-mcp/dist/status-reporter.js +1 -1
  82. package/package/aws-client-agent-mcp/dist/types.d.ts +4 -0
  83. package/package/aws-client-agent-mcp/dist/watchdog-worker.d.ts +1 -0
  84. package/package/aws-client-agent-mcp/dist/watchdog-worker.js +3 -0
  85. package/package/aws-client-agent-mcp/dist/watchdog.d.ts +17 -0
  86. package/package/aws-client-agent-mcp/dist/watchdog.js +2 -0
  87. package/package/aws-client-agent-mcp/dist/watchdog.test.d.ts +1 -0
  88. package/package/aws-client-agent-mcp/dist/watchdog.test.js +124 -0
  89. package/package/aws-client-agent-mcp/dist/websocket-client.d.ts +4 -0
  90. package/package/aws-client-agent-mcp/dist/websocket-client.js +1 -1
  91. package/package/aws-client-agent-mcp/dist/websocket-client.test.js +83 -6
  92. package/package/aws-client-agent-mcp/package.json +1 -1
  93. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import{existsSync as D,readFileSync as B}from"node:fs";import E from"node:os";import x from"node:path";import M from"cors";import I from"express";import{adapterRegistry as O}from"./adapter/index.js";import{attachBrowserWebSocketServer as G}from"./browser/browser-ws-bridge.js";import{allowedCorsOrigins as y,port as h,validateProductionToken as _}from"./config.js";import{attachRemoteDesktopWebSocketServer as F}from"./remote-desktop/index.js";import{acodeTodosRouter as K}from"./routes/acode-todos.js";import{aiSourcesRouter as L}from"./routes/ai-sources.js";import{backgroundTasksRouter as N}from"./routes/background-tasks.js";import{browserListenersRouter as j}from"./routes/browser-listeners.js";import{browserSessionsRouter as W}from"./routes/browser-sessions.js";import{computerRouter as U}from"./routes/computer.js";import{dashboardRouter as z}from"./routes/dashboard.js";import{eventsRouter as J}from"./routes/events.js";import{fileBrowserRouter as Y}from"./routes/file-browser.js";import{fileChangeDiffRouter as q}from"./routes/file-change-diff.js";import{fileDiffRollbackRouter as H}from"./routes/file-diff-rollback.js";import{fileSnapshotRouter as Z}from"./routes/file-snapshot.js";import{gitRouter as Q}from"./routes/git.js";import{instanceRouter as V,setGracefulShutdownFn as X}from"./routes/instance.js";import{mcpRouter as ee}from"./routes/mcp.js";import{processesRouter as re}from"./routes/processes.js";import{propertiesRouter as te}from"./routes/properties.js";import{attachPtyWebSocketServer as ie,closeAllPtySessions as oe,ptyRouter as ne,ptySessions as se}from"./routes/pty.js";import{runtimeBindingRouter as ae,logRuntimeBindingStartupState as me}from"./routes/runtime-binding.js";import{sessionsRouter as ue}from"./routes/sessions.js";import{skillsRouter as ce}from"./routes/skills.js";import{subAgentsRouter as de}from"./routes/sub-agents.js";import{systemMetricsRouter as ge}from"./routes/system-metrics.js";import{taskContextRouter as pe}from"./routes/task-context.js";import{terminalRouter as fe,sdkSessions as S}from"./routes/terminal.js";import{ymlRouter as le}from"./routes/yml.js";import{ensureAcodePackageReleased as be}from"./services/acode-package.js";import{getAgentProcessManager as P}from"./services/agent-process-manager.js";import{autoRegister as we,unregister as he,bridgeRestartCleanup as Se,cancelBackgroundRegisterRetries as Re}from"./services/auto-register.js";import{ensureAwsClientAgentMcpReleased as Ie}from"./services/aws-client-agent-mcp.js";import{disposeBrowserSessionManager as ye}from"./services/browser-session-manager.js";import{handleCliCommand as Pe}from"./services/cli-commands.js";import{migrateOldPathsOnStartup as $e}from"./services/legacy-path-migration.js";import{deliverPendingBridgeLifecycleNotifications as ke}from"./services/lifecycle-state.js";import{startOrphanMonitor as Ce,stopOrphanMonitor as ve}from"./services/orphan-monitor.js";import{detectOrphanProcesses as Te}from"./services/process-detector.js";import{buildGracefulShutdownPlan as Ae,shouldCleanupPersistedSessionsOnStartup as De}from"./services/runtime-lifecycle-policy.js";import{ensureStartupConfig as Be}from"./services/startup-config-wizard.js";import{subAgentStore as Ee}from"./services/sub-agent-manager-instance.js";import{loadPersistedSessions as $,savePersistedSessions as k}from"./services/terminal-persistence.js";import{stopTunnelClient as xe}from"./services/tunnel-client.js";import{logger as e}from"./utils/logger.js";const C=await Pe();C.handled&&process.exit(C.exitCode),await Be(),_(),Ie(),be(),$e(),me();async function Me(){try{if(await we())e.info("[runtime-bridge] \u81EA\u52A8\u6CE8\u518C\u6210\u529F");else{const{loadConfig:i}=await import("./services/auto-register.js"),s=i();!!s.userKey&&s.enabled?e.warn("[runtime-bridge] \u81EA\u52A8\u6CE8\u518C\u6682\u65F6\u5931\u8D25\uFF0C\u5C06\u5728\u540E\u53F0\u6301\u7EED\u91CD\u8BD5\u76F4\u5230\u4E0E\u8C03\u5EA6\u4E2D\u5FC3\u5EFA\u7ACB\u8FDE\u63A5"):(e.info("[runtime-bridge] \u81EA\u52A8\u6CE8\u518C\u672A\u914D\u7F6E\uFF0C\u7EE7\u7EED\u542F\u52A8\u670D\u52A1"),e.info("[runtime-bridge] \u8981\u542F\u7528\u81EA\u52A8\u6CE8\u518C\uFF0C\u8BF7\u521B\u5EFA\u914D\u7F6E\u6587\u4EF6 ~/.aws-bridge/config.json"))}}catch(r){const i=r;e.warn("[runtime-bridge] \u81EA\u52A8\u6CE8\u518C\u51FA\u9519:",i.message)}}Me().then(()=>Oe()).then(()=>ke()).then(()=>{e.info("[runtime-bridge] Bridge \u751F\u547D\u5468\u671F\u901A\u77E5\u540C\u6B65\u5B8C\u6210")}).catch(r=>{e.warn("[runtime-bridge] \u81EA\u52A8\u6CE8\u518C\u6216\u6E05\u7406\u51FA\u9519:",r)}),import("./services/update-notifier.js").then(({startUpdateNotifier:r})=>r()).catch(r=>{e.warn("[runtime-bridge] \u7248\u672C\u68C0\u67E5\u670D\u52A1\u542F\u52A8\u5931\u8D25:",r.message)});async function Oe(){try{if(process.env.AWS_BRIDGE_RESTART_CLEANUP!=="true"){e.info("[runtime-bridge] \u8DF3\u8FC7 Bridge \u91CD\u542F\u6E05\u7406\uFF0C\u4FDD\u7559 Agent \u72B6\u6001\u4F9B\u8C03\u5EA6\u4E2D\u5FC3\u6062\u590D");return}const{getRegistrationState:r}=await import("./services/auto-register.js"),i=r();if(!i.registered||!i.instanceId){e.info("[runtime-bridge] \u5B9E\u4F8B\u672A\u6CE8\u518C\uFF0C\u8DF3\u8FC7 Bridge \u91CD\u542F\u6E05\u7406");return}e.info("[runtime-bridge] \u2605 Bridge \u91CD\u542F\u6E05\u7406\u5DF2\u663E\u5F0F\u542F\u7528\uFF0C\u6B63\u5728\u901A\u77E5\u8C03\u5EA6\u4E2D\u5FC3\u6E05\u7406 Agent \u72B6\u6001...");const s=await Se();s.success?e.info(`[runtime-bridge] \u2605 Bridge \u91CD\u542F\u6E05\u7406\u6210\u529F\uFF0C\u6E05\u7406\u4E86 ${s.agentCount||0} \u4E2A Agent`):e.warn(`[runtime-bridge] \u2605 Bridge \u91CD\u542F\u6E05\u7406\u5931\u8D25: ${s.error}`)}catch(r){const i=r;e.warn("[runtime-bridge] Bridge \u91CD\u542F\u6E05\u7406\u51FA\u9519:",i.message)}}async function Ge(){try{const r=await $();if(r.length===0){e.info("[runtime-bridge] \u65E0\u6301\u4E45\u5316\u4F1A\u8BDD\uFF0C\u8DF3\u8FC7\u8FDB\u7A0B\u6CE8\u518C\u8868\u91CD\u5EFA");return}await P().rebuildRegistry(r.map(s=>({agentId:s.agentId,sessionId:s.sessionId,pid:s.pid,workspacePath:s.workspacePath,command:s.command,mode:"sdk"}))),e.info(`[runtime-bridge] \u8FDB\u7A0B\u6CE8\u518C\u8868\u91CD\u5EFA\u5B8C\u6210: ${r.length} \u6761\u8BB0\u5F55`)}catch(r){const i=r;e.warn("[runtime-bridge] \u8FDB\u7A0B\u6CE8\u518C\u8868\u91CD\u5EFA\u5931\u8D25:",i.message)}}Ge().catch(r=>{e.warn("[runtime-bridge] \u8FDB\u7A0B\u6CE8\u518C\u8868\u91CD\u5EFA\u51FA\u9519:",r)});async function _e(){try{const r=await import("node:fs/promises"),i=await import("node:path"),s=await import("node:os"),f=i.join(s.tmpdir(),"agentswork-runtime-bridge",".preserve-sessions");let u=!1;try{const t=await r.readFile(f,"utf-8");u=JSON.parse(t).preserveSessions===!0,await r.unlink(f),e.info(`[runtime-bridge] \u2605 \u68C0\u6D4B\u5230\u4FDD\u7559\u4F1A\u8BDD\u6807\u8BB0\uFF0CpreserveSessions=${u}`)}catch{}const a=await $();if(a.length===0){e.info("[runtime-bridge] \u542F\u52A8\u65F6\u65E0\u6301\u4E45\u5316\u4F1A\u8BDD\uFF0C\u65E0\u9700\u68C0\u6D4B\u5B64\u513F\u8FDB\u7A0B");return}if(!De(u)){e.info(`[runtime-bridge] \u2605 \u4FDD\u7559 ${a.length} \u4E2A\u6301\u4E45\u5316\u4F1A\u8BDD\u5E76\u91CD\u5EFA\u8FDB\u7A0B\u6CE8\u518C\u8868`),await P().rebuildRegistry(a.map(n=>({agentId:n.agentId,sessionId:n.sessionId,pid:n.pid,workspacePath:n.workspacePath,command:n.command,mode:"sdk"})));return}e.info(`[runtime-bridge] \u2605 \u5DF2\u663E\u5F0F\u542F\u7528\u542F\u52A8\u6E05\u7406\uFF0C\u68C0\u6D4B ${a.length} \u4E2A\u6301\u4E45\u5316\u4F1A\u8BDD\u7684\u5B64\u513F\u8FDB\u7A0B...`);const d=await Te(a.map(t=>({agentId:t.agentId,pid:t.pid,workspacePath:t.workspacePath,command:t.command})));if(d.length>0){e.warn(`[runtime-bridge] \u2605 \u68C0\u6D4B\u5230 ${d.length} \u4E2A\u5B64\u513F\u8FDB\u7A0B\u4ECD\u5728\u8FD0\u884C\uFF1A`);for(const t of d)e.warn(` - agentId=${t.agentId}, pid=${t.process.pid}, command=${t.process.command?.slice(0,50)}`);e.info("[runtime-bridge] \u6B63\u5728\u81EA\u52A8\u6E05\u7406\u6240\u6709\u5B64\u513F\u8FDB\u7A0B...");for(const t of d)try{if(t.process.pid)if(process.platform==="win32"){const{execSync:n}=await import("node:child_process");n(`taskkill /PID ${t.process.pid} /T /F`,{encoding:"utf8",timeout:3e3}),e.info(`[runtime-bridge] \u5DF2\u81EA\u52A8\u7EC8\u6B62\u5B64\u513F\u8FDB\u7A0B: agentId=${t.agentId}, PID=${t.process.pid}`)}else process.kill(t.process.pid,"SIGKILL"),e.info(`[runtime-bridge] \u5DF2\u81EA\u52A8\u7EC8\u6B62\u5B64\u513F\u8FDB\u7A0B: agentId=${t.agentId}, PID=${t.process.pid}`)}catch(n){const c=n;e.warn(`[runtime-bridge] \u81EA\u52A8\u7EC8\u6B62\u5B64\u513F\u8FDB\u7A0B\u5931\u8D25: agentId=${t.agentId}, PID=${t.process.pid}, error=${c.message}`)}await new Promise(t=>setTimeout(t,1e3))}await k([]),e.info(`[runtime-bridge] \u2605 \u542F\u52A8\u6E05\u7406\u5B8C\u6210\uFF0C\u5DF2\u6E05\u7A7A\u6240\u6709\u6301\u4E45\u5316\u4F1A\u8BDD\uFF08\u6E05\u7406\u4E86 ${d.length} \u4E2A\u5B64\u513F\u8FDB\u7A0B\uFF09`)}catch(r){const i=r;e.warn("[runtime-bridge] \u542F\u52A8\u65F6\u5B64\u513F\u8FDB\u7A0B\u68C0\u6D4B\u5931\u8D25:",i.message)}}_e().catch(r=>{e.warn("[runtime-bridge] \u5B64\u513F\u8FDB\u7A0B\u68C0\u6D4B\u51FA\u9519:",r)}),Ce().catch(r=>{e.warn("[runtime-bridge] \u542F\u52A8\u5B64\u513F\u8FDB\u7A0B\u76D1\u63A7\u5931\u8D25:",r)});const o=I();o.use(M({origin(r,i){if(!r){i(null,!0);return}if(y.includes("*")){i(null,!0);return}if(y.includes(r)){i(null,!0);return}try{if(new URL(r).port===String(h)){i(null,!0);return}}catch{}i(new Error(`[runtime-bridge] CORS origin is not allowed: ${r}`))}})),o.use(I.json({limit:"1mb"})),o.use("/runtime",ae),o.use("/runtime",te),o.use("/runtime",le),o.use("/runtime",V),o.use("/runtime",ce),o.use("/runtime",ee),o.use("/runtime",fe),o.use("/runtime",ue),o.use("/runtime",re),o.use("/runtime",ge),o.use("/runtime",J),o.use("/runtime",Q),o.use("/runtime",Z),o.use("/runtime",K),o.use("/runtime",N),o.use("/runtime",de),o.use("/runtime",W),o.use("/runtime",j),o.use("/runtime",L),o.use("/runtime",pe),o.use("/runtime",H),o.use("/runtime",q),o.use("/runtime/computer",U),o.use("/pty",ne),o.use("/",z),o.use("/api/file-browser",Y);function Fe(){const r=o.listen(h,()=>{e.info(`[runtime-bridge] listening on ${h}`)});return ie(r),F(r),G(r),r.on("error",i=>{if(i.code==="EADDRINUSE"){e.error(`[runtime-bridge] port ${h} is already in use. Stop the existing runtime-bridge process or set AWS_RUNTIME_BRIDGE_PORT to another port before starting.`),process.exitCode=1;return}e.error("[runtime-bridge] failed to start server:",i),process.exitCode=1}),r}let v=!1;async function b(r,i,s=!1){if(v){e.info(`[runtime-bridge] \u5DF2\u5728\u5173\u95ED\u4E2D\uFF0C\u5FFD\u7565 ${r}`);return}v=!0,s||R()&&(s=!0,e.info("[runtime-bridge] \u2605 \u68C0\u6D4B\u5230 .preserve-sessions \u6807\u8BB0\u6587\u4EF6\uFF0C\u81EA\u52A8\u5347\u7EA7\u4E3A\u4FDD\u7559\u4F1A\u8BDD\u6A21\u5F0F")),e.info(`[runtime-bridge] \u6536\u5230 ${r} \u4FE1\u53F7\uFF0C\u5F00\u59CB\u4F18\u96C5\u5173\u95ED... (preserveSessions=${s})`);const f=Date.now(),u=Ae(s);try{const a=S.size;if(u.terminateSdkSessions){e.info(`[runtime-bridge] \u6B63\u5728\u505C\u6B62 ${a} \u4E2A SDK \u4F1A\u8BDD...`);for(const[t,n]of S.entries())try{const c=n.providerId||"claude-code",g=O.get(c);let p="";try{const m=g.getSessionPid?.(t);m&&(p=` (PID: ${m})`)}catch{}await g.terminateSession(t),e.info(`[runtime-bridge] SDK \u4F1A\u8BDD ${t} \u5DF2\u7EC8\u6B62${p}`),S.delete(t)}catch(c){const g=c;e.error(`[runtime-bridge] SDK \u4F1A\u8BDD ${t} \u7EC8\u6B62\u5931\u8D25:`,g.message),S.delete(t)}}else e.info(`[runtime-bridge] \u2605 \u4FDD\u7559 ${a} \u4E2A SDK \u4F1A\u8BDD\uFF0C\u4E0D\u5728\u91CD\u542F\u6062\u590D\u573A\u666F\u4E2D\u7EC8\u6B62 Agent \u8FDB\u7A0B`);const l=se.size;u.closePtySessions?(e.info(`[runtime-bridge] \u6B63\u5728\u505C\u6B62 ${l} \u4E2A PTY \u4F1A\u8BDD...`),oe("shutdown")):e.info(`[runtime-bridge] \u2605 \u4FDD\u7559 ${l} \u4E2A PTY \u4F1A\u8BDD\uFF0C\u4E0D\u5728\u91CD\u542F\u6062\u590D\u573A\u666F\u4E2D\u5173\u95ED\u7EC8\u7AEF\u8FDB\u7A0B`),e.info("[runtime-bridge] \u6B63\u5728\u505C\u6B62\u5B64\u513F\u8FDB\u7A0B\u76D1\u63A7...");try{const c=(await import("file://"+require("path").join(__dirname,"..","package","acode","dist","background-command-manager.js")).catch(()=>({backgroundCommandManager:null}))).backgroundCommandManager;c&&(e.info("[runtime-bridge] \u6B63\u5728\u6E05\u7406\u540E\u53F0\u547D\u4EE4..."),await c.disposeAll())}catch(t){e.warn("[runtime-bridge] \u540E\u53F0\u547D\u4EE4\u6E05\u7406\u5931\u8D25: %s",t?.message)}if(ve(),u.terminateResidualProcesses&&(e.info("[runtime-bridge] \u7B49\u5F85\u8FDB\u7A0B\u5B8C\u5168\u7EC8\u6B62..."),await new Promise(t=>setTimeout(t,1e3))),u.terminateResidualProcesses)try{const{detectOrphanProcesses:t,terminateProcessTree:n}=await import("./services/process-detector.js"),{loadPersistedSessions:c}=await import("./services/terminal-persistence.js"),g=await c();if(g.length>0){const p=await t(g.map(m=>({agentId:m.agentId,pid:m.pid,workspacePath:m.workspacePath,command:m.command})));if(p.length>0){e.warn(`[runtime-bridge] \u68C0\u6D4B\u5230 ${p.length} \u4E2A\u6B8B\u7559\u8FDB\u7A0B\uFF0C\u6B63\u5728\u5F3A\u5236\u7EC8\u6B62...`);for(const m of p)if(m.process.pid){const A=await n(m.process.pid);e.info(`[runtime-bridge] \u5DF2\u5F3A\u5236\u7EC8\u6B62\u6B8B\u7559\u8FDB\u7A0B\u6811\uFF1AagentId=${m.agentId}, PID=${m.process.pid}, \u7EC8\u6B62\u4E86 ${A.terminated} \u4E2A\u8FDB\u7A0B`)}}}}catch(t){const n=t;e.warn("[runtime-bridge] \u6B8B\u7559\u8FDB\u7A0B\u68C0\u67E5\u5931\u8D25:",n.message)}else e.info("[runtime-bridge] \u2605 \u4FDD\u7559\u6A21\u5F0F\u8DF3\u8FC7\u6B8B\u7559\u8FDB\u7A0B\u5F3A\u5236\u7EC8\u6B62\uFF0C\u7B49\u5F85\u8C03\u5EA6\u4E2D\u5FC3\u6062\u590D\u4F1A\u8BDD");if(u.unregisterInstance){e.info("[runtime-bridge] \u6B63\u5728\u6CE8\u9500\u5B9E\u4F8B...");try{await he()}catch(t){const n=t;e.error("[runtime-bridge] \u6CE8\u9500\u5B9E\u4F8B\u5931\u8D25:",n.message)}}else e.info("[runtime-bridge] \u2605 \u4FDD\u7559\u6A21\u5F0F\u8DF3\u8FC7\u5B9E\u4F8B\u6CE8\u9500\uFF0C\u907F\u514D\u8C03\u5EA6\u4E2D\u5FC3\u5C06 Agent \u6807\u8BB0\u4E3A stopped");if(Re(),u.stopTunnelClient&&(e.info("[runtime-bridge] \u6B63\u5728\u5173\u95ED\u96A7\u9053\u5BA2\u6237\u7AEF..."),xe()),u.clearPersistedSessions){e.info("[runtime-bridge] \u6B63\u5728\u6E05\u7A7A\u6301\u4E45\u5316\u4F1A\u8BDD\u72B6\u6001...");try{await k([])}catch(t){const n=t;e.error("[runtime-bridge] \u6E05\u7A7A\u6301\u4E45\u5316\u72B6\u6001\u5931\u8D25:",n.message)}}else e.info("[runtime-bridge] \u2605 \u4FDD\u7559\u6301\u4E45\u5316\u4F1A\u8BDD\u72B6\u6001\uFF08aws-mcp-server \u91CD\u542F\u6062\u590D\u573A\u666F\uFF09");try{await ye(),e.info("[runtime-bridge] browser \u5DE5\u5177\u6808\u5DF2\u6E05\u7406")}catch(t){e.warn("[runtime-bridge] browser \u5DE5\u5177\u6808\u6E05\u7406\u5931\u8D25:",t.message)}const d=Date.now()-f;e.info(`[runtime-bridge] \u4F18\u96C5\u5173\u95ED\u5B8C\u6210 (\u8017\u65F6 ${d}ms)\uFF0C\u505C\u6B62 ${a} SDK \u4F1A\u8BDD`),i.close(()=>{e.info("[runtime-bridge] HTTP \u670D\u52A1\u5668\u5DF2\u5173\u95ED"),process.exit(process.exitCode||0)}),setTimeout(()=>{e.warn("[runtime-bridge] \u4F18\u96C5\u5173\u95ED\u8D85\u65F6\uFF0C\u5F3A\u5236\u9000\u51FA"),process.exit(1)},15e3)}catch(a){const l=a;e.error("[runtime-bridge] \u4F18\u96C5\u5173\u95ED\u8FC7\u7A0B\u4E2D\u53D1\u751F\u9519\u8BEF:",l),process.exit(1)}}Ee.reconcileZombies().then(r=>{r>0&&e.info(`[runtime-bridge] \u5DF2\u6E05\u7406 ${r} \u4E2A\u50F5\u5C38 sub-agent \u8BB0\u5F55`)}).catch(r=>{e.warn("[runtime-bridge] \u6E05\u7406\u50F5\u5C38 sub-agent \u8BB0\u5F55\u5931\u8D25:",r.message)});const w=Fe(),T=x.join(E.tmpdir(),"agentswork-runtime-bridge",".preserve-sessions");function R(){try{if(D(T)){const r=B(T,"utf-8");return JSON.parse(r).preserveSessions===!0}}catch{}return!1}X(async(r,i)=>{await b(r,w,i)}),process.on("SIGTERM",()=>{const r=R();b("SIGTERM",w,r)}),process.on("SIGINT",()=>{const r=R();b("SIGINT",w,r)}),process.platform==="win32"&&process.on("SIGBREAK",()=>{const r=R();b("SIGBREAK",w,r)}),process.on("uncaughtException",r=>{e.error("[runtime-bridge] \u672A\u6355\u83B7\u5F02\u5E38:",r),b("uncaughtException",w)}),process.on("unhandledRejection",r=>{e.error("[runtime-bridge] \u672A\u5904\u7406\u7684 Promise \u62D2\u7EDD:",r)});
2
+ import{existsSync as D,readFileSync as B}from"node:fs";import E from"node:os";import M from"node:path";import x from"cors";import I from"express";import{adapterRegistry as O}from"./adapter/index.js";import{attachBrowserWebSocketServer as G}from"./browser/browser-ws-bridge.js";import{allowedCorsOrigins as y,port as h,validateProductionToken as _}from"./config.js";import{attachRemoteDesktopWebSocketServer as F}from"./remote-desktop/index.js";import{acodeTodosRouter as K}from"./routes/acode-todos.js";import{aiSourcesRouter as L}from"./routes/ai-sources.js";import{backgroundTasksRouter as j}from"./routes/background-tasks.js";import{browserListenersRouter as N}from"./routes/browser-listeners.js";import{browserSessionsRouter as W}from"./routes/browser-sessions.js";import{computerRouter as U}from"./routes/computer.js";import{dashboardRouter as z}from"./routes/dashboard.js";import{eventsRouter as J}from"./routes/events.js";import{fileBrowserRouter as Y}from"./routes/file-browser.js";import{fileChangeDiffRouter as q}from"./routes/file-change-diff.js";import{fileDiffRollbackRouter as H}from"./routes/file-diff-rollback.js";import{fileSnapshotRouter as Z}from"./routes/file-snapshot.js";import{gitRouter as Q}from"./routes/git.js";import{instanceRouter as V,setGracefulShutdownFn as X}from"./routes/instance.js";import{jdksRouter as ee}from"./routes/jdks.js";import{launchRouter as re}from"./routes/launch.js";import{mcpRouter as te}from"./routes/mcp.js";import{processesRouter as ie}from"./routes/processes.js";import{propertiesRouter as oe}from"./routes/properties.js";import{attachPtyWebSocketServer as ne,closeAllPtySessions as se,ptyRouter as ae,ptySessions as me}from"./routes/pty.js";import{runtimeBindingRouter as ue,logRuntimeBindingStartupState as ce}from"./routes/runtime-binding.js";import{sessionsRouter as de}from"./routes/sessions.js";import{skillsRouter as ge}from"./routes/skills.js";import{subAgentsRouter as pe}from"./routes/sub-agents.js";import{systemMetricsRouter as fe}from"./routes/system-metrics.js";import{taskContextRouter as le}from"./routes/task-context.js";import{terminalRouter as be,sdkSessions as S}from"./routes/terminal.js";import{ymlRouter as we}from"./routes/yml.js";import{ensureAcodePackageReleased as he}from"./services/acode-package.js";import{getAgentProcessManager as P}from"./services/agent-process-manager.js";import{autoRegister as Se,unregister as Re,bridgeRestartCleanup as Ie,cancelBackgroundRegisterRetries as ye}from"./services/auto-register.js";import{ensureAwsClientAgentMcpReleased as Pe}from"./services/aws-client-agent-mcp.js";import{disposeBrowserSessionManager as $e}from"./services/browser-session-manager.js";import{handleCliCommand as ke}from"./services/cli-commands.js";import{disposeDesktopMcpManager as Ce}from"./services/desktop-mcp-manager.js";import{getLaunchManager as ve}from"./services/launch-manager.js";import{migrateOldPathsOnStartup as Te}from"./services/legacy-path-migration.js";import{deliverPendingBridgeLifecycleNotifications as Ae}from"./services/lifecycle-state.js";import{startOrphanMonitor as De,stopOrphanMonitor as Be}from"./services/orphan-monitor.js";import{detectOrphanProcesses as Ee}from"./services/process-detector.js";import{buildGracefulShutdownPlan as Me,shouldCleanupPersistedSessionsOnStartup as xe}from"./services/runtime-lifecycle-policy.js";import{ensureStartupConfig as Oe}from"./services/startup-config-wizard.js";import{subAgentStore as Ge}from"./services/sub-agent-manager-instance.js";import{loadPersistedSessions as $,savePersistedSessions as k}from"./services/terminal-persistence.js";import{stopTunnelClient as _e}from"./services/tunnel-client.js";import{logger as e}from"./utils/logger.js";const C=await ke();C.handled&&process.exit(C.exitCode),await Oe(),_(),Pe(),he(),Te(),ce();async function Fe(){try{if(await Se())e.info("[runtime-bridge] \u81EA\u52A8\u6CE8\u518C\u6210\u529F");else{const{loadConfig:o}=await import("./services/auto-register.js"),s=o();!!s.userKey&&s.enabled?e.warn("[runtime-bridge] \u81EA\u52A8\u6CE8\u518C\u6682\u65F6\u5931\u8D25\uFF0C\u5C06\u5728\u540E\u53F0\u6301\u7EED\u91CD\u8BD5\u76F4\u5230\u4E0E\u8C03\u5EA6\u4E2D\u5FC3\u5EFA\u7ACB\u8FDE\u63A5"):(e.info("[runtime-bridge] \u81EA\u52A8\u6CE8\u518C\u672A\u914D\u7F6E\uFF0C\u7EE7\u7EED\u542F\u52A8\u670D\u52A1"),e.info("[runtime-bridge] \u8981\u542F\u7528\u81EA\u52A8\u6CE8\u518C\uFF0C\u8BF7\u521B\u5EFA\u914D\u7F6E\u6587\u4EF6 ~/.aws-bridge/config.json"))}}catch(r){const o=r;e.warn("[runtime-bridge] \u81EA\u52A8\u6CE8\u518C\u51FA\u9519:",o.message)}}Fe().then(()=>Ke()).then(()=>Ae()).then(()=>{e.info("[runtime-bridge] Bridge \u751F\u547D\u5468\u671F\u901A\u77E5\u540C\u6B65\u5B8C\u6210")}).catch(r=>{e.warn("[runtime-bridge] \u81EA\u52A8\u6CE8\u518C\u6216\u6E05\u7406\u51FA\u9519:",r)}),import("./services/update-notifier.js").then(({startUpdateNotifier:r})=>r()).catch(r=>{e.warn("[runtime-bridge] \u7248\u672C\u68C0\u67E5\u670D\u52A1\u542F\u52A8\u5931\u8D25:",r.message)});async function Ke(){try{if(process.env.AWS_BRIDGE_RESTART_CLEANUP!=="true"){e.info("[runtime-bridge] \u8DF3\u8FC7 Bridge \u91CD\u542F\u6E05\u7406\uFF0C\u4FDD\u7559 Agent \u72B6\u6001\u4F9B\u8C03\u5EA6\u4E2D\u5FC3\u6062\u590D");return}const{getRegistrationState:r}=await import("./services/auto-register.js"),o=r();if(!o.registered||!o.instanceId){e.info("[runtime-bridge] \u5B9E\u4F8B\u672A\u6CE8\u518C\uFF0C\u8DF3\u8FC7 Bridge \u91CD\u542F\u6E05\u7406");return}e.info("[runtime-bridge] \u2605 Bridge \u91CD\u542F\u6E05\u7406\u5DF2\u663E\u5F0F\u542F\u7528\uFF0C\u6B63\u5728\u901A\u77E5\u8C03\u5EA6\u4E2D\u5FC3\u6E05\u7406 Agent \u72B6\u6001...");const s=await Ie();s.success?e.info(`[runtime-bridge] \u2605 Bridge \u91CD\u542F\u6E05\u7406\u6210\u529F\uFF0C\u6E05\u7406\u4E86 ${s.agentCount||0} \u4E2A Agent`):e.warn(`[runtime-bridge] \u2605 Bridge \u91CD\u542F\u6E05\u7406\u5931\u8D25: ${s.error}`)}catch(r){const o=r;e.warn("[runtime-bridge] Bridge \u91CD\u542F\u6E05\u7406\u51FA\u9519:",o.message)}}async function Le(){try{const r=await $();if(r.length===0){e.info("[runtime-bridge] \u65E0\u6301\u4E45\u5316\u4F1A\u8BDD\uFF0C\u8DF3\u8FC7\u8FDB\u7A0B\u6CE8\u518C\u8868\u91CD\u5EFA");return}await P().rebuildRegistry(r.map(s=>({agentId:s.agentId,sessionId:s.sessionId,pid:s.pid,workspacePath:s.workspacePath,command:s.command,mode:"sdk"}))),e.info(`[runtime-bridge] \u8FDB\u7A0B\u6CE8\u518C\u8868\u91CD\u5EFA\u5B8C\u6210: ${r.length} \u6761\u8BB0\u5F55`)}catch(r){const o=r;e.warn("[runtime-bridge] \u8FDB\u7A0B\u6CE8\u518C\u8868\u91CD\u5EFA\u5931\u8D25:",o.message)}}Le().catch(r=>{e.warn("[runtime-bridge] \u8FDB\u7A0B\u6CE8\u518C\u8868\u91CD\u5EFA\u51FA\u9519:",r)});async function je(){try{const r=await import("node:fs/promises"),o=await import("node:path"),s=await import("node:os"),f=o.join(s.tmpdir(),"agentswork-runtime-bridge",".preserve-sessions");let u=!1;try{const t=await r.readFile(f,"utf-8");u=JSON.parse(t).preserveSessions===!0,await r.unlink(f),e.info(`[runtime-bridge] \u2605 \u68C0\u6D4B\u5230\u4FDD\u7559\u4F1A\u8BDD\u6807\u8BB0\uFF0CpreserveSessions=${u}`)}catch{}const a=await $();if(a.length===0){e.info("[runtime-bridge] \u542F\u52A8\u65F6\u65E0\u6301\u4E45\u5316\u4F1A\u8BDD\uFF0C\u65E0\u9700\u68C0\u6D4B\u5B64\u513F\u8FDB\u7A0B");return}if(!xe(u)){e.info(`[runtime-bridge] \u2605 \u4FDD\u7559 ${a.length} \u4E2A\u6301\u4E45\u5316\u4F1A\u8BDD\u5E76\u91CD\u5EFA\u8FDB\u7A0B\u6CE8\u518C\u8868`),await P().rebuildRegistry(a.map(n=>({agentId:n.agentId,sessionId:n.sessionId,pid:n.pid,workspacePath:n.workspacePath,command:n.command,mode:"sdk"})));return}e.info(`[runtime-bridge] \u2605 \u5DF2\u663E\u5F0F\u542F\u7528\u542F\u52A8\u6E05\u7406\uFF0C\u68C0\u6D4B ${a.length} \u4E2A\u6301\u4E45\u5316\u4F1A\u8BDD\u7684\u5B64\u513F\u8FDB\u7A0B...`);const d=await Ee(a.map(t=>({agentId:t.agentId,pid:t.pid,workspacePath:t.workspacePath,command:t.command})));if(d.length>0){e.warn(`[runtime-bridge] \u2605 \u68C0\u6D4B\u5230 ${d.length} \u4E2A\u5B64\u513F\u8FDB\u7A0B\u4ECD\u5728\u8FD0\u884C\uFF1A`);for(const t of d)e.warn(` - agentId=${t.agentId}, pid=${t.process.pid}, command=${t.process.command?.slice(0,50)}`);e.info("[runtime-bridge] \u6B63\u5728\u81EA\u52A8\u6E05\u7406\u6240\u6709\u5B64\u513F\u8FDB\u7A0B...");for(const t of d)try{if(t.process.pid)if(process.platform==="win32"){const{execSync:n}=await import("node:child_process");n(`taskkill /PID ${t.process.pid} /T /F`,{encoding:"utf8",timeout:3e3}),e.info(`[runtime-bridge] \u5DF2\u81EA\u52A8\u7EC8\u6B62\u5B64\u513F\u8FDB\u7A0B: agentId=${t.agentId}, PID=${t.process.pid}`)}else process.kill(t.process.pid,"SIGKILL"),e.info(`[runtime-bridge] \u5DF2\u81EA\u52A8\u7EC8\u6B62\u5B64\u513F\u8FDB\u7A0B: agentId=${t.agentId}, PID=${t.process.pid}`)}catch(n){const c=n;e.warn(`[runtime-bridge] \u81EA\u52A8\u7EC8\u6B62\u5B64\u513F\u8FDB\u7A0B\u5931\u8D25: agentId=${t.agentId}, PID=${t.process.pid}, error=${c.message}`)}await new Promise(t=>setTimeout(t,1e3))}await k([]),e.info(`[runtime-bridge] \u2605 \u542F\u52A8\u6E05\u7406\u5B8C\u6210\uFF0C\u5DF2\u6E05\u7A7A\u6240\u6709\u6301\u4E45\u5316\u4F1A\u8BDD\uFF08\u6E05\u7406\u4E86 ${d.length} \u4E2A\u5B64\u513F\u8FDB\u7A0B\uFF09`)}catch(r){const o=r;e.warn("[runtime-bridge] \u542F\u52A8\u65F6\u5B64\u513F\u8FDB\u7A0B\u68C0\u6D4B\u5931\u8D25:",o.message)}}je().catch(r=>{e.warn("[runtime-bridge] \u5B64\u513F\u8FDB\u7A0B\u68C0\u6D4B\u51FA\u9519:",r)}),De().catch(r=>{e.warn("[runtime-bridge] \u542F\u52A8\u5B64\u513F\u8FDB\u7A0B\u76D1\u63A7\u5931\u8D25:",r)});const i=I();i.use(x({origin(r,o){if(!r){o(null,!0);return}if(y.includes("*")){o(null,!0);return}if(y.includes(r)){o(null,!0);return}try{if(new URL(r).port===String(h)){o(null,!0);return}}catch{}o(new Error(`[runtime-bridge] CORS origin is not allowed: ${r}`))}})),i.use(I.json({limit:"1mb"})),i.use("/runtime",ue),i.use("/runtime",oe),i.use("/runtime",we),i.use("/runtime",V),i.use("/runtime",ge),i.use("/runtime",te),i.use("/runtime",be),i.use("/runtime",de),i.use("/runtime",ie),i.use("/runtime",re),i.use("/runtime",ee),i.use("/runtime",fe),i.use("/runtime",J),i.use("/runtime",Q),i.use("/runtime",Z),i.use("/runtime",K),i.use("/runtime",j),i.use("/runtime",pe),i.use("/runtime",W),i.use("/runtime",N),i.use("/runtime",L),i.use("/runtime",le),i.use("/runtime",H),i.use("/runtime",q),i.use("/runtime/computer",U),i.use("/pty",ae),i.use("/",z),i.use("/api/file-browser",Y);function Ne(){const r=i.listen(h,()=>{e.info(`[runtime-bridge] listening on ${h}`)});return ne(r),F(r),G(r),r.on("error",o=>{if(o.code==="EADDRINUSE"){e.error(`[runtime-bridge] port ${h} is already in use. Stop the existing runtime-bridge process or set AWS_RUNTIME_BRIDGE_PORT to another port before starting.`),process.exitCode=1;return}e.error("[runtime-bridge] failed to start server:",o),process.exitCode=1}),r}let v=!1;async function b(r,o,s=!1){if(v){e.info(`[runtime-bridge] \u5DF2\u5728\u5173\u95ED\u4E2D\uFF0C\u5FFD\u7565 ${r}`);return}v=!0,s||R()&&(s=!0,e.info("[runtime-bridge] \u2605 \u68C0\u6D4B\u5230 .preserve-sessions \u6807\u8BB0\u6587\u4EF6\uFF0C\u81EA\u52A8\u5347\u7EA7\u4E3A\u4FDD\u7559\u4F1A\u8BDD\u6A21\u5F0F")),e.info(`[runtime-bridge] \u6536\u5230 ${r} \u4FE1\u53F7\uFF0C\u5F00\u59CB\u4F18\u96C5\u5173\u95ED... (preserveSessions=${s})`);const f=Date.now(),u=Me(s);try{const a=S.size;if(u.terminateSdkSessions){e.info(`[runtime-bridge] \u6B63\u5728\u505C\u6B62 ${a} \u4E2A SDK \u4F1A\u8BDD...`);for(const[t,n]of S.entries())try{const c=n.providerId||"claude-code",g=O.get(c);let p="";try{const m=g.getSessionPid?.(t);m&&(p=` (PID: ${m})`)}catch{}await g.terminateSession(t),e.info(`[runtime-bridge] SDK \u4F1A\u8BDD ${t} \u5DF2\u7EC8\u6B62${p}`),S.delete(t)}catch(c){const g=c;e.error(`[runtime-bridge] SDK \u4F1A\u8BDD ${t} \u7EC8\u6B62\u5931\u8D25:`,g.message),S.delete(t)}}else e.info(`[runtime-bridge] \u2605 \u4FDD\u7559 ${a} \u4E2A SDK \u4F1A\u8BDD\uFF0C\u4E0D\u5728\u91CD\u542F\u6062\u590D\u573A\u666F\u4E2D\u7EC8\u6B62 Agent \u8FDB\u7A0B`);const l=me.size;u.closePtySessions?(e.info(`[runtime-bridge] \u6B63\u5728\u505C\u6B62 ${l} \u4E2A PTY \u4F1A\u8BDD...`),se("shutdown")):e.info(`[runtime-bridge] \u2605 \u4FDD\u7559 ${l} \u4E2A PTY \u4F1A\u8BDD\uFF0C\u4E0D\u5728\u91CD\u542F\u6062\u590D\u573A\u666F\u4E2D\u5173\u95ED\u7EC8\u7AEF\u8FDB\u7A0B`);try{const{running:t,stopped:n}=await ve().stopAll();t>0&&e.info(`[runtime-bridge] \u542F\u52A8\u9879\u8FDB\u7A0B\u5DF2\u505C\u6B62: running=${t}, stopped=${n}`)}catch(t){e.warn("[runtime-bridge] \u505C\u6B62\u542F\u52A8\u9879\u8FDB\u7A0B\u5931\u8D25:",t.message)}e.info("[runtime-bridge] \u6B63\u5728\u505C\u6B62\u5B64\u513F\u8FDB\u7A0B\u76D1\u63A7...");try{const c=(await import("file://"+require("path").join(__dirname,"..","package","acode","dist","background-command-manager.js")).catch(()=>({backgroundCommandManager:null}))).backgroundCommandManager;c&&(e.info("[runtime-bridge] \u6B63\u5728\u6E05\u7406\u540E\u53F0\u547D\u4EE4..."),await c.disposeAll())}catch(t){e.warn("[runtime-bridge] \u540E\u53F0\u547D\u4EE4\u6E05\u7406\u5931\u8D25: %s",t?.message)}if(Be(),u.terminateResidualProcesses&&(e.info("[runtime-bridge] \u7B49\u5F85\u8FDB\u7A0B\u5B8C\u5168\u7EC8\u6B62..."),await new Promise(t=>setTimeout(t,1e3))),u.terminateResidualProcesses)try{const{detectOrphanProcesses:t,terminateProcessTree:n}=await import("./services/process-detector.js"),{loadPersistedSessions:c}=await import("./services/terminal-persistence.js"),g=await c();if(g.length>0){const p=await t(g.map(m=>({agentId:m.agentId,pid:m.pid,workspacePath:m.workspacePath,command:m.command})));if(p.length>0){e.warn(`[runtime-bridge] \u68C0\u6D4B\u5230 ${p.length} \u4E2A\u6B8B\u7559\u8FDB\u7A0B\uFF0C\u6B63\u5728\u5F3A\u5236\u7EC8\u6B62...`);for(const m of p)if(m.process.pid){const A=await n(m.process.pid);e.info(`[runtime-bridge] \u5DF2\u5F3A\u5236\u7EC8\u6B62\u6B8B\u7559\u8FDB\u7A0B\u6811\uFF1AagentId=${m.agentId}, PID=${m.process.pid}, \u7EC8\u6B62\u4E86 ${A.terminated} \u4E2A\u8FDB\u7A0B`)}}}}catch(t){const n=t;e.warn("[runtime-bridge] \u6B8B\u7559\u8FDB\u7A0B\u68C0\u67E5\u5931\u8D25:",n.message)}else e.info("[runtime-bridge] \u2605 \u4FDD\u7559\u6A21\u5F0F\u8DF3\u8FC7\u6B8B\u7559\u8FDB\u7A0B\u5F3A\u5236\u7EC8\u6B62\uFF0C\u7B49\u5F85\u8C03\u5EA6\u4E2D\u5FC3\u6062\u590D\u4F1A\u8BDD");if(u.unregisterInstance){e.info("[runtime-bridge] \u6B63\u5728\u6CE8\u9500\u5B9E\u4F8B...");try{await Re()}catch(t){const n=t;e.error("[runtime-bridge] \u6CE8\u9500\u5B9E\u4F8B\u5931\u8D25:",n.message)}}else e.info("[runtime-bridge] \u2605 \u4FDD\u7559\u6A21\u5F0F\u8DF3\u8FC7\u5B9E\u4F8B\u6CE8\u9500\uFF0C\u907F\u514D\u8C03\u5EA6\u4E2D\u5FC3\u5C06 Agent \u6807\u8BB0\u4E3A stopped");if(ye(),u.stopTunnelClient&&(e.info("[runtime-bridge] \u6B63\u5728\u5173\u95ED\u96A7\u9053\u5BA2\u6237\u7AEF..."),_e()),u.clearPersistedSessions){e.info("[runtime-bridge] \u6B63\u5728\u6E05\u7A7A\u6301\u4E45\u5316\u4F1A\u8BDD\u72B6\u6001...");try{await k([])}catch(t){const n=t;e.error("[runtime-bridge] \u6E05\u7A7A\u6301\u4E45\u5316\u72B6\u6001\u5931\u8D25:",n.message)}}else e.info("[runtime-bridge] \u2605 \u4FDD\u7559\u6301\u4E45\u5316\u4F1A\u8BDD\u72B6\u6001\uFF08aws-mcp-server \u91CD\u542F\u6062\u590D\u573A\u666F\uFF09");try{await $e(),e.info("[runtime-bridge] browser \u5DE5\u5177\u6808\u5DF2\u6E05\u7406")}catch(t){e.warn("[runtime-bridge] browser \u5DE5\u5177\u6808\u6E05\u7406\u5931\u8D25:",t.message)}try{await Ce(),e.info("[runtime-bridge] desktop \u5DE5\u5177\u6808\u5DF2\u6E05\u7406")}catch(t){e.warn("[runtime-bridge] desktop \u5DE5\u5177\u6808\u6E05\u7406\u5931\u8D25:",t.message)}const d=Date.now()-f;e.info(`[runtime-bridge] \u4F18\u96C5\u5173\u95ED\u5B8C\u6210 (\u8017\u65F6 ${d}ms)\uFF0C\u505C\u6B62 ${a} SDK \u4F1A\u8BDD`),o.close(()=>{e.info("[runtime-bridge] HTTP \u670D\u52A1\u5668\u5DF2\u5173\u95ED"),process.exit(process.exitCode||0)}),setTimeout(()=>{e.warn("[runtime-bridge] \u4F18\u96C5\u5173\u95ED\u8D85\u65F6\uFF0C\u5F3A\u5236\u9000\u51FA"),process.exit(1)},15e3)}catch(a){const l=a;e.error("[runtime-bridge] \u4F18\u96C5\u5173\u95ED\u8FC7\u7A0B\u4E2D\u53D1\u751F\u9519\u8BEF:",l),process.exit(1)}}Ge.reconcileZombies().then(r=>{r>0&&e.info(`[runtime-bridge] \u5DF2\u6E05\u7406 ${r} \u4E2A\u50F5\u5C38 sub-agent \u8BB0\u5F55`)}).catch(r=>{e.warn("[runtime-bridge] \u6E05\u7406\u50F5\u5C38 sub-agent \u8BB0\u5F55\u5931\u8D25:",r.message)});const w=Ne(),T=M.join(E.tmpdir(),"agentswork-runtime-bridge",".preserve-sessions");function R(){try{if(D(T)){const r=B(T,"utf-8");return JSON.parse(r).preserveSessions===!0}}catch{}return!1}X(async(r,o)=>{await b(r,w,o)}),process.on("SIGTERM",()=>{const r=R();b("SIGTERM",w,r)}),process.on("SIGINT",()=>{const r=R();b("SIGINT",w,r)}),process.platform==="win32"&&process.on("SIGBREAK",()=>{const r=R();b("SIGBREAK",w,r)}),process.on("uncaughtException",r=>{e.error("[runtime-bridge] \u672A\u6355\u83B7\u5F02\u5E38:",r),b("uncaughtException",w)}),process.on("unhandledRejection",r=>{e.error("[runtime-bridge] \u672A\u5904\u7406\u7684 Promise \u62D2\u7EDD:",r)});
3
3
 
@@ -1,4 +1,4 @@
1
- import{Router as O}from"express";import u from"node:os";import{execSync as j}from"node:child_process";import{getRuntimeBindingPublicState as L,getRuntimePairingCode as _}from"../services/runtime-binding.js";import{getAgentProcessManager as I}from"../services/agent-process-manager.js";import{collectSystemMetrics as F}from"../services/system-metrics.js";import{loadPersistedSessions as V}from"../services/terminal-persistence.js";import{createLogger as q}from"../utils/logger.js";import{getBridgeVersion as K}from"../config.js";import{sdkSessions as W}from"./terminal.js";import{createSession as G,getPanelCredentials as B,validateSession as C,destroySession as A,validateCredentials as J,hasConfiguredCredentials as y,isDefaultCredentials as Y,setPanelCredentials as $,getAllSessions as Q,getActiveSessionCount as X}from"../services/panel-auth.js";import{getRecentLogs as Z}from"../utils/logger.js";import{autoRegister as ee,getRegistrationState as U,getAutoRegisterConfigFilePath as te}from"../services/auto-register.js";const P=q("dashboard");let x=null;const ne=1e4;let E=null,D=null;const se=3e4;async function R(){F().then(n=>{E={data:n,ts:Date.now()}}).catch(()=>{}),I().discoverAllProcesses().then(n=>{D={data:n,ts:Date.now()}}).catch(()=>{}),I().cleanupStaleRegistry().catch(()=>{})}R(),setInterval(R,se);const M=K();async function ae(){const n=await import("node:fs/promises"),e=await import("node:path"),t=await import("node:os"),s=e.join(String(process.env.AWS_RUNTIME_HOME_DIR||t.homedir()).trim()||t.homedir(),".aws-bridge","lifecycle-state.json");try{const r=await n.readFile(s,"utf8"),o=JSON.parse(r),d=Array.isArray(o.operations)?o.operations:[];return d.length===0?null:(d.sort((b,h)=>new Date(h.updatedAt).getTime()-new Date(b.updatedAt).getTime()),d[0]||null)}catch{return null}}const i=O(),N="panel_sid";function oe(n){const e={};if(!n)return e;for(const t of n.split(";")){const s=t.indexOf("=");if(s===-1)continue;const r=t.slice(0,s).trim(),o=t.slice(s+1).trim();r&&(e[r]=o)}return e}function w(n){return oe(n.headers.cookie)[N]}function c(n,e){const t=w(n);return t&&C(t)?!0:n.xhr||n.headers.accept?.includes("application/json")?(e.status(401).json({ok:!1,error:"unauthorized",needLogin:!0}),!1):(e.redirect("/login"),!1)}i.post("/runtime/login",(n,e)=>{const{username:t,password:s}=n.body||{};if(!t||!s){e.status(400).json({ok:!1,error:"username and password required"});return}if(!J(String(t),String(s))){e.status(401).json({ok:!1,error:"invalid credentials"});return}const r=G(),o=!y();e.setHeader("Set-Cookie",`${N}=${r}; HttpOnly; SameSite=Lax; Path=/; Max-Age=86400`),e.json({ok:!0,needSetup:o})}),i.post("/runtime/logout",(n,e)=>{const t=w(n);t&&A(t),e.setHeader("Set-Cookie",`${N}=; HttpOnly; SameSite=Lax; Path=/; Max-Age=0`),e.json({ok:!0})}),i.get("/runtime/credentials-status",(n,e)=>{if(!c(n,e))return;const t=B();e.json({ok:!0,isDefault:Y(),configured:y(),username:t.username})}),i.post("/runtime/change-credentials",(n,e)=>{if(!c(n,e))return;const{username:t,password:s,passwordConfirm:r,oldPassword:o}=n.body||{};if(s&&String(s).trim()){if(String(s)!==String(r)){e.status(400).json({ok:!1,error:"passwords do not match"});return}if(y()){if(!o){e.status(400).json({ok:!1,error:"old password required"});return}const b=B();if(String(o)!==b.password){e.status(403).json({ok:!1,error:"old password is incorrect"});return}}}if(!t||!String(t).trim()){e.status(400).json({ok:!1,error:"username required"});return}const d=s&&String(s).trim()?String(s).trim():B().password;$(String(t).trim(),d),e.json({ok:!0})}),i.get("/runtime/node-info",(n,e)=>{if(!c(n,e))return;const t=L(),s=U();e.json({ok:!0,hostname:u.hostname(),platform:u.platform(),arch:u.arch(),uptime:Math.floor(process.uptime()),nodeVersion:process.version,bridgeVersion:M,configPath:te(),homeDir:process.env.AWS_RUNTIME_HOME_DIR||u.homedir(),bindingStatus:t.status,paired:t.paired,instanceId:t.instanceId||s.instanceId,schedulerBaseUrl:t.schedulerBaseUrl,registered:s.registered,pairingCode:t.pairingCode,activeSessions:X()})}),i.get("/runtime/logs",(n,e)=>{if(!c(n,e))return;const t=n.query.level||void 0,s=Math.min(Number(n.query.count)||200,500),o=t&&["debug","info","warn","error"].includes(t)?t:void 0,d=Z(s,o);e.json({ok:!0,logs:d})}),i.post("/runtime/re-register",async(n,e)=>{if(c(n,e))try{const t=await ee();e.json({ok:t})}catch(t){const s=t;e.status(500).json({ok:!1,error:s.message})}}),i.get("/runtime/admin/sessions",(n,e)=>{if(!c(n,e))return;const t=Q().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:t})}),i.post("/runtime/admin/sessions/:token/destroy",(n,e)=>{c(n,e)&&(A(n.params.token),e.json({ok:!0}))}),i.post("/runtime/kill-process",(n,e)=>{if(!c(n,e))return;const t=Number(n.body.pid);if(!t||!Number.isInteger(t)||t<=0){e.status(400).json({ok:!1,error:"Invalid PID"});return}try{const s=u.platform()==="win32"?`taskkill /PID ${t} /F`:`kill -9 ${t}`;j(s,{timeout:5e3}),P.info("Killed process",{pid:t}),e.json({ok:!0})}catch(s){const r=s;P.warn("Failed to kill process",{pid:t,error:r.message}),e.status(500).json({ok:!1,error:r.message})}}),i.get("/runtime/dashboard",async(n,e)=>{if(c(n,e)){if(x&&Date.now()-x.ts<ne)return void e.json(x.data);try{const t=E?.data??null,s=D?.data??null;E||R();const[r,o,d]=await Promise.all([Promise.resolve(L()),Promise.resolve(U()),V().catch(()=>[])]),h=I().getAllManagedProcesses(),v=Array.from(W.entries()).map(([p,l])=>({sessionId:p,agentId:l.agentId,workspacePath:l.workspacePath,runtimeStatus:l.currentRuntimeStatus||"running",runtimeActionType:l.currentRuntimeActionType,runtimeActionLabel:l.currentRuntimeActionLabel,providerId:l.providerId}));let g={managed:0,orphan:0,unknown:0},k=[];if(s){g={managed:s.filter(a=>a.managementStatus==="managed").length,orphan:s.filter(a=>a.managementStatus==="orphan").length,unknown:s.filter(a=>a.managementStatus==="unknown").length};const p=new Map(h.map(a=>[a.pid,a]));k=s.map(a=>{const f=p.get(a.pid);return{agentId:a.agentId||f?.agentId,pid:a.pid,managementStatus:a.managementStatus,sessionId:f?.sessionId,state:f?.state,command:a.command,cwd:a.cwd,startedAt:a.startedAt}});const l={managed:0,orphan:1,unknown:2};k.sort((a,f)=>(l[a.managementStatus]??9)-(l[f.managementStatus]??9))}const z=new Set(v.map(p=>p.sessionId)),S=d.filter(p=>!z.has(p.sessionId)),m=r,H=await ae(),T={ok:!0,collectedAt:new Date().toISOString(),bridge:{version:M,hostname:u.hostname(),platform:u.platform(),arch:u.arch(),uptime:Math.floor(process.uptime()),nodeVersion:process.version},binding:{status:m.status,paired:m.paired,instanceId:m.instanceId,userId:m.userId,schedulerBaseUrl:m.schedulerBaseUrl,pairingCode:_()},registration:{registered:o.registered,instanceId:o.instanceId,lastAttempt:o.lastAttempt?.toISOString(),error:o.error},metrics:t,sessions:{active:v,activeCount:v.length,persisted:S,persistedCount:S.length,total:v.length+S.length},processes:{...g,total:g.managed+g.orphan+g.unknown,list:k,registryCount:h.length},lifecycleOperation:H};x={data:T,ts:Date.now()},e.json(T)}catch(t){const s=t;P.error("Dashboard aggregation failed:",s.message),e.status(500).json({ok:!1,error:s.message})}}}),i.get("/login",(n,e)=>{e.type("text/html; charset=utf-8"),e.send(ie)}),i.get("/",(n,e)=>{const t=w(n);if(!t||!C(t)){e.redirect("/login");return}if(!y()){e.redirect("/setup");return}const s=n.query.lang==="en"?"en":"zh";e.type("text/html; charset=utf-8"),e.send(re.replace("{{LANG}}",s))}),i.get("/setup",(n,e)=>{const t=w(n);if(!t||!C(t)){e.redirect("/login");return}e.type("text/html; charset=utf-8"),e.send(de)});const re=`<!DOCTYPE html>
1
+ import{Router as F}from"express";import p from"node:os";import{execFile as _}from"node:child_process";import{getRuntimeBindingPublicState as T,getRuntimePairingCode as V}from"../services/runtime-binding.js";import{getAgentProcessManager as y}from"../services/agent-process-manager.js";import{collectSystemMetrics as K}from"../services/system-metrics.js";import{loadPersistedSessions as W}from"../services/terminal-persistence.js";import{createLogger as G}from"../utils/logger.js";import{getBridgeVersion as q}from"../config.js";import{sdkSessions as J}from"./terminal.js";import{createSession as Y,getPanelCredentials as P,validateSession as E,destroySession as L,validateCredentials as U,hasConfiguredCredentials as x,isDefaultCredentials as $,setPanelCredentials as Q,getAllSessions as X,getActiveSessionCount as Z,checkLoginAllowed as ee,recordLoginFailure as te,recordLoginSuccess as ne}from"../services/panel-auth.js";import{getRecentLogs as se}from"../utils/logger.js";import{autoRegister as ae,getRegistrationState as D,getAutoRegisterConfigFilePath as oe}from"../services/auto-register.js";const w=G("dashboard");let k=null;const re=1e4;let R=null,S=null;const ie=3e4;async function N(){K().then(n=>{R={data:n,ts:Date.now()}}).catch(()=>{}),y().discoverAllProcesses().then(n=>{S={data:n,ts:Date.now()}}).catch(()=>{}),y().cleanupStaleRegistry().catch(()=>{})}N(),setInterval(N,ie);const M=q();async function de(){const n=await import("node:fs/promises"),e=await import("node:path"),t=await import("node:os"),s=e.join(String(process.env.AWS_RUNTIME_HOME_DIR||t.homedir()).trim()||t.homedir(),".aws-bridge","lifecycle-state.json");try{const i=await n.readFile(s,"utf8"),a=JSON.parse(i),o=Array.isArray(a.operations)?a.operations:[];return o.length===0?null:(o.sort((l,c)=>new Date(c.updatedAt).getTime()-new Date(l.updatedAt).getTime()),o[0]||null)}catch{return null}}const d=F(),z="panel_sid";function le(n){const e={};if(!n)return e;for(const t of n.split(";")){const s=t.indexOf("=");if(s===-1)continue;const i=t.slice(0,s).trim(),a=t.slice(s+1).trim();i&&(e[i]=a)}return e}function I(n){return le(n.headers.cookie)[z]}function H(n,e,t){const s=n.secure||n.headers["x-forwarded-proto"]==="https";return`${z}=${e}; HttpOnly; SameSite=Lax; Path=/${s?"; Secure":""}; Max-Age=${t}`}function g(n,e){const t=I(n);return t&&E(t)?!0:n.xhr||n.headers.accept?.includes("application/json")?(e.status(401).json({ok:!1,error:"unauthorized",needLogin:!0}),!1):(e.redirect("/login"),!1)}d.post("/runtime/login",(n,e)=>{const t=n.socket.remoteAddress||"unknown",s=ee(t);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:a}=n.body||{};if(!i||!a){e.status(400).json({ok:!1,error:"username and password required"});return}if(!U(String(i),String(a))){te(t),e.status(401).json({ok:!1,error:"invalid credentials"});return}ne(t);const o=Y(),l=!x();e.setHeader("Set-Cookie",H(n,o,86400)),e.json({ok:!0,needSetup:l})}),d.post("/runtime/logout",(n,e)=>{const t=I(n);t&&L(t),e.setHeader("Set-Cookie",H(n,"",0)),e.json({ok:!0})}),d.get("/runtime/credentials-status",(n,e)=>{if(!g(n,e))return;const t=P();e.json({ok:!0,isDefault:$(),configured:x(),username:t.username})}),d.post("/runtime/change-credentials",(n,e)=>{if(!g(n,e))return;const{username:t,password:s,passwordConfirm:i,oldPassword:a}=n.body||{};if(s&&String(s).trim()){if(String(s)!==String(i)){e.status(400).json({ok:!1,error:"passwords do not match"});return}if(x()){if(!a){e.status(400).json({ok:!1,error:"old password required"});return}const l=P();if(!U(l.username,String(a))){e.status(403).json({ok:!1,error:"old password is incorrect"});return}}}if(!t||!String(t).trim()){e.status(400).json({ok:!1,error:"username required"});return}const o=s&&String(s).trim()?String(s).trim():P().password;Q(String(t).trim(),o),e.json({ok:!0})}),d.get("/runtime/node-info",(n,e)=>{if(!g(n,e))return;const t=T(),s=D();e.json({ok:!0,hostname:p.hostname(),platform:p.platform(),arch:p.arch(),uptime:Math.floor(process.uptime()),nodeVersion:process.version,bridgeVersion:M,configPath:oe(),homeDir:process.env.AWS_RUNTIME_HOME_DIR||p.homedir(),bindingStatus:t.status,paired:t.paired,instanceId:t.instanceId||s.instanceId,schedulerBaseUrl:t.schedulerBaseUrl,registered:s.registered,pairingCode:t.pairingCode,activeSessions:Z()})}),d.get("/runtime/logs",(n,e)=>{if(!g(n,e))return;const t=n.query.level||void 0,s=Math.min(Number(n.query.count)||200,500),a=t&&["debug","info","warn","error"].includes(t)?t:void 0,o=se(s,a);e.json({ok:!0,logs:o})}),d.post("/runtime/re-register",async(n,e)=>{if(g(n,e))try{const t=await ae();e.json({ok:t})}catch(t){const s=t;e.status(500).json({ok:!1,error:s.message})}}),d.get("/runtime/admin/sessions",(n,e)=>{if(!g(n,e))return;const t=X().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:t})}),d.post("/runtime/admin/sessions/:token/destroy",(n,e)=>{g(n,e)&&(L(n.params.token),e.json({ok:!0}))});function ce(n){return new Promise((e,t)=>{const s=p.platform()==="win32",i=s?"taskkill":"kill",a=s?["/PID",String(n),"/F"]:["-9",String(n)];_(i,a,{timeout:5e3},o=>o?t(o):e())})}d.post("/runtime/kill-process",async(n,e)=>{if(!g(n,e))return;const t=Number(n.body.pid);if(!t||!Number.isInteger(t)||t<=0){e.status(400).json({ok:!1,error:"Invalid PID"});return}const s=y(),i=()=>{const o=new Set,l=S?.data??[];for(const c of l)typeof c.pid=="number"&&o.add(c.pid);for(const c of s.getAllManagedProcesses())typeof c.pid=="number"&&o.add(c.pid);return o};let a=i();if(a.size===0)try{S={data:await s.discoverAllProcesses(),ts:Date.now()},a=i()}catch{}if(!a.has(t)){w.warn("Rejected kill-process for unrecognized PID",{pid:t}),e.status(404).json({ok:!1,error:"PID is not a bridge-discovered or registered agent process"});return}try{await ce(t),w.info("Killed process",{pid:t}),e.json({ok:!0})}catch(o){const l=o;w.warn("Failed to kill process",{pid:t,error:l.message}),e.status(500).json({ok:!1,error:l.message})}}),d.get("/runtime/dashboard",async(n,e)=>{if(g(n,e)){if(k&&Date.now()-k.ts<re)return void e.json(k.data);try{const t=R?.data??null,s=S?.data??null;R||N();const[i,a,o]=await Promise.all([Promise.resolve(T()),Promise.resolve(D()),W().catch(()=>[])]),c=y().getAllManagedProcesses(),v=Array.from(J.entries()).map(([m,u])=>({sessionId:m,agentId:u.agentId,workspacePath:u.workspacePath,runtimeStatus:u.currentRuntimeStatus||"running",runtimeActionType:u.currentRuntimeActionType,runtimeActionLabel:u.currentRuntimeActionLabel,providerId:u.providerId}));let f={managed:0,orphan:0,unknown:0},C=[];if(s){f={managed:s.filter(r=>r.managementStatus==="managed").length,orphan:s.filter(r=>r.managementStatus==="orphan").length,unknown:s.filter(r=>r.managementStatus==="unknown").length};const m=new Map(c.map(r=>[r.pid,r]));C=s.map(r=>{const h=m.get(r.pid);return{agentId:r.agentId||h?.agentId,pid:r.pid,managementStatus:r.managementStatus,sessionId:h?.sessionId,state:h?.state,command:r.command,cwd:r.cwd,startedAt:r.startedAt}});const u={managed:0,orphan:1,unknown:2};C.sort((r,h)=>(u[r.managementStatus]??9)-(u[h.managementStatus]??9))}const j=new Set(v.map(m=>m.sessionId)),B=o.filter(m=>!j.has(m.sessionId)),b=i,O=await de(),A={ok:!0,collectedAt:new Date().toISOString(),bridge:{version:M,hostname:p.hostname(),platform:p.platform(),arch:p.arch(),uptime:Math.floor(process.uptime()),nodeVersion:process.version},binding:{status:b.status,paired:b.paired,instanceId:b.instanceId,userId:b.userId,schedulerBaseUrl:b.schedulerBaseUrl,pairingCode:V()},registration:{registered:a.registered,instanceId:a.instanceId,lastAttempt:a.lastAttempt?.toISOString(),error:a.error},metrics:t,sessions:{active:v,activeCount:v.length,persisted:B,persistedCount:B.length,total:v.length+B.length},processes:{...f,total:f.managed+f.orphan+f.unknown,list:C,registryCount:c.length},lifecycleOperation:O};k={data:A,ts:Date.now()},e.json(A)}catch(t){const s=t;w.error("Dashboard aggregation failed:",s.message),e.status(500).json({ok:!1,error:s.message})}}}),d.get("/login",(n,e)=>{e.type("text/html; charset=utf-8"),e.send(ge)}),d.get("/",(n,e)=>{const t=I(n);if(!t||!E(t)){e.redirect("/login");return}if(!x()){e.redirect("/setup");return}const s=n.query.lang==="en"?"en":"zh";e.type("text/html; charset=utf-8"),e.send(ue.replace("{{LANG}}",s))}),d.get("/setup",(n,e)=>{const t=I(n);if(!t||!E(t)){e.redirect("/login");return}e.type("text/html; charset=utf-8"),e.send(pe)});const ue=`<!DOCTYPE html>
2
2
  <html lang="{{LANG}}">
3
3
  <head>
4
4
  <meta charset="UTF-8">
@@ -1502,7 +1502,7 @@ switchSettingsPage = function(page) {
1502
1502
  };
1503
1503
  </script>
1504
1504
  </body>
1505
- </html>`,ie=`<!DOCTYPE html>
1505
+ </html>`,ge=`<!DOCTYPE html>
1506
1506
  <html lang="zh-CN">
1507
1507
  <head>
1508
1508
  <meta charset="UTF-8">
@@ -1673,7 +1673,7 @@ document.getElementById("loginForm").addEventListener("keydown", function(e) {
1673
1673
  });
1674
1674
  </script>
1675
1675
  </body>
1676
- </html>`,de=`<!DOCTYPE html>
1676
+ </html>`,pe=`<!DOCTYPE html>
1677
1677
  <html lang="zh-CN">
1678
1678
  <head>
1679
1679
  <meta charset="UTF-8">
@@ -1960,5 +1960,5 @@ async function doSkip() {
1960
1960
  document.getElementById("setupForm").addEventListener("submit", doSave);
1961
1961
  </script>
1962
1962
  </body>
1963
- </html>`;export{i as dashboardRouter};
1963
+ </html>`;export{d as dashboardRouter};
1964
1964
 
@@ -26,6 +26,17 @@ interface GitDiffSummaryFile {
26
26
  deletions: number;
27
27
  staged: boolean;
28
28
  }
29
+ export interface GitBranchItem {
30
+
31
+ name: string;
32
+
33
+ ref: string;
34
+ hash: string;
35
+ isCurrent: boolean;
36
+ isRemote: boolean;
37
+
38
+ upstream: string;
39
+ }
29
40
 
30
41
  export declare function shouldIncludeGitDiffSummaryFile(status: GitDiffFileStatus, additions: number, deletions: number, isBinaryDiff: boolean): boolean;
31
42
 
@@ -45,6 +56,8 @@ export declare function parseGitStashListLine(line: string): GitStashItem | null
45
56
 
46
57
  export declare function parseLatestGitStash(output: string): LatestGitStash | null;
47
58
 
59
+ export declare function resolveGitPushTimeoutMs(): number;
60
+
48
61
  export declare function persistAgentsWorkStashSnapshotRef(workspacePath: string, stashRef: string): Promise<string | null>;
49
62
 
50
63
  export declare function parseGitStatusStagedPaths(output: string): Set<string>;
@@ -53,11 +66,37 @@ export declare function parseGitDiffSummary(nameStatusOutput: string, numstatOut
53
66
 
54
67
  export declare function parseGitStatusUntrackedPaths(output: string): string[];
55
68
 
69
+ export declare function countUntrackedFileAdditions(content: string): number;
70
+
71
+ export declare function collectUntrackedFiles(repoRootPath: string, untrackedPaths: string[], existingFilePaths: Set<string>, maxDepth?: number): Promise<GitDiffSummaryFile[]>;
72
+
56
73
  export declare function mergeGitDiffSummaryFiles(unstagedFiles: GitDiffSummaryFile[], stagedFiles: GitDiffSummaryFile[]): GitDiffSummaryFile[];
57
74
  export declare function normalizeGitCommitMessage(message: unknown): string;
58
75
  export declare function createScopedGitPathspecArgs(context: Pick<GitRepositoryContext, 'relativeWorkspacePath'>): string[];
59
76
  export declare function createScopedFilePathspecArgs(filePath: string, context: Pick<GitRepositoryContext, 'relativeWorkspacePath'>): string[];
60
77
 
78
+ export declare function resolveRemoteCommitHashes(repoRootPath: string): Promise<Set<string>>;
79
+
80
+ export declare function resolveCurrentBranchName(repoRootPath: string): Promise<string>;
81
+
82
+ export declare function listGitBranches(repoRootPath: string): Promise<GitBranchItem[]>;
83
+
84
+ export declare function buildCommitDiffBaseArgs(commitHash: string, aggregate: boolean): string[];
85
+ export interface GitPushResult {
86
+ branch: string;
87
+ pushed: boolean;
88
+ message: string;
89
+ output: string;
90
+ }
91
+
92
+ export declare function pushBranchToRemote(repoRootPath: string): Promise<GitPushResult>;
93
+ export interface GitCommitOutcome {
94
+ committedPaths: string[];
95
+ output: string;
96
+ }
97
+
98
+ export declare function commitSelectedFiles(repoRootPath: string, message: string, pathspecs: string[]): Promise<GitCommitOutcome>;
99
+
61
100
  export declare function sanitizeCommitMessage(raw: string): string;
62
101
 
63
102
  export declare function extractOpenAiStreamDelta(chunkText: string): string;
@@ -1,28 +1,36 @@
1
- import{spawn as V}from"node:child_process";import{promises as A}from"node:fs";import k from"node:path";import L from"axios";import{Router as X}from"express";import{validateToken as y}from"../middleware/auth.js";import{logger as h}from"../utils/logger.js";const P=X(),Z=1e4;function O(){const i=Number(process.env.AWS_RUNTIME_GIT_COMMAND_TIMEOUT_MS);return Number.isFinite(i)&&i>=1e3?i:Z}async function Q(i){let t;try{t=await A.stat(i)}catch(e){const a=e;throw a.code==="ENOENT"?new Error(`workspace path does not exist: ${i}`):new Error(`workspace path is not accessible: ${i}${a.message?` (${a.message})`:""}`)}if(!t.isDirectory())throw new Error(`workspace path is not a directory: ${i}`)}const tt=/^[0-9a-f]{40}$/i;function et(i,t,e,a){return i==="added"||i==="deleted"||i==="renamed"?!0:a||t>0||e>0}function rt(i){const t=String(i||"").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 st(){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 ot(i){const t=String(i||"").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 it(i){const t=String(i||"").trim();return tt.test(t)?`refs/agentswork/stash-snapshots/${t.toLowerCase()}`:null}function W(i){const t=String(i||"").trim();if(!t)return null;const e=t.split("\0");if(e.length>=3){const s=e[0]?.trim(),o=e[1]?.trim(),l=e[2]?.trim()||o,c=e[3]?.trim()||null;if(!s||!o)return null;const u=I(l||"");return{ref:s,stashName:o,message:u.message,branch:u.branch,createdAt:c}}const a=t.match(/^(stash@\{\d+\})(?::\s*)?(.*)$/);if(!a)return null;const r=a[1],n=I(a[2]?.trim()||r);return{ref:r,stashName:r,message:n.message,branch:n.branch,createdAt:null}}function I(i){const t=String(i||"").trim(),e=t.match(/^(?:On|WIP on)\s+([^:]+):\s*(.*)$/);return e?{branch:e[1].trim(),message:e[2].trim()||t}:{branch:"",message:t}}function z(i){const t=String(i||"").split(`
2
- `).find(a=>a.trim());if(!t)return null;const e=W(t);return e?{ref:e.ref,stashName:e.stashName||e.ref,message:e.message||e.ref}:null}async function p(i,t){return new Promise(e=>{let a=!1,r=null;const n=c=>{a||(a=!0,r&&clearTimeout(r),e(c))},s=V("git",t,{cwd:i,stdio:["ignore","pipe","pipe"]});let o="",l="";s.stdout?.on("data",c=>{o+=String(c||"")}),s.stderr?.on("data",c=>{l+=String(c||"")}),r=setTimeout(()=>{const c=O();h.warn(`[runtime-bridge] git command timed out after ${c}ms: git ${t.join(" ")} (cwd=${i})`),s.kill("SIGKILL"),n({stdout:o,stderr:l||`git command timed out after ${c}ms`,exitCode:124})},O()),s.on("error",c=>{n({stdout:"",stderr:c.message,exitCode:1})}),s.on("close",c=>{n({stdout:o,stderr:l,exitCode:c??1})})})}async function _(i,t){const e=it(t);if(!e)return null;const a=await p(i,["update-ref",e,t]);return a.exitCode!==0?(h.warn(`[Git] \u6301\u4E45\u5316 stash \u5FEB\u7167 ref \u5931\u8D25: ref=${e}, error=${a.stderr||a.stdout}`),null):e}function S(i){return String(i||"").replace(/\\/g,"/").replace(/^\.\//,"").trim()}async function At(i){const t=k.join(i,".git");try{const e=await A.stat(t);return e.isDirectory()||e.isFile()}catch{return!1}}async function R(i){const t=k.resolve(String(i).trim());await Q(t);const e=await p(t,["rev-parse","--show-toplevel"]);if(e.exitCode!==0)return{workspacePath:t,repositoryRootPath:null,hasRepository:!1,isGitRepo:!1,usingParentGitRepo:!1,relativeWorkspacePath:""};const a=k.resolve(e.stdout.trim()),r=a===t,n=r?"":S(k.relative(a,t));return{workspacePath:t,repositoryRootPath:a,hasRepository:!0,isGitRepo:r,usingParentGitRepo:!r,relativeWorkspacePath:n}}function C(i,t){if(!t.relativeWorkspacePath)return[...i];const e=!i.includes("status");return[...i,...e?[`--relative=${t.relativeWorkspacePath}`]:[],"--",t.relativeWorkspacePath]}function N(i,t){const e=S(i);return t.relativeWorkspacePath?S(k.posix.join(t.relativeWorkspacePath,e)):e}function at(i){const t=String(i||"").trim().toUpperCase();return t.startsWith("A")?"added":t.startsWith("D")?"deleted":t.startsWith("R")?"renamed":"modified"}function T(i){const t=S(i);if(!t.includes("=>"))return t;const e=t.match(/^(.*)\{([^{}]+)=>([^{}]+)\}(.*)$/);if(e){const r=e[1],n=e[3],s=e[4];return S(`${r}${n}${s}`)}const a=t.lastIndexOf("=>");return a>=0?S(t.slice(a+2)):t}function nt(i){const t=new Set,e=String(i||"").split(`
3
- `).filter(a=>a.trim());for(const a of e){const r=a[0]||" ";if(r===" "||r==="?")continue;const n=a.slice(3).trim(),s=n.split(" -> "),o=T(s[s.length-1]||n);o&&t.add(o)}return t}function D(i,t,e=new Set,a=!1){const r=new Map,n=i.split(`
4
- `).filter(l=>l.trim());for(const l of n){const c=l.split(" ");if(c.length<2)continue;const u=at(c[0]),f=u==="renamed"&&c.length>=3?2:1,d=T(c[f]);d&&r.set(d,u)}const s=[],o=t.split(`
5
- `).filter(l=>l.trim());for(const l of o){const c=l.split(" ");if(c.length<3)continue;const u=c[0]==="-"||c[1]==="-",f=c[0]==="-"?0:parseInt(c[0],10)||0,d=c[1]==="-"?0:parseInt(c[1],10)||0,m=T(c.slice(2).join(" ").trim()),g=r.get(m)||"modified";!m||!et(g,f,d,u)||s.push({path:m,status:g,additions:f,deletions:d,staged:a||e.has(m)})}return s}function ct(i){const t=[],e=String(i||"").split(`
6
- `).filter(a=>a.trim());for(const a of e)if(a.startsWith("?? ")){const r=a.slice(3).trim();r&&t.push(r.replace(/\/$/,""))}return t}async function lt(i,t,e,a=10){const r=[];async function n(s,o){if(o>a)return;const l=k.join(i,s);let c;try{c=await A.readdir(l,{withFileTypes:!0})}catch{return}for(const u of c){if(u.name===".git"||u.name==="node_modules")continue;const f=s?`${s}/${u.name}`:u.name;if(u.isDirectory())await n(f,o+1);else if(u.isFile()){const d=S(f);d&&!e.has(d)&&r.push({path:d,status:"added",additions:0,deletions:0,staged:!1})}}}for(const s of t){const o=S(s);if(!o)continue;const l=k.join(i,s);let c;try{c=await A.stat(l)}catch{continue}c.isFile()?e.has(o)||r.push({path:o,status:"added",additions:0,deletions:0,staged:!1}):c.isDirectory()&&await n(o,0)}return r}function ft(i,t){const e=new Map;for(const a of i)e.set(a.path,{...a});for(const a of t){const r=e.get(a.path);if(!r){e.set(a.path,{...a,staged:!0});continue}e.set(a.path,{...r,status:r.status==="modified"?a.status:r.status,additions:r.additions+a.additions,deletions:r.deletions+a.deletions,staged:!0})}return[...e.values()]}function q(i){return String(i||"").trim()}function ut(i){return String(i||"").trim()}function dt(i){return["--",i.relativeWorkspacePath||"."]}function G(i,t){return["--",N(i,t)]}function ht(i){const t=i.split("\0"),e=[];for(let a=0;a+4<t.length;a+=5){const r=t[a]?.trim();r&&e.push({hash:r,shortHash:t[a+1]?.trim()||r.slice(0,8),authorName:t[a+2]?.trim()||"unknown",authorDate:t[a+3]?.trim()||"",subject:t[a+4]?.trim()||r})}return e}P.post("/git/check",y,async(i,t)=>{const{workspacePath:e}=i.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const a=await R(String(e).trim());t.json({ok:!0,isGitRepo:a.isGitRepo,hasRepository:a.hasRepository,usingParentGitRepo:a.usingParentGitRepo,workspacePath:a.workspacePath,repositoryRootPath:a.repositoryRootPath,relativeWorkspacePath:a.relativeWorkspacePath});return}catch(a){const r=a;t.status(500).json({error:r.message||"check git repo failed"})}}),P.post("/git/stash/create",y,async(i,t)=>{const{workspacePath:e,message:a}=i.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const r=k.resolve(String(e).trim()),n=`\u5FEB\u7167\uFF08${new Date().toLocaleString("zh-CN",{year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit"})}\uFF09`,s=a&&String(a).trim()?`\u5FEB\u7167: ${String(a).trim()}`:n,o=await p(r,["stash","push","-m",s]);if(ot(`${o.stdout}
7
- ${o.stderr}`)){const d=await p(r,["stash","list","-n","1","--format=%H%x00%gd%x00%gs%x00%cr"]),m=d.exitCode===0?z(d.stdout):null,g=m?await _(r,m.ref):null;t.json({ok:!0,stashRef:g??m?.ref??null,stashName:m?.stashName??null,message:m?`\u6CA1\u6709\u672C\u5730\u53D8\u66F4\u9700\u8981\u4FDD\u5B58\uFF0C\u5DF2\u590D\u7528\u6700\u8FD1\u5FEB\u7167 ${m.stashName}`:"\u6CA1\u6709\u672C\u5730\u53D8\u66F4\u9700\u8981\u4FDD\u5B58",noChanges:!0,reusedLatestStash:!!m,workspacePath:r});return}if(o.exitCode!==0){if(rt(`${o.stdout}
8
- ${o.stderr}`)){t.status(400).json({error:st()});return}t.status(400).json({error:o.stderr||"git stash push failed"});return}const l=await p(r,["stash","list","-n","1","--format=%H%x00%gd%x00%gs%x00%cr"]),c=l.exitCode===0?z(l.stdout):null,f=(c?await _(r,c.ref):null)??c?.ref??"stash@{0}";t.json({ok:!0,stashRef:f,stashName:c?.stashName??null,message:s,workspacePath:r})}catch(r){const n=r;t.status(500).json({error:n.message||"create stash failed"})}}),P.post("/git/stash/list",y,async(i,t)=>{const{workspacePath:e,limit:a=20}=i.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const r=k.resolve(String(e).trim()),n=Math.min(Math.max(1,Number(a)||20),100),s=await p(r,["stash","list","-n",String(n),"--format=%H%x00%gd%x00%gs%x00%cr"]);if(s.exitCode!==0){t.status(400).json({error:s.stderr||"git stash list failed"});return}const o=s.stdout.split(`
9
- `).map(W).filter(l=>l!==null);t.json({ok:!0,stashes:o,count:o.length,workspacePath:r})}catch(r){const n=r;t.status(500).json({error:n.message||"list stash failed"})}});async function H(i,t,e){const{workspacePath:a,ref:r}=i.body||{};if(!a||!String(a).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const n=k.resolve(String(a).trim()),s=r&&String(r).trim()?String(r).trim():"stash@{0}";await p(n,["checkout","--","."]);const o=await p(n,["stash",e,s]);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 ${s} \u4E0D\u5B58\u5728`});return}t.status(400).json({error:o.stderr||`git stash ${e} failed`});return}t.json({ok:!0,stashRef:s,message:e==="apply"?`\u5DF2\u5E94\u7528 ${s}`:`\u5DF2\u6062\u590D ${s}`,preservedStash:e==="apply",workspacePath:n})}catch(n){const s=n;t.status(500).json({error:s.message||`${e} stash failed`})}}P.post("/git/stash/apply",y,async(i,t)=>{await H(i,t,"apply")}),P.post("/git/stash/pop",y,async(i,t)=>{await H(i,t,"pop")}),P.post("/git/stash/drop",y,async(i,t)=>{const{workspacePath:e,ref:a}=i.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const r=k.resolve(String(e).trim()),n=a&&String(a).trim()?String(a).trim():"stash@{0}",s=await p(r,["stash","drop",n]);if(s.exitCode!==0){if(s.stderr.includes("is not a valid reference")){t.status(404).json({error:`\u5FEB\u7167 ${n} \u4E0D\u5B58\u5728`});return}t.status(400).json({error:s.stderr||"git stash drop failed"});return}t.json({ok:!0,stashRef:n,message:`\u5DF2\u5220\u9664 ${n}`,workspacePath:r})}catch(r){const n=r;t.status(500).json({error:n.message||"drop stash failed"})}}),P.post("/git/diff",y,async(i,t)=>{const{workspacePath:e,repositoryPath:a}=i.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const r=await R(String(a||e).trim());if(!r.hasRepository||!r.repositoryRootPath){t.json({ok:!0,isGitRepo:r.isGitRepo,hasRepository:!1,usingParentGitRepo:!1,hasChanges:!1,files:[],workspacePath:r.workspacePath,repositoryRootPath:null,relativeWorkspacePath:""});return}const n=await p(r.repositoryRootPath,C(["diff","--name-status"],r));if(n.exitCode!==0){t.status(400).json({error:n.stderr||"git diff failed"});return}const s=await p(r.repositoryRootPath,C(["diff","--numstat"],r));if(s.exitCode!==0){t.status(400).json({error:s.stderr||"git diff failed"});return}const o=await p(r.repositoryRootPath,C(["diff","--cached","--name-status"],r));if(o.exitCode!==0){t.status(400).json({error:o.stderr||"git staged diff failed"});return}const l=await p(r.repositoryRootPath,C(["diff","--cached","--numstat"],r));if(l.exitCode!==0){t.status(400).json({error:l.stderr||"git staged diff failed"});return}const c=await p(r.repositoryRootPath,C(["status","--porcelain=v1"],r));if(c.exitCode!==0){t.status(400).json({error:c.stderr||"git status failed"});return}const u=nt(c.stdout),f=ft(D(n.stdout,s.stdout,u),D(o.stdout,l.stdout,u,!0)),d=ct(c.stdout);if(d.length>0){const m=new Set(f.map(x=>x.path)),g=await lt(r.repositoryRootPath,d,m);f.push(...g)}t.json({ok:!0,isGitRepo:r.isGitRepo,hasRepository:!0,usingParentGitRepo:r.usingParentGitRepo,hasChanges:f.length>0,files:f,workspacePath:r.workspacePath,repositoryRootPath:r.repositoryRootPath,relativeWorkspacePath:r.relativeWorkspacePath})}catch(r){const n=r;t.status(500).json({error:n.message||"git diff failed"})}});async function pt(i){const t=[],e=await p(i,["config","user.name"]);(e.exitCode!==0||!e.stdout.trim())&&t.push("-c","user.name=AgentsWork");const a=await p(i,["config","user.email"]);return(a.exitCode!==0||!a.stdout.trim())&&t.push("-c","user.email=agent@agentswork.com"),t}P.post("/git/commit",y,async(i,t)=>{const{workspacePath:e,repositoryPath:a,message:r}=i.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}const n=ut(r);if(!n){t.status(400).json({error:"commit message is required"});return}try{const s=await R(String(a||e).trim());if(!s.hasRepository||!s.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}const o=dt(s),l=await p(s.repositoryRootPath,["add","--all",...o]);if(l.exitCode!==0){t.status(400).json({error:l.stderr||"git add failed"});return}const c=await p(s.repositoryRootPath,["diff","--cached","--quiet",...o]);if(c.exitCode===0){t.status(400).json({error:"\u6CA1\u6709\u53EF\u63D0\u4EA4\u7684\u53D8\u66F4"});return}if(c.exitCode!==1){t.status(400).json({error:c.stderr||"git staged diff failed"});return}const u=await pt(s.repositoryRootPath),f=await p(s.repositoryRootPath,[...u,"commit","-m",n,...o]);if(f.exitCode!==0){t.status(400).json({error:f.stderr||f.stdout||"git commit failed"});return}t.json({ok:!0,message:"\u63D0\u4EA4\u5B8C\u6210",output:f.stdout,workspacePath:s.workspacePath,repositoryRootPath:s.repositoryRootPath,relativeWorkspacePath:s.relativeWorkspacePath})}catch(s){const o=s;t.status(500).json({error:o.message||"git commit failed"})}}),P.post("/git/add-file",y,async(i,t)=>{const{workspacePath:e,repositoryPath:a,filePath:r}=i.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}const n=S(String(r||"").trim());if(!n){t.status(400).json({error:"filePath is required"});return}try{const s=await R(String(a||e).trim());if(!s.hasRepository||!s.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}const o=await p(s.repositoryRootPath,["add",...G(n,s)]);if(o.exitCode!==0){t.status(400).json({error:o.stderr||"git add file failed"});return}t.json({ok:!0,filePath:n,workspacePath:s.workspacePath,repositoryRootPath:s.repositoryRootPath,relativeWorkspacePath:s.relativeWorkspacePath})}catch(s){const o=s;t.status(500).json({error:o.message||"git add file failed"})}}),P.post("/git/add-files",y,async(i,t)=>{const{workspacePath:e,repositoryPath:a,filePaths:r}=i.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}const s=(Array.isArray(r)?r:[]).map(o=>S(String(o||"").trim())).filter(o=>o.length>0);if(s.length===0){t.status(400).json({error:"filePaths is required and must be a non-empty array"});return}try{const o=await R(String(a||e).trim());if(!o.hasRepository||!o.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}const l=s.flatMap(d=>G(d,o)),c=["add"],u=[];for(let d=0;d<l.length;d++)l[d]!=="--"&&u.push(l[d]);c.push("--",...u);const f=await p(o.repositoryRootPath,c);if(f.exitCode!==0){t.status(400).json({error:f.stderr||"git add files failed"});return}t.json({ok:!0,filePaths:s,workspacePath:o.workspacePath,repositoryRootPath:o.repositoryRootPath,relativeWorkspacePath:o.relativeWorkspacePath})}catch(o){const l=o;t.status(500).json({error:l.message||"git add files failed"})}}),P.post("/git/commits",y,async(i,t)=>{const{workspacePath:e,repositoryPath:a,limit:r}=i.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const n=await R(String(a||e).trim());if(!n.hasRepository||!n.repositoryRootPath){t.json({ok:!0,commits:[],workspacePath:n.workspacePath,repositoryRootPath:null});return}const s=Number(r),o=Number.isInteger(s)&&s>0&&s<=100?s:30,l=await p(n.repositoryRootPath,["log",`-${o}`,"--date=iso-strict","--pretty=format:%H%x00%h%x00%an%x00%ad%x00%s%x00","--",n.relativeWorkspacePath||"."]);if(l.exitCode!==0){t.status(400).json({error:l.stderr||"git log failed"});return}t.json({ok:!0,commits:ht(l.stdout),workspacePath:n.workspacePath,repositoryRootPath:n.repositoryRootPath,relativeWorkspacePath:n.relativeWorkspacePath})}catch(n){const s=n;t.status(500).json({error:s.message||"git commits failed"})}}),P.post("/git/commit-diff",y,async(i,t)=>{const{workspacePath:e,repositoryPath:a,commitHash:r}=i.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}const n=q(r);if(!n){t.status(400).json({error:"commitHash is required"});return}try{const s=await R(String(a||e).trim());if(!s.hasRepository||!s.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}const o=["diff",`${n}^!`],l=await p(s.repositoryRootPath,C([...o,"--name-status"],s));if(l.exitCode!==0){t.status(400).json({error:l.stderr||"git commit diff failed"});return}const c=await p(s.repositoryRootPath,C([...o,"--numstat"],s));if(c.exitCode!==0){t.status(400).json({error:c.stderr||"git commit diff failed"});return}const u=D(l.stdout,c.stdout);t.json({ok:!0,commitHash:n,isGitRepo:s.isGitRepo,hasRepository:!0,usingParentGitRepo:s.usingParentGitRepo,hasChanges:u.length>0,files:u,workspacePath:s.workspacePath,repositoryRootPath:s.repositoryRootPath,relativeWorkspacePath:s.relativeWorkspacePath})}catch(s){const o=s;t.status(500).json({error:o.message||"git commit diff failed"})}}),P.post("/git/diff-file",y,async(i,t)=>{const{workspacePath:e,filePath:a,commitHash:r}=i.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}if(!a||!String(a).trim()){t.status(400).json({error:"filePath is required"});return}try{const n=await R(String(e).trim()),s=S(String(a).trim());if(!n.hasRepository||!n.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}const o=N(s,n),l=q(r),c=l?["show","--format=","--no-ext-diff",l,"--",o]:["diff","HEAD","--",o];let u=await p(n.repositoryRootPath,c);if(!l&&u.exitCode!==0&&(u=await p(n.repositoryRootPath,["diff","--",o])),u.exitCode!==0){t.status(400).json({error:u.stderr||"git diff file failed"});return}if(!l&&!u.stdout.trim()){const f=await p(n.repositoryRootPath,["diff","--cached","--",o]);if(f.exitCode!==0){t.status(400).json({error:f.stderr||"git staged diff file failed"});return}u=f}if(!l&&!u.stdout.trim()){const f=k.join(n.repositoryRootPath,o);try{const d=await A.readFile(f,"utf-8");if(d){const m=d.split(`
10
- `).map(x=>`+${x}`).join(`
11
- `),g=d.split(`
12
- `).length;u={stdout:[`diff --git a/${o} b/${o}`,"new file mode 100644","--- /dev/null",`+++ b/${o}`,`@@ -0,0 +1,${g} @@`,m].join(`
13
- `),stderr:"",exitCode:0}}}catch{}}t.json({ok:!0,filePath:s,repositoryRelativeFilePath:o,commitHash:l||null,diffContent:u.stdout,workspacePath:n.workspacePath,repositoryRootPath:n.repositoryRootPath,usingParentGitRepo:n.usingParentGitRepo})}catch(n){const s=n;t.status(500).json({error:s.message||"git diff file failed"})}});const J=["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(`
14
- `);function U(i){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:","",i].join(`
15
- `)}const B=/\b(?:feat|fix|chore|docs|style|refactor|perf|test|build|ci|revert)(?:\s*\([^)]*\))?\s*:/i,mt=/^(?:feat|fix|chore|docs|style|refactor|perf|test|build|ci|revert)\s*:/i;function gt(i){const t=i.split(`
16
- `)[0]?.trim()??"";if(!t||t.length<3)return!1;const e=(t.match(/[\u4e00-\u9fa5]/g)||[]).length,a=(t.match(/[a-zA-Z]{2,}/g)||[]).length;if(e===0&&a===0)return!1;const r=[/^以下是/,/^上面是/,/^生成的/,/^这是/,/^下面/,/^(?:Here|This|This is|That is)/i];for(const n of r)if(n.test(t))return!1;return e>=2||a>=2}const yt=[/^\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 v(i){if(!i)return h.info("[Git/sanitize] \u8F93\u5165\u4E3A\u7A7A, \u8FD4\u56DE fallback"),"chore: update staged changes";let t=i.replace(/\r\n/g,`
17
- `).trim();const e=JSON.stringify(i.substring(0,120)),a=t.match(B);if(a&&a.index!==void 0&&a.index<=80)t=t.slice(a.index);else{for(let f=0;f<5;f+=1){const d=t;for(const m of yt)t=t.replace(m,"");if(t=t.replace(/^[\s*\-`,,']+/,"").trim(),t===d)break}const s=t.split(`
18
- `)[0]?.trim()??"",o=!!t.match(B),l=!!t.match(mt),c=gt(t),u=s.length>100;if(!o&&!l&&!c&&!u)return h.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&&(l||c||u)&&(t=s||t)}const r=t.search(/\n\s*\n/);if(r>0&&(t=t.slice(0,r)),t=t.replace(/\n+\s*```\s*$/g,"").trim(),!t)return h.info(`[Git/sanitize] \u622A\u65AD\u540E\u4E3A\u7A7A, \u8FD4\u56DE fallback. raw=${e}`),"chore: update staged changes";const n=t.split(`
19
- `)[0]?.trim()??"";return n.length>100?(h.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):(h.info(`[Git/sanitize] \u7ED3\u679C: ${JSON.stringify(t.substring(0,120))} (from raw=${e})`),t)}function K(i){const t=i.split(/\r?\n/);let e="";for(const a of t){const r=a.trim();if(!r.startsWith("data:"))continue;const n=r.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 Pt(i,t){if(t>5)return;const e=i.split(/\r?\n/);for(const a of e){const r=a.trim();if(!r.startsWith("data:"))continue;const n=r.slice(5).trim();if(!(!n||n==="[DONE]"))try{const s=JSON.parse(n),o=Object.keys(s),l=s?.choices,c=Array.isArray(l)?l[0]:null,u=c?.delta?Object.keys(c.delta):null,f=c?.delta?.content;h.info(`[Git/extract] chunk#${t} structure: topKeys=${JSON.stringify(o)}, choicesLen=${Array.isArray(l)?l.length:typeof l}, deltaKeys=${JSON.stringify(u)}, content=${JSON.stringify(f?.substring(0,50))}, finishReason=${JSON.stringify(c?.finish_reason)}`)}catch{}}}async function wt(i,t,e,a){const r=`${i.replace(/\/+$/,"")}/chat/completions`,o=(await L.post(r,{model:e,stream:!1,messages:[{role:"system",content:J},{role:"user",content:U(a)}],max_tokens:200,temperature:.2},{headers:{"Content-Type":"application/json",Authorization:`Bearer ${t}`},timeout:3e4})).data?.choices?.[0]?.message?.content?.trim()||"";return v(o)}async function St(i,t,e,a,r,n){const s=`${i.replace(/\/+$/,"")}/chat/completions`,l=(await L.post(s,{model:e,stream:!0,messages:[{role:"system",content:J},{role:"user",content:U(a)}],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 c="",u="",f="",d=0,m=0;return h.info(`[Git/streamAi] AI \u6D41\u5F00\u59CB: aiModel=${e}`),await new Promise((g,x)=>{const b=w=>{l.removeAllListeners("data"),l.removeAllListeners("end"),l.removeAllListeners("error"),l.removeAllListeners("aborted"),h.info(`[Git/streamAi] AI \u6D41\u7ED3\u675F: totalChunks=${d}, totalDeltas=${m}, bufferLength=${c.length}, sanitized=${JSON.stringify(w).substring(0,200)}`),g(w)};l.on("data",w=>{const j=typeof w=="string"?w:w.toString("utf8");d++,f+=j,d<=3&&h.info(`[Git/streamAi] chunk#${d}: length=${j.length}, preview=${JSON.stringify(j.substring(0,80))}`),Pt(j,d);const F=f.split(/\n\n/);f=F.pop()??"";for(const Y of F){const M=K(Y);M&&(m++,c+=M,u=v(c),r(M,u))}}),l.on("end",()=>{if(f.trim()){const w=K(f);w?(h.info(`[Git/streamAi] \u5C3E\u90E8\u6B8B\u7559 delta: length=${w.length}`),c+=w):h.info(`[Git/streamAi] \u5C3E\u90E8\u6B8B\u7559\u65E0 delta, pendingData=${JSON.stringify(f.substring(0,100))}`)}b(v(c))}),l.on("error",w=>{h.error(`[Git/streamAi] \u6D41\u9519\u8BEF: ${w.message}`),x(w)}),l.on("aborted",()=>{h.info(`[Git/streamAi] \u6D41\u88AB\u4E2D\u6B62, bufferLength=${c.length}`),b(v(c))})})}async function E(i,t,e){const a=await R(String(i||t).trim());if(!a.hasRepository||!a.repositoryRootPath)return h.warn(`[Git/collectDiff] \u4E0D\u5728 Git \u4ED3\u5E93\u4E2D: workspacePath=${t}`),{truncatedDiff:"",error:{status:400,message:"workspace is not inside a git repository"}};const r=e.map(c=>c.path).filter(Boolean);h.info(`[Git/collectDiff] \u5F00\u59CB\u6536\u96C6 ${r.length} \u4E2A\u6682\u5B58\u6587\u4EF6\u5DEE\u5F02: ${r.join(", ").substring(0,200)}`);const n=[];for(const c of r){const u=N(c,a),f=await p(a.repositoryRootPath,["diff","--cached","--",u]);f.exitCode===0&&f.stdout.trim()?n.push(`--- ${c} ---
20
- ${f.stdout.trim()}`):h.info(`[Git/collectDiff] \u6587\u4EF6\u65E0\u6682\u5B58\u5DEE\u5F02\u6216\u83B7\u53D6\u5931\u8D25: ${c}, exitCode=${f.exitCode}, stdout.length=${f.stdout.length}`)}if(n.length===0){h.info("[Git/collectDiff] \u9010\u6587\u4EF6 diff \u4E3A\u7A7A, \u56DE\u9000\u5230\u5168\u91CF diff --cached");const c=await p(a.repositoryRootPath,["diff","--cached"]);c.exitCode===0&&c.stdout.trim()?(n.push(c.stdout.trim()),h.info(`[Git/collectDiff] \u5168\u91CF diff \u83B7\u53D6\u6210\u529F: length=${c.stdout.trim().length}`)):h.info(`[Git/collectDiff] \u5168\u91CF diff \u4E5F\u4E3A\u7A7A, exitCode=${c.exitCode}`)}if(n.length===0)return h.info("[Git/collectDiff] \u6682\u5B58\u533A\u6CA1\u6709\u5DEE\u5F02\u5185\u5BB9 (diffParts \u4E3A\u7A7A)"),{truncatedDiff:""};const s=n.join(`
1
+ import{spawn as tt}from"node:child_process";import{promises as C}from"node:fs";import et from"node:os";import R from"node:path";import L from"axios";import{Router as rt}from"express";import{validateToken as k}from"../middleware/auth.js";import{logger as g}from"../utils/logger.js";const S=rt(),st=1e4;function ot(){const s=Number(process.env.AWS_RUNTIME_GIT_COMMAND_TIMEOUT_MS);return Number.isFinite(s)&&s>=1e3?s:st}async function it(s){let t;try{t=await C.stat(s)}catch(e){const i=e;throw i.code==="ENOENT"?new Error(`workspace path does not exist: ${s}`):new Error(`workspace path is not accessible: ${s}${i.message?` (${i.message})`:""}`)}if(!t.isDirectory())throw new Error(`workspace path is not a directory: ${s}`)}const at=/^[0-9a-f]{40}$/i;function nt(s,t,e,i){return s==="added"||s==="deleted"||s==="renamed"?!0:i||t>0||e>0}function ct(s){const t=String(s||"").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 ut(){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 lt(s){const t=String(s||"").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 ft(s){const t=String(s||"").trim();return at.test(t)?`refs/agentswork/stash-snapshots/${t.toLowerCase()}`:null}function O(s){const t=String(s||"").trim();if(!t)return null;const e=t.split("\0");if(e.length>=3){const a=e[0]?.trim(),o=e[1]?.trim(),c=e[2]?.trim()||o,u=e[3]?.trim()||null;if(!a||!o)return null;const f=_(c||"");return{ref:a,stashName:o,message:f.message,branch:f.branch,createdAt:u}}const i=t.match(/^(stash@\{\d+\})(?::\s*)?(.*)$/);if(!i)return null;const r=i[1],n=_(i[2]?.trim()||r);return{ref:r,stashName:r,message:n.message,branch:n.branch,createdAt:null}}function _(s){const t=String(s||"").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(s){const t=String(s||"").split(`
2
+ `).find(i=>i.trim());if(!t)return null;const e=O(t);return e?{ref:e.ref,stashName:e.stashName||e.ref,message:e.message||e.ref}:null}async function p(s,t,e){return new Promise(i=>{let r=!1,n=null;const a=l=>{r||(r=!0,n&&clearTimeout(n),i(l))},o=tt("git",t,{cwd:s,stdio:["ignore","pipe","pipe"]});let c="",u="";o.stdout?.on("data",l=>{c+=String(l||"")}),o.stderr?.on("data",l=>{u+=String(l||"")});const f=e??ot();n=setTimeout(()=>{g.warn(`[runtime-bridge] git command timed out after ${f}ms: git ${t.join(" ")} (cwd=${s})`),o.kill("SIGKILL"),a({stdout:c,stderr:u||`git command timed out after ${f}ms`,exitCode:124})},f),o.on("error",l=>{a({stdout:"",stderr:l.message,exitCode:1})}),o.on("close",l=>{a({stdout:c,stderr:u,exitCode:l??1})})})}const ht=6e4;function dt(){const s=Number(process.env.AWS_RUNTIME_GIT_PUSH_TIMEOUT_MS);return Number.isFinite(s)&&s>=1e3?s:ht}async function z(s,t){const e=ft(t);if(!e)return null;const i=await p(s,["update-ref",e,t]);return i.exitCode!==0?(g.warn(`[Git] \u6301\u4E45\u5316 stash \u5FEB\u7167 ref \u5931\u8D25: ref=${e}, error=${i.stderr||i.stdout}`),null):e}function x(s){return String(s||"").replace(/\\/g,"/").replace(/^\.\//,"").trim()}async function zt(s){const t=R.join(s,".git");try{const e=await C.stat(t);return e.isDirectory()||e.isFile()}catch{return!1}}async function b(s){const t=R.resolve(String(s).trim());await it(t);const e=await p(t,["rev-parse","--show-toplevel"]);if(e.exitCode!==0)return{workspacePath:t,repositoryRootPath:null,hasRepository:!1,isGitRepo:!1,usingParentGitRepo:!1,relativeWorkspacePath:""};const i=R.resolve(e.stdout.trim()),r=i===t,n=r?"":x(R.relative(i,t));return{workspacePath:t,repositoryRootPath:i,hasRepository:!0,isGitRepo:r,usingParentGitRepo:!r,relativeWorkspacePath:n}}function A(s,t){if(!t.relativeWorkspacePath)return[...s];const e=!s.includes("status");return[...s,...e?[`--relative=${t.relativeWorkspacePath}`]:[],"--",t.relativeWorkspacePath]}function G(s,t){const e=x(s);return t.relativeWorkspacePath?x(R.posix.join(t.relativeWorkspacePath,e)):e}function F(s){const t=x(s);if(R.posix.isAbsolute(t)||t.split("/").some(e=>e===".."||e==="."))throw new Error(`unsafe repository relative path: ${s}`)}function pt(s){const t=String(s||"").trim().toUpperCase();return t.startsWith("A")?"added":t.startsWith("D")?"deleted":t.startsWith("R")?"renamed":"modified"}function D(s){const t=x(s);if(!t.includes("=>"))return t;const e=t.match(/^(.*)\{([^{}]+)=>([^{}]+)\}(.*)$/);if(e){const r=e[1],n=e[3],a=e[4];return x(`${r}${n}${a}`)}const i=t.lastIndexOf("=>");return i>=0?x(t.slice(i+2)):t}function mt(s){const t=new Set,e=String(s||"").split(`
3
+ `).filter(i=>i.trim());for(const i of e){const r=i[0]||" ";if(r===" "||r==="?")continue;const n=i.slice(3).trim(),a=n.split(" -> "),o=D(a[a.length-1]||n);o&&t.add(o)}return t}function N(s,t,e=new Set,i=!1){const r=new Map,n=s.split(`
4
+ `).filter(c=>c.trim());for(const c of n){const u=c.split(" ");if(u.length<2)continue;const f=pt(u[0]),l=f==="renamed"&&u.length>=3?2:1,d=D(u[l]);d&&r.set(d,f)}const a=[],o=t.split(`
5
+ `).filter(c=>c.trim());for(const c of o){const u=c.split(" ");if(u.length<3)continue;const f=u[0]==="-"||u[1]==="-",l=u[0]==="-"?0:parseInt(u[0],10)||0,d=u[1]==="-"?0:parseInt(u[1],10)||0,h=D(u.slice(2).join(" ").trim()),m=r.get(h)||"modified";!h||!nt(m,l,d,f)||a.push({path:h,status:m,additions:l,deletions:d,staged:i||e.has(h)})}return a}function gt(s){const t=[],e=String(s||"").split(`
6
+ `).filter(i=>i.trim());for(const i of e)if(i.startsWith("?? ")){const r=i.slice(3).trim();r&&t.push(r.replace(/\/$/,""))}return t}function yt(s){if(!s||s.includes("\0"))return 0;let t=0;for(let e=0;e<s.length;e++)s.charCodeAt(e)===10&&t++;return s.endsWith(`
7
+ `)?t:t+1}async function H(s){try{const t=await C.readFile(s,"utf8");return yt(t)}catch{return 0}}async function Pt(s,t,e,i=10){const r=[];async function n(a,o){if(o>i)return;const c=R.join(s,a);let u;try{u=await C.readdir(c,{withFileTypes:!0})}catch{return}for(const f of u){if(f.name===".git"||f.name==="node_modules")continue;const l=a?`${a}/${f.name}`:f.name;if(f.isDirectory())await n(l,o+1);else if(f.isFile()){const d=x(l);d&&!e.has(d)&&r.push({path:d,status:"added",additions:await H(R.join(c,f.name)),deletions:0,staged:!1})}}}for(const a of t){const o=x(a);if(!o)continue;const c=R.join(s,a);let u;try{u=await C.stat(c)}catch{continue}u.isFile()?e.has(o)||r.push({path:o,status:"added",additions:await H(c),deletions:0,staged:!1}):u.isDirectory()&&await n(o,0)}return r}function wt(s,t){const e=new Map;for(const i of s)e.set(i.path,{...i});for(const i of t){const r=e.get(i.path);if(!r){e.set(i.path,{...i,staged:!0});continue}e.set(i.path,{...r,status:r.status==="modified"?i.status:r.status,additions:r.additions+i.additions,deletions:r.deletions+i.deletions,staged:!0})}return[...e.values()]}function q(s){return String(s||"").trim()}function kt(s){return String(s||"").trim()}function St(s){return["--",s.relativeWorkspacePath||"."]}function E(s,t){return["--",G(s,t)]}function Rt(s){const t=s.split("\0"),e=[];for(let i=0;i+6<t.length;i+=7){const r=t[i]?.trim();if(!r)continue;const n=(t[i+5]?.trim()||"").split(/\s+/).filter(Boolean);e.push({hash:r,shortHash:t[i+1]?.trim()||r.slice(0,8),authorName:t[i+2]?.trim()||"unknown",authorDate:t[i+3]?.trim()||"",subject:t[i+4]?.trim()||r,body:t[i+6]?.trim()||"",parents:n})}return e}async function xt(s){const t=new Set,e=await p(s,["for-each-ref","--format=%(refname)","refs/remotes"]);if(e.exitCode!==0)return t;const i=e.stdout.split(`
8
+ `).map(n=>n.trim()).filter(Boolean);if(i.length===0)return t;const r=await p(s,["rev-list",...i]);if(r.exitCode!==0)return t;for(const n of r.stdout.split(`
9
+ `)){const a=n.trim();a&&t.add(a)}return t}async function B(s){const t=await p(s,["rev-parse","--abbrev-ref","HEAD"]);return t.exitCode!==0||!t.stdout.trim()?"":t.stdout.trim()}async function U(s){const t=await p(s,["for-each-ref","--format=%(refname)%00%(objectname)%00%(HEAD)%00%(upstream:short)","refs/heads","refs/remotes"]);if(t.exitCode!==0)return[];const e=[];for(const i of t.stdout.split(`
10
+ `)){const r=i.split("\0"),n=r[0]?.trim(),a=r[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:r[2]?.trim()==="*",isRemote:o,upstream:r[3]?.trim()||""})}return e}function jt(s,t){return["diff",t?s:`${s}^!`]}async function bt(s){const t=await p(s,["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 p(s,["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 r=await p(s,["push"],dt());if(r.exitCode!==0)throw new Error(r.stderr||r.stdout||"git push failed");const n=`${r.stdout}
11
+ ${r.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(s,t)=>{const{workspacePath:e}=s.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const i=await b(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 r=i;t.status(500).json({error:r.message||"check git repo failed"})}}),S.post("/git/stash/create",k,async(s,t)=>{const{workspacePath:e,message:i}=s.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const r=R.resolve(String(e).trim()),n=`\u5FEB\u7167\uFF08${new Date().toLocaleString("zh-CN",{year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit"})}\uFF09`,a=i&&String(i).trim()?`\u5FEB\u7167: ${String(i).trim()}`:n,o=await p(r,["stash","push","-m",a]);if(lt(`${o.stdout}
12
+ ${o.stderr}`)){const d=await p(r,["stash","list","-n","1","--format=%H%x00%gd%x00%gs%x00%cr"]),h=d.exitCode===0?I(d.stdout):null,m=h?await z(r,h.ref):null;t.json({ok:!0,stashRef:m??h?.ref??null,stashName:h?.stashName??null,message:h?`\u6CA1\u6709\u672C\u5730\u53D8\u66F4\u9700\u8981\u4FDD\u5B58\uFF0C\u5DF2\u590D\u7528\u6700\u8FD1\u5FEB\u7167 ${h.stashName}`:"\u6CA1\u6709\u672C\u5730\u53D8\u66F4\u9700\u8981\u4FDD\u5B58",noChanges:!0,reusedLatestStash:!!h,workspacePath:r});return}if(o.exitCode!==0){if(ct(`${o.stdout}
13
+ ${o.stderr}`)){t.status(400).json({error:ut()});return}t.status(400).json({error:o.stderr||"git stash push failed"});return}const c=await p(r,["stash","list","-n","1","--format=%H%x00%gd%x00%gs%x00%cr"]),u=c.exitCode===0?I(c.stdout):null,l=(u?await z(r,u.ref):null)??u?.ref??"stash@{0}";t.json({ok:!0,stashRef:l,stashName:u?.stashName??null,message:a,workspacePath:r})}catch(r){const n=r;t.status(500).json({error:n.message||"create stash failed"})}}),S.post("/git/stash/list",k,async(s,t)=>{const{workspacePath:e,limit:i=20}=s.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const r=R.resolve(String(e).trim()),n=Math.min(Math.max(1,Number(i)||20),100),a=await p(r,["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(O).filter(c=>c!==null);t.json({ok:!0,stashes:o,count:o.length,workspacePath:r})}catch(r){const n=r;t.status(500).json({error:n.message||"list stash failed"})}});async function J(s,t,e){const{workspacePath:i,ref:r}=s.body||{};if(!i||!String(i).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const n=R.resolve(String(i).trim()),a=r&&String(r).trim()?String(r).trim():"stash@{0}";await p(n,["checkout","--","."]);const o=await p(n,["stash",e,a]);if(o.exitCode!==0){if(o.stdout.includes("CONFLICT")||o.stderr.includes("CONFLICT")){t.json({ok:!1,hasConflict:!0,message:"\u6062\u590D\u65F6\u53D1\u751F\u51B2\u7A81\uFF0C\u8BF7\u624B\u52A8\u89E3\u51B3",details:o.stdout||o.stderr,workspacePath:n});return}if(o.stderr.includes("is not a valid reference")){t.status(404).json({error:`\u5FEB\u7167 ${a} \u4E0D\u5B58\u5728`});return}t.status(400).json({error:o.stderr||`git stash ${e} failed`});return}t.json({ok:!0,stashRef:a,message:e==="apply"?`\u5DF2\u5E94\u7528 ${a}`:`\u5DF2\u6062\u590D ${a}`,preservedStash:e==="apply",workspacePath:n})}catch(n){const a=n;t.status(500).json({error:a.message||`${e} stash failed`})}}S.post("/git/stash/apply",k,async(s,t)=>{await J(s,t,"apply")}),S.post("/git/stash/pop",k,async(s,t)=>{await J(s,t,"pop")}),S.post("/git/stash/drop",k,async(s,t)=>{const{workspacePath:e,ref:i}=s.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const r=R.resolve(String(e).trim()),n=i&&String(i).trim()?String(i).trim():"stash@{0}",a=await p(r,["stash","drop",n]);if(a.exitCode!==0){if(a.stderr.includes("is not a valid reference")){t.status(404).json({error:`\u5FEB\u7167 ${n} \u4E0D\u5B58\u5728`});return}t.status(400).json({error:a.stderr||"git stash drop failed"});return}t.json({ok:!0,stashRef:n,message:`\u5DF2\u5220\u9664 ${n}`,workspacePath:r})}catch(r){const n=r;t.status(500).json({error:n.message||"drop stash failed"})}}),S.post("/git/diff",k,async(s,t)=>{const{workspacePath:e,repositoryPath:i}=s.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const r=await b(String(i||e).trim());if(!r.hasRepository||!r.repositoryRootPath){t.json({ok:!0,isGitRepo:r.isGitRepo,hasRepository:!1,usingParentGitRepo:!1,hasChanges:!1,files:[],workspacePath:r.workspacePath,repositoryRootPath:null,relativeWorkspacePath:""});return}const n=await p(r.repositoryRootPath,A(["diff","--name-status"],r));if(n.exitCode!==0){t.status(400).json({error:n.stderr||"git diff failed"});return}const a=await p(r.repositoryRootPath,A(["diff","--numstat"],r));if(a.exitCode!==0){t.status(400).json({error:a.stderr||"git diff failed"});return}const o=await p(r.repositoryRootPath,A(["diff","--cached","--name-status"],r));if(o.exitCode!==0){t.status(400).json({error:o.stderr||"git staged diff failed"});return}const c=await p(r.repositoryRootPath,A(["diff","--cached","--numstat"],r));if(c.exitCode!==0){t.status(400).json({error:c.stderr||"git staged diff failed"});return}const u=await p(r.repositoryRootPath,A(["status","--porcelain=v1"],r));if(u.exitCode!==0){t.status(400).json({error:u.stderr||"git status failed"});return}const f=mt(u.stdout),l=wt(N(n.stdout,a.stdout,f),N(o.stdout,c.stdout,f,!0)),d=gt(u.stdout);if(d.length>0){const h=new Set(l.map(y=>y.path)),m=await Pt(r.repositoryRootPath,d,h);l.push(...m)}t.json({ok:!0,isGitRepo:r.isGitRepo,hasRepository:!0,usingParentGitRepo:r.usingParentGitRepo,hasChanges:l.length>0,files:l,workspacePath:r.workspacePath,repositoryRootPath:r.repositoryRootPath,relativeWorkspacePath:r.relativeWorkspacePath})}catch(r){const n=r;t.status(500).json({error:n.message||"git diff failed"})}});async function K(s){const t=[],e=await p(s,["config","user.name"]);(e.exitCode!==0||!e.stdout.trim())&&t.push("-c","user.name=AgentsWork");const i=await p(s,["config","user.email"]);return(i.exitCode!==0||!i.stdout.trim())&&t.push("-c","user.email=agent@agentswork.com"),t}async function Ct(s,t,e){if(!t)throw new Error("commit message is required");if(e.length===0)throw new Error("filePaths must be valid workspace-relative paths");const i=e.map(c=>`:(literal)${c}`),r=await p(s,["add","--",...i]);if(r.exitCode!==0)throw new Error(r.stderr||"git add failed");const n=await p(s,["diff","--cached","--quiet","--",...i]);if(n.exitCode===0)throw new Error("\u6240\u9009\u6587\u4EF6\u6CA1\u6709\u53EF\u63D0\u4EA4\u7684\u53D8\u66F4");if(n.exitCode!==1)throw new Error(n.stderr||"git staged diff failed");const a=await K(s),o=await p(s,[...a,"commit","-m",t,"--",...i]);if(o.exitCode!==0)throw new Error(o.stderr||o.stdout||"git commit failed");return{committedPaths:[...e],output:o.stdout}}S.post("/git/commit",k,async(s,t)=>{const{workspacePath:e,repositoryPath:i,message:r,filePaths:n}=s.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}const a=kt(r);if(!a){t.status(400).json({error:"commit message is required"});return}try{const o=await b(String(i||e).trim());if(!o.hasRepository||!o.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}const c=Array.isArray(n)?n.map(m=>x(String(m||"").trim())).filter(Boolean):[];if(c.length>0){const m=c.map(P=>G(P,o)),y=[];for(const P of m)if(P)try{F(P),y.push(P)}catch{t.status(400).json({error:`unsafe file path: ${P}`});return}if(y.length===0){t.status(400).json({error:"filePaths must be valid workspace-relative paths"});return}try{const P=await Ct(o.repositoryRootPath,a,y);t.json({ok:!0,message:"\u63D0\u4EA4\u5B8C\u6210",committedPaths:c,output:P.output,workspacePath:o.workspacePath,repositoryRootPath:o.repositoryRootPath,relativeWorkspacePath:o.relativeWorkspacePath})}catch(P){const w=P;t.status(400).json({error:w.message||"git commit failed"})}return}const u=await K(o.repositoryRootPath),f=St(o),l=await p(o.repositoryRootPath,["add","--all",...f]);if(l.exitCode!==0){t.status(400).json({error:l.stderr||"git add failed"});return}const d=await p(o.repositoryRootPath,["diff","--cached","--quiet",...f]);if(d.exitCode===0){t.status(400).json({error:"\u6CA1\u6709\u53EF\u63D0\u4EA4\u7684\u53D8\u66F4"});return}if(d.exitCode!==1){t.status(400).json({error:d.stderr||"git staged diff failed"});return}const h=await p(o.repositoryRootPath,[...u,"commit","-m",a,...f]);if(h.exitCode!==0){t.status(400).json({error:h.stderr||h.stdout||"git commit failed"});return}t.json({ok:!0,message:"\u63D0\u4EA4\u5B8C\u6210",output:h.stdout,workspacePath:o.workspacePath,repositoryRootPath:o.repositoryRootPath,relativeWorkspacePath:o.relativeWorkspacePath})}catch(o){const c=o;t.status(500).json({error:c.message||"git commit failed"})}}),S.post("/git/add-file",k,async(s,t)=>{const{workspacePath:e,repositoryPath:i,filePath:r}=s.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}const n=x(String(r||"").trim());if(!n){t.status(400).json({error:"filePath is required"});return}try{const a=await b(String(i||e).trim());if(!a.hasRepository||!a.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}const o=await p(a.repositoryRootPath,["add",...E(n,a)]);if(o.exitCode!==0){t.status(400).json({error:o.stderr||"git add file failed"});return}t.json({ok:!0,filePath:n,workspacePath:a.workspacePath,repositoryRootPath:a.repositoryRootPath,relativeWorkspacePath:a.relativeWorkspacePath})}catch(a){const o=a;t.status(500).json({error:o.message||"git add file failed"})}}),S.post("/git/add-files",k,async(s,t)=>{const{workspacePath:e,repositoryPath:i,filePaths:r}=s.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}const a=(Array.isArray(r)?r:[]).map(o=>x(String(o||"").trim())).filter(o=>o.length>0);if(a.length===0){t.status(400).json({error:"filePaths is required and must be a non-empty array"});return}try{const o=await b(String(i||e).trim());if(!o.hasRepository||!o.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}const c=a.flatMap(d=>E(d,o)),u=["add"],f=[];for(let d=0;d<c.length;d++)c[d]!=="--"&&f.push(c[d]);u.push("--",...f);const l=await p(o.repositoryRootPath,u);if(l.exitCode!==0){t.status(400).json({error:l.stderr||"git add files failed"});return}t.json({ok:!0,filePaths:a,workspacePath:o.workspacePath,repositoryRootPath:o.repositoryRootPath,relativeWorkspacePath:o.relativeWorkspacePath})}catch(o){const c=o;t.status(500).json({error:c.message||"git add files failed"})}}),S.post("/git/commits",k,async(s,t)=>{const{workspacePath:e,repositoryPath:i,limit:r,branch:n}=s.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const a=await b(String(i||e).trim());if(!a.hasRepository||!a.repositoryRootPath){t.json({ok:!0,commits:[],currentBranch:"",branchName:null,workspacePath:a.workspacePath,repositoryRootPath:null});return}const o=Number(r),c=!!s.body?.all,u=c?200:100,f=Number.isInteger(o)&&o>0&&o<=u?o:c?100:30,l=await B(a.repositoryRootPath),h=(n&&String(n).trim()?String(n).trim():null)||l||"HEAD",m="%H%x00%h%x00%an%x00%ad%x00%s%x00%P%x00%b%x00";let y;if(c?y=await p(a.repositoryRootPath,["log",`-${f}`,"--all","--topo-order","--date=iso-strict",`--pretty=format:${m}`]):y=await p(a.repositoryRootPath,["log",`-${f}`,"--date=iso-strict",`--pretty=format:${m}`,"--end-of-options",h,"--",a.relativeWorkspacePath||"."]),y.exitCode!==0){t.status(400).json({error:y.stderr||"git log failed"});return}const P=await xt(a.repositoryRootPath),w=Rt(y.stdout).map(v=>({...v,isRemote:P.has(v.hash)})),j=c?await U(a.repositoryRootPath):void 0;t.json({ok:!0,commits:w,currentBranch:l,branchName:c?null:h,...j?{branches:j}:{},workspacePath:a.workspacePath,repositoryRootPath:a.repositoryRootPath,relativeWorkspacePath:a.relativeWorkspacePath})}catch(a){const o=a;t.status(500).json({error:o.message||"git commits failed"})}}),S.post("/git/branches",k,async(s,t)=>{const{workspacePath:e,repositoryPath:i}=s.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const r=await b(String(i||e).trim());if(!r.hasRepository||!r.repositoryRootPath){t.json({ok:!0,branches:[],currentBranch:"",workspacePath:r.workspacePath,repositoryRootPath:null});return}const n=await U(r.repositoryRootPath),a=await B(r.repositoryRootPath);t.json({ok:!0,branches:n,currentBranch:a,workspacePath:r.workspacePath,repositoryRootPath:r.repositoryRootPath,relativeWorkspacePath:r.relativeWorkspacePath})}catch(r){const n=r;t.status(500).json({error:n.message||"git branches failed"})}}),S.post("/git/push",k,async(s,t)=>{const{workspacePath:e,repositoryPath:i}=s.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}try{const r=await b(String(i||e).trim());if(!r.hasRepository||!r.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}const n=await bt(r.repositoryRootPath);t.json({ok:!0,branch:n.branch,pushed:n.pushed,message:n.message,output:n.output,workspacePath:r.workspacePath,repositoryRootPath:r.repositoryRootPath,relativeWorkspacePath:r.relativeWorkspacePath})}catch(r){const n=r;if(n.message&&/上游分支|detached HEAD|分离头指针|不存在|未配置/.test(n.message)){t.status(400).json({error:n.message});return}t.status(500).json({error:n.message||"git push failed"})}}),S.post("/git/commit-diff",k,async(s,t)=>{const{workspacePath:e,repositoryPath:i,commitHash:r,aggregate:n}=s.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}const a=q(r);if(!a){t.status(400).json({error:"commitHash is required"});return}const o=n===!0||n==="true";try{const c=await b(String(i||e).trim());if(!c.hasRepository||!c.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}const u=jt(a,o),f=await p(c.repositoryRootPath,A([...u,"--name-status"],c));if(f.exitCode!==0){t.status(400).json({error:f.stderr||"git commit diff failed"});return}const l=await p(c.repositoryRootPath,A([...u,"--numstat"],c));if(l.exitCode!==0){t.status(400).json({error:l.stderr||"git commit diff failed"});return}const d=N(f.stdout,l.stdout);t.json({ok:!0,commitHash:a,isGitRepo:c.isGitRepo,hasRepository:!0,usingParentGitRepo:c.usingParentGitRepo,hasChanges:d.length>0,files:d,workspacePath:c.workspacePath,repositoryRootPath:c.repositoryRootPath,relativeWorkspacePath:c.relativeWorkspacePath})}catch(c){const u=c;t.status(500).json({error:u.message||"git commit diff failed"})}}),S.post("/git/diff-file",k,async(s,t)=>{const{workspacePath:e,filePath:i,commitHash:r,aggregate:n}=s.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}if(!i||!String(i).trim()){t.status(400).json({error:"filePath is required"});return}try{const a=await b(String(e).trim()),o=x(String(i).trim());if(!a.hasRepository||!a.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}const c=G(o,a),u=q(r),f=!!(u&&(n===!0||n==="true")),l=!u||f,d=u?f?["diff",u,"--",c]:["show","--format=","--no-ext-diff",u,"--",c]:["diff","HEAD","--",c];let h=await p(a.repositoryRootPath,d);if(l&&h.exitCode!==0&&(h=await p(a.repositoryRootPath,["diff","--",c])),h.exitCode!==0){t.status(400).json({error:h.stderr||"git diff file failed"});return}if(!u&&!h.stdout.trim()){const y=await p(a.repositoryRootPath,["diff","--cached","--",c]);if(y.exitCode!==0){t.status(400).json({error:y.stderr||"git staged diff file failed"});return}h=y}if(l&&!h.stdout.trim()){const y=R.join(a.repositoryRootPath,c);try{const P=await C.readFile(y,"utf-8");if(P){const w=P.split(`
15
+ `).map(v=>`+${v}`).join(`
16
+ `),j=P.split(`
17
+ `).length;h={stdout:[`diff --git a/${c} b/${c}`,"new file mode 100644","--- /dev/null",`+++ b/${c}`,`@@ -0,0 +1,${j} @@`,w].join(`
18
+ `),stderr:"",exitCode:0}}}catch{}}F(c);let m="";if(l){const y=R.join(a.repositoryRootPath,c);try{m=await C.readFile(y,"utf-8")}catch{}}else{const y=await p(a.repositoryRootPath,["show",`${u}:${c}`]);y.exitCode===0&&(m=y.stdout)}t.json({ok:!0,filePath:o,repositoryRelativeFilePath:c,commitHash:u||null,rangeFromCommit:f,diffContent:h.stdout,fileContent:m,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 Y=["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(`
19
+ `);function V(s){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:","",s].join(`
20
+ `)}const X=/\b(?:feat|fix|chore|docs|style|refactor|perf|test|build|ci|revert)(?:\s*\([^)]*\))?\s*:/i,$t=/^(?:feat|fix|chore|docs|style|refactor|perf|test|build|ci|revert)\s*:/i;function vt(s){const t=s.split(`
21
+ `)[0]?.trim()??"";if(!t||t.length<3)return!1;const e=(t.match(/[\u4e00-\u9fa5]/g)||[]).length,i=(t.match(/[a-zA-Z]{2,}/g)||[]).length;if(e===0&&i===0)return!1;const r=[/^以下是/,/^上面是/,/^生成的/,/^这是/,/^下面/,/^(?:Here|This|This is|That is)/i];for(const n of r)if(n.test(t))return!1;return e>=2||i>=2}const At=[/^\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(s){if(!s)return g.info("[Git/sanitize] \u8F93\u5165\u4E3A\u7A7A, \u8FD4\u56DE fallback"),"chore: update staged changes";let t=s.replace(/\r\n/g,`
22
+ `).trim();const e=JSON.stringify(s.substring(0,120)),i=t.match(X);if(i&&i.index!==void 0&&i.index<=80)t=t.slice(i.index);else{for(let l=0;l<5;l+=1){const d=t;for(const h of At)t=t.replace(h,"");if(t=t.replace(/^[\s*\-`,,']+/,"").trim(),t===d)break}const a=t.split(`
23
+ `)[0]?.trim()??"",o=!!t.match(X),c=!!t.match($t),u=vt(t),f=a.length>100;if(!o&&!c&&!u&&!f)return g.info(`[Git/sanitize] \u5265\u79BB\u540E\u65E0\u6709\u6548\u5185\u5BB9, \u8FD4\u56DE fallback. raw=${e}, afterStrip=${JSON.stringify(t.substring(0,100))}`),"chore: update staged changes";!o&&(c||u||f)&&(t=a||t)}const r=t.search(/\n\s*\n/);if(r>0&&(t=t.slice(0,r)),t=t.replace(/\n+\s*```\s*$/g,"").trim(),!t)return g.info(`[Git/sanitize] \u622A\u65AD\u540E\u4E3A\u7A7A, \u8FD4\u56DE fallback. raw=${e}`),"chore: update staged changes";const n=t.split(`
24
+ `)[0]?.trim()??"";return n.length>100?(g.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):(g.info(`[Git/sanitize] \u7ED3\u679C: ${JSON.stringify(t.substring(0,120))} (from raw=${e})`),t)}function Z(s){const t=s.split(/\r?\n/);let e="";for(const i of t){const r=i.trim();if(!r.startsWith("data:"))continue;const n=r.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 Gt(s,t){if(t>5)return;const e=s.split(/\r?\n/);for(const i of e){const r=i.trim();if(!r.startsWith("data:"))continue;const n=r.slice(5).trim();if(!(!n||n==="[DONE]"))try{const a=JSON.parse(n),o=Object.keys(a),c=a?.choices,u=Array.isArray(c)?c[0]:null,f=u?.delta?Object.keys(u.delta):null,l=u?.delta?.content;g.info(`[Git/extract] chunk#${t} structure: topKeys=${JSON.stringify(o)}, choicesLen=${Array.isArray(c)?c.length:typeof c}, deltaKeys=${JSON.stringify(f)}, content=${JSON.stringify(l?.substring(0,50))}, finishReason=${JSON.stringify(u?.finish_reason)}`)}catch{}}}async function Et(s,t,e,i){const r=`${s.replace(/\/+$/,"")}/chat/completions`,o=(await L.post(r,{model:e,stream:!1,messages:[{role:"system",content:Y},{role:"user",content:V(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 Tt(s,t,e,i,r,n){const a=`${s.replace(/\/+$/,"")}/chat/completions`,c=(await L.post(a,{model:e,stream:!0,messages:[{role:"system",content:Y},{role:"user",content:V(i)}],max_tokens:200,temperature:.2},{headers:{"Content-Type":"application/json",Authorization:`Bearer ${t}`,Accept:"text/event-stream"},timeout:Number(process.env.AWS_AI_STREAM_TIMEOUT_MS)||6e4,responseType:"stream",signal:n})).data;let u="",f="",l="",d=0,h=0;return g.info(`[Git/streamAi] AI \u6D41\u5F00\u59CB: aiModel=${e}`),await new Promise((m,y)=>{const P=w=>{c.removeAllListeners("data"),c.removeAllListeners("end"),c.removeAllListeners("error"),c.removeAllListeners("aborted"),g.info(`[Git/streamAi] AI \u6D41\u7ED3\u675F: totalChunks=${d}, totalDeltas=${h}, bufferLength=${u.length}, sanitized=${JSON.stringify(w).substring(0,200)}`),m(w)};c.on("data",w=>{const j=typeof w=="string"?w:w.toString("utf8");d++,l+=j,d<=3&&g.info(`[Git/streamAi] chunk#${d}: length=${j.length}, preview=${JSON.stringify(j.substring(0,80))}`),Gt(j,d);const v=l.split(/\n\n/);l=v.pop()??"";for(const Q of v){const M=Z(Q);M&&(h++,u+=M,f=T(u),r(M,f))}}),c.on("end",()=>{if(l.trim()){const w=Z(l);w?(g.info(`[Git/streamAi] \u5C3E\u90E8\u6B8B\u7559 delta: length=${w.length}`),u+=w):g.info(`[Git/streamAi] \u5C3E\u90E8\u6B8B\u7559\u65E0 delta, pendingData=${JSON.stringify(l.substring(0,100))}`)}P(T(u))}),c.on("error",w=>{g.error(`[Git/streamAi] \u6D41\u9519\u8BEF: ${w.message}`),y(w)}),c.on("aborted",()=>{g.info(`[Git/streamAi] \u6D41\u88AB\u4E2D\u6B62, bufferLength=${u.length}`),P(T(u))})})}async function W(s,t,e){const i=await b(String(s||t).trim());if(!i.hasRepository||!i.repositoryRootPath)return g.warn(`[Git/collectDiff] \u4E0D\u5728 Git \u4ED3\u5E93\u4E2D: workspacePath=${t}`),{truncatedDiff:"",error:{status:400,message:"workspace is not inside a git repository"}};const r=e.map(u=>u.path).filter(Boolean);g.info(`[Git/collectDiff] \u5F00\u59CB\u6536\u96C6 ${r.length} \u4E2A\u6682\u5B58\u6587\u4EF6\u5DEE\u5F02: ${r.join(", ").substring(0,200)}`);const n=[];for(const u of r){const f=G(u,i),l=await p(i.repositoryRootPath,["diff","--cached","--",f]);l.exitCode===0&&l.stdout.trim()?n.push(`--- ${u} ---
25
+ ${l.stdout.trim()}`):g.info(`[Git/collectDiff] \u6587\u4EF6\u65E0\u6682\u5B58\u5DEE\u5F02\u6216\u83B7\u53D6\u5931\u8D25: ${u}, exitCode=${l.exitCode}, stdout.length=${l.stdout.length}`)}if(n.length===0){g.info("[Git/collectDiff] \u9010\u6587\u4EF6 diff \u4E3A\u7A7A, \u56DE\u9000\u5230\u5168\u91CF diff --cached");const u=await p(i.repositoryRootPath,["diff","--cached"]);u.exitCode===0&&u.stdout.trim()?(n.push(u.stdout.trim()),g.info(`[Git/collectDiff] \u5168\u91CF diff \u83B7\u53D6\u6210\u529F: length=${u.stdout.trim().length}`)):g.info(`[Git/collectDiff] \u5168\u91CF diff \u4E5F\u4E3A\u7A7A, exitCode=${u.exitCode}`)}if(n.length===0)return g.info("[Git/collectDiff] \u6682\u5B58\u533A\u6CA1\u6709\u5DEE\u5F02\u5185\u5BB9 (diffParts \u4E3A\u7A7A)"),{truncatedDiff:""};const a=n.join(`
21
26
 
22
- `),o=15e3,l=s.length>o?`${s.slice(0,o)}
23
- ...(diff truncated due to length)`:s;return h.info(`[Git/collectDiff] \u6536\u96C6\u5B8C\u6210: diffContent.length=${s.length}, truncated=${l.length}`),{truncatedDiff:l}}function $(i,t,e){try{i.write(`event: ${t}
24
- `),i.write(`data: ${JSON.stringify(e)}
27
+ `),o=15e3,c=a.length>o?`${a.slice(0,o)}
28
+ ...(diff truncated due to length)`:a;return g.info(`[Git/collectDiff] \u6536\u96C6\u5B8C\u6210: diffContent.length=${a.length}, truncated=${c.length}`),{truncatedDiff:c}}function $(s,t,e){try{s.write(`event: ${t}
29
+ `),s.write(`data: ${JSON.stringify(e)}
25
30
 
26
- `)}catch{}}P.post("/git/generate-commit-message",y,async(i,t)=>{const{workspacePath:e,repositoryPath:a,stagedFiles:r,aiBaseUrl:n,aiApiKey:s,aiModel:o}=i.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}if(!Array.isArray(r)||r.length===0){t.status(400).json({error:"stagedFiles is required and must be a non-empty array"});return}if(!n||!String(n).trim()||!s||!String(s).trim()||!o||!String(o).trim()){t.status(400).json({error:"aiBaseUrl, aiApiKey and aiModel are required"});return}try{const{truncatedDiff:l,error:c}=await E(String(a||""),String(e),r);if(c){t.status(c.status).json({error:c.message});return}if(!l){t.json({message:"\u6682\u5B58\u533A\u6CA1\u6709\u53D8\u66F4\u5185\u5BB9"});return}const u=await wt(n,s,o,l);t.json({message:u})}catch(l){const c=l;h.error(`[Git] generate commit message failed: ${c.message}`),t.status(500).json({error:c.message||"generate commit message failed"})}}),P.post("/git/generate-commit-message-stream",y,async(i,t)=>{const{workspacePath:e,repositoryPath:a,stagedFiles:r,aiBaseUrl:n,aiApiKey:s,aiModel:o}=i.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}if(!Array.isArray(r)||r.length===0){t.status(400).json({error:"stagedFiles is required and must be a non-empty array"});return}if(!n||!String(n).trim()||!s||!String(s).trim()||!o||!String(o).trim()){t.status(400).json({error:"aiBaseUrl, aiApiKey and aiModel are required"});return}h.info(`[Git/SSE] \u6536\u5230 generate-commit-message-stream \u8BF7\u6C42: workspacePath=${String(e||"").substring(0,40)}, stagedFiles=${r?.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"),i.socket?.setNoDelay(!0),t.flushHeaders?.();let l=!1,c=null,u=!1;const f={delta:0},d=`${i.ip}:${i.socket?.remotePort??"?"}`;i.on("close",()=>{l||(l=!0,h.info(`[Git/SSE] \u5BA2\u6237\u7AEF\u65AD\u5F00\u8FDE\u63A5: workspacePath=${String(e||"").substring(0,40)}, deltaEventsSent=${f.delta}, peer=${d}`))}),$(t,"init",{status:"generating"});try{const{truncatedDiff:m,error:g}=await E(String(a||""),String(e),r);if(g){h.warn(`[Git/SSE] collectStagedDiffForAi \u8FD4\u56DE\u9519\u8BEF: ${g.message}`),$(t,"error",{message:g.message}),t.end();return}if(!m){h.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}h.info(`[Git/SSE] \u6536\u96C6\u5230 diff: truncatedDiff.length=${m.length}`),l&&h.info("[Git/SSE] \u5BA2\u6237\u7AEF\u5728 collectDiff \u671F\u95F4\u65AD\u5F00, \u4F46\u4ECD\u5C1D\u8BD5 AI \u8C03\u7528"),h.info(`[Git/SSE] \u5F00\u59CB\u8C03\u7528 AI streamAiForCommitMessage: aiModel=${o}`),c=new AbortController;let x="";const b=await St(n,s,o,m,(w,j)=>{f.delta++,j!==x?(x=j,$(t,"delta",{contentDelta:w,message:j})):$(t,"delta",{contentDelta:w,message:j})},c.signal);h.info(`[Git/SSE] AI streamAiForCommitMessage \u5B8C\u6210: finalMessage=${JSON.stringify(b).substring(0,200)}, deltaEventsSent=${f.delta}`),l||$(t,"done",{message:b}),t.end()}catch(m){const g=m;h.error(`[Git/SSE] stream commit message failed: ${g.message}${g.stack?`
27
- `+g.stack:""}`),l||$(t,"error",{message:g.message||"generate commit message failed"}),t.end()}}),P.post("/git/unstage-files",y,async(i,t)=>{const{workspacePath:e,repositoryPath:a,filePaths:r}=i.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}const s=(Array.isArray(r)?r:[]).map(o=>S(String(o||"").trim())).filter(o=>o.length>0);if(s.length===0){t.status(400).json({error:"filePaths is required and must be a non-empty array"});return}try{const o=await R(String(a||e).trim());if(!o.hasRepository||!o.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}let l="";for(const c of s){const u=G(c,o),f=await p(o.repositoryRootPath,["restore","--staged",...u]);f.exitCode!==0&&(l=f.stderr||`git restore --staged failed for ${c}`)}if(l){t.status(400).json({error:l});return}t.json({ok:!0,filePaths:s,workspacePath:o.workspacePath,repositoryRootPath:o.repositoryRootPath,relativeWorkspacePath:o.relativeWorkspacePath})}catch(o){const l=o;t.status(500).json({error:l.message||"git unstage files failed"})}}),P.post("/git/restore-files",y,async(i,t)=>{const{workspacePath:e,repositoryPath:a,filePaths:r}=i.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}const s=(Array.isArray(r)?r:[]).map(o=>S(String(o||"").trim())).filter(o=>o.length>0);if(s.length===0){t.status(400).json({error:"filePaths is required and must be a non-empty array"});return}try{const o=await R(String(a||e).trim());if(!o.hasRepository||!o.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}let l="";for(const c of s){const u=G(c,o),f=await p(o.repositoryRootPath,["restore",...u]);f.exitCode!==0&&(l=f.stderr||`git restore failed for ${c}`)}if(l){t.status(400).json({error:l});return}t.json({ok:!0,filePaths:s,workspacePath:o.workspacePath,repositoryRootPath:o.repositoryRootPath,relativeWorkspacePath:o.relativeWorkspacePath})}catch(o){const l=o;t.status(500).json({error:l.message||"git restore files failed"})}}),P.post("/git/diff-content",y,async(i,t)=>{const{workspacePath:e,repositoryPath:a,stagedFiles:r}=i.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}if(!Array.isArray(r)||r.length===0){t.status(400).json({error:"stagedFiles is required and must be a non-empty array"});return}try{const{truncatedDiff:n,error:s}=await E(String(a||""),String(e),r);if(s){t.status(s.status).json({error:s.message});return}t.json({ok:!0,diff:n||""})}catch(n){const s=n;h.error(`[Git/diff-content] \u83B7\u53D6\u6682\u5B58\u533A\u5DEE\u5F02\u5931\u8D25: ${s.message}`),t.status(500).json({error:s.message||"get diff content failed"})}});export{Q as assertGitWorkspacePathAccessible,wt as callAiForCommitMessage,it as createAgentsWorkStashSnapshotRef,st as createMissingInitialCommitSnapshotError,G as createScopedFilePathspecArgs,dt as createScopedGitPathspecArgs,Pt as debugAiStreamChunk,K as extractOpenAiStreamDelta,P as gitRouter,rt as isGitStashMissingInitialCommitError,ot as isGitStashNoChangesOutput,ft as mergeGitDiffSummaryFiles,ut as normalizeGitCommitMessage,D as parseGitDiffSummary,W as parseGitStashListLine,nt as parseGitStatusStagedPaths,ct as parseGitStatusUntrackedPaths,z as parseLatestGitStash,_ as persistAgentsWorkStashSnapshotRef,O as resolveGitCommandTimeoutMs,v as sanitizeCommitMessage,et as shouldIncludeGitDiffSummaryFile,St as streamAiForCommitMessage};
31
+ `)}catch{}}S.post("/git/generate-commit-message",k,async(s,t)=>{const{workspacePath:e,repositoryPath:i,stagedFiles:r,aiBaseUrl:n,aiApiKey:a,aiModel:o}=s.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}if(!Array.isArray(r)||r.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),r);if(u){t.status(u.status).json({error:u.message});return}if(!c){t.json({message:"\u6682\u5B58\u533A\u6CA1\u6709\u53D8\u66F4\u5185\u5BB9"});return}const f=await Et(n,a,o,c);t.json({message:f})}catch(c){const u=c;g.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(s,t)=>{const{workspacePath:e,repositoryPath:i,stagedFiles:r,aiBaseUrl:n,aiApiKey:a,aiModel:o}=s.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}if(!Array.isArray(r)||r.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}g.info(`[Git/SSE] \u6536\u5230 generate-commit-message-stream \u8BF7\u6C42: workspacePath=${String(e||"").substring(0,40)}, stagedFiles=${r?.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"),s.socket?.setNoDelay(!0),t.flushHeaders?.();let c=!1,u=null,f=!1;const l={delta:0},d=`${s.ip}:${s.socket?.remotePort??"?"}`;s.on("close",()=>{c||(c=!0,g.info(`[Git/SSE] \u5BA2\u6237\u7AEF\u65AD\u5F00\u8FDE\u63A5: workspacePath=${String(e||"").substring(0,40)}, deltaEventsSent=${l.delta}, peer=${d}`))}),$(t,"init",{status:"generating"});try{const{truncatedDiff:h,error:m}=await W(String(i||""),String(e),r);if(m){g.warn(`[Git/SSE] collectStagedDiffForAi \u8FD4\u56DE\u9519\u8BEF: ${m.message}`),$(t,"error",{message:m.message}),t.end();return}if(!h){g.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}g.info(`[Git/SSE] \u6536\u96C6\u5230 diff: truncatedDiff.length=${h.length}`),c&&g.info("[Git/SSE] \u5BA2\u6237\u7AEF\u5728 collectDiff \u671F\u95F4\u65AD\u5F00, \u4F46\u4ECD\u5C1D\u8BD5 AI \u8C03\u7528"),g.info(`[Git/SSE] \u5F00\u59CB\u8C03\u7528 AI streamAiForCommitMessage: aiModel=${o}`),u=new AbortController;let y="";const P=await Tt(n,a,o,h,(w,j)=>{l.delta++,j!==y?(y=j,$(t,"delta",{contentDelta:w,message:j})):$(t,"delta",{contentDelta:w,message:j})},u.signal);g.info(`[Git/SSE] AI streamAiForCommitMessage \u5B8C\u6210: finalMessage=${JSON.stringify(P).substring(0,200)}, deltaEventsSent=${l.delta}`),c||$(t,"done",{message:P}),t.end()}catch(h){const m=h;g.error(`[Git/SSE] stream commit message failed: ${m.message}${m.stack?`
32
+ `+m.stack:""}`),c||$(t,"error",{message:m.message||"generate commit message failed"}),t.end()}}),S.post("/git/unstage-files",k,async(s,t)=>{const{workspacePath:e,repositoryPath:i,filePaths:r}=s.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}const a=(Array.isArray(r)?r:[]).map(o=>x(String(o||"").trim())).filter(o=>o.length>0);if(a.length===0){t.status(400).json({error:"filePaths is required and must be a non-empty array"});return}try{const o=await b(String(i||e).trim());if(!o.hasRepository||!o.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}let c="";for(const u of a){const f=E(u,o),l=await p(o.repositoryRootPath,["restore","--staged",...f]);l.exitCode!==0&&(c=l.stderr||`git restore --staged failed for ${u}`)}if(c){t.status(400).json({error:c});return}t.json({ok:!0,filePaths:a,workspacePath:o.workspacePath,repositoryRootPath:o.repositoryRootPath,relativeWorkspacePath:o.relativeWorkspacePath})}catch(o){const c=o;t.status(500).json({error:c.message||"git unstage files failed"})}}),S.post("/git/restore-files",k,async(s,t)=>{const{workspacePath:e,repositoryPath:i,filePaths:r}=s.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}const a=(Array.isArray(r)?r:[]).map(o=>x(String(o||"").trim())).filter(o=>o.length>0);if(a.length===0){t.status(400).json({error:"filePaths is required and must be a non-empty array"});return}try{const o=await b(String(i||e).trim());if(!o.hasRepository||!o.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}let c="";for(const u of a){const f=G(u,o),l=E(u,o),h=(await p(o.repositoryRootPath,["ls-files","--error-unmatch","--",f])).exitCode===0,m=h?await p(o.repositoryRootPath,["restore",...l]):await p(o.repositoryRootPath,["clean","-f",...l]);m.exitCode!==0&&(c=m.stderr||`${h?"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 Mt(s){const t=s.split(`
33
+ `),e=[];for(let i=0;i<t.length;i++){const r=t[i],n=r.endsWith("\r")?"\r":"",o=(n?r.slice(0,-1):r).match(/^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/);if(!o){e.push(r);continue}let c=0,u=0;for(let f=i+1;f<t.length;f++){const l=t[f].replace(/\r$/,"");if(/^diff --git /.test(l)||/^@@ /.test(l))break;l.startsWith("+")?u++:l.startsWith("-")?c++:l.startsWith(" ")&&(c++,u++)}e.push(`@@ -${o[1]},${c} +${o[2]},${u} @@${n}`)}return e.join(`
34
+ `)}S.post("/git/revert-hunk",k,async(s,t)=>{const{workspacePath:e,repositoryPath:i,filePath:r,patch:n}=s.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}if(!r||!String(r).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 b(String(i||e).trim());if(!o.hasRepository||!o.repositoryRootPath){t.status(400).json({error:"workspace is not inside a git repository"});return}const c=x(String(r).trim()),u=G(c,o);if(F(u),(a.match(/^diff --git /gm)||[]).length!==1){t.status(400).json({error:"patch must contain exactly one file diff"});return}const l=a.match(/^\+\+\+ b\/(.+)$/m),d=l?x(l[1].trim()):"";if(!d||d!==u){t.status(400).json({error:"patch target path does not match filePath"});return}let h=null;try{h=await C.mkdtemp(R.join(et.tmpdir(),"agentswork-git-revert-"));const m=R.join(h,"revert.patch");await C.writeFile(m,Mt(a)+`
35
+ `,"utf-8");const y=await p(o.repositoryRootPath,["apply","--reverse",m]);if(y.exitCode!==0){t.status(400).json({error:y.stderr||"git apply --reverse failed; \u8BE5\u5904\u5185\u5BB9\u53EF\u80FD\u5DF2\u53D8\u5316\uFF0C\u8BF7\u5237\u65B0\u5DEE\u5F02\u540E\u91CD\u8BD5"});return}t.json({ok:!0,filePath:c,repositoryRelativeFilePath:u,workspacePath:o.workspacePath,repositoryRootPath:o.repositoryRootPath})}finally{h&&await C.rm(h,{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(s,t)=>{const{workspacePath:e,repositoryPath:i,stagedFiles:r}=s.body||{};if(!e||!String(e).trim()){t.status(400).json({error:"workspacePath is required"});return}if(!Array.isArray(r)||r.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),r);if(a){t.status(a.status).json({error:a.message});return}t.json({ok:!0,diff:n||""})}catch(n){const a=n;g.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{it as assertGitWorkspacePathAccessible,jt as buildCommitDiffBaseArgs,Et as callAiForCommitMessage,Pt as collectUntrackedFiles,Ct as commitSelectedFiles,yt as countUntrackedFileAdditions,ft as createAgentsWorkStashSnapshotRef,ut as createMissingInitialCommitSnapshotError,E as createScopedFilePathspecArgs,St as createScopedGitPathspecArgs,Gt as debugAiStreamChunk,Z as extractOpenAiStreamDelta,S as gitRouter,ct as isGitStashMissingInitialCommitError,lt as isGitStashNoChangesOutput,U as listGitBranches,wt as mergeGitDiffSummaryFiles,kt as normalizeGitCommitMessage,N as parseGitDiffSummary,O as parseGitStashListLine,mt as parseGitStatusStagedPaths,gt as parseGitStatusUntrackedPaths,I as parseLatestGitStash,z as persistAgentsWorkStashSnapshotRef,bt as pushBranchToRemote,B as resolveCurrentBranchName,ot as resolveGitCommandTimeoutMs,dt as resolveGitPushTimeoutMs,xt as resolveRemoteCommitHashes,T as sanitizeCommitMessage,nt as shouldIncludeGitDiffSummaryFile,Tt as streamAiForCommitMessage};
28
36