aws-runtime-bridge 1.9.119 → 1.9.122

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/README.md +2 -0
  2. package/dist/adapter/AcodeSdkAdapter.d.ts +18 -0
  3. package/dist/adapter/AcodeSdkAdapter.js +5 -5
  4. package/dist/adapter/types.d.ts +4 -0
  5. package/dist/adapter/types.js +1 -1
  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/routes/dashboard.js +4 -4
  13. package/dist/routes/file-browser.js +1 -1
  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/terminal.js +30 -30
  22. package/dist/services/change-cache-retention.d.ts +5 -0
  23. package/dist/services/change-cache-retention.js +2 -0
  24. package/dist/services/desktop-mcp-manager.d.ts +47 -0
  25. package/dist/services/desktop-mcp-manager.js +2 -0
  26. package/dist/services/instance-features.d.ts +6 -0
  27. package/dist/services/instance-features.js +1 -1
  28. package/dist/services/jdk-scanner.d.ts +20 -0
  29. package/dist/services/jdk-scanner.js +2 -0
  30. package/dist/services/launch-manager.d.ts +181 -0
  31. package/dist/services/launch-manager.js +12 -0
  32. package/dist/services/launch-metrics.d.ts +89 -0
  33. package/dist/services/launch-metrics.js +4 -0
  34. package/dist/services/launch-streams.d.ts +24 -0
  35. package/dist/services/launch-streams.js +11 -0
  36. package/dist/services/mcp-launch-binding-queue.js +1 -1
  37. package/dist/services/panel-auth.d.ts +10 -0
  38. package/dist/services/panel-auth.js +1 -1
  39. package/dist/services/panel-env.d.ts +5 -0
  40. package/dist/services/panel-env.js +2 -0
  41. package/dist/services/port-detector.d.ts +3 -0
  42. package/dist/services/port-detector.js +6 -0
  43. package/dist/services/runtime-lifecycle-policy.d.ts +1 -0
  44. package/dist/services/runtime-lifecycle-policy.js +1 -1
  45. package/dist/services/tunnel-client.js +1 -1
  46. package/dist/services/workspace-files.d.ts +31 -0
  47. package/dist/services/workspace-files.js +1 -1
  48. package/dist/services/workspaceTaskChangeWatcher.d.ts +2 -0
  49. package/dist/services/workspaceTaskChangeWatcher.js +3 -3
  50. package/package/acode/dist/built-in-file-tools.d.ts +2 -0
  51. package/package/acode/dist/runtime.d.ts +20 -0
  52. package/package/acode/dist/runtime.js +27 -27
  53. package/package/acode/dist/sub-agent-manager.d.ts +4 -0
  54. package/package/acode/dist/sub-agent-manager.js +1 -1
  55. package/package/acode/dist/sub-agent-tools.d.ts +6 -0
  56. package/package/acode/dist/sub-agent-tools.js +39 -10
  57. package/package/acode/dist/sub-agent-types.d.ts +2 -0
  58. package/package/acode/dist/tool-allocation.d.ts +55 -0
  59. package/package/acode/dist/tool-allocation.js +2 -0
  60. package/package/acode/dist/types.d.ts +6 -0
  61. package/package/aws-client-agent-mcp/dist/agent-client.d.ts +24 -0
  62. package/package/aws-client-agent-mcp/dist/agent-client.js +1 -1
  63. package/package/aws-client-agent-mcp/dist/agent-client.test.js +19 -5
  64. package/package/aws-client-agent-mcp/dist/context-manager.test.js +6 -6
  65. package/package/aws-client-agent-mcp/dist/http-client.d.ts +2 -0
  66. package/package/aws-client-agent-mcp/dist/http-client.js +1 -1
  67. package/package/aws-client-agent-mcp/dist/index.js +1 -1
  68. package/package/aws-client-agent-mcp/dist/launch-config-tools.test.d.ts +1 -0
  69. package/package/aws-client-agent-mcp/dist/launch-config-tools.test.js +106 -0
  70. package/package/aws-client-agent-mcp/dist/logger.d.ts +4 -0
  71. package/package/aws-client-agent-mcp/dist/logger.js +2 -2
  72. package/package/aws-client-agent-mcp/dist/mcp-server.d.ts +21 -0
  73. package/package/aws-client-agent-mcp/dist/mcp-server.js +25 -25
  74. package/package/aws-client-agent-mcp/dist/mcp-server.test.js +24 -16
  75. package/package/aws-client-agent-mcp/dist/mcp-tools.d.ts +20 -0
  76. package/package/aws-client-agent-mcp/dist/mcp-tools.js +8 -8
  77. package/package/aws-client-agent-mcp/dist/mcp-tools.test.js +55 -1
  78. package/package/aws-client-agent-mcp/dist/memory-store.test.js +5 -1
  79. package/package/aws-client-agent-mcp/dist/parent-lifecycle.d.ts +10 -0
  80. package/package/aws-client-agent-mcp/dist/parent-lifecycle.js +2 -0
  81. package/package/aws-client-agent-mcp/dist/parent-lifecycle.test.d.ts +1 -0
  82. package/package/aws-client-agent-mcp/dist/parent-lifecycle.test.js +81 -0
  83. package/package/aws-client-agent-mcp/dist/status-reporter.js +1 -1
  84. package/package/aws-client-agent-mcp/dist/types.d.ts +4 -0
  85. package/package/aws-client-agent-mcp/dist/watchdog-worker.d.ts +1 -0
  86. package/package/aws-client-agent-mcp/dist/watchdog-worker.js +3 -0
  87. package/package/aws-client-agent-mcp/dist/watchdog.d.ts +17 -0
  88. package/package/aws-client-agent-mcp/dist/watchdog.js +2 -0
  89. package/package/aws-client-agent-mcp/dist/watchdog.test.d.ts +1 -0
  90. package/package/aws-client-agent-mcp/dist/watchdog.test.js +124 -0
  91. package/package/aws-client-agent-mcp/dist/websocket-client.d.ts +4 -0
  92. package/package/aws-client-agent-mcp/dist/websocket-client.js +1 -1
  93. package/package/aws-client-agent-mcp/dist/websocket-client.test.js +83 -6
  94. package/package/aws-client-agent-mcp/package.json +1 -1
  95. package/package.json +4 -1
  96. package/scripts/ensure-build-tools.mjs +141 -0
@@ -0,0 +1,181 @@
1
+
2
+ import { type LaunchHistoryEntry, type LaunchHistorySample, type LaunchMetricSample } from "./launch-metrics.js";
3
+ import { type RingBufferSlice } from "./ring-buffer.js";
4
+ export type LaunchRunState = "running" | "exited" | "stopped";
5
+
6
+ export interface LaunchLogEntry {
7
+ stream: "stdout" | "stderr";
8
+ text: string;
9
+ }
10
+
11
+ export interface LaunchRun {
12
+ configId: string;
13
+ pid: number;
14
+ state: LaunchRunState;
15
+ startedAt: string;
16
+ exitedAt?: string;
17
+ exitCode?: number | null;
18
+ command: string;
19
+ cwd: string;
20
+
21
+ startedBy?: string;
22
+
23
+ stoppedBy?: string;
24
+ }
25
+
26
+ export interface LaunchStartRequest {
27
+ configId: string;
28
+
29
+ command: string;
30
+
31
+ cwd?: string;
32
+
33
+ env?: Record<string, string>;
34
+
35
+ jdkPath?: string;
36
+
37
+ workspacePath?: string;
38
+
39
+ readinessPattern?: string;
40
+
41
+ startedBy?: string;
42
+ }
43
+
44
+ export interface LaunchRunSummary extends LaunchRun {
45
+ logTail: LaunchLogEntry[];
46
+ nextLogIndex: number;
47
+
48
+ ports: number[];
49
+
50
+ metrics?: LaunchMetricSample;
51
+ }
52
+
53
+ export interface LaunchLogsResult {
54
+ configId: string;
55
+ state: LaunchRunState;
56
+ entries: RingBufferSlice<LaunchLogEntry>;
57
+ nextIndex: number;
58
+ oldestIndex: number;
59
+ }
60
+
61
+ export interface LaunchStartupWaitResult {
62
+ outcome: "ready" | "exited" | "stopped" | "timeout";
63
+
64
+ waitedMs: number;
65
+
66
+ matchedLine?: string;
67
+
68
+ ports?: number[];
69
+
70
+ exitCode?: number | null;
71
+
72
+ logTail: LaunchLogEntry[];
73
+ }
74
+
75
+ export interface LaunchBatchItemResult {
76
+ configId: string;
77
+ ok: boolean;
78
+ run?: LaunchRun;
79
+ error?: string;
80
+ wait?: LaunchStartupWaitResult;
81
+ }
82
+
83
+ export interface LaunchBatchResult {
84
+ items: LaunchBatchItemResult[];
85
+ summary: {
86
+ total: number;
87
+ started: number;
88
+ failed: number;
89
+ ready: number;
90
+ exited: number;
91
+ stopped: number;
92
+ timeout: number;
93
+ };
94
+ }
95
+
96
+ export declare function resolvePlaceholders(command: string, ctx: {
97
+ workspacePath?: string;
98
+ jdkPath?: string;
99
+ env?: Record<string, string>;
100
+ }): string;
101
+
102
+ export declare class StreamDecoder {
103
+ private readonly utf8Probe;
104
+ private readonly utf8;
105
+ private readonly gbk;
106
+ private readonly onFlush;
107
+ private mode;
108
+ private buffered;
109
+ private bufferedSize;
110
+ private flushTimer;
111
+
112
+ constructor(onFlush?: (text: string) => void);
113
+
114
+ decode(chunk: Buffer): string;
115
+
116
+ finalize(): string;
117
+ private commit;
118
+ }
119
+
120
+ export declare class LaunchManager {
121
+ private static instance;
122
+
123
+ private readonly runs;
124
+
125
+ private readonly logs;
126
+
127
+ private readonly startupWaiters;
128
+
129
+ private readonly readinessMatchers;
130
+
131
+ private readonly logListeners;
132
+
133
+ private readonly metricsMonitor;
134
+
135
+ private restored;
136
+ private constructor();
137
+ static getInstance(): LaunchManager;
138
+
139
+ private runningRefs;
140
+
141
+ start(req: LaunchStartRequest): Promise<LaunchRun>;
142
+
143
+ stop(configId: string, reason?: "manual" | "shutdown", actor?: string): Promise<{
144
+ success: boolean;
145
+ message: string;
146
+ pid?: number;
147
+ }>;
148
+
149
+ restart(req: LaunchStartRequest): Promise<LaunchRun>;
150
+
151
+ startBatch(items: LaunchStartRequest[], waitTimeoutMs: number): Promise<LaunchBatchResult>;
152
+
153
+ stopAll(): Promise<{
154
+ running: number;
155
+ stopped: number;
156
+ }>;
157
+
158
+ waitStartupResult(configId: string, timeoutMs: number): Promise<LaunchStartupWaitResult>;
159
+
160
+ private scanReadinessMatch;
161
+
162
+ private resolveStartupWaiters;
163
+
164
+ listRuns(): Promise<LaunchRunSummary[]>;
165
+
166
+ getLogs(configId: string, sinceIndex: number): Promise<LaunchLogsResult | null>;
167
+
168
+ getRun(configId: string): LaunchRun | undefined;
169
+
170
+ getMetrics(configId: string, limit?: number): LaunchHistorySample[];
171
+
172
+ getMetricsSince(configId: string, sinceIndex: number, limit?: number): LaunchHistoryEntry[];
173
+
174
+ private ensureRestored;
175
+
176
+ private publishRunStatus;
177
+
178
+ private persist;
179
+ }
180
+
181
+ export declare function getLaunchManager(): LaunchManager;
@@ -0,0 +1,12 @@
1
+ import{exec as _,spawn as j}from"node:child_process";import{promises as z}from"node:fs";import A from"node:os";import M from"node:path";import{promisify as F}from"node:util";import{atomicWriteJsonFile as J,withFileLock as U}from"../utils/file-utils.js";import{createLogger as G}from"../utils/logger.js";import{getListeningPortsForProcessTrees as L}from"./port-detector.js";import{LaunchMetricsMonitor as V}from"./launch-metrics.js";import{isProcessRunning as E,terminateProcessTree as k}from"./process-detector.js";import{RingBuffer as O}from"./ring-buffer.js";import{getLaunchStreamHub as P}from"./launch-streams.js";const C=F(_),g=G("launch-manager"),D=5e3,B=4096,X=1500,K=20;function H(){return M.join(A.tmpdir(),"agentswork-runtime-bridge","launch-runs.json")}function Y(l,t){return l.replace(/\$\{([^}]+)\}/g,(e,s)=>{const r=s.trim(),i=r.toUpperCase();if(i==="WORKSPACE")return t.workspacePath||"";if(i==="JAVA_HOME")return t.jdkPath||process.env.JAVA_HOME||"";if(i==="HOME")return A.homedir();if(t.env&&t.env[r]!==void 0)return t.env[r];if(t.env){for(const[n,a]of Object.entries(t.env))if(n.toUpperCase()===i)return a}return e})}function Z(l){const t={...process.env};l.env&&Object.assign(t,l.env);for(const e of Object.keys(t))if(e.toUpperCase()==="PATH"&&e!=="PATH"){const s=t[e];delete t[e],t.PATH===void 0&&typeof s=="string"&&(t.PATH=s)}if(l.jdkPath){t.JAVA_HOME=l.jdkPath;const e=M.join(l.jdkPath,"bin"),s=M.delimiter;t.PATH=t.PATH?`${e}${s}${t.PATH}`:e}return t}const Q=256,q=120;class N{constructor(t=()=>{}){this.utf8Probe=new TextDecoder("utf-8",{fatal:!0}),this.utf8=new TextDecoder("utf-8"),this.gbk=new TextDecoder("gbk"),this.mode=null,this.buffered=[],this.bufferedSize=0,this.flushTimer=null,this.onFlush=t}decode(t){return this.mode!==null?(this.mode==="gbk"?this.gbk:this.utf8).decode(t,{stream:!0}):(this.buffered.push(t),this.bufferedSize+=t.length,this.bufferedSize>=Q?this.commit():(this.flushTimer===null&&(this.flushTimer=setTimeout(()=>{this.flushTimer=null,this.onFlush(this.commit())},q)),""))}finalize(){return this.flushTimer!==null&&(clearTimeout(this.flushTimer),this.flushTimer=null),this.mode===null?this.commit():(this.mode==="gbk"?this.gbk:this.utf8).decode()}commit(){if(this.flushTimer!==null&&(clearTimeout(this.flushTimer),this.flushTimer=null),this.mode!==null)return"";const t=Buffer.concat(this.buffered,this.bufferedSize);this.buffered=[],this.bufferedSize=0;try{return this.utf8Probe.decode(t),this.mode="utf8",this.utf8.decode(t)}catch{return this.mode="gbk",this.gbk.decode(t)}}}function tt(l){const t=l.length>0&&!l.endsWith(`
2
+ `)?l+`
3
+ `:l;return t.length>B?t.slice(-B):t}async function et(l){const t=A.platform();for(let e=0;e<6;e+=1){try{if(t==="win32"){const s=`Get-CimInstance Win32_Process -Filter "ParentProcessId = ${l} AND Name <> 'conhost.exe'" | Select-Object -First 1 ProcessId | ConvertTo-Json -Compress`,r=Buffer.from(s,"utf16le").toString("base64"),{stdout:i}=await C(`powershell -NoProfile -NonInteractive -EncodedCommand ${r}`,{encoding:"utf8",timeout:5e3,windowsHide:!0}),n=i.replace(/#\s*<\s*CLIXML[\s\S]*?(?=\{|\[|$)/g,"").trim(),a=JSON.parse(n||"null"),h=Number(a?.ProcessId);if(Number.isFinite(h)&&h>0)return h}else{const{stdout:s}=await C(`pgrep -P ${l} | head -n 1`,{encoding:"utf8",timeout:3e3}),r=Number(s.trim());if(Number.isFinite(r)&&r>0)return r}}catch{}await new Promise(s=>setTimeout(s,300))}return null}class S{constructor(){this.runs=new Map,this.logs=new Map,this.startupWaiters=new Map,this.readinessMatchers=new Map,this.logListeners=new Map,this.metricsMonitor=new V(()=>this.runningRefs()),this.restored=!1}static getInstance(){return S.instance===null&&(S.instance=new S),S.instance}runningRefs(){const t=[];for(const e of this.runs.values())e.state==="running"&&e.pid>0&&t.push({configId:e.configId,pid:e.pid});return t}async start(t){const e=t.configId;if(!e||!e.trim())throw new Error("configId is required");if(!t.command||!t.command.trim())throw new Error("command is required");let s=null;if(t.readinessPattern&&t.readinessPattern.trim())try{s=new RegExp(t.readinessPattern.trim())}catch(o){throw new Error(`readinessPattern \u6B63\u5219\u8BED\u6CD5\u65E0\u6548: ${o.message}`)}const r=t.startedBy?.trim()||void 0;await this.ensureRestored();const i=this.runs.get(e);if(i&&i.state==="running"&&E(i.pid))throw new Error(`\u542F\u52A8\u9879\u5DF2\u5728\u8FD0\u884C: pid=${i.pid}`);const n=Y(t.command,{workspacePath:t.workspacePath,jdkPath:t.jdkPath,env:t.env}),a=t.cwd||t.workspacePath||process.cwd(),h=Z(t);g.info(`[start] \u542F\u52A8\u547D\u4EE4: configId=${e}, cwd=${a}, command=${n}`);let m;try{m=j(n,{cwd:a,env:h,shell:!0,windowsHide:!0})}catch(o){const c=`\u8FDB\u7A0B\u542F\u52A8\u5931\u8D25: ${o.message}`;throw g.error(`[start] spawn \u5931\u8D25: configId=${e}, error=${c}`),new Error(c)}const w=m.pid??0,T={configId:e,pid:w,state:"running",startedAt:new Date().toISOString(),command:n,cwd:a,...r?{startedBy:r}:{}};this.runs.set(e,T),this.publishRunStatus(e,!0),this.metricsMonitor.clear(e),this.metricsMonitor.ensureRunning();let p=this.logs.get(e);p||(p=new O(D),this.logs.set(e,p));const $=p.getSinceIndex(-1).nextIndex;s?this.readinessMatchers.set(e,{pattern:s,fromIndex:$+1}):this.readinessMatchers.delete(e);const b=p.append({stream:"stdout",text:`> ${n}
4
+ `});P().publish(e,"logs",b.index,b.data);let R=null;(async()=>{const o=await et(w),c=this.runs.get(e);o&&c?.pid===w&&(R=o,this.runs.set(e,{...c,pid:o}),await this.persist(),this.publishRunStatus(e))})();const x=o=>o!==void 0&&(o.pid===w||o.pid===R),u=new N(o=>{f("stdout",o)}),d=new N(o=>{f("stderr",o)}),f=(o,c)=>{const y=tt(c);if(y){const v=p.append({stream:o,text:y});P().publish(e,"logs",v.index,v.data);const I=this.logListeners.get(e);if(I&&I.size>0)for(const W of I)try{W(v)}catch{}}};return m.stdout.on("data",o=>{f("stdout",u.decode(o))}),m.stderr.on("data",o=>{f("stderr",d.decode(o))}),m.on("error",o=>{f("stderr",`[\u542F\u52A8\u5931\u8D25] ${o.message}
5
+ `);const c=this.runs.get(e);c&&x(c)&&c.state==="running"&&(this.runs.set(e,{...c,state:"exited",exitedAt:new Date().toISOString(),exitCode:-1}),P().publishState(e,{configId:e,state:"exited",exitCode:-1}),this.publishRunStatus(e),this.persist(),this.resolveStartupWaiters(e,"exited",-1))}),m.on("exit",(o,c)=>{f("stdout",u.finalize()),f("stderr",d.finalize()),f("stderr",`
6
+ [\u8FDB\u7A0B\u9000\u51FA] code=${o??"-"}${c?` signal=${c}`:""}
7
+ `);const y=this.runs.get(e);y&&x(y)&&y.state==="running"&&(this.runs.set(e,{...y,state:"exited",exitedAt:new Date().toISOString(),exitCode:o}),P().publishState(e,{configId:e,state:"exited",exitCode:o}),this.publishRunStatus(e),this.persist(),this.resolveStartupWaiters(e,"exited",o))}),await this.persist(),T}async stop(t,e="manual",s){await this.ensureRestored();const r=this.runs.get(t);if(!r||r.state!=="running")return{success:!1,message:"\u542F\u52A8\u9879\u672A\u5728\u8FD0\u884C",pid:r?.pid};const i=s?.trim()||void 0;g.info(`[stop] \u7EC8\u6B62\u8FDB\u7A0B\u6811: configId=${t}, pid=${r.pid}, reason=${e}${i?`, actor=${i}`:""}`),this.runs.set(t,{...r,state:"stopped",exitedAt:new Date().toISOString(),...i?{stoppedBy:i}:{}});const n=await k(r.pid),a=e==="shutdown"?`
8
+ [bridge \u505C\u6B62\uFF0C\u8FDB\u7A0B\u5DF2\u8054\u52A8\u7EC8\u6B62]
9
+ `:`
10
+ [\u5DF2\u624B\u52A8\u505C\u6B62]
11
+ `,h=this.logs.get(t)?.append({stream:"stderr",text:a});return h&&P().publish(t,"logs",h.index,h.data),P().publishState(t,{configId:t,state:"stopped"}),this.publishRunStatus(t),await this.persist(),this.resolveStartupWaiters(t,"stopped",void 0),{success:n.failed===0,message:n.failed===0?"\u8FDB\u7A0B\u5DF2\u505C\u6B62":`\u90E8\u5206\u8FDB\u7A0B\u672A\u80FD\u7EC8\u6B62\uFF08terminated=${n.terminated}, failed=${n.failed}\uFF09`,pid:r.pid}}async restart(t){await this.ensureRestored();const e=this.runs.get(t.configId);return e&&e.state==="running"&&await this.stop(t.configId,"manual",t.startedBy),await this.start(t)}async startBatch(t,e){await this.ensureRestored();const s=await Promise.all(t.map(async i=>{try{const n=await this.start(i);return{configId:i.configId,ok:!0,run:n}}catch(n){return{configId:i.configId,ok:!1,error:n.message}}}));if(e>0){const i=await Promise.all(s.map(async n=>{if(!n.ok)return n;try{const a=await this.waitStartupResult(n.configId,e);return{...n,wait:a}}catch(a){return{...n,error:a.message}}}));s.splice(0,s.length,...i)}const r={total:s.length,started:0,failed:0,ready:0,exited:0,stopped:0,timeout:0};for(const i of s){if(!i.ok){r.failed+=1;continue}r.started+=1,i.wait&&(r[i.wait.outcome]+=1)}return{items:s,summary:r}}async stopAll(){await this.ensureRestored();const t=Array.from(this.runs.values()).filter(s=>s.state==="running");let e=0;for(const s of t)try{const r=await this.stop(s.configId,"shutdown");r.success?e++:g.warn(`[stopAll] \u542F\u52A8\u9879\u672A\u80FD\u5B8C\u5168\u7EC8\u6B62: configId=${s.configId}, message=${r.message}`)}catch(r){g.warn(`[stopAll] \u505C\u6B62\u542F\u52A8\u9879\u5931\u8D25: configId=${s.configId}, error=${r.message}`)}return{running:t.length,stopped:e}}async waitStartupResult(t,e){const s=this.runs.get(t);if(!s)throw new Error("launch run not found");const r=Date.now(),i=()=>(this.logs.get(t)?.getLastN(K)??[]).map(a=>a.data);if(s.state!=="running")return{outcome:s.state==="stopped"?"stopped":"exited",waitedMs:0,...s.state==="exited"?{exitCode:s.exitCode??null}:{},logTail:i()};const n=this.readinessMatchers.get(t);if(n){const a=this.scanReadinessMatch(t,n);if(a!==null)return{outcome:"ready",waitedMs:0,matchedLine:a,logTail:i()}}return await new Promise(a=>{let h=!1,m=null,w=null,T=!1,p=null;const $=()=>{m!==null&&(clearInterval(m),m=null),w!==null&&(clearTimeout(w),w=null);const u=this.startupWaiters.get(t);if(u&&(u.delete(R),u.size===0&&this.startupWaiters.delete(t)),p!==null){const d=this.logListeners.get(t);d&&(d.delete(p),d.size===0&&this.logListeners.delete(t)),p=null}},b=u=>{h||(h=!0,$(),a(u))},R=(u,d)=>{b({outcome:u==="stopped"?"stopped":"exited",waitedMs:Date.now()-r,...u==="exited"?{exitCode:d??null}:{},logTail:i()})};let x=this.startupWaiters.get(t);if(x||(x=new Set,this.startupWaiters.set(t,x)),x.add(R),n){p=d=>{if(!(h||d.index<n.fromIndex)){for(const f of d.data.text.split(/\r?\n/))if(f&&n.pattern.test(f)){b({outcome:"ready",waitedMs:Date.now()-r,matchedLine:f.trim(),logTail:i()});return}}};let u=this.logListeners.get(t);u||(u=new Set,this.logListeners.set(t,u)),u.add(p)}else{const u=async()=>{if(h||T)return;const d=this.runs.get(t);if(!(!d||d.state!=="running"||d.pid<=0)){T=!0;try{const o=(await L([d.pid])).get(d.pid)??[];if(o.length>0&&!h){const c=this.runs.get(t);c?.state==="running"&&c.pid===d.pid&&b({outcome:"ready",waitedMs:Date.now()-r,ports:o,logTail:i()})}}catch{}finally{T=!1}}};m=setInterval(()=>{u()},X),u()}w=setTimeout(()=>{b({outcome:"timeout",waitedMs:Date.now()-r,logTail:i()})},e)})}scanReadinessMatch(t,e){const s=this.logs.get(t);if(!s)return null;for(const r of s.getSinceIndex(e.fromIndex-1).entries)for(const i of r.data.text.split(/\r?\n/))if(i&&e.pattern.test(i))return i.trim();return null}resolveStartupWaiters(t,e,s){const r=this.startupWaiters.get(t);if(!(!r||r.size===0)){this.startupWaiters.delete(t);for(const i of r)try{i(e,s)}catch{}}}async listRuns(){await this.ensureRestored();const t=Array.from(this.runs.values()).filter(r=>r.state==="running"&&r.pid>0).map(r=>r.pid);let e=new Map;if(t.length>0)try{e=await L(t)}catch(r){g.debug(`[listRuns] \u7AEF\u53E3\u63A2\u6D4B\u5931\u8D25: ${r.message}`)}const s=[];for(const r of this.runs.values()){const i=this.logs.get(r.configId);s.push({...r,logTail:i?i.getLastN(20).map(n=>n.data):[],nextLogIndex:i?i.getSinceIndex(-1).nextIndex:0,ports:r.state==="running"?e.get(r.pid)??[]:[],metrics:this.metricsMonitor.getLatest(r.configId)??void 0})}return s}async getLogs(t,e){await this.ensureRestored();const s=this.runs.get(t);if(!s)return null;const r=this.logs.get(t);if(!r)return{configId:t,state:s.state,entries:[],nextIndex:0,oldestIndex:0};const{entries:i,nextIndex:n,oldestIndex:a}=r.getSinceIndex(e);return{configId:t,state:s.state,entries:i,nextIndex:n,oldestIndex:a}}getRun(t){return this.runs.get(t)}getMetrics(t,e){return this.metricsMonitor.getHistory(t,e)}getMetricsSince(t,e,s){return this.metricsMonitor.getHistoryEntriesSince(t,e,s)}async ensureRestored(){if(!this.restored){this.restored=!0;try{const t=await z.readFile(H(),"utf-8"),e=JSON.parse(t);for(const s of e){const r=s.pid>0&&E(s.pid);if(r){g.warn(`[restore] \u68C0\u6D4B\u5230\u4E0A\u6B21 bridge \u975E\u4F18\u96C5\u9000\u51FA\u540E\u7684\u6B8B\u7559\u8FDB\u7A0B\uFF0C\u8054\u52A8\u7EC8\u6B62: configId=${s.configId}, pid=${s.pid}`);try{await k(s.pid)}catch(i){g.warn(`[restore] \u7EC8\u6B62\u6B8B\u7559\u8FDB\u7A0B\u5931\u8D25: configId=${s.configId}, pid=${s.pid}, error=${i.message}`)}}this.runs.set(s.configId,{...s,state:"exited",...r?{exitedAt:new Date().toISOString()}:{}}),this.logs.set(s.configId,new O(D))}g.info(`[restore] \u5DF2\u6062\u590D ${e.length} \u6761\u542F\u52A8\u9879\u8FD0\u884C\u8BB0\u5F55`)}catch{}}}publishRunStatus(t,e=!1){const s=this.runs.get(t);s&&(P().publishStatusRun({...s}),!(!e||s.state!=="running"||s.pid<=0)&&setTimeout(()=>{const r=this.runs.get(t);!r||r.state!=="running"||r.pid<=0||(async()=>{let i=[];try{i=(await L([r.pid])).get(r.pid)??[]}catch{}const n=this.runs.get(t);n&&n.state==="running"&&n.pid===r.pid&&P().publishStatusRun({...n,ports:i})})()},2e3))}async persist(){const t=H();try{const e=Array.from(this.runs.values());await U(t,async()=>{await J(t,e)})}catch(e){g.warn(`[persist] \u6301\u4E45\u5316\u5931\u8D25: ${e.message}`)}}}S.instance=null;function pt(){return S.getInstance()}export{S as LaunchManager,N as StreamDecoder,pt as getLaunchManager,Y as resolvePlaceholders};
12
+
@@ -0,0 +1,89 @@
1
+
2
+
3
+ export declare const METRICS_SAMPLE_INTERVAL_MS = 2000;
4
+
5
+ export declare const METRICS_HISTORY_CAPACITY = 900;
6
+
7
+ export interface LaunchMetricSample {
8
+
9
+ cpuPercent: number;
10
+
11
+ memoryBytes: number;
12
+
13
+ threadCount: number;
14
+ }
15
+
16
+ export interface LaunchHistorySample extends LaunchMetricSample {
17
+ at: number;
18
+ }
19
+
20
+ export interface LaunchHistoryEntry {
21
+ index: number;
22
+ at: number;
23
+ sample: LaunchMetricSample;
24
+ }
25
+
26
+ export interface ProcessEntry {
27
+ pid: number;
28
+ ppid: number;
29
+
30
+ cpuMs: number;
31
+
32
+ rssBytes: number;
33
+
34
+ threads: number;
35
+ }
36
+
37
+ export interface RunningRunRef {
38
+ configId: string;
39
+ pid: number;
40
+ }
41
+
42
+ export declare function parseWindowsProcessTable(output: string): ProcessEntry[];
43
+
44
+ export declare function parsePsCpuTime(value: string): number;
45
+
46
+ export declare function parseUnixProcessTable(output: string, threadsColumn: boolean): ProcessEntry[];
47
+
48
+ export interface ProcessTreeAggregate {
49
+ cpuMs: number;
50
+ rssBytes: number;
51
+ threads: number;
52
+
53
+ found: boolean;
54
+ }
55
+
56
+ export declare function aggregateProcessTree(entries: ProcessEntry[], rootPid: number): ProcessTreeAggregate;
57
+
58
+ export declare function computeCpuPercent(previous: {
59
+ at: number;
60
+ totalCpuMs: number;
61
+ }, current: {
62
+ at: number;
63
+ totalCpuMs: number;
64
+ }): number;
65
+
66
+ export declare class LaunchMetricsMonitor {
67
+ private readonly getRuns;
68
+
69
+ private readonly histories;
70
+
71
+ private readonly cpuBaselines;
72
+ private timer;
73
+
74
+ private ticking;
75
+ constructor(getRuns: () => RunningRunRef[]);
76
+
77
+ ensureRunning(): void;
78
+
79
+ stop(): void;
80
+
81
+ clear(configId: string): void;
82
+
83
+ getLatest(configId: string): LaunchMetricSample | null;
84
+
85
+ getHistory(configId: string, limit?: number): LaunchHistorySample[];
86
+
87
+ getHistoryEntriesSince(configId: string, sinceIndex: number, limit?: number): LaunchHistoryEntry[];
88
+ private tick;
89
+ }
@@ -0,0 +1,4 @@
1
+ import{exec as m}from"node:child_process";import h from"node:os";import{promisify as g}from"node:util";import{createLogger as M}from"../utils/logger.js";import{getLaunchStreamHub as b}from"./launch-streams.js";import{RingBuffer as y}from"./ring-buffer.js";const l=g(m),w=M("launch-metrics"),N=2e3,d=900;function x(r){return r.replace(/#\s*<\s*CLIXML[\s\S]*?(?=#<\s*CLIXML|$)/g,"").trim()}function I(r){return r==null?[]:Array.isArray(r)?r:[r]}function S(r){const t=x(r),e=t.search(/[[{]/);if(e<0)return[];const n=JSON.parse(t.slice(e)),s=[];for(const i of I(n)){const o=Number(i?.ProcessId),c=Number(i?.ParentProcessId);if(!Number.isFinite(o)||!Number.isFinite(c))continue;const a=Number(i?.UserModeTime??0)/1e4,u=Number(i?.KernelModeTime??0)/1e4;s.push({pid:o,ppid:c,cpuMs:(Number.isFinite(a)?a:0)+(Number.isFinite(u)?u:0),rssBytes:Math.max(0,Number(i?.WorkingSetSize)||0),threads:Math.max(0,Number(i?.ThreadCount)||0)})}return s}function C(r){const t=r.trim();if(!t)return 0;const[e,...n]=t.split("-");let s=0,i;n.length>0?(s=Number(e)||0,i=n.join("-")):i=e;const o=i.split(":").map(p=>Number(p)||0),c=o[o.length-1]??0,a=o[o.length-2]??0,u=o[o.length-3]??0;return(((s*24+u)*60+a)*60+c)*1e3}function P(r,t){const e=[];for(const n of r.split(`
2
+ `)){const s=n.trim().split(/\s+/),i=t?5:4;if(s.length<i)continue;const o=Number.parseInt(s[0],10),c=Number.parseInt(s[1],10),a=Number.parseInt(s[2],10),u=t?Number.parseInt(s[3],10):0,p=C(s[t?4:3]);!Number.isFinite(o)||!Number.isFinite(c)||e.push({pid:o,ppid:c,cpuMs:p,rssBytes:Number.isFinite(a)&&a>0?a*1024:0,threads:Number.isFinite(u)&&u>0?u:0})}return e}async function T(r){const t=new Map;for(const e of r)try{const{stdout:n}=await l(`ps -M -p ${e}`,{encoding:"utf8",timeout:5e3}),s=n.split(`
3
+ `).filter(i=>i.trim().length>0);t.set(e,Math.max(0,s.length-1))}catch{}return t}async function B(){const e=Buffer.from("Get-CimInstance Win32_Process | Select-Object ProcessId, ParentProcessId, WorkingSetSize, UserModeTime, KernelModeTime, ThreadCount | ConvertTo-Json -Compress","utf16le").toString("base64"),{stdout:n}=await l(`powershell -NoProfile -NonInteractive -EncodedCommand ${e}`,{encoding:"utf8",timeout:1e4,windowsHide:!0,maxBuffer:10*1024*1024});return S(n)}async function L(r){const t=h.platform();if(t==="win32")return B();const e=t==="darwin",n=e?"pid=,ppid=,rss=,time=":"pid=,ppid=,rss=,nlwp=,time=",{stdout:s}=await l(`ps -axo ${n}`,{encoding:"utf8",timeout:5e3}),i=P(s,!e);if(e&&r.length>0){const o=await T(r);for(const c of i){const a=o.get(c.pid);a!==void 0&&(c.threads=a)}}return i}function k(r){const t=new Map;for(const e of r){const n=t.get(e.ppid);n?n.push(e.pid):t.set(e.ppid,[e.pid])}return t}function A(r,t){const e=new Map(r.map(u=>[u.pid,u]));if(!e.has(t))return{cpuMs:0,rssBytes:0,threads:0,found:!1};const n=k(r),s=[t],i=new Set;let o=0,c=0,a=0;for(;s.length>0;){const u=s.shift();if(i.has(u))continue;i.add(u);const p=e.get(u);p&&(o+=p.cpuMs,c+=p.rssBytes,a+=p.threads);for(const f of n.get(u)??[])s.push(f)}return{cpuMs:o,rssBytes:c,threads:a,found:!0}}function F(r,t){const e=t.at-r.at,n=t.totalCpuMs-r.totalCpuMs;return e<=0||n<=0?0:n/e*100}class j{constructor(t){this.getRuns=t,this.histories=new Map,this.cpuBaselines=new Map,this.timer=null,this.ticking=!1}ensureRunning(){if(this.timer===null){const t=setInterval(()=>void this.tick(),N);t.unref?.(),this.timer=t,this.tick()}}stop(){this.timer!==null&&(clearInterval(this.timer),this.timer=null)}clear(t){this.histories.delete(t),this.cpuBaselines.delete(t)}getLatest(t){const e=this.histories.get(t);if(!e)return null;const n=e.getLastN(1)[0];return n?n.data:null}getHistory(t,e=d){const n=this.histories.get(t);if(!n)return[];const s=Math.max(0,Math.min(e,d));return n.getLastN(s).map(i=>({at:i.timestamp,...i.data}))}getHistoryEntriesSince(t,e,n=d){const s=this.histories.get(t);if(!s)return[];const{entries:i}=s.getSinceIndex(e),o=Math.max(0,Math.min(n,d));return i.slice(-o).map(c=>({index:c.index,at:c.timestamp,sample:c.data}))}async tick(){if(!this.ticking){this.ticking=!0;try{const t=this.getRuns();if(t.length===0){this.stop();return}let e=[];try{e=await L(t.map(s=>s.pid))}catch(s){w.debug(`[launch-metrics] \u8FDB\u7A0B\u8868\u91C7\u96C6\u5931\u8D25: ${s.message}`);return}const n=Date.now();for(const s of t){const i=A(e,s.pid);if(!i.found)continue;const o=this.cpuBaselines.get(s.configId),c=o?F(o,{at:n,totalCpuMs:i.cpuMs}):0;this.cpuBaselines.set(s.configId,{at:n,totalCpuMs:i.cpuMs});let a=this.histories.get(s.configId);a||(a=new y(d),this.histories.set(s.configId,a));const u=a.append({cpuPercent:c,memoryBytes:i.rssBytes,threadCount:i.threads});b().publish(s.configId,"metrics",u.index,u.data)}}finally{this.ticking=!1}}}}export{j as LaunchMetricsMonitor,d as METRICS_HISTORY_CAPACITY,N as METRICS_SAMPLE_INTERVAL_MS,A as aggregateProcessTree,F as computeCpuPercent,C as parsePsCpuTime,P as parseUnixProcessTable,S as parseWindowsProcessTable};
4
+
@@ -0,0 +1,24 @@
1
+
2
+ export type LaunchStreamKind = "logs" | "metrics";
3
+ export declare class LaunchStreamHub {
4
+ private static instance;
5
+
6
+ private readonly subscribers;
7
+
8
+ private readonly statusSubscribers;
9
+ static getInstance(): LaunchStreamHub;
10
+
11
+ subscribe(configId: string, kind: LaunchStreamKind, send: (frame: string) => void): () => void;
12
+
13
+ subscriberCount(configId: string, kind: LaunchStreamKind): number;
14
+
15
+ subscribeStatus(send: (frame: string) => void): () => void;
16
+
17
+ publishStatusRun(run: unknown): void;
18
+
19
+ publish(configId: string, kind: LaunchStreamKind, id: number, data: unknown): void;
20
+
21
+ publishState(configId: string, state: unknown): void;
22
+ }
23
+
24
+ export declare function getLaunchStreamHub(): LaunchStreamHub;
@@ -0,0 +1,11 @@
1
+ class i{constructor(){this.subscribers=new Map,this.statusSubscribers=new Set}static getInstance(){return i.instance===null&&(i.instance=new i),i.instance}subscribe(s,t,n){const r=b(s,t);let e=this.subscribers.get(r);e||(e=new Set,this.subscribers.set(r,e));const c={configId:s,kind:t,send:n};return e.add(c),()=>{const u=this.subscribers.get(r);u&&(u.delete(c),u.size===0&&this.subscribers.delete(r))}}subscriberCount(s,t){return this.subscribers.get(b(s,t))?.size??0}subscribeStatus(s){const t={send:s};return this.statusSubscribers.add(t),()=>{this.statusSubscribers.delete(t)}}publishStatusRun(s){if(this.statusSubscribers.size===0)return;const t=`event: run_upsert
2
+ data: ${JSON.stringify({run:s})}
3
+
4
+ `;for(const n of[...this.statusSubscribers])try{n.send(t)}catch{}}publish(s,t,n,r){const e=this.subscribers.get(b(s,t));if(!e||e.size===0)return;const c=`id: ${n}
5
+ data: ${JSON.stringify(r)}
6
+
7
+ `;for(const u of[...e])try{u.send(c)}catch{}}publishState(s,t){const n=`event: run_state
8
+ data: ${JSON.stringify(t)}
9
+
10
+ `;for(const r of["logs","metrics"]){const e=this.subscribers.get(b(s,r));if(!(!e||e.size===0))for(const c of[...e])try{c.send(n)}catch{}}}}i.instance=null;function b(a,s){return`${a}:${s}`}function o(){return i.getInstance()}export{i as LaunchStreamHub,o as getLaunchStreamHub};
11
+
@@ -1,2 +1,2 @@
1
- import B from"node:path";import{schedulerBaseUrl as m}from"../config.js";import{getRuntimeAccessToken as T,loadRuntimeBinding as U,normalizeSchedulerBaseUrl as f}from"./runtime-binding.js";const x=5*60*1e3,k=new Map,n=[];function a(r){const e=String(r||"").trim();return e?B.resolve(e).toLowerCase():""}function d(r){return String(r||"").trim()}function L(r){const e=r.toLowerCase();return e==="localhost"||e==="::1"||e==="[::1]"?"127.0.0.1":e}function w(r){const e=f(r)||String(r||"").trim();if(!e)return"";try{const t=new URL(e);return t.protocol==="ws:"?t.protocol="http:":t.protocol==="wss:"?t.protocol="https:":t.protocol=t.protocol.toLowerCase(),t.hostname=L(t.hostname),t.origin}catch{return e.replace(/\/+$/,"").toLowerCase()}}function p(r=Date.now()){for(let e=n.length-1;e>=0;e-=1){const t=Date.parse(n[e]?.enqueuedAt||"");(!Number.isFinite(t)||r-t>x)&&n.splice(e,1)}}function M(r){return`${r}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,8)}`}function D(r){p();const e=String(r.agentId||"").trim(),t=a(r.workspacePath);if(!e||!t)return null;const s=U(),h=f(r.serverUrl||r.schedulerBaseUrl||s.schedulerBaseUrl||m)||d(r.serverUrl||r.schedulerBaseUrl||s.schedulerBaseUrl||m),l=f(r.schedulerBaseUrl||s.schedulerBaseUrl||m)||d(r.schedulerBaseUrl||s.schedulerBaseUrl||m),c={id:M(e),agentId:e,workspacePath:B.resolve(String(r.workspacePath)),serverUrl:h,mcpHttpUrl:d(r.mcpHttpUrl),runtimeAccessToken:d(r.runtimeAccessToken),userId:d(r.userId),schedulerBaseUrl:l,enqueuedAt:new Date().toISOString()};n.push(c);const u=c.runtimeAccessToken;return u&&k.set(e,{runtimeAccessToken:u,updatedAt:Date.now()}),c}function y(r){p();const e=String(r.agentId||"").trim(),t=a(r.workspacePath),s=w(r.serverUrl);if(!t)return null;const h=n.map((o,i)=>({binding:o,index:i})).filter(({binding:o})=>(!e||o.agentId===e)&&a(o.workspacePath)===t&&s&&w(o.serverUrl||o.schedulerBaseUrl)===s);let l=h.length===1?h[0]?.index??-1:-1;if(l<0){const o=n.map((i,I)=>({binding:i,index:I})).filter(({binding:i})=>(!e||i.agentId===e)&&a(i.workspacePath)===t);o.length===1&&(l=o[0]?.index??-1)}if(l<0)return null;const[c]=n.splice(l,1);if(!c)return null;const u=U(),S=c.userId||u.userId,g=c.schedulerBaseUrl||u.schedulerBaseUrl,A=c.runtimeAccessToken||T(g);return{...c,runtimeAccessToken:A,userId:S,schedulerBaseUrl:g}}function C(r){p();const e=a(r);return e?n.filter(t=>a(t.workspacePath)===e).length:0}function H(r){p();const e=String(r||"").trim();if(!e)return!1;const t=n.findIndex(s=>s.id===e);return t<0?!1:(n.splice(t,1),!0)}function K(){n.splice(0,n.length)}function N(r){const e=k.get(r);return!e||!e.runtimeAccessToken?null:{runtimeAccessToken:e.runtimeAccessToken}}export{y as claimMcpLaunchBinding,K as clearMcpLaunchBindingQueues,D as enqueueMcpLaunchBinding,N as getLatestAgentToken,C as getMcpLaunchQueueSize,H as removeMcpLaunchBinding};
1
+ import B from"node:path";import{schedulerBaseUrl as f}from"../config.js";import{getRuntimeAccessToken as x,loadRuntimeBinding as U,normalizeSchedulerBaseUrl as g}from"./runtime-binding.js";const L=5*60*1e3,w=new Map,s=[];function u(e){const r=String(e||"").trim();return r?B.resolve(r).toLowerCase():""}function h(e){return String(e||"").trim()}function v(e){const r=e.toLowerCase();return r==="localhost"||r==="::1"||r==="[::1]"?"127.0.0.1":r}function k(e){const r=g(e)||String(e||"").trim();if(!r)return"";try{const t=new URL(r);return t.protocol==="ws:"?t.protocol="http:":t.protocol==="wss:"?t.protocol="https:":t.protocol=t.protocol.toLowerCase(),t.hostname=v(t.hostname),t.origin}catch{return r.replace(/\/+$/,"").toLowerCase()}}function p(e=Date.now()){for(let r=s.length-1;r>=0;r-=1){const t=Date.parse(s[r]?.enqueuedAt||"");(!Number.isFinite(t)||e-t>L)&&s.splice(r,1)}}function M(e){return`${e}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,8)}`}function q(e){p();const r=String(e.agentId||"").trim(),t=u(e.workspacePath);if(!r||!t)return null;const n=U(),i=g(e.serverUrl||e.schedulerBaseUrl||n.schedulerBaseUrl||f)||h(e.serverUrl||e.schedulerBaseUrl||n.schedulerBaseUrl||f),o=g(e.schedulerBaseUrl||n.schedulerBaseUrl||f)||h(e.schedulerBaseUrl||n.schedulerBaseUrl||f),c={id:M(r),agentId:r,workspacePath:B.resolve(String(e.workspacePath)),serverUrl:i,mcpHttpUrl:h(e.mcpHttpUrl),runtimeAccessToken:h(e.runtimeAccessToken),userId:h(e.userId),schedulerBaseUrl:o,enqueuedAt:new Date().toISOString()};s.push(c);const d=c.runtimeAccessToken;return d&&w.set(r,{runtimeAccessToken:d,updatedAt:Date.now()}),c}function A(e){if(e.length===0||new Set(e.map(({binding:i})=>i.agentId)).size>1)return-1;let t=e[0],n=Date.parse(t.binding.enqueuedAt||"");for(const i of e.slice(1)){const o=Date.parse(i.binding.enqueuedAt||"");o>n&&(t=i,n=o)}return t.index}function C(e){p();const r=String(e.agentId||"").trim(),t=u(e.workspacePath),n=k(e.serverUrl);if(!t)return null;const i=s.map((l,a)=>({binding:l,index:a})).filter(({binding:l})=>(!r||l.agentId===r)&&u(l.workspacePath)===t&&n&&k(l.serverUrl||l.schedulerBaseUrl)===n);let o=A(i);if(o<0){const l=s.map((a,T)=>({binding:a,index:T})).filter(({binding:a})=>(!r||a.agentId===r)&&u(a.workspacePath)===t);o=A(l)}if(o<0)return null;const[c]=s.splice(o,1);if(!c)return null;const d=U(),I=c.userId||d.userId,m=c.schedulerBaseUrl||d.schedulerBaseUrl,S=c.runtimeAccessToken||x(m);return{...c,runtimeAccessToken:S,userId:I,schedulerBaseUrl:m}}function y(e){p();const r=u(e);return r?s.filter(t=>u(t.workspacePath)===r).length:0}function H(e){p();const r=String(e||"").trim();if(!r)return!1;const t=s.findIndex(n=>n.id===r);return t<0?!1:(s.splice(t,1),!0)}function K(){s.splice(0,s.length)}function N(e){const r=w.get(e);return!r||!r.runtimeAccessToken?null:{runtimeAccessToken:r.runtimeAccessToken}}export{C as claimMcpLaunchBinding,K as clearMcpLaunchBindingQueues,q as enqueueMcpLaunchBinding,N as getLatestAgentToken,y as getMcpLaunchQueueSize,H as removeMcpLaunchBinding};
2
2
 
@@ -13,8 +13,18 @@ export declare function getAllSessions(): Array<{
13
13
  }>;
14
14
 
15
15
  export declare function getActiveSessionCount(): number;
16
+
17
+ export declare function checkLoginAllowed(clientKey: string): {
18
+ allowed: boolean;
19
+ retryAfterSec: number;
20
+ };
21
+
22
+ export declare function recordLoginFailure(clientKey: string): void;
23
+
24
+ export declare function recordLoginSuccess(clientKey: string): void;
16
25
  interface PanelCredentials {
17
26
  username: string;
27
+
18
28
  password: string;
19
29
  }
20
30
 
@@ -1,2 +1,2 @@
1
- import{randomUUID as S}from"node:crypto";import{existsSync as a,mkdirSync as g,readFileSync as c,writeFileSync as w}from"node:fs";import C from"node:os";import f from"node:path";import{logger as d}from"../utils/logger.js";const p=24*60*60*1e3,h=60*60*1e3,r=new Map;let l=null;function x(){l||(l=setInterval(()=>{const e=Date.now();let t=0;for(const[n,s]of r)e-s.lastUsedAt>p&&(r.delete(n),t++);t>0&&d.info(`[panel-auth] \u5DF2\u6E05\u7406 ${t} \u4E2A\u8FC7\u671F\u9762\u677F\u4F1A\u8BDD`)},h),l.unref())}function O(){x();const e=S(),t=Date.now();return r.set(e,{token:e,createdAt:t,lastUsedAt:t}),e}function I(e){if(!e)return!1;const t=r.get(e);if(!t)return!1;const n=Date.now();return n-t.lastUsedAt>p?(r.delete(e),!1):(t.lastUsedAt=n,!0)}function T(e){r.delete(e)}function _(){return Array.from(r.values())}function E(){return r.size}let i=null;function u(){return f.join(C.homedir(),".aws-bridge","config.json")}function y(){const e=u();if(!a(e))return null;try{const t=c(e,"utf-8"),n=JSON.parse(t),s=String(n.panelUsername||"").trim(),o=String(n.panelPassword||"").trim();if(s&&o)return{username:s,password:o}}catch{}return null}function m(){if(i)return i;const e=y();return e?(i=e,e):(i={username:"root",password:"root"},i)}function F(e,t){const n=m();return e===n.username&&t===n.password}function A(){i=null}function J(){const e=u();if(!a(e))return!1;try{const t=c(e,"utf-8"),n=JSON.parse(t);return!!(n.panelUsername&&n.panelPassword)}catch{return!1}}function L(){const e=m();return e.username==="root"&&e.password==="root"}function M(e,t){const n=u(),s=f.dirname(n);let o={};if(a(n))try{o=JSON.parse(c(n,"utf-8"))}catch{}o.panelUsername=e,o.panelPassword=t;try{g(s,{recursive:!0})}catch{}w(n,JSON.stringify(o,null,2),"utf-8"),A(),d.info("[panel-auth] \u9762\u677F\u51ED\u636E\u5DF2\u66F4\u65B0")}export{A as clearCredentialsCache,O as createSession,T as destroySession,E as getActiveSessionCount,_ as getAllSessions,m as getPanelCredentials,J as hasConfiguredCredentials,L as isDefaultCredentials,M as setPanelCredentials,F as validateCredentials,I as validateSession};
1
+ import{randomUUID as U,randomBytes as P,scryptSync as w,timingSafeEqual as L}from"node:crypto";import{existsSync as f,mkdirSync as N,readFileSync as l,writeFileSync as _}from"node:fs";import I from"node:os";import h from"node:path";import{logger as c}from"../utils/logger.js";const g=24*60*60*1e3,O=60*60*1e3,s=new Map;let u=null;function D(){u||(u=setInterval(()=>{const t=Date.now();let e=0;for(const[n,r]of s)t-r.lastUsedAt>g&&(s.delete(n),e++);e>0&&c.info(`[panel-auth] \u5DF2\u6E05\u7406 ${e} \u4E2A\u8FC7\u671F\u9762\u677F\u4F1A\u8BDD`)},O),u.unref())}function j(){D();const t=U(),e=Date.now();return s.set(t,{token:t,createdAt:e,lastUsedAt:e}),t}function q(t){if(!t)return!1;const e=s.get(t);if(!e)return!1;const n=Date.now();return n-e.lastUsedAt>g?(s.delete(t),!1):(e.lastUsedAt=n,!0)}function X(t){s.delete(t)}function Y(){return Array.from(s.values())}function z(){return s.size}const m="scrypt$",x=64;function d(t){return t.startsWith(m)}function E(t){const e=P(16),n=w(t,e,x);return`${m}${e.toString("hex")}$${n.toString("hex")}`}function $(t,e){if(t.length!==e.length)return!1;try{return L(Buffer.from(t,"hex"),Buffer.from(e,"hex"))}catch{return!1}}function y(t,e){if(d(e)){const n=e.split("$");if(n.length!==3)return!1;const r=Buffer.from(n[1],"hex");if(r.length===0)return!1;const o=w(t,r,x).toString("hex");return $(o,n[2])}return t===e}const k=5,p=15*60*1e3,A=10*60*1e3,i=new Map;function M(){const t=Date.now();for(const[e,n]of i)n.lockedUntil<=t&&t-n.windowStart>p&&i.delete(e)}function V(t){const e=Date.now(),n=i.get(t);return n?n.lockedUntil>e?{allowed:!1,retryAfterSec:Math.ceil((n.lockedUntil-e)/1e3)}:(e-n.windowStart>p&&i.delete(t),{allowed:!0,retryAfterSec:0}):{allowed:!0,retryAfterSec:0}}function b(t){const e=Date.now();M();const n=i.get(t);if(!n||e-n.windowStart>p){i.set(t,{failures:1,windowStart:e,lockedUntil:0});return}n.failures+=1,n.failures>=k&&(n.lockedUntil=e+A,c.warn(`[panel-auth] \u767B\u5F55\u5931\u8D25\u6B21\u6570\u8FC7\u591A\uFF0C\u5DF2\u4E34\u65F6\u9501\u5B9A\u6765\u6E90 ${t}\uFF08${A/6e4} \u5206\u949F\uFF09`))}function Q(t){i.delete(t)}let a=null;function S(){return h.join(I.homedir(),".aws-bridge","config.json")}function v(){const t=S();if(!f(t))return null;try{const e=l(t,"utf-8"),n=JSON.parse(e),r=String(n.panelUsername||"").trim(),o=String(n.panelPassword||"").trim();if(r&&o)return{username:r,password:o}}catch{}return null}function C(){if(a)return a;const t=v();return t?(a=t,t):(a={username:"root",password:"root"},a)}function Z(t,e){const n=C();if(t!==n.username||!y(e,n.password))return!1;if(!d(n.password)&&T())try{B(n.username,e),c.info("[panel-auth] \u5DF2\u5C06\u660E\u6587\u9762\u677F\u5BC6\u7801\u81EA\u52A8\u8FC1\u79FB\u4E3A scrypt \u54C8\u5E0C\u5B58\u50A8")}catch{}return!0}function F(){a=null}function T(){const t=S();if(!f(t))return!1;try{const e=l(t,"utf-8"),n=JSON.parse(e);return!!(n.panelUsername&&n.panelPassword)}catch{return!1}}function K(){const t=C();return t.username==="root"&&y("root",t.password)}function B(t,e){const n=S(),r=h.dirname(n);let o={};if(f(n))try{o=JSON.parse(l(n,"utf-8"))}catch{}o.panelUsername=t,o.panelPassword=d(e)?e:E(e);try{N(r,{recursive:!0})}catch{}_(n,JSON.stringify(o,null,2),"utf-8"),F(),c.info("[panel-auth] \u9762\u677F\u51ED\u636E\u5DF2\u66F4\u65B0")}export{V as checkLoginAllowed,F as clearCredentialsCache,j as createSession,X as destroySession,z as getActiveSessionCount,Y as getAllSessions,C as getPanelCredentials,T as hasConfiguredCredentials,K as isDefaultCredentials,b as recordLoginFailure,Q as recordLoginSuccess,B as setPanelCredentials,Z as validateCredentials,q as validateSession};
2
2
 
@@ -0,0 +1,5 @@
1
+
2
+
3
+ export declare function setPanelEnvVars(env: unknown): void;
4
+
5
+ export declare function getPanelEnvVars(): Record<string, string>;
@@ -0,0 +1,2 @@
1
+ let o={};function c(n){const t={};if(n&&typeof n=="object")for(const[a,e]of Object.entries(n)){const r=a.trim();!r||e===void 0||e===null||(t[r]=String(e))}o=t}function i(){return o}export{i as getPanelEnvVars,c as setPanelEnvVars};
2
+
@@ -0,0 +1,3 @@
1
+
2
+
3
+ export declare function getListeningPortsForProcessTrees(rootPids: number[]): Promise<Map<number, number[]>>;
@@ -0,0 +1,6 @@
1
+ import{exec as N}from"node:child_process";import g from"node:os";import{promisify as P}from"node:util";import{createLogger as w}from"../utils/logger.js";const l=P(N),m=w("port-detector");function I(s){return s.replace(/#\s*<\s*CLIXML[\s\S]*?(?=#<\s*CLIXML|$)/g,"").trim()}function b(s){return s==null?[]:Array.isArray(s)?s:[s]}function y(s,o){const i=new Set([s]),t=[s];for(;t.length>0;){const n=t.shift();if(n===void 0)break;for(const e of o.get(n)??[])i.has(e)||(i.add(e),t.push(e))}return i}function M(s){const o=new Map;for(const i of s.split(`
2
+ `)){const t=i.trim().split(/\s+/);if(t.length<2)continue;const n=Number.parseInt(t[0],10),e=Number.parseInt(t[1],10);if(!Number.isFinite(n)||!Number.isFinite(e))continue;const r=o.get(e);r?r.push(n):o.set(e,[n])}return o}async function S(){const i=Buffer.from("$conns = Get-NetTCPConnection -State Listen -ErrorAction SilentlyContinue | Select-Object LocalPort, OwningProcess; $procs = Get-CimInstance Win32_Process | Select-Object ProcessId, ParentProcessId; @{Connections = $conns; Processes = $procs} | ConvertTo-Json -Compress -Depth 3","utf16le").toString("base64"),{stdout:t}=await l(`powershell -NoProfile -NonInteractive -EncodedCommand ${i}`,{encoding:"utf8",timeout:15e3,windowsHide:!0,maxBuffer:10*1024*1024}),n=new Map,e=new Map,r=I(t),d=r.search(/[[{]/);if(d<0)return{portsByPid:n,childrenMap:e};const u=JSON.parse(r.slice(d));for(const c of b(u.Connections)){const a=typeof c?.LocalPort=="number"?c.LocalPort:Number.NaN,p=typeof c?.OwningProcess=="number"?c.OwningProcess:Number.NaN;if(!Number.isFinite(a)||!Number.isFinite(p)||a<=0||p<=0)continue;const f=n.get(p);f?f.push(a):n.set(p,[a])}for(const c of b(u.Processes)){const a=typeof c?.ProcessId=="number"?c.ProcessId:Number.NaN,p=typeof c?.ParentProcessId=="number"?c.ParentProcessId:Number.NaN;if(!Number.isFinite(a)||!Number.isFinite(p))continue;const f=e.get(p);f?f.push(a):e.set(p,[a])}return{portsByPid:n,childrenMap:e}}function h(s,o,i){const t=s.get(o);t?t.push(i):s.set(o,[i])}function L(s,o){for(const i of s.split(`
3
+ `)){if(!i.includes("LISTEN"))continue;const t=i.trim().split(/\s+/);if(t.length<5)continue;const n=t[3].match(/:(\d+)$/);if(!n)continue;const e=Number.parseInt(n[1],10);for(const r of t[t.length-1].matchAll(/pid=(\d+)/g))h(o,Number.parseInt(r[1],10),e)}}function C(s,o){for(const i of s.split(`
4
+ `)){const t=i.trim().split(/\s+/);if(t.length<7||!t[0].startsWith("tcp")||t[5]!=="LISTEN")continue;const n=t[3].match(/:(\d+)$/);if(!n)continue;const e=t[6].match(/^(\d+)\//);e&&h(o,Number.parseInt(e[1],10),Number.parseInt(n[1],10))}}function T(s,o){for(const i of s.split(`
5
+ `)){if(!i.includes("(LISTEN)"))continue;const t=i.trim().split(/\s+/);if(t.length<9)continue;const n=Number.parseInt(t[1],10),e=i.match(/:(\d+)\s+\(LISTEN\)/);!Number.isFinite(n)||!e||h(o,n,Number.parseInt(e[1],10))}}async function $(s){const o=new Map,{stdout:i}=await l("ps -axo pid=,ppid=",{encoding:"utf8",timeout:5e3}),t=M(i);if(s){try{const{stdout:n}=await l("lsof -nP -iTCP -sTCP:LISTEN",{encoding:"utf8",timeout:8e3});T(n,o)}catch(n){m.debug(`[collectUnix] lsof failed: ${n.message}`)}return{portsByPid:o,childrenMap:t}}try{const{stdout:n}=await l("ss -ltnp",{encoding:"utf8",timeout:5e3});L(n,o)}catch(n){m.debug(`[collectUnix] ss failed, fallback to netstat: ${n.message}`);try{const{stdout:e}=await l("netstat -ltnp",{encoding:"utf8",timeout:5e3});C(e,o)}catch(e){m.debug(`[collectUnix] netstat failed: ${e.message}`)}}return{portsByPid:o,childrenMap:t}}async function A(s){const o=new Map,i=s.filter(r=>Number.isFinite(r)&&r>0);if(i.length===0)return o;const t=g.platform();let n,e;if(t==="win32"){const r=await S();n=r.portsByPid,e=r.childrenMap}else{const r=await $(t==="darwin");n=r.portsByPid,e=r.childrenMap}for(const r of i){const d=y(r,e),u=new Set;for(const c of d)for(const a of n.get(c)??[])u.add(a);u.size>0&&o.set(r,[...u].sort((c,a)=>c-a))}return o}export{A as getListeningPortsForProcessTrees};
6
+
@@ -1,6 +1,7 @@
1
1
  export interface GracefulShutdownPlan {
2
2
  terminateSdkSessions: boolean;
3
3
  closePtySessions: boolean;
4
+ stopLaunchRuns: boolean;
4
5
  terminateResidualProcesses: boolean;
5
6
  unregisterInstance: boolean;
6
7
  clearPersistedSessions: boolean;
@@ -1,2 +1,2 @@
1
- function n(s){const e=!s;return{terminateSdkSessions:e,closePtySessions:e,terminateResidualProcesses:e,unregisterInstance:e,clearPersistedSessions:e,stopTunnelClient:e}}function t(s,e=process.env){return s?!1:e.AWS_BRIDGE_CLEAN_ORPHANS_ON_STARTUP==="true"}function r(s=process.env){return s.AWS_BRIDGE_CLEAN_ORPHANS_ON_STARTUP==="true"}export{n as buildGracefulShutdownPlan,r as shouldAutoCleanOrphanProcesses,t as shouldCleanupPersistedSessionsOnStartup};
1
+ function n(s){const e=!s;return{terminateSdkSessions:e,closePtySessions:e,stopLaunchRuns:!0,terminateResidualProcesses:e,unregisterInstance:e,clearPersistedSessions:e,stopTunnelClient:e}}function t(s,e=process.env){return s?!1:e.AWS_BRIDGE_CLEAN_ORPHANS_ON_STARTUP==="true"}function r(s=process.env){return s.AWS_BRIDGE_CLEAN_ORPHANS_ON_STARTUP==="true"}export{n as buildGracefulShutdownPlan,r as shouldAutoCleanOrphanProcesses,t as shouldCleanupPersistedSessionsOnStartup};
2
2
 
@@ -1,2 +1,2 @@
1
- import k,{AxiosError as U}from"axios";import m from"ws";import{getRuntimeAccessToken as E,normalizeSchedulerBaseUrl as h}from"./runtime-binding.js";import{loadConfigs as _,loadPrimaryLifecycleConfig as B}from"./auto-register.js";import{logger as c}from"../utils/logger.js";import{port as T}from"../config.js";const A=15e3,I=5e3,N=200,O=1*1024*1024,M=4001,w=new Map;function L(e){const n=e.serverUrl,r=e.instanceId;if(!n||!r)return null;try{const t=new URL(n),o=t.protocol==="https:"?"wss:":"ws:",i=E(n),l=_().find(a=>h(a.serverUrl)===n)||B(),s=i||l.userKey;if(i?c.debug("[TunnelClient] \u4F7F\u7528 runtime access token \u8FDE\u63A5\u96A7\u9053"):l.userKey&&c.warn("[TunnelClient] getRuntimeAccessToken \u8FD4\u56DE\u7A7A\uFF0C\u56DE\u9000\u5230 userKey\uFF08\u975E runtime token\uFF0C\u9274\u6743\u53EF\u80FD\u5931\u8D25\uFF09: targetUrl=",n),!s)return c.warn("[TunnelClient] \u7F3A\u5C11 runtime token\uFF0C\u65E0\u6CD5\u5EFA\u7ACB\u96A7\u9053: targetUrl=",n),null;const u=new URLSearchParams({instanceId:r});return{url:`${o}//${t.host}/ws/bridge-tunnel?${u.toString()}`,token:s}}catch(t){return c.warn("[TunnelClient] \u65E0\u6CD5\u89E3\u6790 serverUrl:",String(t)),null}}function j(e,n){const r=h(e);if(!r){c.warn("[TunnelClient] serverUrl \u65E0\u6548\uFF0C\u65E0\u6CD5\u542F\u52A8\u96A7\u9053:",e);return}const t=w.get(r);t&&(t.shuttingDown=!0,S(t),w.delete(r));const o={serverUrl:r,instanceId:n,streams:new Map,shuttingDown:!1};w.set(r,o),y(o)}function z(){for(const e of w.values())e.shuttingDown=!0,S(e);w.clear()}function S(e){e.timer&&(clearInterval(e.timer),e.timer=void 0),e.reconnectTimer&&(clearTimeout(e.reconnectTimer),e.reconnectTimer=void 0);for(const[n,r]of e.streams.entries()){try{r.ws.close(1e3,"tunnel client stopped")}catch{}e.streams.delete(n)}if(e.ws){e.ws.removeAllListeners();try{e.ws.close(1e3,"tunnel client stopped")}catch{}e.ws=void 0}}function y(e){if(e.shuttingDown)return;const n=e.instanceId;if(!n)return;const r=L(e);if(!r){g(e);return}c.info("[TunnelClient] \u8FDE\u63A5\u96A7\u9053\u670D\u52A1\u7AEF:",r.url);const t=new m(r.url,{handshakeTimeout:15e3,headers:{"X-Runtime-Token":r.token}});e.ws=t,t.on("open",()=>{c.info("[TunnelClient] \u96A7\u9053\u8FDE\u63A5\u5DF2\u5EFA\u7ACB: server=",e.serverUrl,"instanceId=",n),e.reconnectTimer&&clearTimeout(e.reconnectTimer),e.reconnectTimer=void 0,e.timer=setInterval(()=>{t.readyState===m.OPEN&&t.send(JSON.stringify({t:"ping"}))},A)}),t.on("unexpected-response",(o,i)=>{c.error("[TunnelClient] \u670D\u52A1\u7AEF\u62D2\u7EDD\u8FDE\u63A5\uFF08\u975E 101 \u54CD\u5E94\uFF09: server=",e.serverUrl,"statusCode=",i.statusCode,"statusMessage=",i.statusMessage)}),t.on("upgrade",o=>{c.debug("[TunnelClient] \u6536\u5230\u5347\u7EA7\u54CD\u5E94: statusCode=",o.statusCode)}),t.on("message",o=>{let i;typeof o=="string"?i=o:Buffer.isBuffer(o)?i=o.toString("utf-8"):i=Buffer.from(o).toString("utf-8");try{const l=JSON.parse(i);R(e,l)}catch(l){c.warn("[TunnelClient] \u89E3\u6790\u96A7\u9053\u6D88\u606F\u5931\u8D25:",String(l))}}),t.on("close",(o,i)=>{if(c.warn("[TunnelClient] \u96A7\u9053\u8FDE\u63A5\u5173\u95ED: server=",e.serverUrl,"code=",o,"reason=",String(i)),C(e),o===M){c.warn("[TunnelClient] \u9274\u6743\u5931\u8D25\uFF084001\uFF09\uFF0C\u505C\u6B62\u81EA\u52A8\u91CD\u8FDE\uFF1B\u5F85\u91CD\u65B0\u6CE8\u518C\u540E\u518D\u542F\u52A8\u96A7\u9053: server=",e.serverUrl);return}g(e)}),t.on("error",o=>{c.warn("[TunnelClient] \u96A7\u9053\u8FDE\u63A5\u9519\u8BEF: server=",e.serverUrl,"err=",o.message,"code=",o.code),C(e),g(e)})}function C(e){e.timer&&(clearInterval(e.timer),e.timer=void 0);for(const[n,r]of e.streams.entries()){try{r.ws.close(1011,"tunnel disconnected")}catch{}e.streams.delete(n)}e.ws=void 0}function g(e){e.shuttingDown||e.reconnectTimer||(e.reconnectTimer=setTimeout(()=>{e.reconnectTimer=void 0,y(e)},I))}function R(e,n){switch(n.t){case"req":D(e,n);break;case"wsopen":W(e,n);break;case"wsmsg":x(e,n);break;case"wsclose":P(e,n);break;case"ping":d(e,{t:"pong"});break;case"pong":break;default:c.debug("[TunnelClient] \u672A\u77E5\u6D88\u606F\u7C7B\u578B: t=",n.t)}}async function D(e,n){const r=n.id,t=n.method||"GET",o=n.path||"/",i=`http://127.0.0.1:${T}${o}`;try{const l=n.body?Buffer.from(n.body,"base64"):void 0,s={};if(n.headers)for(const[p,f]of Object.entries(n.headers))f&&f.length>0&&(s[p]=f[0]);s["x-bridge-tunnel-source"]=e.serverUrl;const u=await k.request({url:i,method:t,headers:s,data:l,timeout:12e4,responseType:"arraybuffer",validateStatus:()=>!0}),a={};for(const[p,f]of Object.entries(u.headers))f!==void 0&&(a[p]=Array.isArray(f)?f:[String(f)]);const v=Buffer.isBuffer(u.data)?u.data.toString("base64"):"";d(e,{t:"res",id:r,status:u.status,headers:a,body:v||null})}catch(l){const s=l instanceof U?l.message:String(l);c.warn("[TunnelClient] \u672C\u5730\u8F6C\u53D1\u5931\u8D25:",t,o,"->",s),d(e,{t:"res",id:r,status:502,headers:{"content-type":["text/plain; charset=utf-8"]},body:Buffer.from(s).toString("base64")})}}function W(e,n){const r=n.id,t=n.path||"/",o=`ws://127.0.0.1:${T}${t}`;let i;try{i=new m(o,{handshakeTimeout:1e4})}catch(s){c.warn("[TunnelClient] \u672C\u5730 WS \u8FDE\u63A5\u5931\u8D25:",String(s)),d(e,{t:"wsclose",id:r,code:1011,reason:"local ws connect failed"});return}const l={ws:i,pending:[]};e.streams.set(r,l),i.on("open",()=>{for(const s of l.pending)b(l,s.data,s.bin);l.pending=[],d(e,{t:"wsopened",id:r})}),i.on("message",(s,u)=>{const a=Buffer.isBuffer(s)?s:Buffer.from(s);u&&e.ws&&e.ws.bufferedAmount>O||d(e,{t:"wsmsg",id:r,bin:u,d:a.toString("base64")})}),i.on("close",(s,u)=>{e.streams.delete(r),d(e,{t:"wsclose",id:r,code:s,reason:String(u)})}),i.on("error",s=>{c.warn("[TunnelClient] \u672C\u5730 WS \u9519\u8BEF: id=",r,"err=",s.message),e.streams.delete(r);try{i.close(1011,s.message)}catch{}d(e,{t:"wsclose",id:r,code:1011,reason:s.message})})}function b(e,n,r){r?e.ws.send(n):e.ws.send(n.toString("utf-8"))}function x(e,n){const r=e.streams.get(n.id);if(!r)return;const t=n.d?Buffer.from(n.d,"base64"):Buffer.alloc(0);if(r.ws.readyState!==m.OPEN){r.pending.length>=N&&(r.pending.shift(),c.warn("[TunnelClient] \u672C\u5730 WS \u672A\u5C31\u7EEA\uFF0C\u6392\u961F\u6D88\u606F\u8D85\u4E0A\u9650\uFF0C\u4E22\u5F03\u6700\u65E7\u4E00\u6761: id=",n.id)),r.pending.push({data:t,bin:!!n.bin});return}b(r,t,!!n.bin)}function P(e,n){const r=e.streams.get(n.id);if(r){e.streams.delete(n.id);try{r.ws.close(n.code??1e3,n.reason||"")}catch{}}}function d(e,n){if(!(!e.ws||e.ws.readyState!==m.OPEN))try{e.ws.send(JSON.stringify(n))}catch(r){c.warn("[TunnelClient] sendJson \u5931\u8D25:",String(r))}}export{j as startTunnelClient,z as stopTunnelClient};
1
+ import R,{AxiosError as M}from"axios";import h from"ws";import{getRuntimeAccessToken as L,normalizeSchedulerBaseUrl as E}from"./runtime-binding.js";import{loadConfigs as O,loadPrimaryLifecycleConfig as W}from"./auto-register.js";import{logger as c}from"../utils/logger.js";import{port as k}from"../config.js";const x=15e3,D=5e3,P=200,$=1*1024*1024,q=4001,G=768*1024,J=384*1024,g=new Map;function K(e){const r=e.serverUrl,n=e.instanceId;if(!r||!n)return null;try{const t=new URL(r),o=t.protocol==="https:"?"wss:":"ws:",i=L(r),l=O().find(m=>E(m.serverUrl)===r)||W(),s=i||l.userKey;if(i?c.debug("[TunnelClient] \u4F7F\u7528 runtime access token \u8FDE\u63A5\u96A7\u9053"):l.userKey&&c.warn("[TunnelClient] getRuntimeAccessToken \u8FD4\u56DE\u7A7A\uFF0C\u56DE\u9000\u5230 userKey\uFF08\u975E runtime token\uFF0C\u9274\u6743\u53EF\u80FD\u5931\u8D25\uFF09: targetUrl=",r),!s)return c.warn("[TunnelClient] \u7F3A\u5C11 runtime token\uFF0C\u65E0\u6CD5\u5EFA\u7ACB\u96A7\u9053: targetUrl=",r),null;const a=new URLSearchParams({instanceId:n});return{url:`${o}//${t.host}/ws/bridge-tunnel?${a.toString()}`,token:s}}catch(t){return c.warn("[TunnelClient] \u65E0\u6CD5\u89E3\u6790 serverUrl:",String(t)),null}}function re(e,r){const n=E(e);if(!n){c.warn("[TunnelClient] serverUrl \u65E0\u6548\uFF0C\u65E0\u6CD5\u542F\u52A8\u96A7\u9053:",e);return}const t=g.get(n);t&&(t.shuttingDown=!0,v(t),g.delete(n));const o={serverUrl:n,instanceId:r,streams:new Map,shuttingDown:!1};g.set(n,o),B(o)}function te(){for(const e of g.values())e.shuttingDown=!0,v(e);g.clear()}function v(e){e.timer&&(clearInterval(e.timer),e.timer=void 0),e.reconnectTimer&&(clearTimeout(e.reconnectTimer),e.reconnectTimer=void 0);for(const[r,n]of e.streams.entries()){try{n.ws.close(1e3,"tunnel client stopped")}catch{}e.streams.delete(r)}if(e.ws){e.ws.removeAllListeners();try{e.ws.close(1e3,"tunnel client stopped")}catch{}e.ws=void 0}}function B(e){if(e.shuttingDown)return;const r=e.instanceId;if(!r)return;const n=K(e);if(!n){y(e);return}c.info("[TunnelClient] \u8FDE\u63A5\u96A7\u9053\u670D\u52A1\u7AEF:",n.url);const t=new h(n.url,{handshakeTimeout:15e3,headers:{"X-Runtime-Token":n.token}});e.ws=t,t.on("open",()=>{c.info("[TunnelClient] \u96A7\u9053\u8FDE\u63A5\u5DF2\u5EFA\u7ACB: server=",e.serverUrl,"instanceId=",r),e.reconnectTimer&&clearTimeout(e.reconnectTimer),e.reconnectTimer=void 0,e.timer=setInterval(()=>{t.readyState===h.OPEN&&t.send(JSON.stringify({t:"ping"}))},x)}),t.on("unexpected-response",(o,i)=>{c.error("[TunnelClient] \u670D\u52A1\u7AEF\u62D2\u7EDD\u8FDE\u63A5\uFF08\u975E 101 \u54CD\u5E94\uFF09: server=",e.serverUrl,"statusCode=",i.statusCode,"statusMessage=",i.statusMessage)}),t.on("upgrade",o=>{c.debug("[TunnelClient] \u6536\u5230\u5347\u7EA7\u54CD\u5E94: statusCode=",o.statusCode)}),t.on("message",o=>{let i;typeof o=="string"?i=o:Buffer.isBuffer(o)?i=o.toString("utf-8"):i=Buffer.from(o).toString("utf-8");try{const l=JSON.parse(i);H(e,l)}catch(l){c.warn("[TunnelClient] \u89E3\u6790\u96A7\u9053\u6D88\u606F\u5931\u8D25:",String(l))}}),t.on("close",(o,i)=>{if(c.warn("[TunnelClient] \u96A7\u9053\u8FDE\u63A5\u5173\u95ED: server=",e.serverUrl,"code=",o,"reason=",String(i)),_(e),o===q){c.warn("[TunnelClient] \u9274\u6743\u5931\u8D25\uFF084001\uFF09\uFF0C\u505C\u6B62\u81EA\u52A8\u91CD\u8FDE\uFF1B\u5F85\u91CD\u65B0\u6CE8\u518C\u540E\u518D\u542F\u52A8\u96A7\u9053: server=",e.serverUrl);return}y(e)}),t.on("error",o=>{c.warn("[TunnelClient] \u96A7\u9053\u8FDE\u63A5\u9519\u8BEF: server=",e.serverUrl,"err=",o.message,"code=",o.code),_(e),y(e)})}function _(e){e.timer&&(clearInterval(e.timer),e.timer=void 0);for(const[r,n]of e.streams.entries()){try{n.ws.close(1011,"tunnel disconnected")}catch{}e.streams.delete(r)}e.ws=void 0}function y(e){e.shuttingDown||e.reconnectTimer||(e.reconnectTimer=setTimeout(()=>{e.reconnectTimer=void 0,B(e)},D))}function H(e,r){switch(r.t){case"req":X(e,r);break;case"wsopen":Y(e,r);break;case"wsmsg":j(e,r);break;case"wsclose":z(e,r);break;case"ping":f(e,{t:"pong"});break;case"pong":break;default:c.debug("[TunnelClient] \u672A\u77E5\u6D88\u606F\u7C7B\u578B: t=",r.t)}}async function X(e,r){const n=r.id,t=r.method||"GET",o=r.path||"/",i=`http://127.0.0.1:${k}${o}`;try{const l=r.body?Buffer.from(r.body,"base64"):void 0,s={};if(r.headers)for(const[d,u]of Object.entries(r.headers))u&&u.length>0&&(s[d]=u[0]);s["x-bridge-tunnel-source"]=e.serverUrl;const a=await R.request({url:i,method:t,headers:s,data:l,timeout:0,responseType:"stream",validateStatus:()=>!0}),m={};for(const[d,u]of Object.entries(a.headers))u!==void 0&&(m[d]=Array.isArray(u)?u:[String(u)]);const b=a.data,A=String(m["content-type"]?.[0]||"").toLowerCase().includes("text/event-stream"),p=[];let w=0,T=!1,S=!1;try{if(b)for await(const d of b){const u=Buffer.isBuffer(d)?d:Buffer.from(d);if(u.length!==0){if(T){if(!C(e,n,u))return}else if(A){if(T=!0,S=!0,!f(e,{t:"res",id:n,status:a.status,headers:m,body:null,chunked:!0})||!C(e,n,u))return}else if(p.push(u),w+=u.length,w>G){if(T=!0,S=!0,!f(e,{t:"res",id:n,status:a.status,headers:m,body:null,chunked:!0}))return;for(const N of p)if(!C(e,n,N))return;p.length=0,w=0}}}}catch(d){const u=d instanceof Error?d.message:String(d);c.warn("[TunnelClient] \u672C\u5730\u6D41\u8BFB\u53D6\u4E2D\u65AD:",t,o,"->",u),S?f(e,{t:"resend",id:n,error:u}):f(e,{t:"res",id:n,status:502,headers:{"content-type":["text/plain; charset=utf-8"]},body:Buffer.from(u).toString("base64")});return}if(T){f(e,{t:"resend",id:n});return}const I=w>0?Buffer.concat(p).toString("base64"):"";f(e,{t:"res",id:n,status:a.status,headers:m,body:I||null})}catch(l){const s=l instanceof M?l.message:String(l);c.warn("[TunnelClient] \u672C\u5730\u8F6C\u53D1\u5931\u8D25:",t,o,"->",s),f(e,{t:"res",id:n,status:502,headers:{"content-type":["text/plain; charset=utf-8"]},body:Buffer.from(s).toString("base64")})}}function C(e,r,n){let t=0;for(;t<n.length;){const o=n.subarray(t,t+J);if(!f(e,{t:"reschunk",id:r,d:o.toString("base64")}))return!1;t+=o.length}return!0}function Y(e,r){const n=r.id,t=r.path||"/",o=`ws://127.0.0.1:${k}${t}`;let i;try{i=new h(o,{handshakeTimeout:1e4})}catch(s){c.warn("[TunnelClient] \u672C\u5730 WS \u8FDE\u63A5\u5931\u8D25:",String(s)),f(e,{t:"wsclose",id:n,code:1011,reason:"local ws connect failed"});return}const l={ws:i,pending:[]};e.streams.set(n,l),i.on("open",()=>{for(const s of l.pending)U(l,s.data,s.bin);l.pending=[],f(e,{t:"wsopened",id:n})}),i.on("message",(s,a)=>{const m=Buffer.isBuffer(s)?s:Buffer.from(s);a&&e.ws&&e.ws.bufferedAmount>$||f(e,{t:"wsmsg",id:n,bin:a,d:m.toString("base64")})}),i.on("close",(s,a)=>{e.streams.delete(n),f(e,{t:"wsclose",id:n,code:s,reason:String(a)})}),i.on("error",s=>{c.warn("[TunnelClient] \u672C\u5730 WS \u9519\u8BEF: id=",n,"err=",s.message),e.streams.delete(n);try{i.close(1011,s.message)}catch{}f(e,{t:"wsclose",id:n,code:1011,reason:s.message})})}function U(e,r,n){n?e.ws.send(r):e.ws.send(r.toString("utf-8"))}function j(e,r){const n=e.streams.get(r.id);if(!n)return;const t=r.d?Buffer.from(r.d,"base64"):Buffer.alloc(0);if(n.ws.readyState!==h.OPEN){n.pending.length>=P&&(n.pending.shift(),c.warn("[TunnelClient] \u672C\u5730 WS \u672A\u5C31\u7EEA\uFF0C\u6392\u961F\u6D88\u606F\u8D85\u4E0A\u9650\uFF0C\u4E22\u5F03\u6700\u65E7\u4E00\u6761: id=",r.id)),n.pending.push({data:t,bin:!!r.bin});return}U(n,t,!!r.bin)}function z(e,r){const n=e.streams.get(r.id);if(n){e.streams.delete(r.id);try{n.ws.close(r.code??1e3,r.reason||"")}catch{}}}function f(e,r){if(!e.ws||e.ws.readyState!==h.OPEN)return!1;try{return e.ws.send(JSON.stringify(r)),!0}catch(n){return c.warn("[TunnelClient] sendJson \u5931\u8D25:",String(n)),!1}}export{re as startTunnelClient,te as stopTunnelClient};
2
2
 
@@ -14,6 +14,8 @@ export interface ReadWorkspaceFileResult {
14
14
  workspacePath: string;
15
15
  filePath: string;
16
16
  content: string;
17
+
18
+ mtimeMs: number;
17
19
  }
18
20
  export interface WorkspaceDocumentPreviewResult {
19
21
  workspacePath: string;
@@ -31,6 +33,8 @@ export interface WorkspaceDownloadTarget {
31
33
  fileName: string;
32
34
  contentType: string;
33
35
  isDirectory: boolean;
36
+
37
+ size?: number;
34
38
  }
35
39
  export interface WorkspaceUploadFile {
36
40
  originalname: string;
@@ -95,6 +99,28 @@ export interface SearchWorkspaceFilesParams {
95
99
  maxResults?: number;
96
100
  mode?: 'content' | 'filename';
97
101
  }
102
+ export interface SearchWorkspacePathsParams {
103
+ workspacePath: string;
104
+
105
+ rootPath?: string;
106
+
107
+ keyword: string;
108
+ ignoreCase?: boolean;
109
+ maxResults?: number;
110
+ maxDirectories?: number;
111
+ }
112
+ export interface WorkspacePathSearchItem {
113
+ name: string;
114
+ path: string;
115
+ isDirectory: boolean;
116
+ }
117
+ export interface SearchWorkspacePathsResult {
118
+ workspacePath: string;
119
+ rootPath: string;
120
+ keyword: string;
121
+ items: WorkspacePathSearchItem[];
122
+ truncated: boolean;
123
+ }
98
124
  export interface WorkspaceSearchMatch {
99
125
  filePath: string;
100
126
  lineNumber: number;
@@ -173,6 +199,7 @@ export declare function writeWorkspaceFile(params: WriteWorkspaceFileParams): Pr
173
199
  ok: true;
174
200
  workspacePath: string;
175
201
  filePath: string;
202
+ mtimeMs: number;
176
203
  }>;
177
204
 
178
205
  export declare function createWorkspaceEntry(params: CreateWorkspaceEntryParams): Promise<{
@@ -219,9 +246,13 @@ export declare function resolveWorkspaceDownloadTarget(params: DownloadWorkspace
219
246
 
220
247
  export declare function streamWorkspaceDirectoryZip(directoryPath: string, output: NodeJS.WritableStream): Promise<void>;
221
248
 
249
+ export declare function zipWorkspaceDirectoryToTemp(directoryPath: string, zipName: string, onProgress?: (zippedBytes: number) => void, shouldCancel?: () => boolean): Promise<string>;
250
+
222
251
  export declare function extractWorkspaceArchive(params: ExtractWorkspaceArchiveParams): Promise<WorkspaceArchiveExtractResult>;
223
252
 
224
253
  export declare function searchWorkspaceFiles(params: SearchWorkspaceFilesParams): Promise<SearchWorkspaceFilesResult>;
225
254
 
255
+ export declare function searchWorkspacePaths(params: SearchWorkspacePathsParams): Promise<SearchWorkspacePathsResult>;
256
+
226
257
  export declare function replaceWorkspaceFiles(params: ReplaceWorkspaceFilesParams): Promise<ReplaceWorkspaceFilesResult>;
227
258
  export {};