aws-runtime-bridge 1.9.120 → 1.9.124

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 (105) 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/browser-ws-bridge.js +3 -3
  6. package/dist/browser/built-in-browser-tools.js +1 -1
  7. package/dist/desktop/built-in-desktop-tools.d.ts +49 -0
  8. package/dist/desktop/built-in-desktop-tools.js +2 -0
  9. package/dist/desktop/desktop-mcp-client.d.ts +47 -0
  10. package/dist/desktop/desktop-mcp-client.js +3 -0
  11. package/dist/index.js +1 -1
  12. package/dist/remote-desktop/index.js +2 -2
  13. package/dist/routes/dashboard.js +521 -17
  14. package/dist/routes/git.d.ts +39 -0
  15. package/dist/routes/git.js +33 -25
  16. package/dist/routes/instance.js +1 -1
  17. package/dist/routes/jdks.d.ts +2 -0
  18. package/dist/routes/jdks.js +2 -0
  19. package/dist/routes/launch.d.ts +6 -0
  20. package/dist/routes/launch.js +30 -0
  21. package/dist/routes/pty.js +3 -3
  22. package/dist/routes/runtime-binding.js +1 -1
  23. package/dist/routes/terminal.js +30 -30
  24. package/dist/services/access-control.d.ts +18 -0
  25. package/dist/services/access-control.js +5 -0
  26. package/dist/services/agent-process-manager.js +1 -1
  27. package/dist/services/auto-register.js +1 -1
  28. package/dist/services/change-cache-retention.d.ts +5 -0
  29. package/dist/services/change-cache-retention.js +2 -0
  30. package/dist/services/desktop-mcp-manager.d.ts +47 -0
  31. package/dist/services/desktop-mcp-manager.js +2 -0
  32. package/dist/services/instance-features.d.ts +6 -0
  33. package/dist/services/instance-features.js +1 -1
  34. package/dist/services/jdk-scanner.d.ts +20 -0
  35. package/dist/services/jdk-scanner.js +2 -0
  36. package/dist/services/launch-manager.d.ts +181 -0
  37. package/dist/services/launch-manager.js +12 -0
  38. package/dist/services/launch-metrics.d.ts +89 -0
  39. package/dist/services/launch-metrics.js +4 -0
  40. package/dist/services/launch-streams.d.ts +24 -0
  41. package/dist/services/launch-streams.js +11 -0
  42. package/dist/services/mcp-launch-binding-queue.js +1 -1
  43. package/dist/services/panel-auth.d.ts +11 -1
  44. package/dist/services/panel-auth.js +1 -1
  45. package/dist/services/panel-env.d.ts +5 -0
  46. package/dist/services/panel-env.js +2 -0
  47. package/dist/services/port-detector.d.ts +3 -0
  48. package/dist/services/port-detector.js +6 -0
  49. package/dist/services/runtime-lifecycle-policy.d.ts +1 -0
  50. package/dist/services/runtime-lifecycle-policy.js +1 -1
  51. package/dist/services/system-metrics.js +1 -1
  52. package/dist/services/tunnel-client.d.ts +4 -0
  53. package/dist/services/tunnel-client.js +1 -1
  54. package/dist/services/workspace-files.d.ts +3 -0
  55. package/dist/services/workspace-files.js +1 -1
  56. package/dist/services/workspaceTaskChangeWatcher.d.ts +2 -0
  57. package/dist/services/workspaceTaskChangeWatcher.js +3 -3
  58. package/dist/utils/config-write-queue.d.ts +3 -0
  59. package/dist/utils/config-write-queue.js +2 -0
  60. package/package/acode/dist/built-in-file-tools.d.ts +2 -0
  61. package/package/acode/dist/runtime.d.ts +20 -0
  62. package/package/acode/dist/runtime.js +27 -27
  63. package/package/acode/dist/sub-agent-manager.d.ts +4 -0
  64. package/package/acode/dist/sub-agent-manager.js +1 -1
  65. package/package/acode/dist/sub-agent-tools.d.ts +6 -0
  66. package/package/acode/dist/sub-agent-tools.js +39 -10
  67. package/package/acode/dist/sub-agent-types.d.ts +2 -0
  68. package/package/acode/dist/tool-allocation.d.ts +55 -0
  69. package/package/acode/dist/tool-allocation.js +2 -0
  70. package/package/acode/dist/types.d.ts +6 -0
  71. package/package/aws-client-agent-mcp/dist/agent-client.d.ts +24 -0
  72. package/package/aws-client-agent-mcp/dist/agent-client.js +1 -1
  73. package/package/aws-client-agent-mcp/dist/agent-client.test.js +19 -5
  74. package/package/aws-client-agent-mcp/dist/context-manager.test.js +6 -6
  75. package/package/aws-client-agent-mcp/dist/http-client.d.ts +2 -0
  76. package/package/aws-client-agent-mcp/dist/http-client.js +1 -1
  77. package/package/aws-client-agent-mcp/dist/index.js +1 -1
  78. package/package/aws-client-agent-mcp/dist/launch-config-tools.test.d.ts +1 -0
  79. package/package/aws-client-agent-mcp/dist/launch-config-tools.test.js +106 -0
  80. package/package/aws-client-agent-mcp/dist/logger.d.ts +4 -0
  81. package/package/aws-client-agent-mcp/dist/logger.js +2 -2
  82. package/package/aws-client-agent-mcp/dist/mcp-server.d.ts +21 -0
  83. package/package/aws-client-agent-mcp/dist/mcp-server.js +25 -25
  84. package/package/aws-client-agent-mcp/dist/mcp-server.test.js +24 -16
  85. package/package/aws-client-agent-mcp/dist/mcp-tools.d.ts +20 -0
  86. package/package/aws-client-agent-mcp/dist/mcp-tools.js +8 -8
  87. package/package/aws-client-agent-mcp/dist/mcp-tools.test.js +55 -1
  88. package/package/aws-client-agent-mcp/dist/memory-store.test.js +5 -1
  89. package/package/aws-client-agent-mcp/dist/parent-lifecycle.d.ts +10 -0
  90. package/package/aws-client-agent-mcp/dist/parent-lifecycle.js +2 -0
  91. package/package/aws-client-agent-mcp/dist/parent-lifecycle.test.d.ts +1 -0
  92. package/package/aws-client-agent-mcp/dist/parent-lifecycle.test.js +81 -0
  93. package/package/aws-client-agent-mcp/dist/status-reporter.js +1 -1
  94. package/package/aws-client-agent-mcp/dist/types.d.ts +4 -0
  95. package/package/aws-client-agent-mcp/dist/watchdog-worker.d.ts +1 -0
  96. package/package/aws-client-agent-mcp/dist/watchdog-worker.js +3 -0
  97. package/package/aws-client-agent-mcp/dist/watchdog.d.ts +17 -0
  98. package/package/aws-client-agent-mcp/dist/watchdog.js +2 -0
  99. package/package/aws-client-agent-mcp/dist/watchdog.test.d.ts +1 -0
  100. package/package/aws-client-agent-mcp/dist/watchdog.test.js +124 -0
  101. package/package/aws-client-agent-mcp/dist/websocket-client.d.ts +4 -0
  102. package/package/aws-client-agent-mcp/dist/websocket-client.js +1 -1
  103. package/package/aws-client-agent-mcp/dist/websocket-client.test.js +83 -6
  104. package/package/aws-client-agent-mcp/package.json +1 -1
  105. package/package.json +2 -1
@@ -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
 
@@ -1,2 +1,2 @@
1
- import{Router as H}from"express";import E from"axios";import{spawn as _}from"node:child_process";import{createHash as W,timingSafeEqual as G}from"node:crypto";import{fileURLToPath as V}from"node:url";import k from"node:path";import{getBridgeVersion as R,resolveSchedulerBaseUrlFrom as J}from"../config.js";import{validateToken as S,validateTokenOrPanelSession as v}from"../middleware/auth.js";import{getConfiguredConnectionKeys as A,getRegistrationState as Y,requestRuntimeAccessTokenRefreshForServer as Q,resolveSchedulerBaseUrlFromToken as Z}from"../services/auto-register.js";import{discoverCcSwitchConfiguredItems as ee,loadCcSwitchSdk as te}from"../services/cc-switch-sdk.js";import{initInstance as re,syncLegacyStateFromSdk as se}from"../services/instance-init-service.js";import{setAgentFeatures as oe,getAgentFeatures as x}from"../services/instance-features.js";import{createBridgeLifecycleOperation as L,deliverPendingBridgeLifecycleNotifications as C,loadLifecycleState as ne,removeBridgeLifecycleOperation as ae,upsertBridgeLifecycleOperation as b}from"../services/lifecycle-state.js";import{appendUpdateLog as p,readUpdateLog as ie}from"../services/update-log.js";import{checkBridgeUpdate as O}from"../utils/npm-version.js";import{loadInstanceState as $,saveInstanceState as P}from"../services/instance-state.js";import{getRuntimeAccessToken as ce}from"../services/runtime-binding.js";import{detectToolStatuses as U,ensureToolsInstalled as le,SUPPORTED_INSTALLABLE_TOOLS as j,SUPPORTED_UNINSTALLABLE_TOOLS as q,uninstallTools as de}from"../services/tool-installer.js";import{createLogger as ue}from"../utils/logger.js";const a=ue("instance"),ge=["acode","claude","opencode","codex"];let I=null;function Xe(r){I=r}const d=H();function D(r){return String(r||"").trim()||void 0}function F(r){const t=r.get("host");if(t)return`${r.protocol}://${t}`}function N(){const r=k.resolve(k.dirname(V(import.meta.url)),"..","..");return k.join(r,"dist","index.js")}function fe(){return new Promise((r,t)=>{const e=_(process.execPath,[N(),"update","--no-kill"],{shell:!1,windowsHide:!0});let s="",o="";e.stdout.on("data",n=>{s+=n.toString("utf8")}),e.stderr.on("data",n=>{o+=n.toString("utf8")}),e.on("error",t),e.on("close",n=>{r({exitCode:n,stdout:s,stderr:o})})})}function K(){const r=_(process.execPath,[N(),"service","restart"],{shell:!1,windowsHide:!0,stdio:["ignore","pipe","pipe"]});r.stdout.on("data",t=>{p(String(t)).catch(()=>{})}),r.stderr.on("data",t=>{p(String(t)).catch(()=>{})})}const M=3e3;async function pe(r){const t={...r,status:"running",message:"Bridge \u66F4\u65B0\u6B63\u5728\u540E\u53F0\u6267\u884C\u3002"};await b(t);try{a.info("[update-bridge] \u6B63\u5728\u540E\u53F0\u6267\u884C awsb update --yes"),await p(`===== Bridge \u66F4\u65B0\u5F00\u59CB\uFF1Av${R()} -> latest\uFF08\u64CD\u4F5C ${r.operationId}\uFF09 =====`);const e=await fe();if(e.stdout&&await p(e.stdout),e.stderr&&await p(e.stderr),e.exitCode===0){await p("===== Bridge \u66F4\u65B0\u6210\u529F\uFF0C\u51C6\u5907\u81EA\u52A8\u91CD\u542F ===== ");const s={...t,status:"restarting",message:"Bridge \u66F4\u65B0\u5B8C\u6210\uFF0C\u6B63\u5728\u91CD\u542F..."};await b(s),a.info("[update-bridge] \u6B63\u5728\u6267\u884C awsb service restart"),K(),setTimeout(()=>{if(process.exitCode=42,I){I("BRIDGE_UPDATE_RESTART",!0).catch(o=>{const n=o;a.error("[update-bridge] graceful shutdown failed:",n),process.exit(42)});return}process.exit(42)},M)}else await p(`===== Bridge \u66F4\u65B0\u5931\u8D25\uFF1A\u9000\u51FA\u7801 ${e.exitCode??"unknown"} =====`),await b({...t,status:"failed",message:`Bridge \u66F4\u65B0\u5931\u8D25\uFF0Cnpm \u9000\u51FA\u7801: ${e.exitCode??"unknown"}`,error:e.stderr||e.stdout}),await C()}catch(e){const s=e;a.error("[update-bridge] background update failed:",s),await p(`===== Bridge \u66F4\u65B0\u5F02\u5E38\uFF1A${s.message} =====`).catch(()=>{}),await b({...t,status:"failed",message:"Bridge \u66F4\u65B0\u5931\u8D25\u3002",error:s.message||"update bridge failed"}),await C()}}async function z(r){const t=await import("node:fs/promises"),e=await import("node:path"),s=await import("node:os"),o=e.join(s.tmpdir(),"agentswork-runtime-bridge",".preserve-sessions");if(r){await t.mkdir(e.dirname(o),{recursive:!0}),await t.writeFile(o,JSON.stringify({preserveSessions:!0,timestamp:new Date().toISOString()}),"utf-8"),a.info(`[prepare-restart] \u5DF2\u521B\u5EFA\u4FDD\u7559\u4F1A\u8BDD\u6807\u8BB0\u6587\u4EF6: ${o}`);return}try{await t.unlink(o),a.info("[prepare-restart] \u5DF2\u5220\u9664\u4FDD\u7559\u4F1A\u8BDD\u6807\u8BB0\u6587\u4EF6")}catch{}}function me(r){return W("md5").update(r,"utf8").digest("hex")}function he(r){const t=r.trim().toLowerCase();if(!/^[a-f0-9]{32}$/.test(t))return!1;const e=Buffer.from(t,"hex");return A().map(s=>s.trim()).filter(Boolean).some(s=>{const o=Buffer.from(me(s),"hex");return o.length===e.length&&G(o,e)})}function Se(r){const e=A().length>0,s=R();return e?he(r)?{status:200,body:{ok:!0,runtimeBridge:"healthy",version:s,connectionKeyMatched:!0,connectionKeyRequired:!0}}:{status:401,body:{ok:!1,error:"connection_key_mismatch",version:s,connectionKeyMatched:!1,connectionKeyRequired:!0}}:{status:200,body:{ok:!0,runtimeBridge:"healthy",version:s,connectionKeyMatched:!0,connectionKeyRequired:!1}}}function we(r,t){const s=t.trim()||"http://localhost:8080",o=/^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?(?:\/|$)/i.test(s);return{ok:!1,error:r.message||"scheduler ping failed",failureStage:"scheduler_ping",runtimeBridge:"healthy",schedulerBaseUrl:s,hint:o?"aws-runtime-bridge \u5DF2\u8FDE\u901A\uFF0C\u4F46\u5B83\u56DE\u8FDE\u8C03\u5EA6\u4E2D\u5FC3\u5931\u8D25\u3002\u8BF7\u5728 bridge \u542F\u52A8\u73AF\u5883\u4E2D\u5C06 AWS_RUNTIME_SCHEDULER_BASE_URL \u914D\u7F6E\u4E3A\u8BE5\u5B9E\u4F8B\u53EF\u8BBF\u95EE\u7684\u8C03\u5EA6\u4E2D\u5FC3\u5730\u5740\uFF0C\u4F8B\u5982 http://<server-host>:7380\u3002":"aws-runtime-bridge \u5DF2\u8FDE\u901A\uFF0C\u4F46\u5B83\u56DE\u8FDE\u8C03\u5EA6\u4E2D\u5FC3\u5931\u8D25\u3002\u8BF7\u786E\u8BA4 AWS_RUNTIME_SCHEDULER_BASE_URL \u6307\u5411\u7684\u8C03\u5EA6\u4E2D\u5FC3\u5730\u5740\u53EF\u4ECE bridge \u673A\u5668\u8BBF\u95EE\uFF0C\u5E76\u4E14\u8FD0\u884C\u65F6\u8BBF\u95EE\u4EE4\u724C\u4ECD\u6709\u6548\u3002"}}function be(r){if(typeof r!="object"||r===null)return;const t=Reflect.get(r,"response");if(typeof t!="object"||t===null)return;const e=Reflect.get(t,"status");return typeof e=="number"?e:void 0}async function ye(r,t){try{const e=await E.get(`${r}/api/runtime/ping`,{headers:{"X-Runtime-Token":t}});return{schedulerReachable:e.status>=200&&e.status<300,tokenRefreshed:!1}}catch(e){if(be(e)!==401)throw e;const s=await Q(r);if(!s.success||!s.runtimeAccessToken)throw e;const o=await E.get(`${r}/api/runtime/ping`,{headers:{"X-Runtime-Token":s.runtimeAccessToken}});return{schedulerReachable:o.status>=200&&o.status<300,tokenRefreshed:!0}}}function Ie(r){return{ok:!1,error:"ambiguous_scheduler_base_url",failureStage:"scheduler_ping",runtimeBridge:"healthy",schedulerBaseUrl:"",autoRegisterTargetUrls:r,hint:"Multiple autoRegisterTargets.serverUrl values were found, so bridge cannot safely infer which scheduler /runtime/ping should call back. Set AWS_RUNTIME_SCHEDULER_BASE_URL explicitly, or use future schedulerId/token-bound multi-scheduler routing."}}function Be(r){return Array.from(new Set([...r,...ge].map(t=>String(t||"").trim().toLowerCase()).filter(Boolean)))}function Te(r,t){const e=r.filter(s=>t[s]?.installed);return e.length===0?"":e.map(s=>{const o=t[s],n=o?.error||o?.executable||"command is still available";return`${s}: ${n}`}).join("; ")}function B(r){return Object.fromEntries(Object.entries(r||{}).map(([t,e])=>[t,{installed:!!e?.installed,version:e?.version||null,executable:e?.executable||null,error:e?.error||null}]))}d.get("/check-update",async(r,t)=>{t.json(await O())}),d.get("/update-log",v,async(r,t)=>{try{const e=await ie();t.json({ok:!0,...e})}catch(e){const s=e;a.error("[update-log] Error:",s),t.status(500).json({ok:!1,error:s.message||"read update log failed"})}}),d.get("/healthz",(r,t)=>{t.json({ok:!0,runtimeBridge:"healthy"})}),d.get("/connection-check",(r,t)=>{const e=String(r.header("X-Bridge-Connection-Key-MD5")||""),s=Se(e);t.status(s.status).json(s.body)}),d.get("/ping",S,async(r,t)=>{let e=J(r.header("X-Scheduler-Base-Url"),r.header("X-Bridge-Instance-Id"),r.header("X-Bridge-Instance-Name"));if(e.source==="ambiguous-auto-register-targets"){const o=Z(r.header("X-Runtime-Token"));if(o)e={url:o,source:"token-match",ambiguousTargetUrls:[]};else{const n=r.header("X-Bridge-Instance-Id");if(n){const c=Y(),i=Object.entries(c.registrations).find(([,l])=>l===n)?.[0];i&&(e={url:i,source:"instance-id-match",ambiguousTargetUrls:[]})}if(!e.url||e.source==="ambiguous-auto-register-targets"){t.status(400).json(Ie(e.ambiguousTargetUrls));return}}}const s=e.url;try{const o=ce(s);if(!o){t.status(401).json({ok:!1,error:"runtime_access_token_required"});return}const n=await ye(s,o);t.json({ok:!0,runtimeBridge:"healthy",version:R(),schedulerReachable:n.schedulerReachable,tokenRefreshed:n.tokenRefreshed,schedulerBaseUrl:s,instanceId:e.instanceId,instanceName:e.instanceName})}catch(o){const n=o;t.status(502).json(we(n,s))}}),d.post("/init-instance",S,async(r,t)=>{const{agentId:e,workspacePath:s,skillEnabled:o,mcpEnabled:n,ccSwitchEnabledTools:c,skillPackage:i,skillPackages:l,mcpServers:m}=r.body||{};if(!e||!s){t.status(400).json({error:"agentId and workspacePath are required"});return}try{a.info("[init-instance] Request received",{agentId:String(e),workspacePath:String(s),skillEnabled:o,mcpEnabled:n,ccSwitchEnabledTools:c});const u=await re(e,s,{skillEnabled:o,mcpEnabled:n,ccSwitchEnabledTools:c,skillPackage:i,skillPackages:l,mcpServers:m});a.info("[init-instance] Initialization completed",{agentId:String(e),ok:u.ok,logCount:u.logs?.length||0,enabledTools:u.enabledTools}),t.json(u)}catch(u){const g=u;a.error("[init-instance] Initialization failed",{agentId:String(e),workspacePath:String(s),error:g?.message||String(u),stack:g?.stack||null}),t.status(400).json({error:g?.message||"initialize instance failed"})}}),d.post("/features",S,async(r,t)=>{const{agentId:e,features:s}=r.body||{};if(!e){t.status(400).json({error:"agentId is required"});return}oe(String(e),s),a.info("[instance/features] \u80FD\u529B\u5F00\u5173\u5DF2\u66F4\u65B0",{agentId:String(e),features:x(String(e))}),t.json({ok:!0,agentId:String(e),features:x(String(e))})}),d.post("/cc-switch/state",S,async(r,t)=>{const{agentId:e}=r.body||{};if(!e){t.status(400).json({error:"agentId is required"});return}try{a.info("[cc-switch/state] Request received",{agentId:String(e)});const s=await te(e),o=await ee(s),c=(await se(e,s)).state,i=Be(c.enabledTools||[]);a.info("[cc-switch/state] Detecting SDK statuses",{agentId:String(e),detectionTargets:i});const l=await U(i);a.info("[cc-switch/state] SDK status detection completed",{agentId:String(e),toolStatus:B(l),importedMcpCount:o.mcpServers,importedSkillCount:o.skills}),t.json({ok:!0,agentId:String(e),imported:{mcpServers:o.mcpServers,skills:o.skills},state:{...c,toolStatus:l}})}catch(s){const o=s;a.error("[cc-switch/state] Failed to load state",{agentId:String(e),error:o?.message||String(s),stack:o?.stack||null}),t.status(400).json({error:o?.message||"load state failed"})}}),d.post("/cc-switch/install-tools",S,async(r,t)=>{const{agentId:e,tools:s}=r.body||{};if(!e){t.status(400).json({error:"agentId is required"});return}const o=Array.isArray(s)?s.map(i=>String(i||"").trim().toLowerCase()).filter(Boolean):[];a.info("[cc-switch/install-tools] Request received",{agentId:String(e),requestedTools:o});const n=new Set(j),c=o.filter(i=>n.has(i));if(a.info("[cc-switch/install-tools] Request normalized",{agentId:String(e),requestedTools:o,installableTools:c,supportedInstallableTools:j}),c.length===0){a.warn("[cc-switch/install-tools] No supported installable tools requested",{agentId:String(e),requestedTools:o}),t.status(400).json({error:`tools must include ${j.join(", ")}`});return}try{const i=await $(e),l=Array.from(new Set([...i.enabledTools||[],...c]));a.info("[cc-switch/install-tools] Starting installer",{agentId:String(e),installableTools:c,previousEnabledTools:i.enabledTools||[],nextEnabledTools:l});const m=await le(c);a.info("[cc-switch/install-tools] Installer completed",{agentId:String(e),toolStatus:B(m)});const u=await U(l);a.info("[cc-switch/install-tools] Refreshed enabled SDK statuses",{agentId:String(e),refreshedToolStatus:B(u)});const g={...u,...m},h=await P(e,{...i,enabledTools:l,toolStatus:g}),y=new Set(c),w=Object.entries(g).filter(([f])=>y.has(f)).filter(([,f])=>!f.installed).map(([f,T])=>({tool:f,error:T.error||null}));w.length>0&&a.warn("[cc-switch/install-tools] Some SDKs remain unavailable",{agentId:String(e),failedTools:w}),a.info("[cc-switch/install-tools] State saved",{agentId:String(e),enabledTools:h.enabledTools,toolStatus:B(g)}),t.json({ok:w.length===0,agentId:String(e),toolStatus:g,failedTools:w,state:h})}catch(i){const l=i;a.error("[cc-switch/install-tools] Install route failed",{agentId:String(e),requestedTools:o,installableTools:c,error:l?.message||String(i),stack:l?.stack||null}),t.status(400).json({error:l?.message||"install tools failed"})}}),d.post("/cc-switch/uninstall-tools",S,async(r,t)=>{const{agentId:e,tools:s}=r.body||{};if(!e){t.status(400).json({error:"agentId is required"});return}const o=Array.isArray(s)?s.map(i=>String(i||"").trim().toLowerCase()).filter(Boolean):[],n=new Set(q),c=o.filter(i=>n.has(i));if(c.length===0){t.status(400).json({error:`tools must include ${q.join(", ")}`});return}try{const i=await $(e),l=new Set(c),m=(i.enabledTools||[]).filter(T=>!l.has(String(T||"").trim().toLowerCase())),u=await de(c),h={...await U([...m,...c]),...u},y=Te(c,h),w=y?i.enabledTools||[]:m,f=await P(e,{...i,enabledTools:w,toolStatus:h});if(y){t.status(400).json({ok:!1,agentId:String(e),error:`uninstall incomplete: ${y}`,toolStatus:h,state:f});return}t.json({ok:!0,agentId:String(e),toolStatus:h,state:f})}catch(i){const l=i;t.status(400).json({error:l?.message||"uninstall tools failed"})}}),d.post("/prepare-restart",S,async(r,t)=>{const{preserveSessions:e=!0}=r.body||{};try{a.info(`[prepare-restart] \u6536\u5230\u51C6\u5907\u91CD\u542F\u901A\u77E5\uFF0CpreserveSessions=${e}`),await z(!!e),t.json({ok:!0,preserveSessions:e,message:e?"Bridge will preserve sessions for aws-mcp-server restart":"Bridge will clean up sessions on shutdown"})}catch(s){const o=s;a.error("[prepare-restart] Error:",o),t.status(500).json({error:o.message})}});const ke={update:15*60*1e3,restart:5*60*1e3};async function Re(r){try{const t=await ne(),e=Date.now(),s=ke[r],o=[];for(const n of t.operations){if(n.operationType!==r||!["accepted","running","restarting"].includes(n.status))continue;const c=new Date(n.updatedAt).getTime();if(!Number.isNaN(c)&&e-c<=s)return{operationId:n.operationId};o.push(n.operationId)}for(const n of o)try{await ae(n),a.info(`[${r}-bridge] \u5DF2\u6E05\u7406\u8D85\u65F6\u672A\u4EA4\u4ED8\u7684\u9648\u65E7\u64CD\u4F5C: ${n}`)}catch(c){const i=c;a.warn(`[${r}-bridge] \u6E05\u7406\u9648\u65E7\u64CD\u4F5C\u5931\u8D25\uFF0C\u7EE7\u7EED\u653E\u884C: ${n}, ${i.message}`)}return null}catch(t){const e=t;return a.warn(`[${r}-bridge] \u5728\u9014\u64CD\u4F5C\u68C0\u67E5\u5931\u8D25\uFF0C\u653E\u884C\u8BF7\u6C42: ${e.message}`),null}}async function X(r,t,e){const s=await Re(r);return s?(e.status(409).json({error:t,operationId:s.operationId}),!0):!1}d.post("/update-bridge",v,async(r,t)=>{const e=D(r.body?.requestedByUserId);try{if(!e){t.status(400).json({error:"requestedByUserId is required"});return}if(await X("update","\u5B9E\u4F8B Bridge \u66F4\u65B0\u6B63\u5728\u6267\u884C\u4E2D\uFF0C\u8BF7\u52FF\u91CD\u590D\u89E6\u53D1",t))return;const s=await O(),o=L({operationType:"update",requestedByUserId:e,schedulerBaseUrl:r.body?.schedulerBaseUrl,runtimeBridgeBaseUrl:F(r),status:"accepted",message:"Bridge \u66F4\u65B0\u8BF7\u6C42\u5DF2\u6536\u5230\uFF0C\u5C06\u5728\u540E\u53F0\u6267\u884C\u5E76\u81EA\u52A8\u91CD\u542F\u3002",latestVersion:s.ok&&s.latestVersion||void 0});await b(o),pe(o),t.json({ok:!0,operationId:o.operationId,message:"Bridge \u66F4\u65B0\u5DF2\u5728\u540E\u53F0\u5F00\u59CB\uFF0C\u66F4\u65B0\u5B8C\u6210\u540E\u5C06\u81EA\u52A8\u91CD\u542F\u5B9E\u4F8B Bridge\u3002"})}catch(s){const o=s;a.error("[update-bridge] Error:",o),t.status(500).json({error:o.message||"update bridge failed"})}}),d.post("/restart-bridge",v,async(r,t)=>{const{preserveSessions:e=!0}=r.body||{},s=D(r.body?.requestedByUserId);try{if(!s){t.status(400).json({error:"requestedByUserId is required"});return}if(await X("restart","\u5B9E\u4F8B Bridge \u6B63\u5728\u91CD\u542F\u4E2D\uFF0C\u8BF7\u7A0D\u5019\u518D\u8BD5",t))return;a.info(`[restart-bridge] \u6536\u5230\u5B9E\u4F8B Bridge \u91CD\u542F\u8BF7\u6C42\uFF0CpreserveSessions=${e}`);const o=L({operationType:"restart",requestedByUserId:s,schedulerBaseUrl:r.body?.schedulerBaseUrl,runtimeBridgeBaseUrl:F(r),status:"accepted",message:"Bridge \u5DF2\u6536\u5230\u91CD\u542F\u8BF7\u6C42\uFF1B\u4E0B\u6B21\u542F\u52A8\u540E\u4F1A\u53D1\u9001\u786E\u8BA4\u901A\u77E5\u3002"});await b(o),await z(!!e),t.json({ok:!0,operationId:o.operationId,preserveSessions:!!e,message:"Bridge \u6B63\u5728\u91CD\u542F\uFF0C\u670D\u52A1\u7BA1\u7406\u5668\u4F1A\u81EA\u52A8\u62C9\u8D77\u5B9E\u4F8B\uFF1B\u4E0B\u6B21\u542F\u52A8\u540E\u4F1A\u901A\u8FC7\u6D88\u606F\u4E2D\u5FC3\u901A\u77E5\u7ED3\u679C\u3002"}),a.info("[restart-bridge] \u6B63\u5728\u6267\u884C awsb service restart"),K(),setTimeout(()=>{if(process.exitCode=42,I){I("PANEL_RESTART",!!e).catch(n=>{const c=n;a.error("[restart-bridge] graceful shutdown failed:",c),process.exit(42)});return}process.exit(42)},M)}catch(o){const n=o;a.error("[restart-bridge] Error:",n),t.status(500).json({error:n.message||"restart bridge failed"})}});export{Ie as buildAmbiguousSchedulerBaseUrlResponse,Se as buildConnectionCheckResponse,we as buildSchedulerPingFailureResponse,Be as buildToolStatusDetectionTargets,Te as buildUninstallFailureMessage,d as instanceRouter,Xe as setGracefulShutdownFn,B as summarizeToolStatus};
1
+ import{Router as H}from"express";import A from"axios";import{spawn as E}from"node:child_process";import{createHash as W,timingSafeEqual as G}from"node:crypto";import{fileURLToPath as V}from"node:url";import k from"node:path";import{adapterRegistry as J}from"../adapter/index.js";import{getBridgeVersion as R,resolveSchedulerBaseUrlFrom as Y}from"../config.js";import{validateToken as S,validateTokenOrPanelSession as v}from"../middleware/auth.js";import{getConfiguredConnectionKeys as _,getRegistrationState as Q,requestRuntimeAccessTokenRefreshForServer as Z,resolveSchedulerBaseUrlFromToken as ee}from"../services/auto-register.js";import{discoverCcSwitchConfiguredItems as te,loadCcSwitchSdk as re}from"../services/cc-switch-sdk.js";import{initInstance as se,syncLegacyStateFromSdk as oe}from"../services/instance-init-service.js";import{setAgentFeatures as ne,getAgentFeatures as x}from"../services/instance-features.js";import{createBridgeLifecycleOperation as L,deliverPendingBridgeLifecycleNotifications as C,loadLifecycleState as ae,removeBridgeLifecycleOperation as ie,upsertBridgeLifecycleOperation as b}from"../services/lifecycle-state.js";import{appendUpdateLog as p,readUpdateLog as ce}from"../services/update-log.js";import{checkBridgeUpdate as O}from"../utils/npm-version.js";import{loadInstanceState as $,saveInstanceState as P}from"../services/instance-state.js";import{getRuntimeAccessToken as le}from"../services/runtime-binding.js";import{detectToolStatuses as U,ensureToolsInstalled as de,SUPPORTED_INSTALLABLE_TOOLS as j,SUPPORTED_UNINSTALLABLE_TOOLS as q,uninstallTools as ue}from"../services/tool-installer.js";import{createLogger as ge}from"../utils/logger.js";const a=ge("instance"),fe=["acode","claude","opencode","codex"];let I=null;function We(r){I=r}const d=H();function F(r){return String(r||"").trim()||void 0}function D(r){const t=r.get("host");if(t)return`${r.protocol}://${t}`}function N(){const r=k.resolve(k.dirname(V(import.meta.url)),"..","..");return k.join(r,"dist","index.js")}function pe(){return new Promise((r,t)=>{const e=E(process.execPath,[N(),"update","--no-kill"],{shell:!1,windowsHide:!0});let s="",o="";e.stdout.on("data",n=>{s+=n.toString("utf8")}),e.stderr.on("data",n=>{o+=n.toString("utf8")}),e.on("error",t),e.on("close",n=>{r({exitCode:n,stdout:s,stderr:o})})})}function K(){const r=E(process.execPath,[N(),"service","restart"],{shell:!1,windowsHide:!0,stdio:["ignore","pipe","pipe"]});r.stdout.on("data",t=>{p(String(t)).catch(()=>{})}),r.stderr.on("data",t=>{p(String(t)).catch(()=>{})})}const M=3e3;async function me(r){const t={...r,status:"running",message:"Bridge \u66F4\u65B0\u6B63\u5728\u540E\u53F0\u6267\u884C\u3002"};await b(t);try{a.info("[update-bridge] \u6B63\u5728\u540E\u53F0\u6267\u884C awsb update --yes"),await p(`===== Bridge \u66F4\u65B0\u5F00\u59CB\uFF1Av${R()} -> latest\uFF08\u64CD\u4F5C ${r.operationId}\uFF09 =====`);const e=await pe();if(e.stdout&&await p(e.stdout),e.stderr&&await p(e.stderr),e.exitCode===0){await p("===== Bridge \u66F4\u65B0\u6210\u529F\uFF0C\u51C6\u5907\u81EA\u52A8\u91CD\u542F ===== ");const s={...t,status:"restarting",message:"Bridge \u66F4\u65B0\u5B8C\u6210\uFF0C\u6B63\u5728\u91CD\u542F..."};await b(s),a.info("[update-bridge] \u6B63\u5728\u6267\u884C awsb service restart"),K(),setTimeout(()=>{if(process.exitCode=42,I){I("BRIDGE_UPDATE_RESTART",!0).catch(o=>{const n=o;a.error("[update-bridge] graceful shutdown failed:",n),process.exit(42)});return}process.exit(42)},M)}else await p(`===== Bridge \u66F4\u65B0\u5931\u8D25\uFF1A\u9000\u51FA\u7801 ${e.exitCode??"unknown"} =====`),await b({...t,status:"failed",message:`Bridge \u66F4\u65B0\u5931\u8D25\uFF0Cnpm \u9000\u51FA\u7801: ${e.exitCode??"unknown"}`,error:e.stderr||e.stdout}),await C()}catch(e){const s=e;a.error("[update-bridge] background update failed:",s),await p(`===== Bridge \u66F4\u65B0\u5F02\u5E38\uFF1A${s.message} =====`).catch(()=>{}),await b({...t,status:"failed",message:"Bridge \u66F4\u65B0\u5931\u8D25\u3002",error:s.message||"update bridge failed"}),await C()}}async function z(r){const t=await import("node:fs/promises"),e=await import("node:path"),s=await import("node:os"),o=e.join(s.tmpdir(),"agentswork-runtime-bridge",".preserve-sessions");if(r){await t.mkdir(e.dirname(o),{recursive:!0}),await t.writeFile(o,JSON.stringify({preserveSessions:!0,timestamp:new Date().toISOString()}),"utf-8"),a.info(`[prepare-restart] \u5DF2\u521B\u5EFA\u4FDD\u7559\u4F1A\u8BDD\u6807\u8BB0\u6587\u4EF6: ${o}`);return}try{await t.unlink(o),a.info("[prepare-restart] \u5DF2\u5220\u9664\u4FDD\u7559\u4F1A\u8BDD\u6807\u8BB0\u6587\u4EF6")}catch{}}function he(r){return W("md5").update(r,"utf8").digest("hex")}function Se(r){const t=r.trim().toLowerCase();if(!/^[a-f0-9]{32}$/.test(t))return!1;const e=Buffer.from(t,"hex");return _().map(s=>s.trim()).filter(Boolean).some(s=>{const o=Buffer.from(he(s),"hex");return o.length===e.length&&G(o,e)})}function we(r){const e=_().length>0,s=R();return e?Se(r)?{status:200,body:{ok:!0,runtimeBridge:"healthy",version:s,connectionKeyMatched:!0,connectionKeyRequired:!0}}:{status:401,body:{ok:!1,error:"connection_key_mismatch",version:s,connectionKeyMatched:!1,connectionKeyRequired:!0}}:{status:200,body:{ok:!0,runtimeBridge:"healthy",version:s,connectionKeyMatched:!0,connectionKeyRequired:!1}}}function be(r,t){const s=t.trim()||"http://localhost:8080",o=/^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?(?:\/|$)/i.test(s);return{ok:!1,error:r.message||"scheduler ping failed",failureStage:"scheduler_ping",runtimeBridge:"healthy",schedulerBaseUrl:s,hint:o?"aws-runtime-bridge \u5DF2\u8FDE\u901A\uFF0C\u4F46\u5B83\u56DE\u8FDE\u8C03\u5EA6\u4E2D\u5FC3\u5931\u8D25\u3002\u8BF7\u5728 bridge \u542F\u52A8\u73AF\u5883\u4E2D\u5C06 AWS_RUNTIME_SCHEDULER_BASE_URL \u914D\u7F6E\u4E3A\u8BE5\u5B9E\u4F8B\u53EF\u8BBF\u95EE\u7684\u8C03\u5EA6\u4E2D\u5FC3\u5730\u5740\uFF0C\u4F8B\u5982 http://<server-host>:7380\u3002":"aws-runtime-bridge \u5DF2\u8FDE\u901A\uFF0C\u4F46\u5B83\u56DE\u8FDE\u8C03\u5EA6\u4E2D\u5FC3\u5931\u8D25\u3002\u8BF7\u786E\u8BA4 AWS_RUNTIME_SCHEDULER_BASE_URL \u6307\u5411\u7684\u8C03\u5EA6\u4E2D\u5FC3\u5730\u5740\u53EF\u4ECE bridge \u673A\u5668\u8BBF\u95EE\uFF0C\u5E76\u4E14\u8FD0\u884C\u65F6\u8BBF\u95EE\u4EE4\u724C\u4ECD\u6709\u6548\u3002"}}function ye(r){if(typeof r!="object"||r===null)return;const t=Reflect.get(r,"response");if(typeof t!="object"||t===null)return;const e=Reflect.get(t,"status");return typeof e=="number"?e:void 0}async function Ie(r,t){try{const e=await A.get(`${r}/api/runtime/ping`,{headers:{"X-Runtime-Token":t}});return{schedulerReachable:e.status>=200&&e.status<300,tokenRefreshed:!1}}catch(e){if(ye(e)!==401)throw e;const s=await Z(r);if(!s.success||!s.runtimeAccessToken)throw e;const o=await A.get(`${r}/api/runtime/ping`,{headers:{"X-Runtime-Token":s.runtimeAccessToken}});return{schedulerReachable:o.status>=200&&o.status<300,tokenRefreshed:!0}}}function Be(r){return{ok:!1,error:"ambiguous_scheduler_base_url",failureStage:"scheduler_ping",runtimeBridge:"healthy",schedulerBaseUrl:"",autoRegisterTargetUrls:r,hint:"Multiple autoRegisterTargets.serverUrl values were found, so bridge cannot safely infer which scheduler /runtime/ping should call back. Set AWS_RUNTIME_SCHEDULER_BASE_URL explicitly, or use future schedulerId/token-bound multi-scheduler routing."}}function Te(r){return Array.from(new Set([...r,...fe].map(t=>String(t||"").trim().toLowerCase()).filter(Boolean)))}function ke(r,t){const e=r.filter(s=>t[s]?.installed);return e.length===0?"":e.map(s=>{const o=t[s],n=o?.error||o?.executable||"command is still available";return`${s}: ${n}`}).join("; ")}function B(r){return Object.fromEntries(Object.entries(r||{}).map(([t,e])=>[t,{installed:!!e?.installed,version:e?.version||null,executable:e?.executable||null,error:e?.error||null}]))}d.get("/check-update",async(r,t)=>{t.json(await O())}),d.get("/update-log",v,async(r,t)=>{try{const e=await ce();t.json({ok:!0,...e})}catch(e){const s=e;a.error("[update-log] Error:",s),t.status(500).json({ok:!1,error:s.message||"read update log failed"})}}),d.get("/healthz",(r,t)=>{t.json({ok:!0,runtimeBridge:"healthy"})}),d.get("/connection-check",(r,t)=>{const e=String(r.header("X-Bridge-Connection-Key-MD5")||""),s=we(e);t.status(s.status).json(s.body)}),d.get("/ping",S,async(r,t)=>{let e=Y(r.header("X-Scheduler-Base-Url"),r.header("X-Bridge-Instance-Id"),r.header("X-Bridge-Instance-Name"));if(e.source==="ambiguous-auto-register-targets"){const o=ee(r.header("X-Runtime-Token"));if(o)e={url:o,source:"token-match",ambiguousTargetUrls:[]};else{const n=r.header("X-Bridge-Instance-Id");if(n){const i=Q(),c=Object.entries(i.registrations).find(([,l])=>l===n)?.[0];c&&(e={url:c,source:"instance-id-match",ambiguousTargetUrls:[]})}if(!e.url||e.source==="ambiguous-auto-register-targets"){t.status(400).json(Be(e.ambiguousTargetUrls));return}}}const s=e.url;try{const o=le(s);if(!o){t.status(401).json({ok:!1,error:"runtime_access_token_required"});return}const n=await Ie(s,o);t.json({ok:!0,runtimeBridge:"healthy",version:R(),schedulerReachable:n.schedulerReachable,tokenRefreshed:n.tokenRefreshed,schedulerBaseUrl:s,instanceId:e.instanceId,instanceName:e.instanceName})}catch(o){const n=o;t.status(502).json(be(n,s))}}),d.post("/init-instance",S,async(r,t)=>{const{agentId:e,workspacePath:s,skillEnabled:o,mcpEnabled:n,ccSwitchEnabledTools:i,skillPackage:c,skillPackages:l,mcpServers:m}=r.body||{};if(!e||!s){t.status(400).json({error:"agentId and workspacePath are required"});return}try{a.info("[init-instance] Request received",{agentId:String(e),workspacePath:String(s),skillEnabled:o,mcpEnabled:n,ccSwitchEnabledTools:i});const u=await se(e,s,{skillEnabled:o,mcpEnabled:n,ccSwitchEnabledTools:i,skillPackage:c,skillPackages:l,mcpServers:m});a.info("[init-instance] Initialization completed",{agentId:String(e),ok:u.ok,logCount:u.logs?.length||0,enabledTools:u.enabledTools}),t.json(u)}catch(u){const g=u;a.error("[init-instance] Initialization failed",{agentId:String(e),workspacePath:String(s),error:g?.message||String(u),stack:g?.stack||null}),t.status(400).json({error:g?.message||"initialize instance failed"})}}),d.post("/features",S,async(r,t)=>{const{agentId:e,features:s}=r.body||{};if(!e){t.status(400).json({error:"agentId is required"});return}ne(String(e),s),a.info("[instance/features] \u80FD\u529B\u5F00\u5173\u5DF2\u66F4\u65B0",{agentId:String(e),features:x(String(e))});let o=0;for(const n of J.getAll())if(typeof n.applyAgentFeatures=="function")try{await n.applyAgentFeatures(String(e)),o+=1}catch(i){a.error("[instance/features] adapter \u70ED\u540C\u6B65\u5931\u8D25",{agentId:String(e),providerId:n.providerId,error:i?.message||String(i)})}t.json({ok:!0,agentId:String(e),features:x(String(e)),hotSyncedAdapters:o})}),d.post("/cc-switch/state",S,async(r,t)=>{const{agentId:e}=r.body||{};if(!e){t.status(400).json({error:"agentId is required"});return}try{a.info("[cc-switch/state] Request received",{agentId:String(e)});const s=await re(e),o=await te(s),i=(await oe(e,s)).state,c=Te(i.enabledTools||[]);a.info("[cc-switch/state] Detecting SDK statuses",{agentId:String(e),detectionTargets:c});const l=await U(c);a.info("[cc-switch/state] SDK status detection completed",{agentId:String(e),toolStatus:B(l),importedMcpCount:o.mcpServers,importedSkillCount:o.skills}),t.json({ok:!0,agentId:String(e),imported:{mcpServers:o.mcpServers,skills:o.skills},state:{...i,toolStatus:l}})}catch(s){const o=s;a.error("[cc-switch/state] Failed to load state",{agentId:String(e),error:o?.message||String(s),stack:o?.stack||null}),t.status(400).json({error:o?.message||"load state failed"})}}),d.post("/cc-switch/install-tools",S,async(r,t)=>{const{agentId:e,tools:s}=r.body||{};if(!e){t.status(400).json({error:"agentId is required"});return}const o=Array.isArray(s)?s.map(c=>String(c||"").trim().toLowerCase()).filter(Boolean):[];a.info("[cc-switch/install-tools] Request received",{agentId:String(e),requestedTools:o});const n=new Set(j),i=o.filter(c=>n.has(c));if(a.info("[cc-switch/install-tools] Request normalized",{agentId:String(e),requestedTools:o,installableTools:i,supportedInstallableTools:j}),i.length===0){a.warn("[cc-switch/install-tools] No supported installable tools requested",{agentId:String(e),requestedTools:o}),t.status(400).json({error:`tools must include ${j.join(", ")}`});return}try{const c=await $(e),l=Array.from(new Set([...c.enabledTools||[],...i]));a.info("[cc-switch/install-tools] Starting installer",{agentId:String(e),installableTools:i,previousEnabledTools:c.enabledTools||[],nextEnabledTools:l});const m=await de(i);a.info("[cc-switch/install-tools] Installer completed",{agentId:String(e),toolStatus:B(m)});const u=await U(l);a.info("[cc-switch/install-tools] Refreshed enabled SDK statuses",{agentId:String(e),refreshedToolStatus:B(u)});const g={...u,...m},h=await P(e,{...c,enabledTools:l,toolStatus:g}),y=new Set(i),w=Object.entries(g).filter(([f])=>y.has(f)).filter(([,f])=>!f.installed).map(([f,T])=>({tool:f,error:T.error||null}));w.length>0&&a.warn("[cc-switch/install-tools] Some SDKs remain unavailable",{agentId:String(e),failedTools:w}),a.info("[cc-switch/install-tools] State saved",{agentId:String(e),enabledTools:h.enabledTools,toolStatus:B(g)}),t.json({ok:w.length===0,agentId:String(e),toolStatus:g,failedTools:w,state:h})}catch(c){const l=c;a.error("[cc-switch/install-tools] Install route failed",{agentId:String(e),requestedTools:o,installableTools:i,error:l?.message||String(c),stack:l?.stack||null}),t.status(400).json({error:l?.message||"install tools failed"})}}),d.post("/cc-switch/uninstall-tools",S,async(r,t)=>{const{agentId:e,tools:s}=r.body||{};if(!e){t.status(400).json({error:"agentId is required"});return}const o=Array.isArray(s)?s.map(c=>String(c||"").trim().toLowerCase()).filter(Boolean):[],n=new Set(q),i=o.filter(c=>n.has(c));if(i.length===0){t.status(400).json({error:`tools must include ${q.join(", ")}`});return}try{const c=await $(e),l=new Set(i),m=(c.enabledTools||[]).filter(T=>!l.has(String(T||"").trim().toLowerCase())),u=await ue(i),h={...await U([...m,...i]),...u},y=ke(i,h),w=y?c.enabledTools||[]:m,f=await P(e,{...c,enabledTools:w,toolStatus:h});if(y){t.status(400).json({ok:!1,agentId:String(e),error:`uninstall incomplete: ${y}`,toolStatus:h,state:f});return}t.json({ok:!0,agentId:String(e),toolStatus:h,state:f})}catch(c){const l=c;t.status(400).json({error:l?.message||"uninstall tools failed"})}}),d.post("/prepare-restart",S,async(r,t)=>{const{preserveSessions:e=!0}=r.body||{};try{a.info(`[prepare-restart] \u6536\u5230\u51C6\u5907\u91CD\u542F\u901A\u77E5\uFF0CpreserveSessions=${e}`),await z(!!e),t.json({ok:!0,preserveSessions:e,message:e?"Bridge will preserve sessions for aws-mcp-server restart":"Bridge will clean up sessions on shutdown"})}catch(s){const o=s;a.error("[prepare-restart] Error:",o),t.status(500).json({error:o.message})}});const Re={update:15*60*1e3,restart:5*60*1e3};async function ve(r){try{const t=await ae(),e=Date.now(),s=Re[r],o=[];for(const n of t.operations){if(n.operationType!==r||!["accepted","running","restarting"].includes(n.status))continue;const i=new Date(n.updatedAt).getTime();if(!Number.isNaN(i)&&e-i<=s)return{operationId:n.operationId};o.push(n.operationId)}for(const n of o)try{await ie(n),a.info(`[${r}-bridge] \u5DF2\u6E05\u7406\u8D85\u65F6\u672A\u4EA4\u4ED8\u7684\u9648\u65E7\u64CD\u4F5C: ${n}`)}catch(i){const c=i;a.warn(`[${r}-bridge] \u6E05\u7406\u9648\u65E7\u64CD\u4F5C\u5931\u8D25\uFF0C\u7EE7\u7EED\u653E\u884C: ${n}, ${c.message}`)}return null}catch(t){const e=t;return a.warn(`[${r}-bridge] \u5728\u9014\u64CD\u4F5C\u68C0\u67E5\u5931\u8D25\uFF0C\u653E\u884C\u8BF7\u6C42: ${e.message}`),null}}async function X(r,t,e){const s=await ve(r);return s?(e.status(409).json({error:t,operationId:s.operationId}),!0):!1}d.post("/update-bridge",v,async(r,t)=>{const e=F(r.body?.requestedByUserId);try{if(!e){t.status(400).json({error:"requestedByUserId is required"});return}if(await X("update","\u5B9E\u4F8B Bridge \u66F4\u65B0\u6B63\u5728\u6267\u884C\u4E2D\uFF0C\u8BF7\u52FF\u91CD\u590D\u89E6\u53D1",t))return;const s=await O(),o=L({operationType:"update",requestedByUserId:e,schedulerBaseUrl:r.body?.schedulerBaseUrl,runtimeBridgeBaseUrl:D(r),status:"accepted",message:"Bridge \u66F4\u65B0\u8BF7\u6C42\u5DF2\u6536\u5230\uFF0C\u5C06\u5728\u540E\u53F0\u6267\u884C\u5E76\u81EA\u52A8\u91CD\u542F\u3002",latestVersion:s.ok&&s.latestVersion||void 0});await b(o),me(o),t.json({ok:!0,operationId:o.operationId,message:"Bridge \u66F4\u65B0\u5DF2\u5728\u540E\u53F0\u5F00\u59CB\uFF0C\u66F4\u65B0\u5B8C\u6210\u540E\u5C06\u81EA\u52A8\u91CD\u542F\u5B9E\u4F8B Bridge\u3002"})}catch(s){const o=s;a.error("[update-bridge] Error:",o),t.status(500).json({error:o.message||"update bridge failed"})}}),d.post("/restart-bridge",v,async(r,t)=>{const{preserveSessions:e=!0}=r.body||{},s=F(r.body?.requestedByUserId);try{if(!s){t.status(400).json({error:"requestedByUserId is required"});return}if(await X("restart","\u5B9E\u4F8B Bridge \u6B63\u5728\u91CD\u542F\u4E2D\uFF0C\u8BF7\u7A0D\u5019\u518D\u8BD5",t))return;a.info(`[restart-bridge] \u6536\u5230\u5B9E\u4F8B Bridge \u91CD\u542F\u8BF7\u6C42\uFF0CpreserveSessions=${e}`);const o=L({operationType:"restart",requestedByUserId:s,schedulerBaseUrl:r.body?.schedulerBaseUrl,runtimeBridgeBaseUrl:D(r),status:"accepted",message:"Bridge \u5DF2\u6536\u5230\u91CD\u542F\u8BF7\u6C42\uFF1B\u4E0B\u6B21\u542F\u52A8\u540E\u4F1A\u53D1\u9001\u786E\u8BA4\u901A\u77E5\u3002"});await b(o),await z(!!e),t.json({ok:!0,operationId:o.operationId,preserveSessions:!!e,message:"Bridge \u6B63\u5728\u91CD\u542F\uFF0C\u670D\u52A1\u7BA1\u7406\u5668\u4F1A\u81EA\u52A8\u62C9\u8D77\u5B9E\u4F8B\uFF1B\u4E0B\u6B21\u542F\u52A8\u540E\u4F1A\u901A\u8FC7\u6D88\u606F\u4E2D\u5FC3\u901A\u77E5\u7ED3\u679C\u3002"}),a.info("[restart-bridge] \u6B63\u5728\u6267\u884C awsb service restart"),K(),setTimeout(()=>{if(process.exitCode=42,I){I("PANEL_RESTART",!!e).catch(n=>{const i=n;a.error("[restart-bridge] graceful shutdown failed:",i),process.exit(42)});return}process.exit(42)},M)}catch(o){const n=o;a.error("[restart-bridge] Error:",n),t.status(500).json({error:n.message||"restart bridge failed"})}});export{Be as buildAmbiguousSchedulerBaseUrlResponse,we as buildConnectionCheckResponse,be as buildSchedulerPingFailureResponse,Te as buildToolStatusDetectionTargets,ke as buildUninstallFailureMessage,d as instanceRouter,We as setGracefulShutdownFn,B as summarizeToolStatus};
2
2
 
@@ -0,0 +1,2 @@
1
+
2
+ export declare const jdksRouter: import("express-serve-static-core").Router;
@@ -0,0 +1,2 @@
1
+ import{Router as c}from"express";import{validateToken as e}from"../middleware/auth.js";import{addManualJdk as j,listJdks as m,removeManualJdk as k}from"../services/jdk-scanner.js";import{createLogger as i}from"../utils/logger.js";const a=i("jdks"),n=c();n.get("/jdks",e,async(s,o)=>{try{const r=await m();o.json({ok:!0,jdks:r})}catch(r){const t=r;a.error("[jdks/list] Error:",t),o.status(500).json({error:t.message})}}),n.post("/jdks",e,async(s,o)=>{try{const r=s.body?.path;if(typeof r!="string"||!r.trim()){o.status(400).json({error:"JDK \u8DEF\u5F84\u4E0D\u80FD\u4E3A\u7A7A"});return}const t=await j(r);o.json({ok:!0,jdk:t})}catch(r){const t=r;a.warn(`[jdks/add] error=${t.message}`),o.status(400).json({error:t.message})}}),n.delete("/jdks/:id",e,async(s,o)=>{const r=s.params.id;try{if(!await k(r)){o.status(404).json({error:"JDK \u4E0D\u5B58\u5728"});return}o.json({ok:!0})}catch(t){const d=t;a.warn(`[jdks/remove] id=${r}, error=${d.message}`),o.status(400).json({error:d.message})}});export{n as jdksRouter};
2
+
@@ -0,0 +1,6 @@
1
+
2
+ export declare const launchRouter: import("express-serve-static-core").Router;
3
+
4
+ export declare const MAX_START_WAIT_MS = 300000;
5
+
6
+ export declare const MAX_BATCH_START_ITEMS = 20;
@@ -0,0 +1,30 @@
1
+ import{Router as M}from"express";import b from"node:fs/promises";import w from"node:path";import{validateToken as u}from"../middleware/auth.js";import{METRICS_HISTORY_CAPACITY as v}from"../services/launch-metrics.js";import{getLaunchManager as c}from"../services/launch-manager.js";import{getLaunchStreamHub as j}from"../services/launch-streams.js";import{createLogger as P}from"../utils/logger.js";const l=P("launch"),d=M(),k=3e5,I=20;d.put("/launch-configs/file",u,async(a,t)=>{try{const r=a.body||{},e=typeof r.workspacePath=="string"?r.workspacePath.trim():"";if(!e||!w.isAbsolute(e)){t.status(400).json({error:"workspacePath must be a non-empty absolute path"});return}if(!r.content||typeof r.content!="object"||Array.isArray(r.content)){t.status(400).json({error:"content must be an object"});return}const n=w.join(e,".agentswork","launcher","launcher-config.json");await b.mkdir(w.dirname(n),{recursive:!0});const s=`${n}.${process.pid}.tmp`;await b.writeFile(s,JSON.stringify(r.content,null,2)+`
2
+ `,"utf-8"),await b.rename(s,n),l.info(`[launch/file] \u542F\u52A8\u9879\u5FEB\u7167\u5DF2\u5199\u5165: ${n}`),t.json({ok:!0,path:n})}catch(r){const e=r;l.error("[launch/file] Error:",e),t.status(500).json({error:e.message})}}),d.get("/launch-configs/file",u,async(a,t)=>{try{const r=typeof a.query.workspacePath=="string"?a.query.workspacePath.trim():"";if(!r||!w.isAbsolute(r)){t.status(400).json({error:"workspacePath must be a non-empty absolute path"});return}const e=w.join(r,".agentswork","launcher","launcher-config.json");try{const n=await b.readFile(e,"utf-8");t.json({ok:!0,found:!0,path:e,content:JSON.parse(n)})}catch(n){if(n.code==="ENOENT"){t.json({ok:!0,found:!1,path:e});return}throw n}}catch(r){const e=r;l.error("[launch/file:read] Error:",e),t.status(500).json({error:e.message})}}),d.get("/launch-configs/runs",u,async(a,t)=>{try{const r=await c().listRuns();t.json({ok:!0,runs:r})}catch(r){const e=r;l.error("[launch/runs] Error:",e),t.status(500).json({error:e.message})}}),d.post("/launch-configs/:id/start",u,async(a,t)=>{const r=a.params.id;try{const e=a.body||{},n=Number(e.waitTimeoutMs),s=Number.isFinite(n)&&n>0?Math.min(n,k):0,o=await c().start({configId:r,command:e.command,cwd:e.cwd,env:e.env,jdkPath:e.jdkPath,workspacePath:e.workspacePath,readinessPattern:e.readinessPattern,startedBy:e.startedBy});if(s>0){const m=await c().waitStartupResult(r,s);t.json({ok:!0,run:o,wait:m});return}t.json({ok:!0,run:o})}catch(e){const n=e;l.warn(`[launch/start] configId=${r}, error=${n.message}`),t.status(400).json({error:n.message})}}),d.post("/launch-configs/:id/restart",u,async(a,t)=>{const r=a.params.id;try{const e=a.body||{},n=Number(e.waitTimeoutMs),s=Number.isFinite(n)&&n>0?Math.min(n,k):0,o=await c().restart({configId:r,command:e.command,cwd:e.cwd,env:e.env,jdkPath:e.jdkPath,workspacePath:e.workspacePath,readinessPattern:e.readinessPattern,startedBy:e.startedBy});if(s>0){const m=await c().waitStartupResult(r,s);t.json({ok:!0,run:o,wait:m});return}t.json({ok:!0,run:o})}catch(e){const n=e;l.warn(`[launch/restart] configId=${r}, error=${n.message}`),t.status(400).json({error:n.message})}}),d.post("/launch-configs/start-batch",u,async(a,t)=>{try{const r=a.body||{};if(!Array.isArray(r.items)||r.items.length===0){t.status(400).json({error:"items must be a non-empty array"});return}if(r.items.length>I){t.status(400).json({error:`items exceeds limit ${I}`});return}const e=Number(r.waitTimeoutMs),n=Number.isFinite(e)&&e>0?Math.min(e,k):0,s=await c().startBatch(r.items,n);t.json({ok:!0,...s})}catch(r){const e=r;l.warn(`[launch/start-batch] error=${e.message}`),t.status(400).json({error:e.message})}}),d.post("/launch-configs/stop-all",u,async(a,t)=>{try{const r=await c().stopAll();t.json({ok:!0,...r})}catch(r){const e=r;l.warn(`[launch/stop-all] error: ${e.message}`),t.status(500).json({error:e.message})}}),d.post("/launch-configs/:id/stop",u,async(a,t)=>{const r=a.params.id;try{const e=a.body?.actor,n=typeof e=="string"&&e.trim()?e.trim():void 0,s=await c().stop(r,"manual",n);t.json({ok:s.success,...s})}catch(e){const n=e;l.warn(`[launch/stop] configId=${r}, error=${n.message}`),t.status(400).json({error:n.message})}}),d.get("/launch-configs/:id/logs",u,async(a,t)=>{const r=a.params.id,e=Number(a.query.since)||0;try{const n=await c().getLogs(r,e);if(!n){t.status(404).json({error:"launch run not found"});return}t.json({ok:!0,...n})}catch(n){const s=n;l.error(`[launch/logs] configId=${r}, error:`,s),t.status(500).json({error:s.message})}}),d.get("/launch-configs/:id/metrics",u,async(a,t)=>{const r=a.params.id,e=Number(a.query.limit),n=Number.isFinite(e)&&e>0?Math.min(Math.floor(e),v):v;try{const s=c().getRun(r);if(!s){t.status(404).json({error:"launch run not found"});return}const o=c().getMetrics(r,n);t.json({ok:!0,configId:r,state:s.state,metrics:o})}catch(s){const o=s;l.error(`[launch/metrics] configId=${r}, error:`,o),t.status(500).json({error:o.message})}}),d.get("/launch-configs/:id/logs/stream",u,async(a,t)=>{const r=a.params.id;if(!c().getRun(r)){t.status(404).json({error:"launch run not found"});return}const n=Number(a.headers["last-event-id"]),s=Number.isFinite(n)&&n>=0?n:-1;t.on("error",()=>{});let o=null,m=null;try{t.writeHead(200,{"Content-Type":"text/event-stream; charset=utf-8","Cache-Control":"no-cache",Connection:"keep-alive","X-Accel-Buffering":"no"}),t.write(`: connected
3
+
4
+ `);const g=h=>{t.destroyed||t.write(h)};let f=-1,y=!1;if(o=j().subscribe(r,"logs",h=>{if(t.destroyed)return;g(h),h.startsWith("event: run_state")&&(y=!0);const p=/^id: (\d+)\n/.exec(h);p&&(f=Math.max(f,Number(p[1])))}),a.on("close",()=>{o?.(),m&&clearInterval(m);try{t.end()}catch{}}),s>=-1){const h=await c().getLogs(r,Math.max(s,f));if(h)for(const p of h.entries)p.index<=f||g(`id: ${p.index}
5
+ data: ${JSON.stringify(p.data)}
6
+
7
+ `)}const i=c().getRun(r);if(!y&&i&&i.state!=="running"){const h={configId:r,state:i.state};i.state==="exited"&&(h.exitCode=i.exitCode??null),g(`event: run_state
8
+ data: ${JSON.stringify(h)}
9
+
10
+ `)}m=setInterval(()=>{g(`: heartbeat
11
+
12
+ `)},15e3)}catch(g){const f=g;if(l.error(`[launch/logs/stream] configId=${r}, error:`,f),o?.(),m&&clearInterval(m),t.headersSent){t.end();return}t.status(500).json({error:f.message})}}),d.get("/launch-configs/:id/metrics/stream",u,(a,t)=>{const r=a.params.id;if(!c().getRun(r)){t.status(404).json({error:"launch run not found"});return}const n=Number(a.headers["last-event-id"]),s=Number.isFinite(n)&&n>=0?n:-1;t.on("error",()=>{}),t.writeHead(200,{"Content-Type":"text/event-stream; charset=utf-8","Cache-Control":"no-cache",Connection:"keep-alive","X-Accel-Buffering":"no"}),t.write(`: connected
13
+
14
+ `);const o=i=>{t.destroyed||t.write(i)};if(s>=0)for(const i of c().getMetricsSince(r,s))o(`id: ${i.index}
15
+ data: ${JSON.stringify(i.sample)}
16
+
17
+ `);let m=!1,g=null,f=null;g=j().subscribe(r,"metrics",i=>{t.destroyed||(o(i),i.startsWith("event: run_state")&&(m=!0))}),a.on("close",()=>{g?.(),f&&clearInterval(f);try{t.end()}catch{}});const y=c().getRun(r);if(!m&&y&&y.state!=="running"){const i={configId:r,state:y.state};y.state==="exited"&&(i.exitCode=y.exitCode??null),o(`event: run_state
18
+ data: ${JSON.stringify(i)}
19
+
20
+ `)}f=setInterval(()=>{o(`: heartbeat
21
+
22
+ `)},15e3)}),d.get("/launch-configs/status/stream",u,async(a,t)=>{t.on("error",()=>{});let r=null,e=null;try{t.writeHead(200,{"Content-Type":"text/event-stream; charset=utf-8","Cache-Control":"no-cache",Connection:"keep-alive","X-Accel-Buffering":"no"}),t.write(`: connected
23
+
24
+ `);const n=o=>{t.destroyed||t.write(o)};r=j().subscribeStatus(o=>{t.destroyed||n(o)}),a.on("close",()=>{r?.(),e&&clearInterval(e);try{t.end()}catch{}});const s=await c().listRuns();t.destroyed||n(`event: snapshot
25
+ data: ${JSON.stringify({runs:s})}
26
+
27
+ `),e=setInterval(()=>{n(`: heartbeat
28
+
29
+ `)},15e3)}catch(n){const s=n;if(l.error("[launch/status/stream] error:",s),r?.(),e&&clearInterval(e),t.headersSent){t.end();return}t.status(500).json({error:s.message})}});export{I as MAX_BATCH_START_ITEMS,k as MAX_START_WAIT_MS,d as launchRouter};
30
+
@@ -1,6 +1,6 @@
1
- import L from"node:crypto";import A from"node:fs";import l from"node:path";import{Router as N}from"express";import*as D from"node-pty";import{v4 as M}from"uuid";import U,{WebSocketServer as z}from"ws";import{isRuntimeTokenValid as x,validateToken as h}from"../middleware/auth.js";import{createLogger as R}from"../utils/logger.js";const p=R("pty"),j=80,W=24,_=2e5,E=30*60*1e3,$=5*60*1e3,v=60*1e3,b=8,F=new Set(["false","halt","nologin","reboot","shutdown","sync","true"]),c=new Map,w=new Map;function B(e=process.env){const t=String(e.AWS_PTY_IDLE_TTL_MS||"").trim();if(!t)return E;const r=Number(t);return!Number.isFinite(r)||r<1e3?E:r}function Y(e=process.platform,t=process.env,r=A.existsSync){return e==="win32"?{shell:String(t.ComSpec||t.COMSPEC||"").trim()||"powershell.exe",args:[]}:{shell:[String(t.SHELL||"").trim(),"/bin/bash","/bin/sh"].find(o=>X(o,r))||"/bin/sh",args:[]}}function X(e,t){const r=e.trim();if(!r)return!1;const n=l.basename(r).toLowerCase();return F.has(n)?!1:!l.isAbsolute(r)||t(r)}function H(e,t=process.platform,r=process.env){const n=String(e||"auto").trim();if(!n||n==="auto")return Y(t,r);const i=new Set(["bash","sh","zsh","fish","pwsh","pwsh.exe","powershell","powershell.exe","cmd","cmd.exe"]),o=(t==="win32"?l.win32.basename(n):l.basename(n)).toLowerCase();if(!i.has(o))throw new Error(`shell is not allowed: ${n}`);return{shell:/^[A-Za-z]:[\\/]/.test(n)||n.startsWith("\\")||n.startsWith("/")?n:t==="win32"?{cmd:"cmd.exe",powershell:"powershell.exe",pwsh:"pwsh.exe"}[o]??n:n,args:[]}}function J(e,t){const r=l.relative(l.resolve(t),l.resolve(e));return r===""||!r.startsWith("..")&&!l.isAbsolute(r)}function V(e=process.env){return String(e.AWS_PTY_ALLOWED_ROOTS||"").split(l.delimiter).map(t=>t.trim()).filter(Boolean).map(t=>l.resolve(t))}function K(e,t=process.env){const r=String(e||"").trim();if(!r)throw new Error("workspacePath is required");if(!l.isAbsolute(r))throw new Error("workspacePath must be absolute");const n=l.resolve(r);if(!A.statSync(n).isDirectory())throw new Error("workspacePath must be an existing directory");const o=V(t);if(o.length>0&&!o.some(a=>J(n,a)))throw new Error("workspacePath is outside allowed roots");return n}function T(e,t){const r=Number(e);return!Number.isInteger(r)||r<=0?t:Math.min(r,500)}function q(e){return{...e,TERM:e.TERM||"xterm-256color",COLORTERM:e.COLORTERM||"truecolor"}}function Z(e,t){e.outputBuffer=`${e.outputBuffer}${t}`,e.outputBuffer.length>_&&(e.outputBuffer=e.outputBuffer.slice(-_))}function g(e){return{id:e.id,title:e.title,workspacePath:e.workspacePath,shell:e.shell,cols:e.cols,rows:e.rows,status:e.status,createdAt:e.createdAt,lastActiveAt:e.lastActiveAt,exitCode:e.exitCode,signal:e.signal,attachedClients:e.sockets.size,ownerUserId:e.ownerUserId,tenantId:e.tenantId,projectId:e.projectId}}function k(e,t){e.readyState===U.OPEN&&e.send(JSON.stringify(t))}function I(e,t){for(const r of e.sockets)k(r,t)}function P(e){e.idleTimer&&clearTimeout(e.idleTimer),e.idleTimer=setTimeout(()=>{e.sockets.size===0&&S(e.id,"idle-timeout")},B())}function C(e){e.idleTimer&&(clearTimeout(e.idleTimer),e.idleTimer=null)}function G(e){if(c.size>=re())throw new Error("maximum PTY sessions reached");const t=K(e.workspacePath),r=H(e.shell),n=T(e.cols,j),i=T(e.rows,W),o=`pty_${M()}`,a=new Date().toISOString(),d=String(e.title||"").trim()||`Terminal ${c.size+1}`,u=D.spawn(r.shell,r.args,{name:"xterm-256color",cols:n,rows:i,cwd:t,env:q(process.env)}),s={id:o,title:d,workspacePath:t,shell:r.shell,cols:n,rows:i,status:"running",createdAt:a,lastActiveAt:a,attachedClients:0,ptyProcess:u,sockets:new Set,outputBuffer:"",idleTimer:null,persistent:e.persistent===!0,ownerUserId:String(e.ownerUserId||"").trim()||void 0,tenantId:String(e.tenantId||"").trim()||void 0,projectId:String(e.projectId||"").trim()||void 0};return u.onData(f=>{s.lastActiveAt=new Date().toISOString(),Z(s,f),I(s,{type:"output",data:f})}),u.onExit(({exitCode:f,signal:m})=>{s.status="exited",s.exitCode=f,s.signal=m,s.lastActiveAt=new Date().toISOString(),I(s,{type:"exit",exitCode:f,signal:m}),s.sockets.size===0&&!s.persistent?P(s):s.persistent&&(s.idleTimer&&clearTimeout(s.idleTimer),s.idleTimer=setTimeout(()=>{S(s.id,"exited-timeout")},te()))}),c.set(o,s),s.persistent||P(s),Q(s,e.preCommand),p.info(`created pty session id=${o}, cwd=${t}, shell=${r.shell}`),g(s)}function Q(e,t){const r=typeof t=="string"?t:"";if(!r.trim())return;const n=`${r.replace(/\n/g,"\r").replace(/\r+$/g,"")}\r`;try{e.ptyProcess.write(n)}catch(i){const o=i;p.warn(`failed to inject pty pre-command sessionId=${e.id}: ${o.message}`)}}function ee(e=process.env){const t=String(e.AWS_PTY_CONNECT_TOKEN_TTL_MS||"").trim();if(!t)return v;const r=Number(t);return!Number.isFinite(r)||r<5e3||r>10*60*1e3?v:r}function te(e=process.env){const t=String(e.AWS_PTY_EXITED_TTL_MS||"").trim();if(!t)return $;const r=Number(t);return!Number.isFinite(r)||r<1e3?$:r}function re(e=process.env){const t=String(e.AWS_PTY_MAX_SESSIONS||"").trim();if(!t)return b;const r=Number(t);return!Number.isInteger(r)||r<1||r>100?b:r}function ne(e){if(!e||typeof e!="object")return{workspacePath:void 0};const t=e;return{workspacePath:t.workspacePath,title:t.title,shell:t.shell,cols:t.cols,rows:t.rows,persistent:t.persistent,preCommand:t.preCommand,ownerUserId:t.ownerUserId,tenantId:t.tenantId,projectId:t.projectId}}function se(){return Array.from(c.values()).map(g)}function oe(e){const t=c.get(e);return t?g(t):void 0}function ie(e){if(!c.has(e))return;const t=`ptyws_${L.randomBytes(32).toString("base64url")}`,r=Date.now()+ee();return w.set(t,{sessionId:e,expiresAt:r}),{token:t,expiresAt:new Date(r).toISOString()}}function ce(e,t){const r=w.get(t);return r?r.expiresAt<Date.now()?(w.delete(t),p.warn(`[pty-ws] consumePtyConnectToken: token \u5DF2\u8FC7\u671F, sessionId=${e}, expiredAt=${new Date(r.expiresAt).toISOString()}`),!1):r.sessionId!==e?(p.warn(`[pty-ws] consumePtyConnectToken: sessionId \u4E0D\u5339\u914D, expected=${r.sessionId}, actual=${e}`),!1):(w.delete(t),p.info(`[pty-ws] consumePtyConnectToken: \u6210\u529F, sessionId=${e}`),!0):(p.warn(`[pty-ws] consumePtyConnectToken: token \u4E0D\u5B58\u5728, sessionId=${e}, ptyConnectTokens.size=${w.size}`),!1)}function ae(e){for(const[t,r]of w.entries())r.sessionId===e&&w.delete(t)}function S(e,t="closed"){const r=c.get(e);if(!r)return!1;C(r),ae(e),I(r,{type:"status",status:"closing",reason:t});for(const n of r.sockets)n.close(1e3,t);return r.sockets.clear(),r.status==="running"&&r.ptyProcess.kill(),c.delete(e),p.info(`closed pty session id=${e}, reason=${t}`),!0}function ge(e="shutdown"){for(const t of Array.from(c.keys()))S(t,e)}const y=N();y.get("/sessions",h,(e,t)=>{t.json({ok:!0,sessions:se()})}),y.post("/sessions",h,(e,t)=>{try{const r=G(ne(e.body));t.status(201).json({ok:!0,session:r})}catch(r){const n=r;t.status(400).json({error:n.message})}}),y.get("/sessions/:sessionId",h,(e,t)=>{const r=oe(e.params.sessionId);if(!r){t.status(404).json({error:"pty session not found"});return}t.json({ok:!0,session:r})}),y.delete("/sessions/:sessionId",h,(e,t)=>{if(!S(e.params.sessionId,"explicit-close")){t.status(404).json({error:"pty session not found"});return}t.json({ok:!0})}),y.post("/sessions/:sessionId/connect-token",h,(e,t)=>{const r=ie(e.params.sessionId);if(!r){t.status(404).json({error:"pty session not found"});return}t.json({ok:!0,...r})});function ue(e){const t=e.toString();try{return JSON.parse(t)}catch{return{type:"input",data:t}}}function le(e,t){C(e),e.sockets.add(t),e.lastActiveAt=new Date().toISOString(),k(t,{type:"status",status:e.status,session:g(e)}),e.outputBuffer&&k(t,{type:"output",data:e.outputBuffer,replay:!0}),t.on("message",r=>{const n=ue(r);if(!n||typeof n!="object")return;const i=n,o=String(i.type||"");if(e.lastActiveAt=new Date().toISOString(),o==="input"){e.ptyProcess.write(String(i.data||""));return}if(o==="resize"){const a=T(i.cols,e.cols),d=T(i.rows,e.rows);e.cols=a,e.rows=d,e.ptyProcess.resize(a,d);return}if(o==="close"){S(e.id,"client-close");return}o==="ping"&&k(t,{type:"pong"})}),t.on("close",()=>{e.sockets.delete(t),e.lastActiveAt=new Date().toISOString(),e.sockets.size===0&&e.status==="running"&&!e.persistent&&P(e)})}function pe(e,t){const r=t.searchParams.get("token")||"";if(r.trim())return r.trim();const n=e.headers["x-runtime-token"];return Array.isArray(n)?n[0]?.trim()||"":typeof n=="string"?n.trim():String(e.headers.authorization||"").match(/^Bearer\s+(.+)$/i)?.[1]?.trim()||""}function ke(e){const t=new z({noServer:!0});e.on("upgrade",(r,n,i)=>{const o=r.headers.host||"localhost",a=new URL(r.url||"/",`http://${o}`),d=a.pathname.match(/^\/pty\/sessions\/([^/]+)\/stream$/);if(!d)return;const u=pe(r,a),s=decodeURIComponent(d[1]||""),f=u?u.length>12?u.substring(0,12)+"...":u:"empty";if(p.info(`[pty-ws] upgrade \u8BF7\u6C42: sessionId=${s}, token=${f}, pathname=${a.pathname}`),!x(u)&&!ce(s,u)){p.warn(`[pty-ws] token \u6821\u9A8C\u5931\u8D25: sessionId=${s}, token=${f}, runtimeTokenValid=${x(u)}, ptySessions.size=${c.size}`),n.write(`HTTP/1.1 401 Unauthorized\r
1
+ import L from"node:crypto";import A from"node:fs";import u from"node:path";import{Router as N}from"express";import*as D from"node-pty";import{v4 as M}from"uuid";import U,{WebSocketServer as z}from"ws";import{isRuntimeTokenValid as x,validateToken as h}from"../middleware/auth.js";import{isUpgradeAllowed as R}from"../services/access-control.js";import{createLogger as j}from"../utils/logger.js";const p=j("pty"),W=80,F=24,_=2e5,E=30*60*1e3,$=5*60*1e3,v=60*1e3,b=8,B=new Set(["false","halt","nologin","reboot","shutdown","sync","true"]),c=new Map,w=new Map;function Y(e=process.env){const t=String(e.AWS_PTY_IDLE_TTL_MS||"").trim();if(!t)return E;const r=Number(t);return!Number.isFinite(r)||r<1e3?E:r}function X(e=process.platform,t=process.env,r=A.existsSync){return e==="win32"?{shell:String(t.ComSpec||t.COMSPEC||"").trim()||"powershell.exe",args:[]}:{shell:[String(t.SHELL||"").trim(),"/bin/bash","/bin/sh"].find(s=>H(s,r))||"/bin/sh",args:[]}}function H(e,t){const r=e.trim();if(!r)return!1;const n=u.basename(r).toLowerCase();return B.has(n)?!1:!u.isAbsolute(r)||t(r)}function J(e,t=process.platform,r=process.env){const n=String(e||"auto").trim();if(!n||n==="auto")return X(t,r);const i=new Set(["bash","sh","zsh","fish","pwsh","pwsh.exe","powershell","powershell.exe","cmd","cmd.exe"]),s=(t==="win32"?u.win32.basename(n):u.basename(n)).toLowerCase();if(!i.has(s))throw new Error(`shell is not allowed: ${n}`);return{shell:/^[A-Za-z]:[\\/]/.test(n)||n.startsWith("\\")||n.startsWith("/")?n:t==="win32"?{cmd:"cmd.exe",powershell:"powershell.exe",pwsh:"pwsh.exe"}[s]??n:n,args:[]}}function V(e,t){const r=u.relative(u.resolve(t),u.resolve(e));return r===""||!r.startsWith("..")&&!u.isAbsolute(r)}function K(e=process.env){return String(e.AWS_PTY_ALLOWED_ROOTS||"").split(u.delimiter).map(t=>t.trim()).filter(Boolean).map(t=>u.resolve(t))}function Z(e,t=process.env){const r=String(e||"").trim();if(!r)throw new Error("workspacePath is required");if(!u.isAbsolute(r))throw new Error("workspacePath must be absolute");const n=u.resolve(r);if(!A.statSync(n).isDirectory())throw new Error("workspacePath must be an existing directory");const s=K(t);if(s.length>0&&!s.some(a=>V(n,a)))throw new Error("workspacePath is outside allowed roots");return n}function T(e,t){const r=Number(e);return!Number.isInteger(r)||r<=0?t:Math.min(r,500)}function q(e){return{...e,TERM:e.TERM||"xterm-256color",COLORTERM:e.COLORTERM||"truecolor"}}function G(e,t){e.outputBuffer=`${e.outputBuffer}${t}`,e.outputBuffer.length>_&&(e.outputBuffer=e.outputBuffer.slice(-_))}function g(e){return{id:e.id,title:e.title,workspacePath:e.workspacePath,shell:e.shell,cols:e.cols,rows:e.rows,status:e.status,createdAt:e.createdAt,lastActiveAt:e.lastActiveAt,exitCode:e.exitCode,signal:e.signal,attachedClients:e.sockets.size,ownerUserId:e.ownerUserId,tenantId:e.tenantId,projectId:e.projectId}}function I(e,t){e.readyState===U.OPEN&&e.send(JSON.stringify(t))}function P(e,t){for(const r of e.sockets)I(r,t)}function k(e){e.idleTimer&&clearTimeout(e.idleTimer),e.idleTimer=setTimeout(()=>{e.sockets.size===0&&S(e.id,"idle-timeout")},Y())}function C(e){e.idleTimer&&(clearTimeout(e.idleTimer),e.idleTimer=null)}function Q(e){if(c.size>=ne())throw new Error("maximum PTY sessions reached");const t=Z(e.workspacePath),r=J(e.shell),n=T(e.cols,W),i=T(e.rows,F),s=`pty_${M()}`,a=new Date().toISOString(),d=String(e.title||"").trim()||`Terminal ${c.size+1}`,l=D.spawn(r.shell,r.args,{name:"xterm-256color",cols:n,rows:i,cwd:t,env:q(process.env)}),o={id:s,title:d,workspacePath:t,shell:r.shell,cols:n,rows:i,status:"running",createdAt:a,lastActiveAt:a,attachedClients:0,ptyProcess:l,sockets:new Set,outputBuffer:"",idleTimer:null,persistent:e.persistent===!0,ownerUserId:String(e.ownerUserId||"").trim()||void 0,tenantId:String(e.tenantId||"").trim()||void 0,projectId:String(e.projectId||"").trim()||void 0};return l.onData(f=>{o.lastActiveAt=new Date().toISOString(),G(o,f),P(o,{type:"output",data:f})}),l.onExit(({exitCode:f,signal:m})=>{o.status="exited",o.exitCode=f,o.signal=m,o.lastActiveAt=new Date().toISOString(),P(o,{type:"exit",exitCode:f,signal:m}),o.sockets.size===0&&!o.persistent?k(o):o.persistent&&(o.idleTimer&&clearTimeout(o.idleTimer),o.idleTimer=setTimeout(()=>{S(o.id,"exited-timeout")},re()))}),c.set(s,o),o.persistent||k(o),ee(o,e.preCommand),p.info(`created pty session id=${s}, cwd=${t}, shell=${r.shell}`),g(o)}function ee(e,t){const r=typeof t=="string"?t:"";if(!r.trim())return;const n=`${r.replace(/\n/g,"\r").replace(/\r+$/g,"")}\r`;try{e.ptyProcess.write(n)}catch(i){const s=i;p.warn(`failed to inject pty pre-command sessionId=${e.id}: ${s.message}`)}}function te(e=process.env){const t=String(e.AWS_PTY_CONNECT_TOKEN_TTL_MS||"").trim();if(!t)return v;const r=Number(t);return!Number.isFinite(r)||r<5e3||r>10*60*1e3?v:r}function re(e=process.env){const t=String(e.AWS_PTY_EXITED_TTL_MS||"").trim();if(!t)return $;const r=Number(t);return!Number.isFinite(r)||r<1e3?$:r}function ne(e=process.env){const t=String(e.AWS_PTY_MAX_SESSIONS||"").trim();if(!t)return b;const r=Number(t);return!Number.isInteger(r)||r<1||r>100?b:r}function oe(e){if(!e||typeof e!="object")return{workspacePath:void 0};const t=e;return{workspacePath:t.workspacePath,title:t.title,shell:t.shell,cols:t.cols,rows:t.rows,persistent:t.persistent,preCommand:t.preCommand,ownerUserId:t.ownerUserId,tenantId:t.tenantId,projectId:t.projectId}}function se(){return Array.from(c.values()).map(g)}function ie(e){const t=c.get(e);return t?g(t):void 0}function ce(e){if(!c.has(e))return;const t=`ptyws_${L.randomBytes(32).toString("base64url")}`,r=Date.now()+te();return w.set(t,{sessionId:e,expiresAt:r}),{token:t,expiresAt:new Date(r).toISOString()}}function ae(e,t){const r=w.get(t);return r?r.expiresAt<Date.now()?(w.delete(t),p.warn(`[pty-ws] consumePtyConnectToken: token \u5DF2\u8FC7\u671F, sessionId=${e}, expiredAt=${new Date(r.expiresAt).toISOString()}`),!1):r.sessionId!==e?(p.warn(`[pty-ws] consumePtyConnectToken: sessionId \u4E0D\u5339\u914D, expected=${r.sessionId}, actual=${e}`),!1):(w.delete(t),p.info(`[pty-ws] consumePtyConnectToken: \u6210\u529F, sessionId=${e}`),!0):(p.warn(`[pty-ws] consumePtyConnectToken: token \u4E0D\u5B58\u5728, sessionId=${e}, ptyConnectTokens.size=${w.size}`),!1)}function le(e){for(const[t,r]of w.entries())r.sessionId===e&&w.delete(t)}function S(e,t="closed"){const r=c.get(e);if(!r)return!1;C(r),le(e),P(r,{type:"status",status:"closing",reason:t});for(const n of r.sockets)n.close(1e3,t);return r.sockets.clear(),r.status==="running"&&r.ptyProcess.kill(),c.delete(e),p.info(`closed pty session id=${e}, reason=${t}`),!0}function Pe(e="shutdown"){for(const t of Array.from(c.keys()))S(t,e)}const y=N();y.get("/sessions",h,(e,t)=>{t.json({ok:!0,sessions:se()})}),y.post("/sessions",h,(e,t)=>{try{const r=Q(oe(e.body));t.status(201).json({ok:!0,session:r})}catch(r){const n=r;t.status(400).json({error:n.message})}}),y.get("/sessions/:sessionId",h,(e,t)=>{const r=ie(e.params.sessionId);if(!r){t.status(404).json({error:"pty session not found"});return}t.json({ok:!0,session:r})}),y.delete("/sessions/:sessionId",h,(e,t)=>{if(!S(e.params.sessionId,"explicit-close")){t.status(404).json({error:"pty session not found"});return}t.json({ok:!0})}),y.post("/sessions/:sessionId/connect-token",h,(e,t)=>{const r=ce(e.params.sessionId);if(!r){t.status(404).json({error:"pty session not found"});return}t.json({ok:!0,...r})});function ue(e){const t=e.toString();try{return JSON.parse(t)}catch{return{type:"input",data:t}}}function pe(e,t){C(e),e.sockets.add(t),e.lastActiveAt=new Date().toISOString(),I(t,{type:"status",status:e.status,session:g(e)}),e.outputBuffer&&I(t,{type:"output",data:e.outputBuffer,replay:!0}),t.on("message",r=>{const n=ue(r);if(!n||typeof n!="object")return;const i=n,s=String(i.type||"");if(e.lastActiveAt=new Date().toISOString(),s==="input"){e.ptyProcess.write(String(i.data||""));return}if(s==="resize"){const a=T(i.cols,e.cols),d=T(i.rows,e.rows);e.cols=a,e.rows=d,e.ptyProcess.resize(a,d);return}if(s==="close"){S(e.id,"client-close");return}s==="ping"&&I(t,{type:"pong"})}),t.on("close",()=>{e.sockets.delete(t),e.lastActiveAt=new Date().toISOString(),e.sockets.size===0&&e.status==="running"&&!e.persistent&&k(e)})}function de(e,t){const r=t.searchParams.get("token")||"";if(r.trim())return r.trim();const n=e.headers["x-runtime-token"];return Array.isArray(n)?n[0]?.trim()||"":typeof n=="string"?n.trim():String(e.headers.authorization||"").match(/^Bearer\s+(.+)$/i)?.[1]?.trim()||""}function ke(e){const t=new z({noServer:!0});e.on("upgrade",(r,n,i)=>{if(!R(r,n))return;const s=r.headers.host||"localhost",a=new URL(r.url||"/",`http://${s}`),d=a.pathname.match(/^\/pty\/sessions\/([^/]+)\/stream$/);if(!d)return;const l=de(r,a),o=decodeURIComponent(d[1]||""),f=l?l.length>12?l.substring(0,12)+"...":l:"empty";if(p.info(`[pty-ws] upgrade \u8BF7\u6C42: sessionId=${o}, token=${f}, pathname=${a.pathname}`),!x(l)&&!ae(o,l)){p.warn(`[pty-ws] token \u6821\u9A8C\u5931\u8D25: sessionId=${o}, token=${f}, runtimeTokenValid=${x(l)}, ptySessions.size=${c.size}`),n.write(`HTTP/1.1 401 Unauthorized\r
2
2
  \r
3
- `),n.destroy();return}const m=c.get(s);if(!m){p.warn(`[pty-ws] session \u4E0D\u5B58\u5728: sessionId=${s}, ptySessions.size=${c.size}, keys=${Array.from(c.keys()).join(",")}`),n.write(`HTTP/1.1 404 Not Found\r
3
+ `),n.destroy();return}const m=c.get(o);if(!m){p.warn(`[pty-ws] session \u4E0D\u5B58\u5728: sessionId=${o}, ptySessions.size=${c.size}, keys=${Array.from(c.keys()).join(",")}`),n.write(`HTTP/1.1 404 Not Found\r
4
4
  \r
5
- `),n.destroy();return}p.info(`[pty-ws] handleUpgrade: sessionId=${s}, status=${m.status}, persistent=${m.persistent}, sockets=${m.sockets.size}`),t.handleUpgrade(r,n,i,O=>{le(m,O)})})}export{ke as attachPtyWebSocketServer,ge as closeAllPtySessions,S as closePtySession,ie as createPtyConnectToken,G as createPtySession,oe as getPtySessionSummary,J as isPathInsideRoot,se as listPtySessions,y as ptyRouter,c as ptySessions,Y as resolveDefaultShell,re as resolveMaxPtySessions,te as resolvePtyExitedTtlMs,B as resolvePtyIdleTtlMs,K as resolvePtyWorkspacePath,H as resolveRequestedShell};
5
+ `),n.destroy();return}p.info(`[pty-ws] handleUpgrade: sessionId=${o}, status=${m.status}, persistent=${m.persistent}, sockets=${m.sockets.size}`),t.handleUpgrade(r,n,i,O=>{pe(m,O)})})}export{ke as attachPtyWebSocketServer,Pe as closeAllPtySessions,S as closePtySession,ce as createPtyConnectToken,Q as createPtySession,ie as getPtySessionSummary,V as isPathInsideRoot,se as listPtySessions,y as ptyRouter,c as ptySessions,X as resolveDefaultShell,ne as resolveMaxPtySessions,re as resolvePtyExitedTtlMs,Y as resolvePtyIdleTtlMs,Z as resolvePtyWorkspacePath,J as resolveRequestedShell};
6
6