nuwax-file-server 1.4.4 → 1.4.5

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.
@@ -1 +1 @@
1
- import E from"path";import n from"fs";import I from"os";import{fileURLToPath as T}from"url";import o from"dotenv";const L=E.dirname(T(import.meta.url));o.config();function p(){const e=process.env.LOG_BASE_DIR,_=E.join(I.tmpdir(),"nuwax-file-server","project_logs");if(!e)return _;try{return n.mkdirSync(e,{recursive:!0}),e}catch{try{n.mkdirSync(_,{recursive:!0})}catch{}return _}}const r=process.env.NODE_ENV||"development";function R(e){const _=E.join(L,"..",`env.${e}`);if(n.existsSync(_))o.config({path:_}),console.log(`Environment configuration file env.${e} loaded`);else{const s=`Environment configuration file env.${e} does not exist, please create the corresponding environment configuration file and try again`;throw console.error(s),new Error(s)}}R(r);const O={NODE_ENV:r,PORT:parseInt(process.env.PORT),DEPLOYMENT_MODE:process.env.DEPLOYMENT_MODE||"docker-compose",FAST_RESTART_ENABLED:process.env.FAST_RESTART_ENABLED?.toLowerCase()==="true",INIT_PROJECT_NAME_REACT:process.env.INIT_PROJECT_NAME_REACT||"react-vite-template",INIT_PROJECT_NAME_VUE3:process.env.INIT_PROJECT_NAME_VUE3||"vue3-vite-template",INIT_PROJECT_DIR:process.env.INIT_PROJECT_DIR,PROJECT_SOURCE_DIR:process.env.PROJECT_SOURCE_DIR,DIST_TARGET_DIR:process.env.DIST_TARGET_DIR,UPLOAD_PROJECT_DIR:process.env.UPLOAD_PROJECT_DIR,MAX_BUILD_CONCURRENCY:process.env.MAX_BUILD_CONCURRENCY?parseInt(process.env.MAX_BUILD_CONCURRENCY,10):void 0,MAX_INLINE_FILE_SIZE_BYTES:process.env.MAX_INLINE_FILE_SIZE_BYTES?parseInt(process.env.MAX_INLINE_FILE_SIZE_BYTES,10):void 0,UPLOAD_MAX_FILE_SIZE_BYTES:process.env.UPLOAD_MAX_FILE_SIZE_BYTES?parseInt(process.env.UPLOAD_MAX_FILE_SIZE_BYTES,10):void 0,UPLOAD_ALLOWED_EXTENSIONS:process.env.UPLOAD_ALLOWED_EXTENSIONS?process.env.UPLOAD_ALLOWED_EXTENSIONS.split(",").map(e=>e.trim().toLowerCase()).filter(Boolean):[],UPLOAD_SINGLE_FILE_SIZE_BYTES:process.env.UPLOAD_SINGLE_FILE_SIZE_BYTES?parseInt(process.env.UPLOAD_SINGLE_FILE_SIZE_BYTES,10):void 0,DOWNLOAD_MAX_FILE_SIZE_BYTES:process.env.DOWNLOAD_MAX_FILE_SIZE_BYTES?parseInt(process.env.DOWNLOAD_MAX_FILE_SIZE_BYTES,10):void 0,REQUEST_BODY_LIMIT:process.env.REQUEST_BODY_LIMIT,TRAVERSE_EXCLUDE_DIRS:process.env.TRAVERSE_EXCLUDE_DIRS?process.env.TRAVERSE_EXCLUDE_DIRS.split(",").map(e=>e.trim()).filter(Boolean):[],BACKUP_TRAVERSE_EXCLUDE_FILES:process.env.BACKUP_TRAVERSE_EXCLUDE_FILES?process.env.BACKUP_TRAVERSE_EXCLUDE_FILES.split(",").map(e=>e.trim()).filter(Boolean):[],CONTENT_TRAVERSE_EXCLUDE_FILES:process.env.CONTENT_TRAVERSE_EXCLUDE_FILES?process.env.CONTENT_TRAVERSE_EXCLUDE_FILES.split(",").map(e=>e.trim()).filter(Boolean):[],INLINE_IMAGE_EXTENSIONS:process.env.INLINE_IMAGE_EXTENSIONS?process.env.INLINE_IMAGE_EXTENSIONS.split(",").map(e=>e.trim().toLowerCase()).filter(Boolean):[],TOP_LEVEL_NOISE_PATTERNS:process.env.TOP_LEVEL_NOISE_PATTERNS?process.env.TOP_LEVEL_NOISE_PATTERNS.split(",").map(e=>e.trim()).filter(Boolean):[],LOG_BASE_DIR:p(),LOG_LEVEL:process.env.LOG_LEVEL?process.env.LOG_LEVEL.toLowerCase():void 0,LOG_PREFIX_API:process.env.LOG_PREFIX_API,LOG_PREFIX_BUILD:process.env.LOG_PREFIX_BUILD,LOG_CONSOLE_ENABLED:typeof process.env.LOG_CONSOLE_ENABLED=="string"?process.env.LOG_CONSOLE_ENABLED.toLowerCase()==="true":void 0,LOG_CACHE_ENABLED:typeof process.env.LOG_CACHE_ENABLED=="string"?process.env.LOG_CACHE_ENABLED.toLowerCase()==="true":void 0,LOG_CACHE_DURATION:process.env.LOG_CACHE_DURATION?parseInt(process.env.LOG_CACHE_DURATION,10):void 0,LOG_CACHE_MAX_ENTRIES:process.env.LOG_CACHE_MAX_ENTRIES?parseInt(process.env.LOG_CACHE_MAX_ENTRIES,10):void 0,LOG_CACHE_MAX_FILE_SIZE:process.env.LOG_CACHE_MAX_FILE_SIZE?parseInt(process.env.LOG_CACHE_MAX_FILE_SIZE,10):void 0,DEV_SERVER_PORT_TIMEOUT:process.env.DEV_SERVER_PORT_TIMEOUT?parseInt(process.env.DEV_SERVER_PORT_TIMEOUT,10):void 0,DEV_SERVER_STOP_TIMEOUT:process.env.DEV_SERVER_STOP_TIMEOUT?parseInt(process.env.DEV_SERVER_STOP_TIMEOUT,10):void 0,DEV_SERVER_STOP_CHECK_INTERVAL:process.env.DEV_SERVER_STOP_CHECK_INTERVAL?parseInt(process.env.DEV_SERVER_STOP_CHECK_INTERVAL,10):void 0,DEV_SERVER_STOP_MAX_ATTEMPTS:process.env.DEV_SERVER_STOP_MAX_ATTEMPTS?parseInt(process.env.DEV_SERVER_STOP_MAX_ATTEMPTS,10):void 0,COMPUTER_WORKSPACE_DIR:process.env.COMPUTER_WORKSPACE_DIR,COMPUTER_LOG_DIR:process.env.COMPUTER_LOG_DIR,USERAPP_WORKSPACE_DIR:process.env.USERAPP_WORKSPACE_DIR,USERAPP_LOG_DIR:process.env.USERAPP_LOG_DIR,CLI_SERVICE_NAME:"nuwax-file-server",CLI_PID_DIR:process.env.CLI_PID_DIR||(process.platform==="win32"?E.join(process.env.TEMP||"","nuwax-file-server"):E.join("/tmp","nuwax-file-server")),CLI_PID_FILE:"server.pid",CLI_STOP_TIMEOUT:process.env.CLI_STOP_TIMEOUT?parseInt(process.env.CLI_STOP_TIMEOUT,10):3e4,CLI_CHECK_INTERVAL:process.env.CLI_CHECK_INTERVAL?parseInt(process.env.CLI_CHECK_INTERVAL,10):500,CLI_LOG_DIR:process.env.CLI_LOG_DIR||(process.platform==="win32"?E.join(process.env.TEMP||"","nuwax-file-server","logs"):E.join("/tmp","nuwax-file-server","logs")),CLI_IS_WINDOWS:process.platform==="win32",GIT_DEFAULT_AUTHOR_NAME:process.env.GIT_DEFAULT_AUTHOR_NAME||"Nuwax File Server",GIT_DEFAULT_AUTHOR_EMAIL:process.env.GIT_DEFAULT_AUTHOR_EMAIL||"git@nuwax.local",GIT_AUTO_GITIGNORE:process.env.GIT_AUTO_GITIGNORE?.toLowerCase()!=="false",GIT_GITIGNORE_ENTRIES:process.env.GIT_GITIGNORE_ENTRIES?process.env.GIT_GITIGNORE_ENTRIES.split("|").map(e=>e.trim()).filter(Boolean):["node_modules/",".pnpm-store/","dist/","build/",".idea/",".vscode/",".DS_Store",".npmrc",".agents/",".claude/",".opencode/",".codex/",".tmp/",".logs/","pnpm-lock.yaml","yarn.lock","package-lock.json"],GIT_ENABLED:process.env.GIT_ENABLED?.toLowerCase()==="true",GIT_USE_NATIVE:process.env.GIT_USE_NATIVE?.toLowerCase()!=="false",BASH_PATH:process.env.BASH_PATH||"",ZIP_WORKSPACE_EXCLUDE:process.env.ZIP_WORKSPACE_EXCLUDE?process.env.ZIP_WORKSPACE_EXCLUDE.split(",").map(e=>e.trim()).filter(Boolean):[".git",".tmp",".claude",".agents",".codex",".opencode",".logs",".npmrc","__pycache__","node_modules","dist","pnpm-lock.yaml","yarn.lock","package-lock.json"]};export default O;
1
+ import E from"path";import n from"fs";import I from"os";import{fileURLToPath as T}from"url";import o from"dotenv";const L=E.dirname(T(import.meta.url));o.config();function p(){const e=process.env.LOG_BASE_DIR,_=E.join(I.tmpdir(),"nuwax-file-server","project_logs");if(!e)return _;try{return n.mkdirSync(e,{recursive:!0}),e}catch{try{n.mkdirSync(_,{recursive:!0})}catch{}return _}}const r=process.env.NODE_ENV||"development";function R(e){const _=E.join(L,"..",`env.${e}`);if(n.existsSync(_))o.config({path:_}),console.log(`Environment configuration file env.${e} loaded`);else{const s=`Environment configuration file env.${e} does not exist, please create the corresponding environment configuration file and try again`;throw console.error(s),new Error(s)}}R(r);const O={NODE_ENV:r,PORT:parseInt(process.env.PORT),DEPLOYMENT_MODE:process.env.DEPLOYMENT_MODE||"docker-compose",FAST_RESTART_ENABLED:process.env.FAST_RESTART_ENABLED?.toLowerCase()==="true",INIT_PROJECT_NAME_REACT:process.env.INIT_PROJECT_NAME_REACT||"react-vite-template",INIT_PROJECT_NAME_VUE3:process.env.INIT_PROJECT_NAME_VUE3||"vue3-vite-template",INIT_PROJECT_DIR:process.env.INIT_PROJECT_DIR,PROJECT_SOURCE_DIR:process.env.PROJECT_SOURCE_DIR,DIST_TARGET_DIR:process.env.DIST_TARGET_DIR,UPLOAD_PROJECT_DIR:process.env.UPLOAD_PROJECT_DIR,MAX_BUILD_CONCURRENCY:process.env.MAX_BUILD_CONCURRENCY?parseInt(process.env.MAX_BUILD_CONCURRENCY,10):void 0,MAX_INLINE_FILE_SIZE_BYTES:process.env.MAX_INLINE_FILE_SIZE_BYTES?parseInt(process.env.MAX_INLINE_FILE_SIZE_BYTES,10):void 0,UPLOAD_MAX_FILE_SIZE_BYTES:process.env.UPLOAD_MAX_FILE_SIZE_BYTES?parseInt(process.env.UPLOAD_MAX_FILE_SIZE_BYTES,10):void 0,UPLOAD_ALLOWED_EXTENSIONS:process.env.UPLOAD_ALLOWED_EXTENSIONS?process.env.UPLOAD_ALLOWED_EXTENSIONS.split(",").map(e=>e.trim().toLowerCase()).filter(Boolean):[],UPLOAD_SINGLE_FILE_SIZE_BYTES:process.env.UPLOAD_SINGLE_FILE_SIZE_BYTES?parseInt(process.env.UPLOAD_SINGLE_FILE_SIZE_BYTES,10):void 0,DOWNLOAD_MAX_FILE_SIZE_BYTES:process.env.DOWNLOAD_MAX_FILE_SIZE_BYTES?parseInt(process.env.DOWNLOAD_MAX_FILE_SIZE_BYTES,10):void 0,REQUEST_BODY_LIMIT:process.env.REQUEST_BODY_LIMIT,TRAVERSE_EXCLUDE_DIRS:process.env.TRAVERSE_EXCLUDE_DIRS?process.env.TRAVERSE_EXCLUDE_DIRS.split(",").map(e=>e.trim()).filter(Boolean):[],BACKUP_TRAVERSE_EXCLUDE_FILES:process.env.BACKUP_TRAVERSE_EXCLUDE_FILES?process.env.BACKUP_TRAVERSE_EXCLUDE_FILES.split(",").map(e=>e.trim()).filter(Boolean):[],CONTENT_TRAVERSE_EXCLUDE_FILES:process.env.CONTENT_TRAVERSE_EXCLUDE_FILES?process.env.CONTENT_TRAVERSE_EXCLUDE_FILES.split(",").map(e=>e.trim()).filter(Boolean):[],INLINE_IMAGE_EXTENSIONS:process.env.INLINE_IMAGE_EXTENSIONS?process.env.INLINE_IMAGE_EXTENSIONS.split(",").map(e=>e.trim().toLowerCase()).filter(Boolean):[],TOP_LEVEL_NOISE_PATTERNS:process.env.TOP_LEVEL_NOISE_PATTERNS?process.env.TOP_LEVEL_NOISE_PATTERNS.split(",").map(e=>e.trim()).filter(Boolean):[],LOG_BASE_DIR:p(),LOG_LEVEL:process.env.LOG_LEVEL?process.env.LOG_LEVEL.toLowerCase():void 0,LOG_PREFIX_API:process.env.LOG_PREFIX_API,LOG_PREFIX_BUILD:process.env.LOG_PREFIX_BUILD,LOG_CONSOLE_ENABLED:typeof process.env.LOG_CONSOLE_ENABLED=="string"?process.env.LOG_CONSOLE_ENABLED.toLowerCase()==="true":void 0,LOG_CACHE_ENABLED:typeof process.env.LOG_CACHE_ENABLED=="string"?process.env.LOG_CACHE_ENABLED.toLowerCase()==="true":void 0,LOG_CACHE_DURATION:process.env.LOG_CACHE_DURATION?parseInt(process.env.LOG_CACHE_DURATION,10):void 0,LOG_CACHE_MAX_ENTRIES:process.env.LOG_CACHE_MAX_ENTRIES?parseInt(process.env.LOG_CACHE_MAX_ENTRIES,10):void 0,LOG_CACHE_MAX_FILE_SIZE:process.env.LOG_CACHE_MAX_FILE_SIZE?parseInt(process.env.LOG_CACHE_MAX_FILE_SIZE,10):void 0,DEV_SERVER_PORT_TIMEOUT:process.env.DEV_SERVER_PORT_TIMEOUT?parseInt(process.env.DEV_SERVER_PORT_TIMEOUT,10):void 0,DEV_SERVER_STOP_TIMEOUT:process.env.DEV_SERVER_STOP_TIMEOUT?parseInt(process.env.DEV_SERVER_STOP_TIMEOUT,10):void 0,DEV_SERVER_STOP_CHECK_INTERVAL:process.env.DEV_SERVER_STOP_CHECK_INTERVAL?parseInt(process.env.DEV_SERVER_STOP_CHECK_INTERVAL,10):void 0,DEV_SERVER_STOP_MAX_ATTEMPTS:process.env.DEV_SERVER_STOP_MAX_ATTEMPTS?parseInt(process.env.DEV_SERVER_STOP_MAX_ATTEMPTS,10):void 0,COMPUTER_WORKSPACE_DIR:process.env.COMPUTER_WORKSPACE_DIR,COMPUTER_LOG_DIR:process.env.COMPUTER_LOG_DIR,USERAPP_WORKSPACE_DIR:process.env.USERAPP_WORKSPACE_DIR,USERAPP_LOG_DIR:process.env.USERAPP_LOG_DIR,CLI_SERVICE_NAME:"nuwax-file-server",CLI_PID_DIR:process.env.CLI_PID_DIR||(process.platform==="win32"?E.join(process.env.TEMP||"","nuwax-file-server"):E.join("/tmp","nuwax-file-server")),CLI_PID_FILE:"server.pid",CLI_STOP_TIMEOUT:process.env.CLI_STOP_TIMEOUT?parseInt(process.env.CLI_STOP_TIMEOUT,10):3e4,CLI_CHECK_INTERVAL:process.env.CLI_CHECK_INTERVAL?parseInt(process.env.CLI_CHECK_INTERVAL,10):500,CLI_LOG_DIR:process.env.CLI_LOG_DIR||(process.platform==="win32"?E.join(process.env.TEMP||"","nuwax-file-server","logs"):E.join("/tmp","nuwax-file-server","logs")),CLI_IS_WINDOWS:process.platform==="win32",GIT_DEFAULT_AUTHOR_NAME:process.env.GIT_DEFAULT_AUTHOR_NAME||"Nuwax File Server",GIT_DEFAULT_AUTHOR_EMAIL:process.env.GIT_DEFAULT_AUTHOR_EMAIL||"git@nuwax.local",GIT_AUTO_GITIGNORE:process.env.GIT_AUTO_GITIGNORE?.toLowerCase()!=="false",GIT_GITIGNORE_ENTRIES:process.env.GIT_GITIGNORE_ENTRIES?process.env.GIT_GITIGNORE_ENTRIES.split("|").map(e=>e.trim()).filter(Boolean):["node_modules/",".pnpm-store/","dist/","build/",".idea/",".vscode/",".DS_Store",".npmrc",".agents/",".claude/",".opencode/",".codex/",".agent-store/",".tmp/",".logs/","pnpm-lock.yaml","yarn.lock","package-lock.json"],GIT_ENABLED:process.env.GIT_ENABLED?.toLowerCase()==="true",GIT_USE_NATIVE:process.env.GIT_USE_NATIVE?.toLowerCase()!=="false",BASH_PATH:process.env.BASH_PATH||"",ZIP_WORKSPACE_EXCLUDE:process.env.ZIP_WORKSPACE_EXCLUDE?process.env.ZIP_WORKSPACE_EXCLUDE.split(",").map(e=>e.trim()).filter(Boolean):[".git",".tmp",".claude",".agents",".codex",".opencode",".agent-store",".logs",".npmrc","__pycache__","node_modules","dist","pnpm-lock.yaml","yarn.lock","package-lock.json"]};export default O;
package/dist/cli.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import{Command as ee}from"commander";import{createRequire as te}from"module";import f from"path";import U from"os";import c from"fs-extra";import{spawn as E}from"cross-spawn";import V from"tree-kill";import{fileURLToPath as q}from"url";import{execFileSync as R}from"child_process";import M from"http";import{createRequire as G}from"module";var I=f.dirname(q(import.meta.url)),H=G(import.meta.url),n={name:"nuwax-file-server",pidDir:f.join(U.tmpdir(),"nuwax-file-server"),pidFileName:"server.pid",lockFileName:"start.lock",defaultStopTimeout:3e4,defaultStartTimeout:3e4,staleLockTimeout:12e4,checkInterval:500};function $(){return f.join(n.pidDir,n.pidFileName)}function F(){return f.join(n.pidDir,n.lockFileName)}function J(){let e=[f.join(I,"..","server.js"),f.join(I,"server.js")];for(let t of e)if(c.existsSync(t))return t;return e[0]}function W(){let e=["../../package.json","../package.json"];for(let t of e)try{let o=H(t);if(o?.version)return o.version}catch{}return"unknown"}function x(){try{let e=$();if(!c.existsSync(e))return null;let t=c.readFileSync(e,"utf8"),o=JSON.parse(t);return!o||typeof o.pid!="number"?null:o}catch(e){return e.code!=="ENOENT"&&console.error(`Read PID file failed: ${e.message}`),null}}function B(e){try{let t=$();c.ensureDirSync(n.pidDir),c.writeFileSync(t,JSON.stringify(e,null,2)),console.debug(`PID file written: ${t}`)}catch(t){throw console.error(`Write PID file failed: ${t.message}`),t}}function v(){try{let e=$();c.existsSync(e)&&(c.removeSync(e),console.debug(`PID file deleted: ${e}`))}catch(e){console.error(`Delete PID file failed: ${e.message}`)}}function g(e){try{return process.kill(e,0),!0}catch(t){return t.code!=="ESRCH"}}function K(e){try{return k()?R("powershell",["-NoProfile","-Command",`(Get-CimInstance Win32_Process -Filter "ProcessId = ${e}").CommandLine`],{encoding:"utf8",stdio:["ignore","pipe","ignore"]}).trim():R("ps",["-p",String(e),"-o","command="],{encoding:"utf8",stdio:["ignore","pipe","ignore"]}).trim()}catch{return""}}function X(e){if(!g(e))return!1;let t=K(e);if(!t)return!1;let o=t.toLowerCase(),s=f.basename(f.join(I,"..","server.js")).toLowerCase(),i=n.name.toLowerCase();return o.includes(s)||o.includes(i)}function z(){c.ensureDirSync(n.pidDir);let e=F(),t=`${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2,10)}`,o=JSON.stringify({pid:process.pid,token:t,createdAt:new Date().toISOString()},null,2);try{let s=c.openSync(e,"wx");return c.writeSync(s,o),{fd:s,token:t}}catch(s){if(s.code!=="EEXIST")throw s;try{let i=c.readFileSync(e,"utf8"),r=JSON.parse(i),a=Number(r?.pid),u=r?.createdAt?new Date(r.createdAt).getTime():0,l=Date.now()-u,C=Number.isFinite(a)&&a>0&&g(a),p=!u||Number.isNaN(l)||l>n.staleLockTimeout;if(!C||p){c.removeSync(e);let S=c.openSync(e,"wx");return c.writeSync(S,o),console.warn("Detected stale start lock, auto cleaned"),{fd:S,token:t}}}catch{c.removeSync(e);let r=c.openSync(e,"wx");return c.writeSync(r,o),console.warn("Detected invalid start lock, auto cleaned"),{fd:r,token:t}}throw s}}function Q(e){let t=e?.fd,o=e?.token;try{t!=null&&c.closeSync(t)}catch{}try{let s=F();if(c.existsSync(s)){let i=!1;if(!o)i=!0;else try{let r=c.readFileSync(s,"utf8");i=JSON.parse(r)?.token===o}catch{i=!0}i&&c.removeSync(s)}}catch{}}function k(){return process.platform==="win32"}async function Y(e=n.defaultStopTimeout){let t=x();return t?X(t.pid)?(console.log(`Existing service process detected (PID: ${t.pid}), stopping before start...`),await h(t.pid,!1)&&await D(t.pid,e)?(v(),{success:!0,message:`Existing process ${t.pid} stopped gracefully`}):(console.warn(`Graceful stop timeout or failed for PID ${t.pid}, force stop...`),await h(t.pid,!0)?await D(t.pid,e)?(v(),{success:!0,message:`Existing process ${t.pid} stopped forcibly`}):{success:!1,message:`Existing process ${t.pid} did not exit after force stop`}:{success:!1,message:`Failed to stop existing process ${t.pid}`})):(console.log(`Found stale PID file (PID: ${t.pid}), clean it...`),v(),{success:!0,message:"Stale PID file cleaned"}):{success:!0,message:"No existing service process"}}async function h(e,t=!1){return new Promise(o=>{if(!g(e)){console.debug(`Process ${e} does not exist, already stopped`),o(!0);return}let s=t?"SIGKILL":"SIGTERM",i=k()?"taskkill":"tree-kill";if(console.debug(`Use ${i} to stop process ${e} (signal: ${s})`),k()){let r=t?["/F","/PID",String(e)]:["/PID",String(e)],a=E("taskkill",r,{stdio:["ignore","pipe","pipe"],windowsHide:!0}),u="";a.stdout.on("data",l=>{u+=l.toString()}),a.stderr.on("data",l=>{u+=l.toString()}),a.on("error",l=>{console.error(`Stop process failed: ${l.message}`),o(!1)}),a.on("close",l=>{l===0?(console.debug(`Process ${e} stopped`),o(!0)):(console.warn(`taskkill exit code: ${l}, output: ${u}`),t?o(!1):h(e,!0).then(o))})}else V(e,s,r=>{r?r.code==="ESRCH"?(console.debug(`Process ${e} does not exist`),o(!0)):(console.error(`Stop process failed: ${r.message}`),o(!1)):(console.debug(`Process ${e} stopped`),o(!0))})})}async function D(e,t=n.defaultStopTimeout){let o=Date.now();for(;g(e);){if(Date.now()-o>t)return console.warn(`Wait for process ${e} to stop timeout (${t}ms)`),!1;await new Promise(i=>setTimeout(i,n.checkInterval))}let s=Date.now()-o;return console.debug(`Process ${e} stopped after ${s}ms`),!0}async function Z(e,t=n.defaultStartTimeout){let o=Number(e);if(!Number.isFinite(o)||o<=0)return!1;let s=Date.now();for(;Date.now()-s<=t;){if(await new Promise(r=>{let a=M.get({host:"127.0.0.1",port:o,path:"/health",timeout:Math.min(2e3,n.checkInterval*4)},u=>{r(u.statusCode>=200&&u.statusCode<300),u.resume()});a.on("timeout",()=>{a.destroy(),r(!1)}),a.on("error",()=>r(!1))}))return!0;await new Promise(r=>setTimeout(r,n.checkInterval))}return!1}async function N(e={}){let{env:t,port:o,config:s}=e,i=null;console.log(`Start service ${n.name}...`);try{i=z()}catch(r){return r.code==="EEXIST"?{success:!1,pid:null,message:"Another start operation is in progress, please retry later"}:{success:!1,pid:null,message:`Acquire start lock failed: ${r.message}`}}try{let r=Number(e.timeout)||n.defaultStopTimeout,a=await Y(r);if(!a.success)return{success:!1,pid:null,message:`Service start blocked: ${a.message}`};let u={...process.env};t&&(u.NODE_ENV=t,console.log(`Environment: ${t}`)),o&&(u.PORT=o,console.log(`Port: ${o}`)),s&&(u.CONFIG_FILE=s,console.log(`Configuration file: ${s}`));let l=J(),p=E("node",[l,...[]],{env:u,stdio:["pipe","pipe","pipe"],detached:!0,cwd:process.cwd()});if(p.stdout.on("data",m=>{process.stdout.write(m)}),p.stderr.on("data",m=>{process.stderr.write(m)}),p.on("error",m=>{console.error(`Start service failed: ${m.message}`)}),await new Promise(m=>setTimeout(m,2e3)),!g(p.pid))return{success:!1,pid:null,message:"Service start failed"};let S={pid:p.pid,startedAt:new Date().toISOString(),env:t||process.env.NODE_ENV||"production",port:o||process.env.PORT||"60000",version:W(),platform:process.platform};B(S);let P=Number(e.startTimeout)||n.defaultStartTimeout;return await Z(S.port,P)?(p.unref(),console.log(`Service started (PID: ${p.pid})`),console.log(`Service address: http://localhost:${S.port}`),{success:!0,pid:p.pid,message:"Service started successfully"}):(console.error(`Service health check timeout (${P}ms), stop failed instance...`),await h(p.pid,!0),v(),{success:!1,pid:null,message:`Service health check timeout (${P}ms)`})}finally{Q(i)}}async function T(e={}){let{force:t=!1,timeout:o=n.defaultStopTimeout}=e;console.log(`Stop service ${n.name}...`);let s=x();if(!s)return{success:!1,message:"Service not found"};if(!g(s.pid))return console.log("Service process has stopped, clean PID file..."),v(),{success:!0,message:"Service has stopped (process has exited)"};if(!await h(s.pid,t))return{success:!1,message:"Stop service failed"};let r=await D(s.pid,o);return v(),r?{success:!0,message:"Service has stopped"}:{success:!1,message:"Service stop timeout"}}async function O(e={}){console.log(`Restart service ${n.name}...`);let t=await T(e);!t.success&&t.message!=="Service not found"&&console.warn(`Stop service failed: ${t.message}`),await new Promise(s=>setTimeout(s,2e3));let o=await N(e);return o.success?{success:!0,pid:o.pid,message:"Service has restarted"}:{success:!1,pid:null,message:`Restart failed: ${o.message}`}}function _(){let e=x();return e?g(e.pid)?{running:!0,pidInfo:e,message:"Service running"}:{running:!1,pidInfo:e,message:"Service process does not exist"}:{running:!1,pidInfo:null,message:"Service not running"}}function L(e){try{let t=new Date(e),o=new Date;if(isNaN(t.getTime()))return"unknown";let s=Math.floor((o-t)/1e3),i=Math.floor(s/3600),r=Math.floor(s%3600/60),a=s%60;return i>0?`${i} hours ${r} minutes ${a} seconds`:r>0?`${r} minutes ${a} seconds`:`${a} seconds`}catch{return"unknown"}}var Pe=te(import.meta.url),A="1.4.4",d=new ee,w={reset:"\x1B[0m",red:"\x1B[31m",green:"\x1B[32m",yellow:"\x1B[33m",blue:"\x1B[34m",cyan:"\x1B[36m"};function j(e){console.error(`${w.red}ERROR: ${e}${w.reset}`)}function b(e){console.log(`${w.green}${e}${w.reset}`)}function oe(e){console.log(`${w.blue}${e}${w.reset}`)}function y(e){return async(...t)=>{try{let o=await e(...t);o&&o.success===!1?(j(o.message||"Command failed"),process.exitCode=1):process.exitCode=0}catch(o){j(o?.message||"Command failed with unexpected error"),process.exitCode=1}}}async function se(e){let t=await N({env:e.env,port:e.port,config:e.config,timeout:Number(e.timeout),startTimeout:Number(e.startTimeout)});return t.success&&b(`Service started (PID: ${t.pid})`),t}async function re(e){let t=await T({force:e.force,timeout:Number(e.timeout)});return t.success&&b(t.message||"Service stopped"),t}async function ne(e){let t=await O({env:e.env,port:e.port,config:e.config,timeout:Number(e.timeout),startTimeout:Number(e.startTimeout)});return t.success&&b(t.message||"Service restarted"),t}function ie(){let e=_();return oe(`${n.name} service status:`),console.log(""),console.log(` Service name: ${n.name}`),console.log(` Running status: ${e.running?"Running":"Stopped"}`),console.log(` Message: ${e.message}`),console.log(` PID file: ${$()}`),e.pidInfo&&(console.log(` Process ID: ${e.pidInfo.pid}`),console.log(` Environment: ${e.pidInfo.env||"Unknown"}`),console.log(` Port: ${e.pidInfo.port||"Unknown"}`),console.log(` Version: ${e.pidInfo.version||A}`),console.log(` Platform: ${e.pidInfo.platform||process.platform}`),console.log(` Started at: ${e.pidInfo.startedAt||"Unknown"}`),console.log(` Uptime: ${L(e.pidInfo.startedAt)}`)),console.log(""),{success:e.running,message:e.message}}function ce(){d.name("nuwax-file-server").description("Cross-platform file service deployment tool, supporting start/stop/restart/status").version(A,"-v, --version","Display version number").helpOption("-h, --help","Display help information"),d.command("start").allowUnknownOption().description("Start service").option("--env <environment>","\u73AF\u5883: development|production|test","production").option("--port <port>","Service port").option("--config <path>","Custom configuration file path").option("--timeout <ms>","\u505C\u6B62\u65E7\u8FDB\u7A0B\u7B49\u5F85\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStopTimeout}`).option("--start-timeout <ms>","\u542F\u52A8\u5065\u5EB7\u68C0\u67E5\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStartTimeout}`).action(y(se)),d.command("stop").description("Stop service").option("--force","Force stop").option("--timeout <ms>","\u505C\u6B62\u670D\u52A1\u7B49\u5F85\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStopTimeout}`).action(y(re)),d.command("restart").allowUnknownOption().description("Restart service").option("--env <environment>","\u73AF\u5883: development|production|test","production").option("--port <port>","Service port").option("--config <path>","Custom configuration file path").option("--timeout <ms>","\u505C\u6B62\u65E7\u8FDB\u7A0B\u7B49\u5F85\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStopTimeout}`).option("--start-timeout <ms>","\u542F\u52A8\u5065\u5EB7\u68C0\u67E5\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStartTimeout}`).action(y(ne)),d.command("status").description("View service status").action(y(ie)),d.command("help").description("Display help information").action(()=>{d.outputHelp()}),d.parse(process.argv),process.argv.slice(2).length||(d.outputHelp(),process.exitCode=0)}ce();
2
+ import{Command as ee}from"commander";import{createRequire as te}from"module";import f from"path";import U from"os";import c from"fs-extra";import{spawn as E}from"cross-spawn";import V from"tree-kill";import{fileURLToPath as q}from"url";import{execFileSync as R}from"child_process";import M from"http";import{createRequire as G}from"module";var I=f.dirname(q(import.meta.url)),H=G(import.meta.url),n={name:"nuwax-file-server",pidDir:f.join(U.tmpdir(),"nuwax-file-server"),pidFileName:"server.pid",lockFileName:"start.lock",defaultStopTimeout:3e4,defaultStartTimeout:3e4,staleLockTimeout:12e4,checkInterval:500};function $(){return f.join(n.pidDir,n.pidFileName)}function F(){return f.join(n.pidDir,n.lockFileName)}function J(){let e=[f.join(I,"..","server.js"),f.join(I,"server.js")];for(let t of e)if(c.existsSync(t))return t;return e[0]}function W(){let e=["../../package.json","../package.json"];for(let t of e)try{let o=H(t);if(o?.version)return o.version}catch{}return"unknown"}function x(){try{let e=$();if(!c.existsSync(e))return null;let t=c.readFileSync(e,"utf8"),o=JSON.parse(t);return!o||typeof o.pid!="number"?null:o}catch(e){return e.code!=="ENOENT"&&console.error(`Read PID file failed: ${e.message}`),null}}function B(e){try{let t=$();c.ensureDirSync(n.pidDir),c.writeFileSync(t,JSON.stringify(e,null,2)),console.debug(`PID file written: ${t}`)}catch(t){throw console.error(`Write PID file failed: ${t.message}`),t}}function v(){try{let e=$();c.existsSync(e)&&(c.removeSync(e),console.debug(`PID file deleted: ${e}`))}catch(e){console.error(`Delete PID file failed: ${e.message}`)}}function g(e){try{return process.kill(e,0),!0}catch(t){return t.code!=="ESRCH"}}function K(e){try{return k()?R("powershell",["-NoProfile","-Command",`(Get-CimInstance Win32_Process -Filter "ProcessId = ${e}").CommandLine`],{encoding:"utf8",stdio:["ignore","pipe","ignore"]}).trim():R("ps",["-p",String(e),"-o","command="],{encoding:"utf8",stdio:["ignore","pipe","ignore"]}).trim()}catch{return""}}function X(e){if(!g(e))return!1;let t=K(e);if(!t)return!1;let o=t.toLowerCase(),s=f.basename(f.join(I,"..","server.js")).toLowerCase(),i=n.name.toLowerCase();return o.includes(s)||o.includes(i)}function z(){c.ensureDirSync(n.pidDir);let e=F(),t=`${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2,10)}`,o=JSON.stringify({pid:process.pid,token:t,createdAt:new Date().toISOString()},null,2);try{let s=c.openSync(e,"wx");return c.writeSync(s,o),{fd:s,token:t}}catch(s){if(s.code!=="EEXIST")throw s;try{let i=c.readFileSync(e,"utf8"),r=JSON.parse(i),a=Number(r?.pid),u=r?.createdAt?new Date(r.createdAt).getTime():0,l=Date.now()-u,C=Number.isFinite(a)&&a>0&&g(a),p=!u||Number.isNaN(l)||l>n.staleLockTimeout;if(!C||p){c.removeSync(e);let S=c.openSync(e,"wx");return c.writeSync(S,o),console.warn("Detected stale start lock, auto cleaned"),{fd:S,token:t}}}catch{c.removeSync(e);let r=c.openSync(e,"wx");return c.writeSync(r,o),console.warn("Detected invalid start lock, auto cleaned"),{fd:r,token:t}}throw s}}function Q(e){let t=e?.fd,o=e?.token;try{t!=null&&c.closeSync(t)}catch{}try{let s=F();if(c.existsSync(s)){let i=!1;if(!o)i=!0;else try{let r=c.readFileSync(s,"utf8");i=JSON.parse(r)?.token===o}catch{i=!0}i&&c.removeSync(s)}}catch{}}function k(){return process.platform==="win32"}async function Y(e=n.defaultStopTimeout){let t=x();return t?X(t.pid)?(console.log(`Existing service process detected (PID: ${t.pid}), stopping before start...`),await h(t.pid,!1)&&await D(t.pid,e)?(v(),{success:!0,message:`Existing process ${t.pid} stopped gracefully`}):(console.warn(`Graceful stop timeout or failed for PID ${t.pid}, force stop...`),await h(t.pid,!0)?await D(t.pid,e)?(v(),{success:!0,message:`Existing process ${t.pid} stopped forcibly`}):{success:!1,message:`Existing process ${t.pid} did not exit after force stop`}:{success:!1,message:`Failed to stop existing process ${t.pid}`})):(console.log(`Found stale PID file (PID: ${t.pid}), clean it...`),v(),{success:!0,message:"Stale PID file cleaned"}):{success:!0,message:"No existing service process"}}async function h(e,t=!1){return new Promise(o=>{if(!g(e)){console.debug(`Process ${e} does not exist, already stopped`),o(!0);return}let s=t?"SIGKILL":"SIGTERM",i=k()?"taskkill":"tree-kill";if(console.debug(`Use ${i} to stop process ${e} (signal: ${s})`),k()){let r=t?["/F","/PID",String(e)]:["/PID",String(e)],a=E("taskkill",r,{stdio:["ignore","pipe","pipe"],windowsHide:!0}),u="";a.stdout.on("data",l=>{u+=l.toString()}),a.stderr.on("data",l=>{u+=l.toString()}),a.on("error",l=>{console.error(`Stop process failed: ${l.message}`),o(!1)}),a.on("close",l=>{l===0?(console.debug(`Process ${e} stopped`),o(!0)):(console.warn(`taskkill exit code: ${l}, output: ${u}`),t?o(!1):h(e,!0).then(o))})}else V(e,s,r=>{r?r.code==="ESRCH"?(console.debug(`Process ${e} does not exist`),o(!0)):(console.error(`Stop process failed: ${r.message}`),o(!1)):(console.debug(`Process ${e} stopped`),o(!0))})})}async function D(e,t=n.defaultStopTimeout){let o=Date.now();for(;g(e);){if(Date.now()-o>t)return console.warn(`Wait for process ${e} to stop timeout (${t}ms)`),!1;await new Promise(i=>setTimeout(i,n.checkInterval))}let s=Date.now()-o;return console.debug(`Process ${e} stopped after ${s}ms`),!0}async function Z(e,t=n.defaultStartTimeout){let o=Number(e);if(!Number.isFinite(o)||o<=0)return!1;let s=Date.now();for(;Date.now()-s<=t;){if(await new Promise(r=>{let a=M.get({host:"127.0.0.1",port:o,path:"/health",timeout:Math.min(2e3,n.checkInterval*4)},u=>{r(u.statusCode>=200&&u.statusCode<300),u.resume()});a.on("timeout",()=>{a.destroy(),r(!1)}),a.on("error",()=>r(!1))}))return!0;await new Promise(r=>setTimeout(r,n.checkInterval))}return!1}async function N(e={}){let{env:t,port:o,config:s}=e,i=null;console.log(`Start service ${n.name}...`);try{i=z()}catch(r){return r.code==="EEXIST"?{success:!1,pid:null,message:"Another start operation is in progress, please retry later"}:{success:!1,pid:null,message:`Acquire start lock failed: ${r.message}`}}try{let r=Number(e.timeout)||n.defaultStopTimeout,a=await Y(r);if(!a.success)return{success:!1,pid:null,message:`Service start blocked: ${a.message}`};let u={...process.env};t&&(u.NODE_ENV=t,console.log(`Environment: ${t}`)),o&&(u.PORT=o,console.log(`Port: ${o}`)),s&&(u.CONFIG_FILE=s,console.log(`Configuration file: ${s}`));let l=J(),p=E("node",[l,...[]],{env:u,stdio:["pipe","pipe","pipe"],detached:!0,cwd:process.cwd()});if(p.stdout.on("data",m=>{process.stdout.write(m)}),p.stderr.on("data",m=>{process.stderr.write(m)}),p.on("error",m=>{console.error(`Start service failed: ${m.message}`)}),await new Promise(m=>setTimeout(m,2e3)),!g(p.pid))return{success:!1,pid:null,message:"Service start failed"};let S={pid:p.pid,startedAt:new Date().toISOString(),env:t||process.env.NODE_ENV||"production",port:o||process.env.PORT||"60000",version:W(),platform:process.platform};B(S);let P=Number(e.startTimeout)||n.defaultStartTimeout;return await Z(S.port,P)?(p.unref(),console.log(`Service started (PID: ${p.pid})`),console.log(`Service address: http://localhost:${S.port}`),{success:!0,pid:p.pid,message:"Service started successfully"}):(console.error(`Service health check timeout (${P}ms), stop failed instance...`),await h(p.pid,!0),v(),{success:!1,pid:null,message:`Service health check timeout (${P}ms)`})}finally{Q(i)}}async function T(e={}){let{force:t=!1,timeout:o=n.defaultStopTimeout}=e;console.log(`Stop service ${n.name}...`);let s=x();if(!s)return{success:!1,message:"Service not found"};if(!g(s.pid))return console.log("Service process has stopped, clean PID file..."),v(),{success:!0,message:"Service has stopped (process has exited)"};if(!await h(s.pid,t))return{success:!1,message:"Stop service failed"};let r=await D(s.pid,o);return v(),r?{success:!0,message:"Service has stopped"}:{success:!1,message:"Service stop timeout"}}async function O(e={}){console.log(`Restart service ${n.name}...`);let t=await T(e);!t.success&&t.message!=="Service not found"&&console.warn(`Stop service failed: ${t.message}`),await new Promise(s=>setTimeout(s,2e3));let o=await N(e);return o.success?{success:!0,pid:o.pid,message:"Service has restarted"}:{success:!1,pid:null,message:`Restart failed: ${o.message}`}}function _(){let e=x();return e?g(e.pid)?{running:!0,pidInfo:e,message:"Service running"}:{running:!1,pidInfo:e,message:"Service process does not exist"}:{running:!1,pidInfo:null,message:"Service not running"}}function L(e){try{let t=new Date(e),o=new Date;if(isNaN(t.getTime()))return"unknown";let s=Math.floor((o-t)/1e3),i=Math.floor(s/3600),r=Math.floor(s%3600/60),a=s%60;return i>0?`${i} hours ${r} minutes ${a} seconds`:r>0?`${r} minutes ${a} seconds`:`${a} seconds`}catch{return"unknown"}}var Pe=te(import.meta.url),A="1.4.5",d=new ee,w={reset:"\x1B[0m",red:"\x1B[31m",green:"\x1B[32m",yellow:"\x1B[33m",blue:"\x1B[34m",cyan:"\x1B[36m"};function j(e){console.error(`${w.red}ERROR: ${e}${w.reset}`)}function b(e){console.log(`${w.green}${e}${w.reset}`)}function oe(e){console.log(`${w.blue}${e}${w.reset}`)}function y(e){return async(...t)=>{try{let o=await e(...t);o&&o.success===!1?(j(o.message||"Command failed"),process.exitCode=1):process.exitCode=0}catch(o){j(o?.message||"Command failed with unexpected error"),process.exitCode=1}}}async function se(e){let t=await N({env:e.env,port:e.port,config:e.config,timeout:Number(e.timeout),startTimeout:Number(e.startTimeout)});return t.success&&b(`Service started (PID: ${t.pid})`),t}async function re(e){let t=await T({force:e.force,timeout:Number(e.timeout)});return t.success&&b(t.message||"Service stopped"),t}async function ne(e){let t=await O({env:e.env,port:e.port,config:e.config,timeout:Number(e.timeout),startTimeout:Number(e.startTimeout)});return t.success&&b(t.message||"Service restarted"),t}function ie(){let e=_();return oe(`${n.name} service status:`),console.log(""),console.log(` Service name: ${n.name}`),console.log(` Running status: ${e.running?"Running":"Stopped"}`),console.log(` Message: ${e.message}`),console.log(` PID file: ${$()}`),e.pidInfo&&(console.log(` Process ID: ${e.pidInfo.pid}`),console.log(` Environment: ${e.pidInfo.env||"Unknown"}`),console.log(` Port: ${e.pidInfo.port||"Unknown"}`),console.log(` Version: ${e.pidInfo.version||A}`),console.log(` Platform: ${e.pidInfo.platform||process.platform}`),console.log(` Started at: ${e.pidInfo.startedAt||"Unknown"}`),console.log(` Uptime: ${L(e.pidInfo.startedAt)}`)),console.log(""),{success:e.running,message:e.message}}function ce(){d.name("nuwax-file-server").description("Cross-platform file service deployment tool, supporting start/stop/restart/status").version(A,"-v, --version","Display version number").helpOption("-h, --help","Display help information"),d.command("start").allowUnknownOption().description("Start service").option("--env <environment>","\u73AF\u5883: development|production|test","production").option("--port <port>","Service port").option("--config <path>","Custom configuration file path").option("--timeout <ms>","\u505C\u6B62\u65E7\u8FDB\u7A0B\u7B49\u5F85\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStopTimeout}`).option("--start-timeout <ms>","\u542F\u52A8\u5065\u5EB7\u68C0\u67E5\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStartTimeout}`).action(y(se)),d.command("stop").description("Stop service").option("--force","Force stop").option("--timeout <ms>","\u505C\u6B62\u670D\u52A1\u7B49\u5F85\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStopTimeout}`).action(y(re)),d.command("restart").allowUnknownOption().description("Restart service").option("--env <environment>","\u73AF\u5883: development|production|test","production").option("--port <port>","Service port").option("--config <path>","Custom configuration file path").option("--timeout <ms>","\u505C\u6B62\u65E7\u8FDB\u7A0B\u7B49\u5F85\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStopTimeout}`).option("--start-timeout <ms>","\u542F\u52A8\u5065\u5EB7\u68C0\u67E5\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStartTimeout}`).action(y(ne)),d.command("status").description("View service status").action(y(ie)),d.command("help").description("Display help information").action(()=>{d.outputHelp()}),d.parse(process.argv),process.argv.slice(2).length||(d.outputHelp(),process.exitCode=0)}ce();
@@ -1 +1 @@
1
- import l from"express";import{asyncHandler as c,ValidationError as p}from"../utils/error/errorHandler.js";import d from"../service/gitService.js";import{log as i}from"../utils/log/logUtils.js";import{extractIsolationContext as I}from"../utils/common/projectPathUtils.js";const u=l.Router();function m(a){const{workspaceType:o,projectId:t,userId:e,cId:s}=a||{},r=I(a||{});return{workspaceType:o?String(o):void 0,projectId:t?String(t):void 0,userId:e?String(e):void 0,cId:s?String(s):void 0,isolationContext:r}}const y=[{path:"/init",method:"post",handler:c(async(a,o)=>{const t=m(a.body);i(t.projectId||`computer:${t.userId}:${t.cId}`,"INFO","Git init",t);const e=await d.init(t);o.status(200).json(e)})},{path:"/status",method:"get",handler:c(async(a,o)=>{const t=m(a.query);i(t.projectId||`computer:${t.userId}:${t.cId}`,"INFO","Git status",t);const e=await d.status(t);o.status(200).json(e)})},{path:"/commit",method:"post",handler:c(async(a,o)=>{const{message:t,files:e,authorName:s,authorEmail:r}=a.body||{},n={...m(a.body),message:t,files:e,authorName:s,authorEmail:r};if(!t)throw new p("Commit message cannot be empty",{field:"message"});i(n.projectId||`computer:${n.userId}:${n.cId}`,"INFO","Git commit",{...n,filesCount:e?e.length:"all"});const h=await d.commit(n);o.status(200).json(h)})},{path:"/add",method:"post",handler:c(async(a,o)=>{const{files:t}=a.body||{},e={...m(a.body),files:t};i(e.projectId||`computer:${e.userId}:${e.cId}`,"INFO","Git add",{...e,filesCount:t?t.length:"all"});const s=await d.add(e);o.status(200).json(s)})},{path:"/unstage",method:"post",handler:c(async(a,o)=>{const{files:t}=a.body||{},e={...m(a.body),files:t};i(e.projectId||`computer:${e.userId}:${e.cId}`,"INFO","Git unstage",{...e,filesCount:t?t.length:"all"});const s=await d.unstage(e);o.status(200).json(s)})},{path:"/discard",method:"post",handler:c(async(a,o)=>{const{files:t}=a.body||{},e={...m(a.body),files:t};i(e.projectId||`computer:${e.userId}:${e.cId}`,"INFO","Git discard",{...e,filesCount:t?t.length:"all"});const s=await d.discard(e);o.status(200).json(s)})},{path:"/log",method:"get",handler:c(async(a,o)=>{const{maxCount:t,branch:e,skip:s,filePath:r}=a.query||{},n={...m(a.query),maxCount:t?parseInt(t,10):50,skip:s?parseInt(s,10):0,branch:e,filePath:r};i(n.projectId||`computer:${n.userId}:${n.cId}`,"INFO","Git log",n);const h=await d.logHistory(n);o.status(200).json(h)})},{path:"/diff",method:"post",handler:c(async(a,o)=>{const{from:t,to:e,paths:s,source:r}=a.body||{},n={...m(a.body),from:t,to:e,paths:s,source:r};i(n.projectId||`computer:${n.userId}:${n.cId}`,"INFO","Git diff",{from:t,to:e});const h=await d.diff(n);o.status(200).json(h)})},{path:"/file-content",method:"post",handler:c(async(a,o)=>{const{ref:t,filePath:e}=a.body||{},s={...m(a.body),ref:t,filePath:e};i(s.projectId||`computer:${s.userId}:${s.cId}`,"INFO","Git file content",{ref:t,filePath:e});const r=await d.fileContent(s);o.status(200).json(r)})},{path:"/reset",method:"post",handler:c(async(a,o)=>{const{target:t,mode:e}=a.body||{},s={...m(a.body),target:t,mode:e||"mixed"};if(!t)throw new p("Reset target cannot be empty",{field:"target"});i(s.projectId||`computer:${s.userId}:${s.cId}`,"INFO","Git reset",{target:t,mode:e});const r=await d.reset(s);o.status(200).json(r)})},{path:"/revert",method:"post",handler:c(async(a,o)=>{const{target:t,message:e,authorName:s,authorEmail:r}=a.body||{},n={...m(a.body),target:t,message:e,authorName:s,authorEmail:r};if(!t)throw new p("Revert target cannot be empty",{field:"target"});i(n.projectId||`computer:${n.userId}:${n.cId}`,"INFO","Git revert",{target:t});const h=await d.revert(n);o.status(200).json(h)})},{path:"/checkout",method:"post",handler:c(async(a,o)=>{const{target:t}=a.body||{},e={...m(a.body),target:t};if(!t)throw new p("Checkout target cannot be empty",{field:"target"});i(e.projectId||`computer:${e.userId}:${e.cId}`,"INFO","Git checkout files",{target:t});const s=await d.checkout(e);o.status(200).json(s)})},{path:"/tags",method:"get",handler:c(async(a,o)=>{const t=m(a.query);i(t.projectId||`computer:${t.userId}:${t.cId}`,"INFO","Git list tags",t);const e=await d.listTags(t);o.status(200).json(e)})},{path:"/tag-create",method:"post",handler:c(async(a,o)=>{const{tagName:t,message:e}=a.body||{},s={...m(a.body),tagName:t,message:e};if(!t)throw new p("Tag name cannot be empty",{field:"tagName"});i(s.projectId||`computer:${s.userId}:${s.cId}`,"INFO","Git create tag",{tagName:t});const r=await d.createTag(s);o.status(200).json(r)})},{path:"/tag-delete",method:"post",handler:c(async(a,o)=>{const{tagName:t}=a.body||{},e={...m(a.body),tagName:t};if(!t)throw new p("Tag name cannot be empty",{field:"tagName"});i(e.projectId||`computer:${e.userId}:${e.cId}`,"INFO","Git delete tag",{tagName:t});const s=await d.deleteTag(e);o.status(200).json(s)})},{path:"/branches",method:"get",handler:c(async(a,o)=>{const t=m(a.query);i(t.projectId||`computer:${t.userId}:${t.cId}`,"INFO","Git list branches",t);const e=await d.listBranches(t);o.status(200).json(e)})},{path:"/branch-create",method:"post",handler:c(async(a,o)=>{const{branchName:t,startPoint:e}=a.body||{},s={...m(a.body),branchName:t,startPoint:e};if(!t)throw new p("Branch name cannot be empty",{field:"branchName"});i(s.projectId||`computer:${s.userId}:${s.cId}`,"INFO","Git create branch",{branchName:t,startPoint:e});const r=await d.createBranch(s);o.status(200).json(r)})},{path:"/branch-switch",method:"post",handler:c(async(a,o)=>{const{branchName:t}=a.body||{},e={...m(a.body),branchName:t};if(!t)throw new p("Branch name cannot be empty",{field:"branchName"});i(e.projectId||`computer:${e.userId}:${e.cId}`,"INFO","Git switch branch",{branchName:t});const s=await d.switchBranch(e);o.status(200).json(s)})},{path:"/branch-delete",method:"post",handler:c(async(a,o)=>{const{branchName:t,force:e}=a.body||{},s={...m(a.body),branchName:t,force:e===!0};if(!t)throw new p("Branch name cannot be empty",{field:"branchName"});i(s.projectId||`computer:${s.userId}:${s.cId}`,"INFO","Git delete branch",{branchName:t,force:e});const r=await d.deleteBranch(s);o.status(200).json(r)})}];y.forEach(a=>{a.middleware?u[a.method](a.path,a.middleware,a.handler):u[a.method](a.path,a.handler)});export default u;
1
+ import I from"express";import{asyncHandler as r,ValidationError as h}from"../utils/error/errorHandler.js";import d from"../service/gitService.js";import{log as i}from"../utils/log/logUtils.js";import{extractIsolationContext as y}from"../utils/common/projectPathUtils.js";import{resolveServiceContext as b}from"../utils/computer/workspaceContext.js";const l=I.Router();function m(t){const{workspaceType:o,projectId:e,userId:s,cId:a}=t||{},c=y(t||{});return{workspaceType:o?String(o):void 0,projectId:e?String(e):void 0,userId:s?String(s):void 0,cId:a?String(a):void 0,isolationContext:c}}function p(t){try{const o={headers:t.headers||{},body:t.body&&typeof t.body=="object"?t.body:{},query:t.query||{}};return b(o)}catch{return null}}const f=[{path:"/init",method:"post",handler:r(async(t,o)=>{const e={...m(t.body),serviceContext:p(t)};i(e.projectId||`computer:${e.userId}:${e.cId}`,"INFO","Git init",e);const s=await d.init(e);o.status(200).json(s)})},{path:"/status",method:"get",handler:r(async(t,o)=>{const e={...m(t.query),serviceContext:p(t)};i(e.projectId||`computer:${e.userId}:${e.cId}`,"INFO","Git status",e);const s=await d.status(e);o.status(200).json(s)})},{path:"/commit",method:"post",handler:r(async(t,o)=>{const{message:e,files:s,authorName:a,authorEmail:c}=t.body||{},n={...m(t.body),serviceContext:p(t),message:e,files:s,authorName:a,authorEmail:c};if(!e)throw new h("Commit message cannot be empty",{field:"message"});i(n.projectId||`computer:${n.userId}:${n.cId}`,"INFO","Git commit",{...n,filesCount:s?s.length:"all"});const u=await d.commit(n);o.status(200).json(u)})},{path:"/add",method:"post",handler:r(async(t,o)=>{const{files:e}=t.body||{},s={...m(t.body),serviceContext:p(t),files:e};i(s.projectId||`computer:${s.userId}:${s.cId}`,"INFO","Git add",{...s,filesCount:e?e.length:"all"});const a=await d.add(s);o.status(200).json(a)})},{path:"/unstage",method:"post",handler:r(async(t,o)=>{const{files:e}=t.body||{},s={...m(t.body),serviceContext:p(t),files:e};i(s.projectId||`computer:${s.userId}:${s.cId}`,"INFO","Git unstage",{...s,filesCount:e?e.length:"all"});const a=await d.unstage(s);o.status(200).json(a)})},{path:"/discard",method:"post",handler:r(async(t,o)=>{const{files:e}=t.body||{},s={...m(t.body),serviceContext:p(t),files:e};i(s.projectId||`computer:${s.userId}:${s.cId}`,"INFO","Git discard",{...s,filesCount:e?e.length:"all"});const a=await d.discard(s);o.status(200).json(a)})},{path:"/log",method:"get",handler:r(async(t,o)=>{const{maxCount:e,branch:s,skip:a,filePath:c}=t.query||{},n={...m(t.query),maxCount:e?parseInt(e,10):50,skip:a?parseInt(a,10):0,branch:s,filePath:c};i(n.projectId||`computer:${n.userId}:${n.cId}`,"INFO","Git log",n);const u=await d.logHistory(n);o.status(200).json(u)})},{path:"/diff",method:"post",handler:r(async(t,o)=>{const{from:e,to:s,paths:a,source:c}=t.body||{},n={...m(t.body),serviceContext:p(t),from:e,to:s,paths:a,source:c};i(n.projectId||`computer:${n.userId}:${n.cId}`,"INFO","Git diff",{from:e,to:s});const u=await d.diff(n);o.status(200).json(u)})},{path:"/file-content",method:"post",handler:r(async(t,o)=>{const{ref:e,filePath:s}=t.body||{},a={...m(t.body),serviceContext:p(t),ref:e,filePath:s};i(a.projectId||`computer:${a.userId}:${a.cId}`,"INFO","Git file content",{ref:e,filePath:s});const c=await d.fileContent(a);o.status(200).json(c)})},{path:"/reset",method:"post",handler:r(async(t,o)=>{const{target:e,mode:s}=t.body||{},a={...m(t.body),serviceContext:p(t),target:e,mode:s||"mixed"};if(!e)throw new h("Reset target cannot be empty",{field:"target"});i(a.projectId||`computer:${a.userId}:${a.cId}`,"INFO","Git reset",{target:e,mode:s});const c=await d.reset(a);o.status(200).json(c)})},{path:"/revert",method:"post",handler:r(async(t,o)=>{const{target:e,message:s,authorName:a,authorEmail:c}=t.body||{},n={...m(t.body),serviceContext:p(t),target:e,message:s,authorName:a,authorEmail:c};if(!e)throw new h("Revert target cannot be empty",{field:"target"});i(n.projectId||`computer:${n.userId}:${n.cId}`,"INFO","Git revert",{target:e});const u=await d.revert(n);o.status(200).json(u)})},{path:"/checkout",method:"post",handler:r(async(t,o)=>{const{target:e}=t.body||{},s={...m(t.body),serviceContext:p(t),target:e};if(!e)throw new h("Checkout target cannot be empty",{field:"target"});i(s.projectId||`computer:${s.userId}:${s.cId}`,"INFO","Git checkout files",{target:e});const a=await d.checkout(s);o.status(200).json(a)})},{path:"/tags",method:"get",handler:r(async(t,o)=>{const e={...m(t.query),serviceContext:p(t)};i(e.projectId||`computer:${e.userId}:${e.cId}`,"INFO","Git list tags",e);const s=await d.listTags(e);o.status(200).json(s)})},{path:"/tag-create",method:"post",handler:r(async(t,o)=>{const{tagName:e,message:s}=t.body||{},a={...m(t.body),serviceContext:p(t),tagName:e,message:s};if(!e)throw new h("Tag name cannot be empty",{field:"tagName"});i(a.projectId||`computer:${a.userId}:${a.cId}`,"INFO","Git create tag",{tagName:e});const c=await d.createTag(a);o.status(200).json(c)})},{path:"/tag-delete",method:"post",handler:r(async(t,o)=>{const{tagName:e}=t.body||{},s={...m(t.body),serviceContext:p(t),tagName:e};if(!e)throw new h("Tag name cannot be empty",{field:"tagName"});i(s.projectId||`computer:${s.userId}:${s.cId}`,"INFO","Git delete tag",{tagName:e});const a=await d.deleteTag(s);o.status(200).json(a)})},{path:"/branches",method:"get",handler:r(async(t,o)=>{const e={...m(t.query),serviceContext:p(t)};i(e.projectId||`computer:${e.userId}:${e.cId}`,"INFO","Git list branches",e);const s=await d.listBranches(e);o.status(200).json(s)})},{path:"/branch-create",method:"post",handler:r(async(t,o)=>{const{branchName:e,startPoint:s}=t.body||{},a={...m(t.body),serviceContext:p(t),branchName:e,startPoint:s};if(!e)throw new h("Branch name cannot be empty",{field:"branchName"});i(a.projectId||`computer:${a.userId}:${a.cId}`,"INFO","Git create branch",{branchName:e,startPoint:s});const c=await d.createBranch(a);o.status(200).json(c)})},{path:"/branch-switch",method:"post",handler:r(async(t,o)=>{const{branchName:e}=t.body||{},s={...m(t.body),serviceContext:p(t),branchName:e};if(!e)throw new h("Branch name cannot be empty",{field:"branchName"});i(s.projectId||`computer:${s.userId}:${s.cId}`,"INFO","Git switch branch",{branchName:e});const a=await d.switchBranch(s);o.status(200).json(a)})},{path:"/branch-delete",method:"post",handler:r(async(t,o)=>{const{branchName:e,force:s}=t.body||{},a={...m(t.body),serviceContext:p(t),branchName:e,force:s===!0};if(!e)throw new h("Branch name cannot be empty",{field:"branchName"});i(a.projectId||`computer:${a.userId}:${a.cId}`,"INFO","Git delete branch",{branchName:e,force:s});const c=await d.deleteBranch(a);o.status(200).json(c)})}];f.forEach(t=>{t.middleware?l[t.method](t.path,t.middleware,t.handler):l[t.method](t.path,t.handler)});export default l;
@@ -1,8 +1,8 @@
1
- import v from"path";import i from"fs";import dt from"crypto";import l from"isomorphic-git";import{createPatch as ut}from"diff";import C from"../appConfig/index.js";import{log as g}from"../utils/log/logUtils.js";import{ValidationError as P,BusinessError as H,SystemError as A,ResourceError as W}from"../utils/error/errorHandler.js";import{resolveProjectPath as ht}from"../utils/common/projectPathUtils.js";import{isGitRepo as mt,ensureGitRepo as N,ensureGitignore as _,addAll as gt,commitAllChanges as wt,stageFiles as L,getDefaultAuthor as yt,hasGitHead as M}from"../utils/git/gitUtils.js";function k(o){const{workspaceType:t,projectId:s,isolationContext:e,userId:r,cId:a}=o||{};if(!t||!["pageApp","taskAgent"].includes(t))throw new P("workspaceType is required and must be pageApp or taskAgent",{field:"workspaceType"});if(t==="taskAgent"){if(!r||!a)throw new P("taskAgent mode requires userId and cId",{field:"userId/cId"});const c=v.join(C.COMPUTER_WORKSPACE_DIR,String(r),String(a));if(!i.existsSync(c))throw new W("Computer workspace does not exist",{userId:r,cId:a});return{targetPath:c,logId:`computer:${r}:${a}`}}if(!s)throw new P("pageApp mode requires projectId",{field:"projectId"});const n=ht(s,e||{});if(!i.existsSync(n))throw new W("Project does not exist",{projectId:s});return{targetPath:n,logId:s}}function q(o){for(let t=0;t<Math.min(o.length,8e3);t++)if(o[t]===0)return!0;return!1}function D(o){const t=Buffer.from(`blob ${o.length}\0`);return dt.createHash("sha1").update(Buffer.concat([t,o])).digest("hex")}function pt(o,t,s,e,r,a,n){const u=ut(o,e?t:"",r?s:"",e?`a/${o}`:"/dev/null",r?`b/${o}`:"/dev/null").split(`
2
- `).slice(4);u.length>0&&u[u.length-1]===""&&u.pop();const d=[];let h=0,m=0;for(const y of u){if(y.startsWith("@@")){const p=y.match(/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/);if(p){const S=p[1],F=p[2]!==void 0?parseInt(p[2],10):1,T=p[3],b=p[4]!==void 0?parseInt(p[4],10):1,$=F===1?S:`${S},${F}`,G=b===1?T:`${T},${b}`;d.push(`@@ -${$} +${G} @@`);continue}}y.startsWith("+")&&!y.startsWith("+++")?h++:y.startsWith("-")&&!y.startsWith("---")&&m++,d.push(y)}const f=e&&t!==""&&!t.endsWith(`
3
- `),I=r&&s!==""&&!s.endsWith(`
4
- `);if((f||I)&&d.length>0){let y=0;for(let b=0;b<d.length;b++)d[b].startsWith("@@")&&(y=b);let p=-1,S=-1,F=-1;for(let b=d.length-1;b>y;b--){const $=d[b];$.startsWith("-")&&!$.startsWith("---")&&p<0?p=b:$.startsWith("+")&&!$.startsWith("++")&&S<0?S=b:$.startsWith(" ")&&F<0&&(F=b)}const T=Math.max(p,S,F);if(T>=0){const b=d[T];if(b.startsWith(" "))(f||I)&&d.splice(T+1,0,"\");else if(b.startsWith("+")){let $=-1;for(let G=T-1;G>y;G--)if(d[G].startsWith("-")&&!d[G].startsWith("---")){$=G;break}$>=0&&f?(d.splice($+1,0,"\"),I&&d.splice(T+2,0,"\")):I&&d.splice(T+1,0,"\")}else b.startsWith("-")&&f&&d.splice(T+1,0,"\")}}const O=e&&a?D(a).substring(0,7):"0000000",x=r&&n?D(n).substring(0,7):"0000000",R=[];return R.push(`diff --git a/${o} b/${o}`),e?r?R.push(`index ${O}..${x} 100644`):(R.push("deleted file mode 100644"),R.push(`index ${O}..0000000`)):(R.push("new file mode 100644"),R.push(`index 0000000..${x}`)),R.push(`--- ${e?`a/${o}`:"/dev/null"}`),R.push(`+++ ${r?`b/${o}`:"/dev/null"}`),{diff:[...R,...d].join(`
1
+ import{resolveWorkspaceDir as dt}from"../utils/computer/workspaceContext.js";import P from"path";import n from"fs";import ut from"crypto";import l from"isomorphic-git";import{createPatch as ht}from"diff";import G from"../appConfig/index.js";import{log as w}from"../utils/log/logUtils.js";import{ValidationError as k,BusinessError as C,SystemError as A,ResourceError as _}from"../utils/error/errorHandler.js";import{resolveProjectPath as mt}from"../utils/common/projectPathUtils.js";import{isGitRepo as gt,ensureGitRepo as N,ensureGitignore as M,addAll as wt,commitAllChanges as yt,stageFiles as L,getDefaultAuthor as pt,hasGitHead as W}from"../utils/git/gitUtils.js";function v(o){const{workspaceType:t,projectId:s,isolationContext:e,userId:a,cId:r,serviceContext:i}=o||{};if(i&&(i.workspacePath||i.appId)){if(!a||!r)throw new k("serviceContext mode requires userId and cId",{field:"userId/cId"});const h=dt(i,a,r);if(!n.existsSync(h))throw new _("Workspace does not exist",{userId:a,cId:r,targetPath:h});return{targetPath:h,logId:`computer:${a}:${r}`}}if(!t||!["pageApp","taskAgent"].includes(t))throw new k("workspaceType is required and must be pageApp or taskAgent",{field:"workspaceType"});if(t==="taskAgent"){if(!a||!r)throw new k("taskAgent mode requires userId and cId",{field:"userId/cId"});const h=P.join(G.COMPUTER_WORKSPACE_DIR,String(a),String(r));if(!n.existsSync(h))throw new _("Computer workspace does not exist",{userId:a,cId:r});return{targetPath:h,logId:`computer:${a}:${r}`}}if(!s)throw new k("pageApp mode requires projectId",{field:"projectId"});const c=mt(s,e||{});if(!n.existsSync(c))throw new _("Project does not exist",{projectId:s});return{targetPath:c,logId:s}}function q(o){for(let t=0;t<Math.min(o.length,8e3);t++)if(o[t]===0)return!0;return!1}function D(o){const t=Buffer.from(`blob ${o.length}\0`);return ut.createHash("sha1").update(Buffer.concat([t,o])).digest("hex")}function Ft(o,t,s,e,a,r,i){const u=ht(o,e?t:"",a?s:"",e?`a/${o}`:"/dev/null",a?`b/${o}`:"/dev/null").split(`
2
+ `).slice(4);u.length>0&&u[u.length-1]===""&&u.pop();const d=[];let m=0,g=0;for(const y of u){if(y.startsWith("@@")){const p=y.match(/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/);if(p){const S=p[1],F=p[2]!==void 0?parseInt(p[2],10):1,T=p[3],b=p[4]!==void 0?parseInt(p[4],10):1,$=F===1?S:`${S},${F}`,H=b===1?T:`${T},${b}`;d.push(`@@ -${$} +${H} @@`);continue}}y.startsWith("+")&&!y.startsWith("+++")?m++:y.startsWith("-")&&!y.startsWith("---")&&g++,d.push(y)}const f=e&&t!==""&&!t.endsWith(`
3
+ `),E=a&&s!==""&&!s.endsWith(`
4
+ `);if((f||E)&&d.length>0){let y=0;for(let b=0;b<d.length;b++)d[b].startsWith("@@")&&(y=b);let p=-1,S=-1,F=-1;for(let b=d.length-1;b>y;b--){const $=d[b];$.startsWith("-")&&!$.startsWith("---")&&p<0?p=b:$.startsWith("+")&&!$.startsWith("++")&&S<0?S=b:$.startsWith(" ")&&F<0&&(F=b)}const T=Math.max(p,S,F);if(T>=0){const b=d[T];if(b.startsWith(" "))(f||E)&&d.splice(T+1,0,"\");else if(b.startsWith("+")){let $=-1;for(let H=T-1;H>y;H--)if(d[H].startsWith("-")&&!d[H].startsWith("---")){$=H;break}$>=0&&f?(d.splice($+1,0,"\"),E&&d.splice(T+2,0,"\")):E&&d.splice(T+1,0,"\")}else b.startsWith("-")&&f&&d.splice(T+1,0,"\")}}const O=e&&r?D(r).substring(0,7):"0000000",x=a&&i?D(i).substring(0,7):"0000000",R=[];return R.push(`diff --git a/${o} b/${o}`),e?a?R.push(`index ${O}..${x} 100644`):(R.push("deleted file mode 100644"),R.push(`index ${O}..0000000`)):(R.push("new file mode 100644"),R.push(`index 0000000..${x}`)),R.push(`--- ${e?`a/${o}`:"/dev/null"}`),R.push(`+++ ${a?`b/${o}`:"/dev/null"}`),{diff:[...R,...d].join(`
5
5
  `)+`
6
- `,insertions:h,deletions:m}}async function j(o){return l.resolveRef({fs:i,dir:o,ref:"HEAD"})}async function B(o,t,s){try{const{blob:e}=await l.readBlob({fs:i,dir:o,oid:t,filepath:s}),r=Buffer.from(e);return{content:r.toString("utf8"),buf:r}}catch{return null}}async function z(o={}){const{targetPath:t,logId:s}=k(o);if(mt(t))return{success:!0,message:"Git repository already initialized",logId:s,alreadyExists:!0};try{return await l.init({fs:i,dir:t,defaultBranch:"main"}),_(t),await l.setConfig({fs:i,dir:t,path:"user.name",value:C.GIT_DEFAULT_AUTHOR_NAME}),await l.setConfig({fs:i,dir:t,path:"user.email",value:C.GIT_DEFAULT_AUTHOR_EMAIL}),g(s,"INFO","Git repository initialized",{logId:s,targetPath:t}),{success:!0,message:"Git repository initialized successfully",logId:s,alreadyExists:!1}}catch(e){throw g(s,"ERROR","Failed to initialize Git repository",{logId:s,error:e.message}),new A("Failed to initialize Git repository",{originalError:e.message})}}async function K(o={}){const{targetPath:t,logId:s}=k(o);await N(t);try{const e=await l.currentBranch({fs:i,dir:t,fullname:!1}),r=await l.statusMatrix({fs:i,dir:t}),a=[],n=[],c=[],w=[],u=[];for(const[d,h,m,f]of r)h!==f&&a.push(d),h===0&&f!==0&&c.push(d),(h===1&&f===0||m===0&&f!==0)&&w.push(d),f!==0&&m!==0&&m!==f&&n.push(d),h===0&&f===0&&m!==0&&u.push(d);return{success:!0,logId:s,current:e,staged:[...new Set(a)],modified:[...new Set(n)],created:[...new Set(c)],deleted:[...new Set(w)],untracked:[...new Set(u)],conflicted:[],ahead:0,behind:0,tracking:null}}catch(e){throw g(s,"ERROR","Failed to get Git status",{logId:s,error:e.message}),new A("Failed to get Git status",{originalError:e.message})}}async function V(o={}){const{message:t,files:s,authorName:e,authorEmail:r}=o;if(!t||typeof t!="string")throw new P("Commit message cannot be empty",{field:"message"});const{targetPath:a,logId:n}=k(o);await N(a);try{const c={name:e||C.GIT_DEFAULT_AUTHOR_NAME,email:r||C.GIT_DEFAULT_AUTHOR_EMAIL};if(Array.isArray(s)&&s.length>0){const u={};if(await L(a,s,{cache:u,force:!0}),!(await l.statusMatrix({fs:i,dir:a,cache:u,filepaths:s})).some(([,,,f])=>f!==1))return{success:!0,message:"Nothing to commit",logId:n,nothingToCommit:!0};const m=await l.commit({fs:i,dir:a,message:t,author:c,cache:u});return g(n,"INFO","Git commit successful",{logId:n,commitHash:m,message:t}),{success:!0,message:"Commit successful",logId:n,commit:m,summary:{changes:1}}}const w=await wt(a,{message:t,authorName:c.name,authorEmail:c.email,cache:{}});return w.nothingToCommit?{success:!0,message:"Nothing to commit",logId:n,nothingToCommit:!0}:(g(n,"INFO","Git commit successful",{logId:n,commitHash:w.commitHash,message:t,viaNative:w.viaNative===!0}),{success:!0,message:"Commit successful",logId:n,commit:w.commitHash,summary:{changes:1}})}catch(c){throw g(n,"ERROR","Failed to commit",{logId:n,error:c.message}),new A("Failed to commit",{originalError:c.message})}}async function J(o={}){const{files:t}=o,{targetPath:s,logId:e}=k(o);await N(s);try{const r={};return Array.isArray(t)&&t.length>0?await L(s,t,{cache:r,force:!0}):await gt(s,{cache:r}),g(e,"INFO","Git add successful",{logId:e,filesCount:t?t.length:"all"}),{success:!0,message:"Files staged successfully",logId:e}}catch(r){throw g(e,"ERROR","Failed to add files",{logId:e,error:r.message}),new A("Failed to add files",{originalError:r.message})}}async function Q(o={}){const{files:t}=o,{targetPath:s,logId:e}=k(o);await N(s);try{if(Array.isArray(t)&&t.length>0){for(const n of t)await l.resetIndex({fs:i,dir:s,filepath:n});return g(e,"INFO","Git unstage specified files",{logId:e,files:t}),{success:!0,message:"Specified files unstaged successfully",logId:e,files:t}}const a=(await l.statusMatrix({fs:i,dir:s})).filter(([,,,n])=>n!==1).map(([n])=>n);for(const n of a)await l.resetIndex({fs:i,dir:s,filepath:n});return g(e,"INFO","Git unstage all files",{logId:e}),{success:!0,message:"All files unstaged successfully",logId:e,files:"all"}}catch(r){throw g(e,"ERROR","Failed to unstage",{logId:e,error:r.message}),new A("Failed to unstage files",{originalError:r.message})}}async function U(o,t){const s=new Set;for(const r of t){let a=v.dirname(r);for(;a&&a!==".";)s.add(a),a=v.dirname(a)}const e=[...s].sort((r,a)=>{const n=r.split(/[/\\]/).length;return a.split(/[/\\]/).length-n});for(const r of e){const a=v.join(o,r);try{(await i.promises.readdir(a)).length===0&&await i.promises.rmdir(a)}catch{}}}async function X(o={}){const{files:t}=o,{targetPath:s,logId:e}=k(o);await N(s);try{const r=await l.statusMatrix({fs:i,dir:s}),a=Array.isArray(t)&&t.length>0?new Set(t):null,n=[],c=[],w=[];for(const[d,h,m,f]of r)a&&!a.has(d)||(h!==0?(f!==1||m!==1)&&n.push(d):h===0&&f!==0?c.push(d):h===0&&f===0&&m!==0&&w.push(d));if(n.length>0){const d=await j(s);for(const h of n){const m=await B(s,d,h);if(!m)continue;const f=v.join(s,h);i.mkdirSync(v.dirname(f),{recursive:!0}),i.writeFileSync(f,m.buf),await l.add({fs:i,dir:s,filepath:h})}}for(const d of c){try{await l.remove({fs:i,dir:s,filepath:d})}catch{}const h=v.join(s,d);i.existsSync(h)&&await i.promises.unlink(h)}c.length>0&&await U(s,c);for(const d of w){const h=v.join(s,d);i.existsSync(h)&&await i.promises.unlink(h)}w.length>0&&await U(s,w);const u=n.length+c.length+w.length;return g(e,"INFO","Git discard",{logId:e,trackedFiles:n.length,newFiles:c.length,untrackedFiles:w.length}),{success:!0,message:"Files discarded successfully",logId:e,discardedCount:u,trackedFiles:n,newFiles:c,untrackedFiles:w}}catch(r){throw g(e,"ERROR","Failed to discard",{logId:e,error:r.message}),new A("Failed to discard files",{originalError:r.message})}}async function Y(o={}){const{maxCount:t=50,branch:s,skip:e=0,filePath:r}=o,{targetPath:a,logId:n}=k(o);await N(a);try{if(!await M(a))return{success:!0,logId:n,commits:[],total:0};const c=Math.min(Math.max(1,t),500),w=Math.max(0,e),u={fs:i,dir:a,depth:c+w};s&&(u.ref=s),r&&(u.filepath=r);const m=(await l.log(u)).slice(w,w+c).map(f=>({hash:f.oid,date:new Date(f.commit.author.timestamp*1e3).toISOString(),message:f.commit.message,author_name:f.commit.author.name,author_email:f.commit.author.email}));return{success:!0,logId:n,commits:m,total:m.length}}catch(c){if(c?.code==="NotFoundError"||/NotFoundError|Could not find|unable to resolve|does not have any commits/i.test(String(c?.message||"")))return{success:!0,logId:n,commits:[],total:0};throw g(n,"ERROR","Failed to get Git log",{logId:n,error:c.message}),new A("Failed to get Git log",{originalError:c.message})}}async function Z(o={}){const{source:t="worktree",from:s,to:e,paths:r}=o,{targetPath:a,logId:n}=k(o);await N(a);try{const c=Array.isArray(r)&&r.length>0?new Set(r):null;let w="";const u=[];let d=0,h=0;async function m(f,I,O){const x=!!I,R=!!O;if(x&&R&&I.content===O.content)return;let E=!1;if(x&&q(I.buf)&&(E=!0),R&&!E&&q(O.buf)&&(E=!0),E){const F=x?D(I.buf).substring(0,7):"0000000",T=R?D(O.buf).substring(0,7):"0000000",b=[`diff --git a/${f} b/${f}`];x?R?b.push(`index ${F}..${T} 100644`):(b.push("deleted file mode 100644"),b.push(`index ${F}..0000000`)):(b.push("new file mode 100644"),b.push(`index 0000000..${T}`)),b.push(`Binary files ${x?`a/${f}`:"/dev/null"} and ${R?`b/${f}`:"/dev/null"} differ`),w+=b.join(`
6
+ `,insertions:m,deletions:g}}async function j(o){return l.resolveRef({fs:n,dir:o,ref:"HEAD"})}async function B(o,t,s){try{const{blob:e}=await l.readBlob({fs:n,dir:o,oid:t,filepath:s}),a=Buffer.from(e);return{content:a.toString("utf8"),buf:a}}catch{return null}}async function z(o={}){const{targetPath:t,logId:s}=v(o);if(gt(t))return{success:!0,message:"Git repository already initialized",logId:s,alreadyExists:!0};try{return await l.init({fs:n,dir:t,defaultBranch:"main"}),M(t),await l.setConfig({fs:n,dir:t,path:"user.name",value:G.GIT_DEFAULT_AUTHOR_NAME}),await l.setConfig({fs:n,dir:t,path:"user.email",value:G.GIT_DEFAULT_AUTHOR_EMAIL}),w(s,"INFO","Git repository initialized",{logId:s,targetPath:t}),{success:!0,message:"Git repository initialized successfully",logId:s,alreadyExists:!1}}catch(e){throw w(s,"ERROR","Failed to initialize Git repository",{logId:s,error:e.message}),new A("Failed to initialize Git repository",{originalError:e.message})}}async function K(o={}){const{targetPath:t,logId:s}=v(o);await N(t);try{const e=await l.currentBranch({fs:n,dir:t,fullname:!1}),a=await l.statusMatrix({fs:n,dir:t}),r=[],i=[],c=[],h=[],u=[];for(const[d,m,g,f]of a)m!==f&&r.push(d),m===0&&f!==0&&c.push(d),(m===1&&f===0||g===0&&f!==0)&&h.push(d),f!==0&&g!==0&&g!==f&&i.push(d),m===0&&f===0&&g!==0&&u.push(d);return{success:!0,logId:s,current:e,staged:[...new Set(r)],modified:[...new Set(i)],created:[...new Set(c)],deleted:[...new Set(h)],untracked:[...new Set(u)],conflicted:[],ahead:0,behind:0,tracking:null}}catch(e){throw w(s,"ERROR","Failed to get Git status",{logId:s,error:e.message}),new A("Failed to get Git status",{originalError:e.message})}}async function V(o={}){const{message:t,files:s,authorName:e,authorEmail:a}=o;if(!t||typeof t!="string")throw new k("Commit message cannot be empty",{field:"message"});const{targetPath:r,logId:i}=v(o);await N(r);try{const c={name:e||G.GIT_DEFAULT_AUTHOR_NAME,email:a||G.GIT_DEFAULT_AUTHOR_EMAIL};if(Array.isArray(s)&&s.length>0){const u={};if(await L(r,s,{cache:u,force:!0}),!(await l.statusMatrix({fs:n,dir:r,cache:u,filepaths:s})).some(([,,,f])=>f!==1))return{success:!0,message:"Nothing to commit",logId:i,nothingToCommit:!0};const g=await l.commit({fs:n,dir:r,message:t,author:c,cache:u});return w(i,"INFO","Git commit successful",{logId:i,commitHash:g,message:t}),{success:!0,message:"Commit successful",logId:i,commit:g,summary:{changes:1}}}const h=await yt(r,{message:t,authorName:c.name,authorEmail:c.email,cache:{}});return h.nothingToCommit?{success:!0,message:"Nothing to commit",logId:i,nothingToCommit:!0}:(w(i,"INFO","Git commit successful",{logId:i,commitHash:h.commitHash,message:t,viaNative:h.viaNative===!0}),{success:!0,message:"Commit successful",logId:i,commit:h.commitHash,summary:{changes:1}})}catch(c){throw w(i,"ERROR","Failed to commit",{logId:i,error:c.message}),new A("Failed to commit",{originalError:c.message})}}async function J(o={}){const{files:t}=o,{targetPath:s,logId:e}=v(o);await N(s);try{const a={};return Array.isArray(t)&&t.length>0?await L(s,t,{cache:a,force:!0}):await wt(s,{cache:a}),w(e,"INFO","Git add successful",{logId:e,filesCount:t?t.length:"all"}),{success:!0,message:"Files staged successfully",logId:e}}catch(a){throw w(e,"ERROR","Failed to add files",{logId:e,error:a.message}),new A("Failed to add files",{originalError:a.message})}}async function Q(o={}){const{files:t}=o,{targetPath:s,logId:e}=v(o);await N(s);try{if(Array.isArray(t)&&t.length>0){for(const i of t)await l.resetIndex({fs:n,dir:s,filepath:i});return w(e,"INFO","Git unstage specified files",{logId:e,files:t}),{success:!0,message:"Specified files unstaged successfully",logId:e,files:t}}const r=(await l.statusMatrix({fs:n,dir:s})).filter(([,,,i])=>i!==1).map(([i])=>i);for(const i of r)await l.resetIndex({fs:n,dir:s,filepath:i});return w(e,"INFO","Git unstage all files",{logId:e}),{success:!0,message:"All files unstaged successfully",logId:e,files:"all"}}catch(a){throw w(e,"ERROR","Failed to unstage",{logId:e,error:a.message}),new A("Failed to unstage files",{originalError:a.message})}}async function U(o,t){const s=new Set;for(const a of t){let r=P.dirname(a);for(;r&&r!==".";)s.add(r),r=P.dirname(r)}const e=[...s].sort((a,r)=>{const i=a.split(/[/\\]/).length;return r.split(/[/\\]/).length-i});for(const a of e){const r=P.join(o,a);try{(await n.promises.readdir(r)).length===0&&await n.promises.rmdir(r)}catch{}}}async function X(o={}){const{files:t}=o,{targetPath:s,logId:e}=v(o);await N(s);try{const a=await l.statusMatrix({fs:n,dir:s}),r=Array.isArray(t)&&t.length>0?new Set(t):null,i=[],c=[],h=[];for(const[d,m,g,f]of a)r&&!r.has(d)||(m!==0?(f!==1||g!==1)&&i.push(d):m===0&&f!==0?c.push(d):m===0&&f===0&&g!==0&&h.push(d));if(i.length>0){const d=await j(s);for(const m of i){const g=await B(s,d,m);if(!g)continue;const f=P.join(s,m);n.mkdirSync(P.dirname(f),{recursive:!0}),n.writeFileSync(f,g.buf),await l.add({fs:n,dir:s,filepath:m})}}for(const d of c){try{await l.remove({fs:n,dir:s,filepath:d})}catch{}const m=P.join(s,d);n.existsSync(m)&&await n.promises.unlink(m)}c.length>0&&await U(s,c);for(const d of h){const m=P.join(s,d);n.existsSync(m)&&await n.promises.unlink(m)}h.length>0&&await U(s,h);const u=i.length+c.length+h.length;return w(e,"INFO","Git discard",{logId:e,trackedFiles:i.length,newFiles:c.length,untrackedFiles:h.length}),{success:!0,message:"Files discarded successfully",logId:e,discardedCount:u,trackedFiles:i,newFiles:c,untrackedFiles:h}}catch(a){throw w(e,"ERROR","Failed to discard",{logId:e,error:a.message}),new A("Failed to discard files",{originalError:a.message})}}async function Y(o={}){const{maxCount:t=50,branch:s,skip:e=0,filePath:a}=o,{targetPath:r,logId:i}=v(o);await N(r);try{if(!await W(r))return{success:!0,logId:i,commits:[],total:0};const c=Math.min(Math.max(1,t),500),h=Math.max(0,e),u={fs:n,dir:r,depth:c+h};s&&(u.ref=s),a&&(u.filepath=a);const g=(await l.log(u)).slice(h,h+c).map(f=>({hash:f.oid,date:new Date(f.commit.author.timestamp*1e3).toISOString(),message:f.commit.message,author_name:f.commit.author.name,author_email:f.commit.author.email}));return{success:!0,logId:i,commits:g,total:g.length}}catch(c){if(c?.code==="NotFoundError"||/NotFoundError|Could not find|unable to resolve|does not have any commits/i.test(String(c?.message||"")))return{success:!0,logId:i,commits:[],total:0};throw w(i,"ERROR","Failed to get Git log",{logId:i,error:c.message}),new A("Failed to get Git log",{originalError:c.message})}}async function Z(o={}){const{source:t="worktree",from:s,to:e,paths:a}=o,{targetPath:r,logId:i}=v(o);await N(r);try{const c=Array.isArray(a)&&a.length>0?new Set(a):null;let h="";const u=[];let d=0,m=0;async function g(f,E,O){const x=!!E,R=!!O;if(x&&R&&E.content===O.content)return;let I=!1;if(x&&q(E.buf)&&(I=!0),R&&!I&&q(O.buf)&&(I=!0),I){const F=x?D(E.buf).substring(0,7):"0000000",T=R?D(O.buf).substring(0,7):"0000000",b=[`diff --git a/${f} b/${f}`];x?R?b.push(`index ${F}..${T} 100644`):(b.push("deleted file mode 100644"),b.push(`index ${F}..0000000`)):(b.push("new file mode 100644"),b.push(`index 0000000..${T}`)),b.push(`Binary files ${x?`a/${f}`:"/dev/null"} and ${R?`b/${f}`:"/dev/null"} differ`),h+=b.join(`
7
7
  `)+`
8
- `,u.push({file:f,changes:0,insertions:0,deletions:0,binary:!0});return}const{diff:y,insertions:p,deletions:S}=pt(f,x?I.content:"",R?O.content:"",x,R,x?I.buf:null,R?O.buf:null);w+=y,u.push({file:f,changes:p+S,insertions:p,deletions:S,binary:!1}),d+=p,h+=S}if(t==="commit"){let f,I;if(s&&e)f=s,I=e;else if(s){const E=await l.readCommit({fs:i,dir:a,oid:s});E.commit.parent&&E.commit.parent.length>0?(f=E.commit.parent[0],I=s):(f=null,I=s)}else throw new P("source=commit requires at least 'from'",{field:"from"});const O=f?new Set(await l.listFiles({fs:i,dir:a,ref:f})):new Set,x=new Set(await l.listFiles({fs:i,dir:a,ref:I})),R=new Set([...O,...x]);for(const E of R){if(c&&!c.has(E))continue;const y=O.has(E)?await B(a,f,E):null,p=x.has(E)?await B(a,I,E):null;await m(E,y,p)}}else{if(!await M(a))return{success:!0,logId:n,source:t,diff:"",summary:{files:[],insertions:0,deletions:0}};const f=await j(a),I=await l.statusMatrix({fs:i,dir:a});for(const[O,x,R,E]of I){if(c&&!c.has(O))continue;if(t==="staged"){if(E===1||x===0&&E===0)continue}else if(x===0&&E===0||x===1&&R===1&&E===1)continue;const y=x!==0?await B(a,f,O):null;let p=null;if(R!==0){const S=v.join(a,O);if(i.existsSync(S))try{const F=i.readFileSync(S);p={content:F.toString("utf8"),buf:F}}catch{}}await m(O,y,p)}}return{success:!0,logId:n,source:t,diff:w,summary:{files:u,insertions:d,deletions:h}}}catch(c){throw c instanceof P?c:(g(n,"ERROR","Failed to get Git diff",{logId:n,error:c.message}),new A("Failed to get Git diff",{originalError:c.message}))}}async function tt(o={}){const{ref:t="HEAD",filePath:s}=o;if(!s)throw new P("filePath is required",{field:"filePath"});const{targetPath:e,logId:r}=k(o);await N(e);try{let a;if(t==="worktree"||t==="staged"||t===""){const n=v.join(e,s);a=i.existsSync(n)?i.readFileSync(n,"utf8"):""}else try{const n=await l.resolveRef({fs:i,dir:e,ref:t}),{blob:c}=await l.readBlob({fs:i,dir:e,oid:n,filepath:s});a=Buffer.from(c).toString("utf8")}catch{a=""}return{success:!0,logId:r,filePath:s,ref:t,content:a}}catch(a){throw a instanceof W?a:(g(r,"ERROR","Failed to get file content",{logId:r,ref:t,filePath:s,error:a.message}),new A("Failed to get file content",{originalError:a.message}))}}async function et(o={}){const{target:t,mode:s="mixed"}=o;if(!t)throw new P("Reset target cannot be empty",{field:"target"});if(!["soft","mixed","hard"].includes(s))throw new P("Mode must be soft, mixed or hard",{field:"mode"});const{targetPath:e,logId:r}=k(o);await N(e);try{if(!await M(e))throw new H("Cannot reset: repository has no commits yet");const a=await l.log({fs:i,dir:e,depth:1}),n=a.length>0?a[0].oid:null,c=await l.currentBranch({fs:i,dir:e,fullname:!1});if(!c)throw new H("Cannot reset: detached HEAD");const w=s==="hard"?new Set(await l.listFiles({fs:i,dir:e,ref:"HEAD"})):null;if(await l.writeRef({fs:i,dir:e,ref:`refs/heads/${c}`,value:t,force:!0}),s==="mixed"||s==="hard"){const u=await l.listFiles({fs:i,dir:e,ref:t}),d=new Set(u);for(const m of u)await l.resetIndex({fs:i,dir:e,filepath:m,ref:t});const h=await l.statusMatrix({fs:i,dir:e});for(const[m,,,f]of h)if(!d.has(m)&&f!==0)try{await l.remove({fs:i,dir:e,filepath:m})}catch{}}if(s==="hard"){const u=await l.listFiles({fs:i,dir:e,ref:t}),d=new Set(u);for(const h of u){const m=await B(e,t,h);if(!m)continue;const f=v.join(e,h);i.mkdirSync(v.dirname(f),{recursive:!0}),i.writeFileSync(f,m.buf)}if(w){for(const h of w)if(!d.has(h)){const m=v.join(e,h);i.existsSync(m)&&i.unlinkSync(m)}}_(e),await l.add({fs:i,dir:e,filepath:".gitignore"})}return g(r,"INFO","Git reset successful",{logId:r,target:t,mode:s,previousHead:n}),{success:!0,message:`Reset (${s}) to ${t} successful`,logId:r,target:t,mode:s,previousHead:n}}catch(a){throw a instanceof H?a:(g(r,"ERROR","Failed to reset",{logId:r,target:t,mode:s,error:a.message}),new A("Failed to reset",{originalError:a.message}))}}async function st(o={}){const{target:t,message:s,authorName:e,authorEmail:r}=o;if(!t)throw new P("Revert target cannot be empty",{field:"target"});const{targetPath:a,logId:n}=k(o);await N(a);try{if(!await M(a))throw new H("Cannot revert: repository has no commits yet");let c;try{c=(await l.readCommit({fs:i,dir:a,oid:t})).oid}catch{throw new P("Revert target commit does not exist",{field:"target",target:t})}const w=await l.statusMatrix({fs:i,dir:a});if(w.some(([y,p,S,F])=>p===0&&F===0?!1:S!==1||F!==1)){const y=w.filter(([,F])=>F!==0),p=y.filter(([,,F])=>F!==1).map(([F])=>F),S=y.filter(([,,,F])=>F!==1).map(([F])=>F);throw new H("Working directory is not clean, please commit or stash your changes before reverting",{staged:S,modified:p})}const d=new Set(await l.listFiles({fs:i,dir:a,ref:c})),h=await j(a),m=new Set(await l.listFiles({fs:i,dir:a,ref:h})),f=[];for(const y of d){const p=await B(a,c,y);if(!p)continue;const S=v.join(a,y);i.mkdirSync(v.dirname(S),{recursive:!0}),i.writeFileSync(S,p.buf),await l.add({fs:i,dir:a,filepath:y})}for(const y of m)if(!d.has(y)){f.push(y);try{await l.remove({fs:i,dir:a,filepath:y})}catch{}const p=v.join(a,y);i.existsSync(p)&&await i.promises.unlink(p)}if(f.length>0&&await U(a,f),_(a),await l.add({fs:i,dir:a,filepath:".gitignore"}),!(await l.statusMatrix({fs:i,dir:a})).some(([,,,y])=>y!==1))return g(n,"INFO","Nothing to revert, current HEAD already matches target",{logId:n,target:c}),{success:!0,message:"Nothing to revert, already at target state",logId:n,nothingToCommit:!0,target:c};const x={name:e||C.GIT_DEFAULT_AUTHOR_NAME,email:r||C.GIT_DEFAULT_AUTHOR_EMAIL},R=s||`Revert to ${c.substring(0,7)}`,E=await l.commit({fs:i,dir:a,message:R,author:x});return g(n,"INFO","Git revert successful",{logId:n,target:c,commitHash:E,previousHead:h,removedFilesCount:f.length}),{success:!0,message:"Revert successful",logId:n,commit:E,target:c,previousHead:h}}catch(c){throw c instanceof P||c instanceof H?c:(g(n,"ERROR","Failed to revert",{logId:n,target:t,error:c.message}),new A("Failed to revert",{originalError:c.message}))}}async function at(o={}){const{target:t}=o;if(!t)throw new P("Checkout target cannot be empty",{field:"target"});const{targetPath:s,logId:e}=k(o);await N(s);try{const r=await l.listFiles({fs:i,dir:s,ref:t});for(const a of r){const n=await B(s,t,a);if(!n)continue;const c=v.join(s,a);i.mkdirSync(v.dirname(c),{recursive:!0}),i.writeFileSync(c,n.buf),await l.add({fs:i,dir:s,filepath:a})}return _(s),await l.add({fs:i,dir:s,filepath:".gitignore"}),g(e,"INFO","Git checkout files successful",{logId:e,target:t}),{success:!0,message:`Checkout files from ${t} successful`,logId:e,target:t}}catch(r){throw g(e,"ERROR","Failed to checkout files",{logId:e,target:t,error:r.message}),new A("Failed to checkout files",{originalError:r.message})}}async function rt(o={}){const{targetPath:t,logId:s}=k(o);await N(t);try{const e=await l.listTags({fs:i,dir:t});return{success:!0,logId:s,tags:e,latest:e.length>0?e[e.length-1]:null}}catch(e){throw g(s,"ERROR","Failed to list tags",{logId:s,error:e.message}),new A("Failed to list tags",{originalError:e.message})}}async function it(o={}){const{tagName:t,message:s}=o;if(!t)throw new P("Tag name cannot be empty",{field:"tagName"});const{targetPath:e,logId:r}=k(o);await N(e);try{return s?await l.annotatedTag({fs:i,dir:e,ref:t,message:s,tagger:yt()}):await l.tag({fs:i,dir:e,ref:t}),g(r,"INFO","Git tag created",{logId:r,tagName:t,annotated:!!s}),{success:!0,message:"Tag created successfully",logId:r,tagName:t}}catch(a){throw g(r,"ERROR","Failed to create tag",{logId:r,tagName:t,error:a.message}),new A("Failed to create tag",{originalError:a.message})}}async function nt(o={}){const{tagName:t}=o;if(!t)throw new P("Tag name cannot be empty",{field:"tagName"});const{targetPath:s,logId:e}=k(o);await N(s);try{return await l.deleteRef({fs:i,dir:s,ref:`refs/tags/${t}`}),g(e,"INFO","Git tag deleted",{logId:e,tagName:t}),{success:!0,message:"Tag deleted successfully",logId:e,tagName:t}}catch(r){throw g(e,"ERROR","Failed to delete tag",{logId:e,tagName:t,error:r.message}),new A("Failed to delete tag",{originalError:r.message})}}async function ot(o={}){const{targetPath:t,logId:s}=k(o);await N(t);try{const e=await l.listBranches({fs:i,dir:t}),r=await l.currentBranch({fs:i,dir:t,fullname:!1}),a={};for(const n of e)a[n]={name:n,current:n===r};return{success:!0,logId:s,branches:a,current:r}}catch(e){throw g(s,"ERROR","Failed to list branches",{logId:s,error:e.message}),new A("Failed to list branches",{originalError:e.message})}}async function ct(o={}){const{branchName:t,startPoint:s}=o;if(!t)throw new P("Branch name cannot be empty",{field:"branchName"});const{targetPath:e,logId:r}=k(o);await N(e);try{const a=s||"HEAD";return await l.branch({fs:i,dir:e,ref:t,object:a,checkout:!0}),g(r,"INFO","Git branch created",{logId:r,branchName:t,startPoint:s}),{success:!0,message:"Branch created and switched to",logId:r,branchName:t}}catch(a){throw g(r,"ERROR","Failed to create branch",{logId:r,branchName:t,error:a.message}),new A("Failed to create branch",{originalError:a.message})}}async function lt(o={}){const{branchName:t}=o;if(!t)throw new P("Branch name cannot be empty",{field:"branchName"});const{targetPath:s,logId:e}=k(o);await N(s);try{const r=await l.statusMatrix({fs:i,dir:s});if(r.some(([n,c,w,u])=>c===0&&u===0?!1:w!==1||u!==1)){const n=r.filter(([,u])=>u!==0),c=n.filter(([,,u])=>u!==1).map(([u])=>u),w=n.filter(([,,,u])=>u!==1).map(([u])=>u);throw new H("Working directory is not clean, please commit or stash your changes before switching branches",{staged:w,modified:c})}return await l.checkout({fs:i,dir:s,ref:t}),g(e,"INFO","Git branch switched",{logId:e,branchName:t}),{success:!0,message:"Branch switched successfully",logId:e,branchName:t}}catch(r){throw r instanceof H?r:(g(e,"ERROR","Failed to switch branch",{logId:e,branchName:t,error:r.message}),new A("Failed to switch branch",{originalError:r.message}))}}async function ft(o={}){const{branchName:t,force:s=!1}=o;if(!t)throw new P("Branch name cannot be empty",{field:"branchName"});const{targetPath:e,logId:r}=k(o);await N(e);try{if(await l.currentBranch({fs:i,dir:e,fullname:!1})===t)throw new H("Cannot delete the current branch, please switch to another branch first");return await l.deleteBranch({fs:i,dir:e,ref:t,force:s}),g(r,"INFO","Git branch deleted",{logId:r,branchName:t,force:s}),{success:!0,message:"Branch deleted successfully",logId:r,branchName:t}}catch(a){throw a instanceof H?a:(g(r,"ERROR","Failed to delete branch",{logId:r,branchName:t,error:a.message}),new A("Failed to delete branch",{originalError:a.message}))}}export{z as init,K as status,V as commit,J as add,Q as unstage,X as discard,Y as logHistory,Z as diff,tt as fileContent,et as reset,st as revert,at as checkout,rt as listTags,it as createTag,nt as deleteTag,ot as listBranches,ct as createBranch,lt as switchBranch,ft as deleteBranch};export default{init:z,status:K,commit:V,add:J,unstage:Q,discard:X,logHistory:Y,diff:Z,fileContent:tt,reset:et,revert:st,checkout:at,listTags:rt,createTag:it,deleteTag:nt,listBranches:ot,createBranch:ct,switchBranch:lt,deleteBranch:ft};
8
+ `,u.push({file:f,changes:0,insertions:0,deletions:0,binary:!0});return}const{diff:y,insertions:p,deletions:S}=Ft(f,x?E.content:"",R?O.content:"",x,R,x?E.buf:null,R?O.buf:null);h+=y,u.push({file:f,changes:p+S,insertions:p,deletions:S,binary:!1}),d+=p,m+=S}if(t==="commit"){let f,E;if(s&&e)f=s,E=e;else if(s){const I=await l.readCommit({fs:n,dir:r,oid:s});I.commit.parent&&I.commit.parent.length>0?(f=I.commit.parent[0],E=s):(f=null,E=s)}else throw new k("source=commit requires at least 'from'",{field:"from"});const O=f?new Set(await l.listFiles({fs:n,dir:r,ref:f})):new Set,x=new Set(await l.listFiles({fs:n,dir:r,ref:E})),R=new Set([...O,...x]);for(const I of R){if(c&&!c.has(I))continue;const y=O.has(I)?await B(r,f,I):null,p=x.has(I)?await B(r,E,I):null;await g(I,y,p)}}else{if(!await W(r))return{success:!0,logId:i,source:t,diff:"",summary:{files:[],insertions:0,deletions:0}};const f=await j(r),E=await l.statusMatrix({fs:n,dir:r});for(const[O,x,R,I]of E){if(c&&!c.has(O))continue;if(t==="staged"){if(I===1||x===0&&I===0)continue}else if(x===0&&I===0||x===1&&R===1&&I===1)continue;const y=x!==0?await B(r,f,O):null;let p=null;if(R!==0){const S=P.join(r,O);if(n.existsSync(S))try{const F=n.readFileSync(S);p={content:F.toString("utf8"),buf:F}}catch{}}await g(O,y,p)}}return{success:!0,logId:i,source:t,diff:h,summary:{files:u,insertions:d,deletions:m}}}catch(c){throw c instanceof k?c:(w(i,"ERROR","Failed to get Git diff",{logId:i,error:c.message}),new A("Failed to get Git diff",{originalError:c.message}))}}async function tt(o={}){const{ref:t="HEAD",filePath:s}=o;if(!s)throw new k("filePath is required",{field:"filePath"});const{targetPath:e,logId:a}=v(o);await N(e);try{let r;if(t==="worktree"||t==="staged"||t===""){const i=P.join(e,s);r=n.existsSync(i)?n.readFileSync(i,"utf8"):""}else try{const i=await l.resolveRef({fs:n,dir:e,ref:t}),{blob:c}=await l.readBlob({fs:n,dir:e,oid:i,filepath:s});r=Buffer.from(c).toString("utf8")}catch{r=""}return{success:!0,logId:a,filePath:s,ref:t,content:r}}catch(r){throw r instanceof _?r:(w(a,"ERROR","Failed to get file content",{logId:a,ref:t,filePath:s,error:r.message}),new A("Failed to get file content",{originalError:r.message}))}}async function et(o={}){const{target:t,mode:s="mixed"}=o;if(!t)throw new k("Reset target cannot be empty",{field:"target"});if(!["soft","mixed","hard"].includes(s))throw new k("Mode must be soft, mixed or hard",{field:"mode"});const{targetPath:e,logId:a}=v(o);await N(e);try{if(!await W(e))throw new C("Cannot reset: repository has no commits yet");const r=await l.log({fs:n,dir:e,depth:1}),i=r.length>0?r[0].oid:null,c=await l.currentBranch({fs:n,dir:e,fullname:!1});if(!c)throw new C("Cannot reset: detached HEAD");const h=s==="hard"?new Set(await l.listFiles({fs:n,dir:e,ref:"HEAD"})):null;if(await l.writeRef({fs:n,dir:e,ref:`refs/heads/${c}`,value:t,force:!0}),s==="mixed"||s==="hard"){const u=await l.listFiles({fs:n,dir:e,ref:t}),d=new Set(u);for(const g of u)await l.resetIndex({fs:n,dir:e,filepath:g,ref:t});const m=await l.statusMatrix({fs:n,dir:e});for(const[g,,,f]of m)if(!d.has(g)&&f!==0)try{await l.remove({fs:n,dir:e,filepath:g})}catch{}}if(s==="hard"){const u=await l.listFiles({fs:n,dir:e,ref:t}),d=new Set(u);for(const m of u){const g=await B(e,t,m);if(!g)continue;const f=P.join(e,m);n.mkdirSync(P.dirname(f),{recursive:!0}),n.writeFileSync(f,g.buf)}if(h){for(const m of h)if(!d.has(m)){const g=P.join(e,m);n.existsSync(g)&&n.unlinkSync(g)}}M(e),await l.add({fs:n,dir:e,filepath:".gitignore"})}return w(a,"INFO","Git reset successful",{logId:a,target:t,mode:s,previousHead:i}),{success:!0,message:`Reset (${s}) to ${t} successful`,logId:a,target:t,mode:s,previousHead:i}}catch(r){throw r instanceof C?r:(w(a,"ERROR","Failed to reset",{logId:a,target:t,mode:s,error:r.message}),new A("Failed to reset",{originalError:r.message}))}}async function st(o={}){const{target:t,message:s,authorName:e,authorEmail:a}=o;if(!t)throw new k("Revert target cannot be empty",{field:"target"});const{targetPath:r,logId:i}=v(o);await N(r);try{if(!await W(r))throw new C("Cannot revert: repository has no commits yet");let c;try{c=(await l.readCommit({fs:n,dir:r,oid:t})).oid}catch{throw new k("Revert target commit does not exist",{field:"target",target:t})}const h=await l.statusMatrix({fs:n,dir:r});if(h.some(([y,p,S,F])=>p===0&&F===0?!1:S!==1||F!==1)){const y=h.filter(([,F])=>F!==0),p=y.filter(([,,F])=>F!==1).map(([F])=>F),S=y.filter(([,,,F])=>F!==1).map(([F])=>F);throw new C("Working directory is not clean, please commit or stash your changes before reverting",{staged:S,modified:p})}const d=new Set(await l.listFiles({fs:n,dir:r,ref:c})),m=await j(r),g=new Set(await l.listFiles({fs:n,dir:r,ref:m})),f=[];for(const y of d){const p=await B(r,c,y);if(!p)continue;const S=P.join(r,y);n.mkdirSync(P.dirname(S),{recursive:!0}),n.writeFileSync(S,p.buf),await l.add({fs:n,dir:r,filepath:y})}for(const y of g)if(!d.has(y)){f.push(y);try{await l.remove({fs:n,dir:r,filepath:y})}catch{}const p=P.join(r,y);n.existsSync(p)&&await n.promises.unlink(p)}if(f.length>0&&await U(r,f),M(r),await l.add({fs:n,dir:r,filepath:".gitignore"}),!(await l.statusMatrix({fs:n,dir:r})).some(([,,,y])=>y!==1))return w(i,"INFO","Nothing to revert, current HEAD already matches target",{logId:i,target:c}),{success:!0,message:"Nothing to revert, already at target state",logId:i,nothingToCommit:!0,target:c};const x={name:e||G.GIT_DEFAULT_AUTHOR_NAME,email:a||G.GIT_DEFAULT_AUTHOR_EMAIL},R=s||`Revert to ${c.substring(0,7)}`,I=await l.commit({fs:n,dir:r,message:R,author:x});return w(i,"INFO","Git revert successful",{logId:i,target:c,commitHash:I,previousHead:m,removedFilesCount:f.length}),{success:!0,message:"Revert successful",logId:i,commit:I,target:c,previousHead:m}}catch(c){throw c instanceof k||c instanceof C?c:(w(i,"ERROR","Failed to revert",{logId:i,target:t,error:c.message}),new A("Failed to revert",{originalError:c.message}))}}async function rt(o={}){const{target:t}=o;if(!t)throw new k("Checkout target cannot be empty",{field:"target"});const{targetPath:s,logId:e}=v(o);await N(s);try{const a=await l.listFiles({fs:n,dir:s,ref:t});for(const r of a){const i=await B(s,t,r);if(!i)continue;const c=P.join(s,r);n.mkdirSync(P.dirname(c),{recursive:!0}),n.writeFileSync(c,i.buf),await l.add({fs:n,dir:s,filepath:r})}return M(s),await l.add({fs:n,dir:s,filepath:".gitignore"}),w(e,"INFO","Git checkout files successful",{logId:e,target:t}),{success:!0,message:`Checkout files from ${t} successful`,logId:e,target:t}}catch(a){throw w(e,"ERROR","Failed to checkout files",{logId:e,target:t,error:a.message}),new A("Failed to checkout files",{originalError:a.message})}}async function at(o={}){const{targetPath:t,logId:s}=v(o);await N(t);try{const e=await l.listTags({fs:n,dir:t});return{success:!0,logId:s,tags:e,latest:e.length>0?e[e.length-1]:null}}catch(e){throw w(s,"ERROR","Failed to list tags",{logId:s,error:e.message}),new A("Failed to list tags",{originalError:e.message})}}async function it(o={}){const{tagName:t,message:s}=o;if(!t)throw new k("Tag name cannot be empty",{field:"tagName"});const{targetPath:e,logId:a}=v(o);await N(e);try{return s?await l.annotatedTag({fs:n,dir:e,ref:t,message:s,tagger:pt()}):await l.tag({fs:n,dir:e,ref:t}),w(a,"INFO","Git tag created",{logId:a,tagName:t,annotated:!!s}),{success:!0,message:"Tag created successfully",logId:a,tagName:t}}catch(r){throw w(a,"ERROR","Failed to create tag",{logId:a,tagName:t,error:r.message}),new A("Failed to create tag",{originalError:r.message})}}async function nt(o={}){const{tagName:t}=o;if(!t)throw new k("Tag name cannot be empty",{field:"tagName"});const{targetPath:s,logId:e}=v(o);await N(s);try{return await l.deleteRef({fs:n,dir:s,ref:`refs/tags/${t}`}),w(e,"INFO","Git tag deleted",{logId:e,tagName:t}),{success:!0,message:"Tag deleted successfully",logId:e,tagName:t}}catch(a){throw w(e,"ERROR","Failed to delete tag",{logId:e,tagName:t,error:a.message}),new A("Failed to delete tag",{originalError:a.message})}}async function ot(o={}){const{targetPath:t,logId:s}=v(o);await N(t);try{const e=await l.listBranches({fs:n,dir:t}),a=await l.currentBranch({fs:n,dir:t,fullname:!1}),r={};for(const i of e)r[i]={name:i,current:i===a};return{success:!0,logId:s,branches:r,current:a}}catch(e){throw w(s,"ERROR","Failed to list branches",{logId:s,error:e.message}),new A("Failed to list branches",{originalError:e.message})}}async function ct(o={}){const{branchName:t,startPoint:s}=o;if(!t)throw new k("Branch name cannot be empty",{field:"branchName"});const{targetPath:e,logId:a}=v(o);await N(e);try{const r=s||"HEAD";return await l.branch({fs:n,dir:e,ref:t,object:r,checkout:!0}),w(a,"INFO","Git branch created",{logId:a,branchName:t,startPoint:s}),{success:!0,message:"Branch created and switched to",logId:a,branchName:t}}catch(r){throw w(a,"ERROR","Failed to create branch",{logId:a,branchName:t,error:r.message}),new A("Failed to create branch",{originalError:r.message})}}async function lt(o={}){const{branchName:t}=o;if(!t)throw new k("Branch name cannot be empty",{field:"branchName"});const{targetPath:s,logId:e}=v(o);await N(s);try{const a=await l.statusMatrix({fs:n,dir:s});if(a.some(([i,c,h,u])=>c===0&&u===0?!1:h!==1||u!==1)){const i=a.filter(([,u])=>u!==0),c=i.filter(([,,u])=>u!==1).map(([u])=>u),h=i.filter(([,,,u])=>u!==1).map(([u])=>u);throw new C("Working directory is not clean, please commit or stash your changes before switching branches",{staged:h,modified:c})}return await l.checkout({fs:n,dir:s,ref:t}),w(e,"INFO","Git branch switched",{logId:e,branchName:t}),{success:!0,message:"Branch switched successfully",logId:e,branchName:t}}catch(a){throw a instanceof C?a:(w(e,"ERROR","Failed to switch branch",{logId:e,branchName:t,error:a.message}),new A("Failed to switch branch",{originalError:a.message}))}}async function ft(o={}){const{branchName:t,force:s=!1}=o;if(!t)throw new k("Branch name cannot be empty",{field:"branchName"});const{targetPath:e,logId:a}=v(o);await N(e);try{if(await l.currentBranch({fs:n,dir:e,fullname:!1})===t)throw new C("Cannot delete the current branch, please switch to another branch first");return await l.deleteBranch({fs:n,dir:e,ref:t,force:s}),w(a,"INFO","Git branch deleted",{logId:a,branchName:t,force:s}),{success:!0,message:"Branch deleted successfully",logId:a,branchName:t}}catch(r){throw r instanceof C?r:(w(a,"ERROR","Failed to delete branch",{logId:a,branchName:t,error:r.message}),new A("Failed to delete branch",{originalError:r.message}))}}export{z as init,K as status,V as commit,J as add,Q as unstage,X as discard,Y as logHistory,Z as diff,tt as fileContent,et as reset,st as revert,rt as checkout,at as listTags,it as createTag,nt as deleteTag,ot as listBranches,ct as createBranch,lt as switchBranch,ft as deleteBranch};export default{init:z,status:K,commit:V,add:J,unstage:Q,discard:X,logHistory:Y,diff:Z,fileContent:tt,reset:et,revert:st,checkout:rt,listTags:at,createTag:it,deleteTag:nt,listBranches:ot,createBranch:ct,switchBranch:lt,deleteBranch:ft};
@@ -1 +1 @@
1
- import l from"fs";import i from"path";import{log as y}from"../log/logUtils.js";const d={agents:".agents",claudecode:".claude",opencode:".opencode",codex:".codex"},m=Object.keys(d),g="agents",k=8;function N(t){let e=0;const n=[];return{async acquire(){if(e<t){e+=1;return}await new Promise(s=>{n.push(s)})},release(){const s=n.shift();if(s){s();return}e=Math.max(0,e-1)}}}const j=N(k);async function f(t){try{await l.promises.lstat(t)}catch{return}await l.promises.rm(t,{recursive:!0,force:!0})}async function c(t){l.existsSync(t)||await l.promises.mkdir(t,{recursive:!0})}async function E(t,e){await j.acquire();let n=!1;try{if((await l.promises.lstat(t)).isDirectory()){await c(e);const r=await l.promises.readdir(t);j.release(),n=!0,await Promise.all(r.map(a=>E(i.join(t,a),i.join(e,a))));return}await c(i.dirname(e)),await l.promises.copyFile(t,e)}finally{n||j.release()}}async function w(t,e){if(!l.existsSync(t))return;await c(e);const n=await l.promises.readdir(t,{withFileTypes:!0});await Promise.all(n.map(s=>E(i.join(t,s.name),i.join(e,s.name))))}async function T(t){const e=i.join(t,d[g]),n=i.join(e,"skills"),s=i.join(e,"agents");return await c(e),await c(n),await c(s),{rootDir:e,skillsDir:n,agentsDir:s,agentTypes:m}}async function A(t,e,n){await c(i.dirname(t)),await c(e),await f(t);const s=i.resolve(e);if(process.platform==="win32")try{await l.promises.symlink(s,t,"junction"),y(n||"symlink","DEBUG","Created dir junction (windows)",{linkPath:t,targetPath:s,type:"junction"});return}catch(o){y(n||"symlink","WARN","Windows junction failed, try dir symlink",{linkPath:t,targetPath:s,error:o.message}),await f(t),await l.promises.symlink(s,t,"dir"),y(n||"symlink","DEBUG","Created dir symlink (windows)",{linkPath:t,targetPath:s,type:"dir"});return}let a=i.relative(i.dirname(t),s);if(a=a.split(i.sep).join("/"),!a||i.isAbsolute(a))throw new Error(`Cannot create relative symlink from ${t} to ${e} (got: ${a||"<empty>"})`);await l.promises.symlink(a,t),y(n||"symlink","DEBUG","Created dir symlink",{linkPath:t,targetPath:s,relativeTarget:a,type:"symlink"})}async function D(t,e,n,s){const r=s||"materialize-copy";for(const p of m){const u=i.join(t,d[p]);await f(i.join(u,"skills")),await f(i.join(u,"agents"))}const a=await T(t);await f(a.skillsDir),await f(a.agentsDir),await c(a.skillsDir),await c(a.agentsDir),await w(e,a.skillsDir),await w(n,a.agentsDir);const o=await S(t,r);return y(r,"INFO","Materialized agent store by copy (legacy mode)",{workspaceRoot:t,agentSkillsDir:e,agentAgentsDir:n,syncElapsedMs:o?.elapsedMs}),o}async function M(t,e,n,s){const r=Date.now(),a=s||i.basename(t)||"linkWorkspace";y(a,"INFO","Start linkWorkspaceToAgentStore",{workspaceRoot:t,agentSkillsDir:e,agentAgentsDir:n,platform:process.platform});try{for(const p of m){const u=i.join(t,d[p]);await c(u),await A(i.join(u,"skills"),e,a),await A(i.join(u,"agents"),n,a)}const o=Date.now()-r;return y(a,"INFO","linkWorkspaceToAgentStore completed",{workspaceRoot:t,agentTypes:m,mode:"symlink",elapsedMs:o}),{agentTypes:m,elapsedMs:o,mode:"symlink"}}catch(o){y(a,"WARN","Link to agent store failed, fallback to copy mode",{workspaceRoot:t,platform:process.platform,error:o.message});const p=await D(t,e,n,a),u=Date.now()-r;return y(a,"INFO","linkWorkspaceToAgentStore completed (copy fallback)",{workspaceRoot:t,agentTypes:m,mode:"copy-fallback",elapsedMs:u,syncElapsedMs:p?.elapsedMs}),{agentTypes:m,elapsedMs:u,mode:"copy-fallback",syncElapsedMs:p?.elapsedMs}}}async function C(t,e,n,s){const r=i.join(t,d[e]),a=i.join(r,"skills"),o=i.join(r,"agents");await c(r),await f(a),await f(o),await c(a),await c(o),await Promise.all([w(n.skillsDir,a),w(n.agentsDir,o)]),y(s,"DEBUG","syncAgents target completed",{agentType:e,targetRoot:r})}async function S(t,e){const n=Date.now(),s=e||i.basename(t)||"syncAgents",r=m.filter(p=>p!==g);y(s,"INFO","Start syncAgents",{workspaceRoot:t,targetAgentTypes:r,concurrency:k});const a=await T(t);await Promise.all(r.map(p=>C(t,p,a,s)));const o=Date.now()-n;return y(s,"INFO","syncAgents completed",{workspaceRoot:t,agentTypes:m,elapsedMs:o}),{agentTypes:m,elapsedMs:o}}function O(t){if(!t)return!1;const e=i.join(t,d[g],"skills");try{return l.lstatSync(e).isSymbolicLink()}catch{return!1}}export{d as AGENT_ROOT_MAP,m as ALL_AGENT_TYPES,T as ensurePrimaryAgentDirs,S as syncAgents,M as linkWorkspaceToAgentStore,A as forceDirSymlink,O as isWorkspaceSkillsSymlinked};
1
+ import l from"fs";import i from"path";import{log as y}from"../log/logUtils.js";const d={agents:".agents",claudecode:".claude",opencode:".opencode",codex:".codex"},m=Object.keys(d),g="agents",k=8;function N(t){let e=0;const n=[];return{async acquire(){if(e<t){e+=1;return}await new Promise(s=>{n.push(s)})},release(){const s=n.shift();if(s){s();return}e=Math.max(0,e-1)}}}const j=N(k);async function f(t){try{await l.promises.lstat(t)}catch{return}await l.promises.rm(t,{recursive:!0,force:!0})}async function c(t){l.existsSync(t)||await l.promises.mkdir(t,{recursive:!0})}async function E(t,e){await j.acquire();let n=!1;try{if((await l.promises.lstat(t)).isDirectory()){await c(e);const r=await l.promises.readdir(t);j.release(),n=!0,await Promise.all(r.map(a=>E(i.join(t,a),i.join(e,a))));return}await c(i.dirname(e)),await l.promises.copyFile(t,e)}finally{n||j.release()}}async function w(t,e){if(!l.existsSync(t))return;await c(e);const n=await l.promises.readdir(t,{withFileTypes:!0});await Promise.all(n.map(s=>E(i.join(t,s.name),i.join(e,s.name))))}async function T(t){const e=i.join(t,d[g]),n=i.join(e,"skills"),s=i.join(e,"agents");return await c(e),await c(n),await c(s),{rootDir:e,skillsDir:n,agentsDir:s,agentTypes:m}}async function A(t,e,n){await c(i.dirname(t)),await c(e),await f(t);const s=i.resolve(e);if(process.platform==="win32")try{await l.promises.symlink(s,t,"junction"),y(n||"symlink","DEBUG","Created dir junction (windows)",{linkPath:t,targetPath:s,type:"junction"});return}catch(o){y(n||"symlink","WARN","Windows junction failed, try dir symlink",{linkPath:t,targetPath:s,error:o.message}),await f(t),await l.promises.symlink(s,t,"dir"),y(n||"symlink","DEBUG","Created dir symlink (windows)",{linkPath:t,targetPath:s,type:"dir"});return}let a=i.relative(i.dirname(t),s);if(a=a.split(i.sep).join("/"),!a||i.isAbsolute(a))throw new Error(`Cannot create relative symlink from ${t} to ${e} (got: ${a||"<empty>"})`);await l.promises.symlink(a,t),y(n||"symlink","DEBUG","Created dir symlink",{linkPath:t,targetPath:s,relativeTarget:a,type:"symlink"})}async function D(t,e,n,s){const r=s||"materialize-copy";for(const p of m){const u=i.join(t,d[p]);await f(i.join(u,"skills")),await f(i.join(u,"agents"))}const a=await T(t);await f(a.skillsDir),await f(a.agentsDir),await c(a.skillsDir),await c(a.agentsDir),await w(e,a.skillsDir),await w(n,a.agentsDir);const o=await S(t,r);return y(r,"INFO","Materialized agent store by copy (legacy mode)",{workspaceRoot:t,agentSkillsDir:e,agentAgentsDir:n,syncElapsedMs:o?.elapsedMs}),o}async function M(t,e,n,s){const r=Date.now(),a=s||i.basename(t)||"linkWorkspace";y(a,"INFO","Start linkWorkspaceToAgentStore",{workspaceRoot:t,agentSkillsDir:e,agentAgentsDir:n,platform:process.platform});try{for(const p of m){const u=i.join(t,d[p]);await c(u),await A(i.join(u,"skills"),e,a),await A(i.join(u,"agents"),n,a)}const o=Date.now()-r;return y(a,"INFO","linkWorkspaceToAgentStore completed",{workspaceRoot:t,agentTypes:m,mode:"symlink",elapsedMs:o}),{agentTypes:m,elapsedMs:o,mode:"symlink"}}catch(o){y(a,"WARN","Link to agent store failed, fallback to copy mode",{workspaceRoot:t,platform:process.platform,error:o.message});const p=await D(t,e,n,a),u=Date.now()-r;return y(a,"INFO","linkWorkspaceToAgentStore completed (copy fallback)",{workspaceRoot:t,agentTypes:m,mode:"copy-fallback",elapsedMs:u,syncElapsedMs:p?.elapsedMs}),{agentTypes:m,elapsedMs:u,mode:"copy-fallback",syncElapsedMs:p?.elapsedMs}}}async function C(t,e,n,s){const r=i.join(t,d[e]),a=i.join(r,"skills"),o=i.join(r,"agents");await c(r),await f(a),await f(o),await c(a),await c(o),await Promise.all([w(n.skillsDir,a),w(n.agentsDir,o)]),y(s,"DEBUG","syncAgents target completed",{agentType:e,targetRoot:r})}async function S(t,e){const n=Date.now(),s=e||i.basename(t)||"syncAgents",r=m.filter(p=>p!==g);y(s,"INFO","Start syncAgents",{workspaceRoot:t,targetAgentTypes:r,concurrency:k});const a=await T(t);await Promise.all(r.map(p=>C(t,p,a,s)));const o=Date.now()-n;return y(s,"INFO","syncAgents completed",{workspaceRoot:t,agentTypes:m,elapsedMs:o}),{agentTypes:m,elapsedMs:o}}function O(t){if(!t)return!1;const e=i.join(t,d[g],"skills");try{return l.lstatSync(e).isSymbolicLink()}catch{return!1}}export{d as AGENT_ROOT_MAP,m as ALL_AGENT_TYPES,T as ensurePrimaryAgentDirs,S as syncAgents,M as linkWorkspaceToAgentStore,A as forceDirSymlink,w as copyDirContents,O as isWorkspaceSkillsSymlinked};
@@ -1,2 +1,2 @@
1
- import r from"fs";import a from"path";import"../../appConfig/index.js";import{log as m}from"../log/logUtils.js";import{ValidationError as l}from"../error/errorHandler.js";import{resolveWorkspaceRoot as S}from"../computer/workspaceContext.js";const p=".dynamic_add.lock",k=".sync.lock",h=5*60*1e3;function y(e,t,o=null){const i=S(o);if(!i)throw new l("COMPUTER_WORKSPACE_DIR configuration does not exist",{field:"COMPUTER_WORKSPACE_DIR"});return o?.isUserApp?a.join(i,".agent-store",String(t)):a.join(i,String(e),".agent-store",String(t))}async function A(e,t,o,i=null){const s=y(e,t,i);if(i?.isUserApp&&!r.existsSync(a.dirname(s)))throw new l(`userapp agent-store mount does not exist: ${a.dirname(s)}`,{field:"USERAPP_WORKSPACE_DIR"});const n=a.join(s,"skills"),c=a.join(s,"agents");return await r.promises.mkdir(n,{recursive:!0}),await r.promises.mkdir(c,{recursive:!0}),m(o||"agent-store","DEBUG","Agent store dirs ready",{agentStorePath:s,skillsDir:n,agentsDir:c}),{agentStorePath:s,skillsDir:n,agentsDir:c}}async function E(e,t){await r.promises.mkdir(e,{recursive:!0});const o=a.join(e,k);try{const i=await r.promises.open(o,"wx");return await i.writeFile(`${process.pid}:${Date.now()}`),await i.close(),{acquired:!0,lockPath:o}}catch(i){if(i&&i.code==="EEXIST"){try{const s=await r.promises.stat(o),n=Date.now()-s.mtimeMs;if(n>h){await r.promises.rm(o,{force:!0});const c=await r.promises.open(o,"wx");return await c.writeFile(`${process.pid}:${Date.now()}`),await c.close(),m(t||"agent-store","WARN","Stole stale agent store lock",{lockPath:o,ageMs:n}),{acquired:!0,lockPath:o}}}catch(s){m(t||"agent-store","WARN","Check agent store lock failed",{lockPath:o,error:s.message})}return m(t||"agent-store","INFO","Skip agent store update, lock held by another request",{lockPath:o}),{acquired:!1,lockPath:o}}throw i}}async function R(e,t){if(e)try{await r.promises.rm(e,{force:!0})}catch(o){m(t||"agent-store","WARN","Release agent store lock failed",{lockPath:e,error:o.message})}}function d(e){const t=a.join(e,p);return r.existsSync(t)&&r.statSync(t).isFile()}async function g(e){await r.promises.mkdir(e,{recursive:!0});const t=a.join(e,p);await r.promises.writeFile(t,`${Date.now()}
2
- `,"utf8")}async function v(e,t,o){const i=new Set((Array.isArray(t)?t:[]).map(u=>String(u||"").trim()).filter(Boolean));if(!r.existsSync(e))return{removed:[],keptDynamic:[]};const s=[],n=[],c=await r.promises.readdir(e,{withFileTypes:!0});for(const u of c){if(!u.isDirectory()||i.has(u.name))continue;const w=a.join(e,u.name);if(d(w)){n.push(u.name);continue}await r.promises.rm(w,{recursive:!0,force:!0}),s.push(u.name)}return m(o||"agent-store","INFO","Prune agent skills completed",{skillsDir:e,keepCount:i.size,removed:s,keptDynamic:n}),{removed:s,keptDynamic:n}}async function C(e,t,o,i={}){const{asDynamic:s=!1}=i,n=a.join(t,o);try{await r.promises.lstat(n),await r.promises.rm(n,{recursive:!0,force:!0})}catch{}if(await r.promises.mkdir(t,{recursive:!0}),await f(e,n),s)await g(n);else{const c=a.join(n,p);r.existsSync(c)&&await r.promises.rm(c,{force:!0})}return n}async function f(e,t){try{await r.promises.rename(e,t)}catch(o){if(o&&o.code==="EXDEV"){await r.promises.cp(e,t,{recursive:!0}),await r.promises.rm(e,{recursive:!0,force:!0});return}throw o}}async function _(e,t){await r.promises.rm(t,{recursive:!0,force:!0}),await r.promises.mkdir(a.dirname(t),{recursive:!0}),e&&r.existsSync(e)?await f(e,t):await r.promises.mkdir(t,{recursive:!0})}function P(e,t){if(!e||!t)return!1;try{return r.statSync(a.join(e,t)).isDirectory()}catch{return!1}}export{p as DYNAMIC_ADD_LOCK,y as getAgentStorePath,A as ensureAgentStoreDirs,E as tryAcquireAgentStoreLock,R as releaseAgentStoreLock,d as hasDynamicAddLock,g as ensureDynamicAddLock,v as pruneAgentSkills,C as installSkillDir,_ as replaceAgentsDir,f as moveOrCopyDirectory,P as agentSkillExists};
1
+ import n from"fs";import s from"path";import"../../appConfig/index.js";import{log as m}from"../log/logUtils.js";import{ValidationError as d}from"../error/errorHandler.js";import{resolveWorkspaceRoot as f}from"../computer/workspaceContext.js";const p=".dynamic_add.lock",j=".sync.lock",A=5*60*1e3,w="manifest.json";function g(e,t=null){if(t?.isUserApp&&t?.appId)return t.workspacePath?s.join(t.workspacePath,".agent-store"):s.join(f(t),".agent-store",String(t.appId));if(t?.isNormalProject&&t?.appId){const r=f(t);return s.join(r,String(e),".agent-store",`np-${t.appId}`)}return null}function S(e,t,r=null){const a=g(e,r);if(r?.isUserApp&&a)return s.join(a,String(t));const o=f(r);if(!o)throw new d("COMPUTER_WORKSPACE_DIR configuration does not exist",{field:"COMPUTER_WORKSPACE_DIR"});return s.join(o,String(e),".agent-store",String(t))}async function E(e){try{const t=await n.promises.readFile(s.join(e,w),"utf8"),r=JSON.parse(t);return r&&typeof r=="object"&&typeof r.agents=="object"?r:{agents:{}}}catch{return{agents:{}}}}async function R(e,t){await n.promises.mkdir(e,{recursive:!0});const r=s.join(e,`${w}.${process.pid}.${Date.now()}.tmp`);await n.promises.writeFile(r,JSON.stringify(t,null,2),"utf8"),await n.promises.rename(r,s.join(e,w))}function P(e,t){const r={};for(const[a,o]of Object.entries(e.agents||{}))for(const i of o?.[t]||[])(r[i]=r[i]||[]).push(a);return r}async function _(e,t,r,a=null){const o=S(e,t,a);if(a?.isUserApp){const u=s.dirname(s.dirname(o));if(!n.existsSync(u))throw new d(`userapp agent-store mount does not exist: ${u}`,{field:"USERAPP_WORKSPACE_DIR"})}const i=s.join(o,"skills"),c=s.join(o,"agents");return await n.promises.mkdir(i,{recursive:!0}),await n.promises.mkdir(c,{recursive:!0}),m(r||"agent-store","DEBUG","Agent store dirs ready",{agentStorePath:o,skillsDir:i,agentsDir:c}),{agentStorePath:o,skillsDir:i,agentsDir:c}}async function C(e,t){await n.promises.mkdir(e,{recursive:!0});const r=s.join(e,j);try{const a=await n.promises.open(r,"wx");return await a.writeFile(`${process.pid}:${Date.now()}`),await a.close(),{acquired:!0,lockPath:r}}catch(a){if(a&&a.code==="EEXIST"){try{const o=await n.promises.stat(r),i=Date.now()-o.mtimeMs;if(i>A){await n.promises.rm(r,{force:!0});const c=await n.promises.open(r,"wx");return await c.writeFile(`${process.pid}:${Date.now()}`),await c.close(),m(t||"agent-store","WARN","Stole stale agent store lock",{lockPath:r,ageMs:i}),{acquired:!0,lockPath:r}}}catch(o){m(t||"agent-store","WARN","Check agent store lock failed",{lockPath:r,error:o.message})}return m(t||"agent-store","INFO","Skip agent store update, lock held by another request",{lockPath:r}),{acquired:!1,lockPath:r}}throw a}}async function O(e,t){if(e)try{await n.promises.rm(e,{force:!0})}catch(r){m(t||"agent-store","WARN","Release agent store lock failed",{lockPath:e,error:r.message})}}function k(e){const t=s.join(e,p);return n.existsSync(t)&&n.statSync(t).isFile()}async function h(e){await n.promises.mkdir(e,{recursive:!0});const t=s.join(e,p);await n.promises.writeFile(t,`${Date.now()}
2
+ `,"utf8")}async function D(e,t,r){const a=new Set((Array.isArray(t)?t:[]).map(u=>String(u||"").trim()).filter(Boolean));if(!n.existsSync(e))return{removed:[],keptDynamic:[]};const o=[],i=[],c=await n.promises.readdir(e,{withFileTypes:!0});for(const u of c){if(!u.isDirectory()||a.has(u.name))continue;const y=s.join(e,u.name);if(k(y)){i.push(u.name);continue}await n.promises.rm(y,{recursive:!0,force:!0}),o.push(u.name)}return m(r||"agent-store","INFO","Prune agent skills completed",{skillsDir:e,keepCount:a.size,removed:o,keptDynamic:i}),{removed:o,keptDynamic:i}}async function x(e,t,r,a={}){const{asDynamic:o=!1}=a,i=s.join(t,r);try{await n.promises.lstat(i),await n.promises.rm(i,{recursive:!0,force:!0})}catch{}if(await n.promises.mkdir(t,{recursive:!0}),await l(e,i),o)await h(i);else{const c=s.join(i,p);n.existsSync(c)&&await n.promises.rm(c,{force:!0})}return i}async function l(e,t){try{await n.promises.rename(e,t)}catch(r){if(r&&r.code==="EXDEV"){await n.promises.cp(e,t,{recursive:!0}),await n.promises.rm(e,{recursive:!0,force:!0});return}throw r}}async function F(e,t){await n.promises.rm(t,{recursive:!0,force:!0}),await n.promises.mkdir(s.dirname(t),{recursive:!0}),e&&n.existsSync(e)?await l(e,t):await n.promises.mkdir(t,{recursive:!0})}function N(e,t){if(!e||!t)return!1;try{return n.statSync(s.join(e,t)).isDirectory()}catch{return!1}}export{p as DYNAMIC_ADD_LOCK,S as getAgentStorePath,g as getProjectStoreRoot,E as readManifest,R as writeManifest,P as reverseRefs,_ as ensureAgentStoreDirs,C as tryAcquireAgentStoreLock,O as releaseAgentStoreLock,k as hasDynamicAddLock,h as ensureDynamicAddLock,D as pruneAgentSkills,x as installSkillDir,F as replaceAgentsDir,l as moveOrCopyDirectory,N as agentSkillExists};
@@ -1,2 +1,2 @@
1
- import c from"fs";import d from"path";import re from"archiver";import P from"../../appConfig/index.js";import{log as u}from"../log/logUtils.js";import{ValidationError as E,SystemError as O,FileError as pe}from"../error/errorHandler.js";import{extractZip as he}from"../common/zipUtils.js";import{moveDirectory as we,movePath as J}from"../common/fileSystemUtils.js";import{resolveLogDir as ge,resolveWorkspaceDir as _,resolveWorkspaceRoot as ie}from"./workspaceContext.js";const j=new Set([".git",".agents",".claude",".codex",".opencode",".tmp",".logs"]),ye=100*1024*1024,X=P.DOWNLOAD_MAX_FILE_SIZE_BYTES||ye;async function Ee(t,i,e,l,a){const o=[],m=await c.promises.readdir(t,{withFileTypes:!0});m.sort((s,n)=>s.isDirectory()&&!n.isDirectory()?-1:!s.isDirectory()&&n.isDirectory()?1:s.name.toLowerCase().localeCompare(n.name.toLowerCase()));for(const s of m){const n=d.join(t,s.name);if(s.name.startsWith(".")&&s.name!==".gitignore"||(P.CONTENT_TRAVERSE_EXCLUDE_FILES||[]).includes(s.name)||s.isDirectory()&&P.TRAVERSE_EXCLUDE_DIRS.includes(s.name))continue;const f=d.relative(i,n).replace(/\\/g,"/");if(s.isDirectory()){o.push({name:f,isDir:!0});continue}try{o.push({name:f,isDir:!1,fileProxyUrl:G(l,f,a),isLink:s.isSymbolicLink()})}catch(r){u(e,"WARN",`\u5904\u7406\u6587\u4EF6\u5931\u8D25: ${n}`,{error:r.message})}}return o}async function se(t,i,e,l,a){const o=[],m=await c.promises.readdir(t,{withFileTypes:!0});m.sort((s,n)=>s.isDirectory()&&!n.isDirectory()?-1:!s.isDirectory()&&n.isDirectory()?1:s.name.toLowerCase().localeCompare(n.name.toLowerCase()));for(const s of m){const n=d.join(t,s.name);if(!(s.name.startsWith(".")&&s.name!==".gitignore"||(P.CONTENT_TRAVERSE_EXCLUDE_FILES||[]).includes(s.name))&&!(s.isDirectory()&&P.TRAVERSE_EXCLUDE_DIRS.includes(s.name)))if(s.isDirectory()){const f=await se(n,i,e,l,a);if(f.length===0){const r=i||t,h=d.relative(r,n).replace(/\\/g,"/");o.push({name:h,isDir:!0})}else o.push(...f)}else try{const f=i||t,r=d.relative(f,n).replace(/\\/g,"/");o.push({name:r,isDir:!1,fileProxyUrl:G(l,r,a),isLink:s.isSymbolicLink()})}catch(f){u(e,"WARN",`\u5904\u7406\u6587\u4EF6\u5931\u8D25: ${n}`,{error:f.message})}}return o}function ne(t,i){const e=(i==null?"":String(i)).trim();if(!e||e==="."||e==="/")return d.resolve(t);const l=d.normalize(e).replace(/^[\/\\]+/,"");if(!l||l===".")return d.resolve(t);if(d.isAbsolute(e)||l.split(d.sep).includes(".."))throw new E("relativePath \u975E\u6CD5\uFF0C\u4E0D\u5141\u8BB8\u8D8A\u51FA\u76EE\u6807\u76EE\u5F55",{field:"relativePath",relativePath:i});const a=d.resolve(t),o=d.resolve(t,l);if(o!==a&&!o.startsWith(a+d.sep))throw new E("relativePath \u975E\u6CD5\uFF0C\u4E0D\u5141\u8BB8\u8D8A\u51FA\u76EE\u6807\u76EE\u5F55",{field:"relativePath",relativePath:i});return o}function oe(t,i){const e=(i==null?"":String(i)).trim();if(!e)return null;const l=d.resolve(t);let a;if(d.isAbsolute(e))a=d.resolve(e);else{const m=d.normalize(e).replace(/^[\/\\]+/,"");if(!m||m==="."||m.split(d.sep).includes(".."))return null;a=d.resolve(t,m)}if(a!==l&&!a.startsWith(l+d.sep))return null;const o=d.relative(l,a).replace(/\\/g,"/");return!o||o.startsWith("..")?null:{absPath:a,name:o}}function G(t,i,e){if(!t||!i)return null;const l=i.split("/").map(o=>encodeURIComponent(o)).join("/");let a=`${t}/${l}`;return e&&(a+=`?customTargetDir=${encodeURIComponent(e)}`),a}async function Se(t,i,e,l,a,o=null){if(!t)throw new E("userId \u4E0D\u80FD\u4E3A\u7A7A",{field:"userId"});if(!i)throw new E("cId \u4E0D\u80FD\u4E3A\u7A7A",{field:"cId"});if(!e||!String(e).trim())throw new E("filePath \u4E0D\u80FD\u4E3A\u7A7A",{field:"filePath"});const m=String(t),s=String(i),n=a&&a.trim()?a.trim():null,y=n||_(o,m,s);if(!c.existsSync(y))return{exists:!1};let f=oe(y,e);if(!f){const r=String(e).trim().replace(/^[\/\\]+/,"");r&&r!==String(e).trim()&&(f=oe(y,r))}if(!f)return{exists:!1};try{if(!(await c.promises.stat(f.absPath)).isFile())return{exists:!1}}catch{return{exists:!1}}return{exists:!0,name:f.name,fileProxyUrl:G(l,f.name,n)}}async function ae(t,i,e,l,a=""){const o=await c.promises.readdir(d.join(t,a),{withFileTypes:!0});let m=0;for(const s of o){const n=a?d.join(a,s.name):s.name,y=n.split(d.sep).filter(Boolean);if(y.some(w=>w.startsWith(".")))continue;const f=y[y.length-1];if(i.includes(f)||y.some(w=>e.includes(w)))continue;const r=d.join(t,n);let h;try{h=await c.promises.lstat(r)}catch(w){u(l,"WARN","Error occurred when getting file stats, skipping",{filePath:n.replace(/\\/g,"/"),error:w.message});continue}h.isSymbolicLink()||h.nlink>1||(h.isDirectory()?m+=await ae(t,i,e,l,n):h.isFile()&&(m+=h.size))}return m}async function Re(t,i,e,l,a,o,m=null){const s=Date.now(),n=`computer:${t}:${i}`,y=!(o===!1||o==="false");if(!t)throw new E("userId \u4E0D\u80FD\u4E3A\u7A7A",{field:"userId"});if(!i)throw new E("cId \u4E0D\u80FD\u4E3A\u7A7A",{field:"cId"});const f=String(t),r=String(i),h=l&&l.trim()?l.trim():null,w=h||_(m,f,r);if(!c.existsSync(w))return u(n,"INFO","Directory does not exist, returning empty list",{targetDir:w,userId:f,cId:r}),{files:[],recursive:y};const p=ne(w,a);if(!c.existsSync(p))return u(n,"INFO","List path does not exist, returning empty list",{targetDir:w,listDir:p,relativePath:a||"",userId:f,cId:r}),{files:[],recursive:y};if(!(await c.promises.stat(p)).isDirectory())throw new E("relativePath \u5FC5\u987B\u662F\u76EE\u5F55",{field:"relativePath",relativePath:a});u(n,"DEBUG","Start getting user file list",{targetDir:w,listDir:p,relativePath:a||"",recursive:y,userId:f,cId:r});try{const S=y?await se(p,w,n,e,h):await Ee(p,w,n,e,h);return u(n,"INFO","User file list obtained successfully",{fileCount:S.length,targetDir:w,listDir:p,relativePath:a||"",recursive:y,userId:f,cId:r,elapsedMs:Date.now()-s}),{files:S,recursive:y}}catch(S){throw S instanceof E?S:(u(n,"ERROR","Failed to get user file list",{targetDir:w,listDir:p,relativePath:a||"",recursive:y,userId:f,cId:r,error:S.message,elapsedMs:Date.now()-s}),new O(`Failed to get file list: ${S.message}`,{targetDir:w,originalError:S.message}))}}const Fe=5e3;function Q(t,i){const e=Number.parseInt(t,10);if(!Number.isFinite(e)||e<=0)throw new E(`${i} \u5FC5\u987B\u4E3A\u6B63\u6574\u6570`,{field:i,value:t});return e}function ce(t,i,e){return e?i.toLowerCase().includes(e)?!0:t.toLowerCase().includes(e):!1}function le(t,i,e){return!!(t.name.startsWith(".")&&t.name!==".gitignore"||i.includes(t.name)||t.isDirectory()&&e.includes(t.name))}function me(t,i,e,l){for(let a=i;a<t.length;a++)if(!le(t[a],e,l))return!0;return!1}function ue(t,i){return Date.now()-t>=i}async function De(t,i,e,l,a,o,m,s,n,y=null){const f=Date.now(),r=`computer:${t}:${i}`;if(!t)throw new E("userId \u4E0D\u80FD\u4E3A\u7A7A",{field:"userId"});if(!i)throw new E("cId \u4E0D\u80FD\u4E3A\u7A7A",{field:"cId"});const h=(l==null?"":String(l)).trim();if(!h)throw new E("kw \u4E0D\u80FD\u4E3A\u7A7A",{field:"kw"});const w=Q(m,"limit"),p=Q(s,"maxVisit"),g=Q(n,"timeoutMs"),S=h.toLowerCase(),I=String(t),R=String(i),v=a&&a.trim()?a.trim():null,F=v||_(y,I,R);if(!c.existsSync(F))return{files:[],truncated:!1,visited:0};const D=ne(F,o);if(!c.existsSync(D))return{files:[],truncated:!1,visited:0};if(!(await c.promises.stat(D)).isDirectory())throw new E("relativePath \u5FC5\u987B\u662F\u76EE\u5F55",{field:"relativePath",relativePath:o});const $=d.relative(F,D).replace(/\\/g,"/"),q=P.CONTENT_TRAVERSE_EXCLUDE_FILES||[],Z=P.TRAVERSE_EXCLUDE_DIRS||[],b=[],K=new Set;let A=0,Y=!1,C=!1;const L=[$],U=()=>A>=p||ue(f,g);for(;L.length>0;){if(U()){C=!0;break}if(b.length>=w){L.length>0&&(C=!0);break}const N=L.shift();if(K.has(N))continue;K.add(N);const M=N?d.join(F,N):F;let W;try{W=await c.promises.readdir(M,{withFileTypes:!0})}catch(k){u(r,"WARN","search readdir failed",{dirRel:N,error:k.message});continue}W.sort((k,x)=>k.isDirectory()&&!x.isDirectory()?-1:!k.isDirectory()&&x.isDirectory()?1:k.name.toLowerCase().localeCompare(x.name.toLowerCase()));const H=[];let B=!1,V=!1;for(let k=0;k<W.length;k++){const x=W[k];if(le(x,q,Z))continue;if(U()){C=!0,B=!0,V=!0;break}A+=1;const te=d.join(M,x.name),T=d.relative(F,te).replace(/\\/g,"/");if(x.isDirectory()){if(H.push(T),b.length<w&&ce(T,x.name,S)&&(b.push({name:T,isDir:!0}),b.length>=w)){B=!0,V=me(W,k+1,q,Z);break}continue}try{if(b.length<w&&ce(T,x.name,S)&&(b.push({name:T,isDir:!1,fileProxyUrl:G(e,T,v),isLink:x.isSymbolicLink()}),b.length>=w)){B=!0,V=me(W,k+1,q,Z);break}}catch(de){u(r,"WARN",`search \u5904\u7406\u6587\u4EF6\u5931\u8D25: ${te}`,{error:de.message})}}if(B||U()){(U()||V||H.length>0||L.length>0)&&(C=!0);continue}for(const k of H){if(U()){C=!0;break}if(L.length>=Fe){C=!0;break}if(A+L.length>=p){C=!0;break}K.has(k)||L.push(k)}}return b.sort((N,M)=>N.isDir!==M.isDir?N.isDir?-1:1:String(N.name).localeCompare(String(M.name))),Y=C||L.length>0||A>=p||ue(f,g),u(r,"INFO","File search completed",{kw:h,matchCount:b.length,visited:A,truncated:Y,relativePath:o||"",elapsedMs:Date.now()-f}),{files:b,truncated:Y,visited:A}}async function Ie(t,i,e,l,a=null){const o=Date.now(),m=`computer:${t}:${i}`;if(!t)throw new E("userId cannot be empty",{field:"userId"});if(!i)throw new E("cId cannot be empty",{field:"cId"});if(!Array.isArray(e))throw new E("files must be an array",{field:"files"});const s=String(t),n=String(i),y=l&&l.trim()?l.trim():null,f=y||_(a,s,n);c.existsSync(f)||c.mkdirSync(f,{recursive:!0});for(let r=0;r<e.length;r++){const h=e[r];if(!h||typeof h.operation!="string")throw new E(`files[${r}].operation cannot be empty`,{field:`files[${r}].operation`});if(!h.name||typeof h.name!="string")throw new E(`files[${r}].name cannot be empty`,{field:`files[${r}].name`});const w=h.operation.toLowerCase();if(!["create","delete","rename","modify"].includes(w))throw new E(`files[${r}].operation must be one of create, delete, rename or modify`,{field:`files[${r}].operation`});if(w==="rename"&&!h.renameFrom)throw new E(`files[${r}].renameFrom cannot be empty (rename operation requires)`,{field:`files[${r}].renameFrom`});if(w==="modify"&&h.isDir!==!0&&typeof h.contents!="string")throw new E(`files[${r}].contents must be a string (modify operation requires)`,{field:`files[${r}].contents`})}u(m,"DEBUG","Start updating user files",{userId:s,cId:n,filesCount:e.length});try{for(const r of e){const h=r.operation.toLowerCase(),w=r.name,p=d.normalize(w).replace(/^[\/\\]+/,""),g=d.join(f,p),S=d.resolve(g),I=d.resolve(f);if(!S.startsWith(I+d.sep)&&S!==I){u(m,"WARN","File path is not secure, skipping",{filePath:p,resolvedPath:S});continue}switch(h){case"create":{if(r.isDir===!0){if(c.existsSync(g)){if((await c.promises.stat(g)).isFile())throw new E("Cannot create directory, file with the same name already exists",{filePath:p});u(m,"INFO","Directory already exists, skipping creation",{filePath:p});break}await c.promises.mkdir(g,{recursive:!0}),u(m,"INFO","Directory created successfully",{filePath:p});break}if(c.existsSync(g)){if((await c.promises.stat(g)).isDirectory())throw new E("Cannot create file, directory with the same name already exists",{filePath:p});u(m,"INFO","File already exists, skipping creation",{filePath:p});break}await c.promises.mkdir(d.dirname(g),{recursive:!0});const R=r.contents||"";await c.promises.writeFile(g,R,"utf8"),u(m,"INFO","File created successfully",{filePath:p});break}case"delete":{c.existsSync(g)?(await c.promises.stat(g)).isDirectory()?(await c.promises.rm(g,{recursive:!0,force:!0}),u(m,"INFO","Directory deleted successfully",{filePath:p})):(await c.promises.unlink(g),u(m,"INFO","File deleted successfully",{filePath:p})):u(m,"WARN","The file or directory to be deleted does not exist",{filePath:p});break}case"rename":{const R=r.renameFrom;if(!R||typeof R!="string"){u(m,"WARN","Rename operation missing renameFrom",{filePath:p});break}const v=d.normalize(R).replace(/^[\/\\]+/,""),F=d.join(f,v),D=d.resolve(F);if(!D.startsWith(I+d.sep)&&D!==I){u(m,"WARN","Source path is not secure, skipping rename",{sourcePath:v,targetPath:p});break}if(c.existsSync(F)){const $=(await c.promises.stat(F)).isDirectory();await c.promises.mkdir(d.dirname(g),{recursive:!0}),await c.promises.rename(F,g),u(m,"INFO",$?"Directory renamed successfully":"File renamed successfully",{sourcePath:v,targetPath:p})}else u(m,"WARN","The file or directory to be renamed does not exist",{sourcePath:v});break}case"modify":{if(!c.existsSync(g)){u(m,"WARN","The file to be modified does not exist",{filePath:p});break}if((await c.promises.stat(g)).isDirectory()){u(m,"INFO","The target is a directory, skipping modification",{filePath:p});break}const v=typeof r.contents=="string"?r.contents:"";if(await c.promises.readFile(g,"utf8")===v){u(m,"INFO","File content has no changes, skipping write",{filePath:p});break}await c.promises.writeFile(g,v,"utf8"),u(m,"INFO","File modified successfully",{filePath:p});break}default:{u(m,"WARN","Unsupported operation type",{operation:h,filePath:p});break}}}return u(m,"INFO","User files updated successfully",{userId:s,cId:n,filesCount:e.length,elapsedMs:Date.now()-o}),{success:!0,message:"User files updated successfully",userId:s,cId:n,filesCount:e.length}}catch(r){throw u(m,"ERROR","User files updated failed",{userId:s,cId:n,error:r.message,elapsedMs:Date.now()-o}),new O(`User files updated failed: ${r.message}`,{userId:s,cId:n,originalError:r.message})}}async function ee(t,i,e,l,a,o=null){const m=Date.now(),s=`computer:${t}:${i}`;if(!t)throw new E("userId cannot be empty",{field:"userId"});if(!i)throw new E("cId cannot be empty",{field:"cId"});if(!e)throw new E("file cannot be empty",{field:"file"});if(!l||typeof l!="string")throw new E("filePath cannot be empty",{field:"filePath"});const n=String(t),y=String(i),f=a&&a.trim()?a.trim():null,r=f||_(o,n,y);c.existsSync(r)||c.mkdirSync(r,{recursive:!0});const h=d.normalize(l).replace(/^[\/\\]+/,""),w=d.join(r,h),p=d.resolve(w),g=d.resolve(r);if(!p.startsWith(g+d.sep)&&p!==g)throw new E("File path is not secure, cannot exceed user directory",{field:"filePath",providedPath:l,resolvedPath:p});try{if(await c.promises.mkdir(d.dirname(w),{recursive:!0}),e.buffer)await c.promises.writeFile(w,e.buffer);else if(typeof e.contents=="string")await c.promises.writeFile(w,e.contents,"utf8");else throw new E("File content format is incorrect",{field:"file",hasBuffer:!!e.buffer,hasContents:typeof e.contents});return u(s,"INFO","File uploaded successfully",{userId:n,cId:y,filePath:h,targetPath:p,fileSize:e.buffer?e.buffer.length:e.contents?e.contents.length:0,elapsedMs:Date.now()-m}),{success:!0,message:"File uploaded successfully",fileSize:e.buffer?e.buffer.length:e.contents?e.contents.length:0}}catch(S){throw u(s,"ERROR","File upload failed",{userId:n,cId:y,filePath:h,error:S.message,elapsedMs:Date.now()-m}),new O(`File upload failed: ${S.message}`,{userId:n,cId:y,filePath:h,originalError:S.message})}}async function ve(t,i,e,l,a,o=null){const m=Date.now(),s=`computer:${t}:${i}`;if(!t)throw new E("userId cannot be empty",{field:"userId"});if(!i)throw new E("cId cannot be empty",{field:"cId"});if(!Array.isArray(e))throw new E("files must be an array",{field:"files"});if(!Array.isArray(l))throw new E("filePaths must be an array",{field:"filePaths"});if(e.length!==l.length)throw new E(`File count (${e.length}) does not match path count (${l.length})`,{field:"filePaths"});u(s,"DEBUG","Start batch uploading files",{userId:t,cId:i,filesCount:e.length});const n=[];try{for(let r=0;r<e.length;r++){const h=e[r],w=l[r];if(!h){u(s,"WARN","Empty file object encountered in batch upload, skipping",{index:r,filePath:w}),n.push({success:!1,filePath:w,error:"Empty file object"});continue}if(!w||typeof w!="string"){u(s,"WARN","Invalid file path in batch upload, skipping",{index:r,originalname:h.originalname}),n.push({success:!1,filePath:w||"",originalname:h.originalname,error:"Invalid file path"});continue}try{const p=await ee(t,i,h,w,a,o);n.push({success:!0,filePath:w,originalname:h.originalname,...p})}catch(p){u(s,"ERROR","Single file upload failed in batch upload",{filePath:w,originalname:h.originalname,error:p.message}),n.push({success:!1,filePath:w,originalname:h.originalname,error:p.message})}}const y=n.filter(r=>r.success).length,f=n.filter(r=>!r.success).length;return u(s,"INFO","Batch upload files completed",{userId:t,cId:i,totalCount:e.length,successCount:y,failCount:f,elapsedMs:Date.now()-m}),{success:!0,message:"Batch upload completed",totalCount:e.length,successCount:y,failCount:f,results:n}}catch(y){throw u(s,"ERROR","Batch upload files failed",{userId:t,cId:i,error:y.message,elapsedMs:Date.now()-m}),new O(`Batch upload files failed: ${y.message}`,{userId:t,cId:i,originalError:y.message})}}async function ke(t,i,e,l=null){const a=Date.now(),o=`computer:${t}:${i}`,m=ie(l);if(!t)throw new E("userId cannot be empty",{field:"userId"});if(!i)throw new E("cId cannot be empty",{field:"cId"});if(!m&&!l?.workspaceDir)throw new O("COMPUTER_WORKSPACE_DIR is not configured, cannot create zip");const s=String(t),n=String(i),y=e&&e.trim()?e.trim():null,f=y||_(l,s,n);if(!c.existsSync(f)){const S=`${s}_${n}.zip`;u(o,"WARN","Workspace directory does not exist, returning empty zip",{targetDir:f,userId:s,cId:n,zipFileName:S});const I=re("zip",{zlib:{level:9}});return I.append(null,{name:`${s}_${n}/`,type:"directory"}),I.on("warning",R=>{if(R.code==="ENOENT")u(o,"WARN","Encountered file problem when creating empty zip",{message:R.message,code:R.code});else throw u(o,"ERROR","Encountered warning when creating empty zip",{message:R.message,code:R.code}),R}),I.on("error",R=>{u(o,"ERROR","Failed to create empty zip",{message:R.message})}),{archive:I,zipFileName:S}}const r=`${s}_${n}.zip`;u(o,"DEBUG","Start creating workspace directory zip",{targetDir:f,zipFileName:r});const h=re("zip",{zlib:{level:9}}),w=P.CONTENT_TRAVERSE_EXCLUDE_FILES||[],p=P.TRAVERSE_EXCLUDE_DIRS||[],g=await ae(f,w,p,o);if(g>X){const S=X/1024/1024,I=(g/1024/1024).toFixed(2);throw u(o,"WARN","Download rejected due to oversized workspace",{targetDir:f,downloadableSize:g,maxSizeBytes:X}),new E(`Download failed: total file size ${I}MB exceeds limit ${S}MB`,{field:"downloadSize",downloadableSize:g,maxSizeBytes:X})}return h.directory(f,`${s}_${n}`,S=>{const I=S.name||"",R=I.split(/[\/\\]/).filter(Boolean);if(R.some(F=>F.startsWith(".")))return!1;const v=R[R.length-1];if(w.includes(v)||R.some(F=>p.includes(F)))return!1;try{const F=d.join(f,I),D=c.lstatSync(F);if(D.isSymbolicLink()||D.nlink>1)return!1}catch(F){return u(o,"WARN","Error occurred when detecting link file, skipping",{filePath:I,error:F.message}),!1}return S}),h.on("warning",S=>{if(S.code==="ENOENT")u(o,"WARN","Encountered file problem when creating zip",{message:S.message,code:S.code});else throw u(o,"ERROR","Encountered warning when creating zip",{message:S.message,code:S.code}),S}),h.on("error",S=>{u(o,"ERROR","Failed to create zip",{message:S.message,elapsedMs:Date.now()-a})}),h.on("end",()=>{u(o,"INFO","Workspace directory zip created successfully",{targetDir:f,zipFileName:r,elapsedMs:Date.now()-a})}),{archive:h,zipFileName:r}}async function be(t,i,e=200,l=null){const a=Date.now(),o=`computer:${t}:${i}`;if(!t)throw new E("userId cannot be empty",{field:"userId"});if(!i)throw new E("cId cannot be empty",{field:"cId"});const m=String(t),s=String(i),n=ge(l,m,s);if(!n)return u(o,"WARN","COMPUTER_WORKSPACE_DIR is not configured"),{success:!0,message:"Log workspace is not configured",logs:[],totalLines:0,startIndex:1,logFileName:null};if(!c.existsSync(n))return u(o,"DEBUG","Log directory does not exist",{logDir:n}),{success:!0,message:"Log directory does not exist",logs:[],totalLines:0,startIndex:1,logFileName:null};const f=(await c.promises.readdir(n,{withFileTypes:!0})).filter(D=>D.isFile()).map(D=>D.name);if(f.length===0)return u(o,"DEBUG","No log file found",{logDir:n}),{success:!0,message:"No log file found",logs:[],totalLines:0,startIndex:1,logFileName:null};const r=[];for(const D of f){const z=d.join(n,D),$=await c.promises.stat(z);r.push({name:D,fullPath:z,mtime:$.mtimeMs,size:$.size})}r.sort((D,z)=>z.mtime-D.mtime);const h=r[0],p=(await c.promises.readFile(h.fullPath,"utf8")).split(`
2
- `).filter(D=>D.length>0),g=p.length,S=Math.max(1,e),I=Math.max(0,g-S),R=I+1,F=p.slice(I).map((D,z)=>({line:R+z,content:D}));return u(o,"DEBUG","Get latest logs",{fileName:h.name,fileSize:h.size,totalLines:g,returnedLines:F.length,elapsedMs:Date.now()-a}),{success:!0,message:"Get log successfully",logs:F,totalLines:g,startIndex:R,logFileName:h.name}}async function fe(t,i){try{const l=(await c.promises.readdir(t,{withFileTypes:!0})).filter(s=>!s.name.startsWith(".")&&s.name!=="node_modules");if(l.length!==1||!l[0].isDirectory())return;const a=d.join(t,l[0].name),o=d.join(t,`..tmp_${Date.now()}_${Math.round(Math.random()*1e6)}`);try{await c.promises.rename(a,o)}catch(s){if(s.code==="EXDEV")await c.promises.cp(a,o,{recursive:!0}),await c.promises.rm(a,{recursive:!0,force:!0});else throw s}const m=await c.promises.readdir(o);for(const s of m){const n=d.join(o,s),y=d.join(t,s);try{await c.promises.rename(n,y)}catch(f){if(f.code==="EXDEV")(await c.promises.lstat(n)).isDirectory()?await we(n,y):(await c.promises.copyFile(n,y),await c.promises.unlink(n));else throw f}}await c.promises.rm(o,{recursive:!0,force:!0}),u(i,"INFO","Removed single top-level directory from imported zip",{removedDir:l[0].name})}catch(e){u(i,"WARN","Failed to remove top-level directory from imported zip, continuing",{error:e.message})}}async function xe(t,i){if(!c.existsSync(t)){await c.promises.mkdir(t,{recursive:!0});return}const e=await c.promises.readdir(t,{withFileTypes:!0});for(const l of e){if(j.has(l.name))continue;const a=d.join(t,l.name);await c.promises.rm(a,{recursive:!0,force:!0}),u(i,"DEBUG","Removed workspace entry during import",{name:l.name})}}async function Ne(t,i,e){if(await c.promises.mkdir(i,{recursive:!0}),!c.existsSync(t)){await c.promises.mkdir(t,{recursive:!0});return}const l=await c.promises.readdir(t,{withFileTypes:!0});for(const a of l){if(j.has(a.name))continue;const o=d.join(t,a.name),m=d.join(i,a.name);await J(o,m),u(e,"DEBUG","Backed up workspace entry before import",{name:a.name})}}async function Pe(t,i,e){if(!c.existsSync(t))return;const l=await c.promises.readdir(t,{withFileTypes:!0});for(const a of l){if(j.has(a.name))continue;const o=d.join(t,a.name),m=d.join(i,a.name);await J(o,m)}u(e,"INFO","Workspace restored from backup after import failure")}async function ze(t,i,e){const l=await c.promises.readdir(t,{withFileTypes:!0}),a=[];try{for(const o of l){if(j.has(o.name)){u(e,"INFO","Skipping preserved entry from imported zip",{name:o.name});continue}const m=d.join(t,o.name),s=d.join(i,o.name);await J(m,s),a.push(o.name)}}catch(o){throw o.mergedEntries=a,o}}async function Ce(t,i,e,l,a=null){const o=Date.now(),m=`computer:${t}:${i}`,s=ie(a);if(!t)throw new E("userId cannot be empty",{field:"userId"});if(!i)throw new E("cId cannot be empty",{field:"cId"});if(!e||!e.path)throw new E("file is required",{field:"file"});if(!s&&!a?.workspaceDir)throw new O("COMPUTER_WORKSPACE_DIR is not configured");if(d.extname(e.originalname||e.filename||"").toLowerCase()!==".zip")throw new E("Only zip files are supported",{field:"file",originalName:e.originalname});const y=String(t),f=String(i),r=l&&l.trim()?l.trim():_(a,y,f),h=d.join(r,".tmp"),w=d.join(h,`import_project_extract_${Date.now()}_${Math.round(Math.random()*1e6)}`);let p=null;u(m,"INFO","Start importing project from zip",{userId:y,cId:f,targetDir:r,fileName:e.originalname});try{await c.promises.mkdir(r,{recursive:!0}),await c.promises.mkdir(h,{recursive:!0}),await c.promises.mkdir(w,{recursive:!0}),await he(e.path,w),await fe(w,m),p=d.join(h,`import_backup_${Date.now()}_${Math.round(Math.random()*1e6)}`),await Ne(r,p,m);try{await ze(w,r,m)}catch(g){throw u(m,"ERROR","Merge imported project failed, restoring workspace",{error:g.message,mergedEntries:g.mergedEntries||[]}),await xe(r,m),await Pe(p,r,m),g}return c.existsSync(p)&&(await c.promises.rm(p,{recursive:!0,force:!0}),p=null),u(m,"INFO","Project imported successfully",{userId:y,cId:f,targetDir:r,elapsedMs:Date.now()-o}),{success:!0,message:"Project imported successfully",userId:y,cId:f,targetDir:r}}catch(g){throw u(m,"ERROR","Failed to import project",{userId:y,cId:f,targetDir:r,error:g.message,elapsedMs:Date.now()-o}),g instanceof E||g instanceof O||g instanceof pe?g:new O(`Failed to import project: ${g.message}`,{userId:y,cId:f,originalError:g.message})}finally{if(c.existsSync(w))try{await c.promises.rm(w,{recursive:!0,force:!0})}catch(g){u(m,"WARN","Failed to clean import extract directory",{extractRoot:w,error:g.message})}if(p&&c.existsSync(p))try{await c.promises.rm(p,{recursive:!0,force:!0})}catch(g){u(m,"WARN","Failed to clean import backup directory",{backupDir:p,error:g.message})}if(e?.path&&c.existsSync(e.path))try{await c.promises.unlink(e.path)}catch(g){u(m,"WARN","Failed to clean uploaded zip file",{tempZipPath:e.path,error:g.message})}}}async function Le(t,i,e,l,a,o=null){const m=`computer:${t}:${i}`;if(!e||typeof e!="string"||!e.trim())throw new E("fileName cannot be empty",{field:"fileName"});if(l!=null&&typeof l!="string")throw new E("content must be a string",{field:"content"});const s=e.trim(),n=l??"";return u(m,"INFO","Generate file from text content",{userId:t,cId:i,fileName:s,contentLength:n.length}),{...await ee(t,i,{contents:n,originalname:d.basename(s),size:Buffer.byteLength(n,"utf8")},s,a,o),message:"File generated successfully",fileName:s}}export{Re as getFileList,Se as resolveExistingFile,De as searchFiles,Ie as updateFiles,ee as uploadFile,ve as uploadFiles,Le as generateFile,ke as downloadAllFiles,be as getLatestLogs,Ce as importProject,j as IMPORT_PROJECT_PRESERVED_ENTRIES,fe as removeTopLevelDir};
1
+ import c from"fs";import d from"path";import re from"archiver";import P from"../../appConfig/index.js";import{log as u}from"../log/logUtils.js";import{ValidationError as E,SystemError as O,FileError as pe}from"../error/errorHandler.js";import{extractZip as he}from"../common/zipUtils.js";import{moveDirectory as we,movePath as J}from"../common/fileSystemUtils.js";import{resolveLogDir as ge,resolveWorkspaceDir as _,resolveWorkspaceRoot as ie}from"./workspaceContext.js";const j=new Set([".git",".agents",".claude",".codex",".opencode",".tmp",".logs"]),ye=100*1024*1024,X=P.DOWNLOAD_MAX_FILE_SIZE_BYTES||ye;async function Ee(t,i,e,l,a){const o=[],m=await c.promises.readdir(t,{withFileTypes:!0});m.sort((s,n)=>s.isDirectory()&&!n.isDirectory()?-1:!s.isDirectory()&&n.isDirectory()?1:s.name.toLowerCase().localeCompare(n.name.toLowerCase()));for(const s of m){const n=d.join(t,s.name);if(s.name.startsWith(".")&&s.name!==".gitignore"||(P.CONTENT_TRAVERSE_EXCLUDE_FILES||[]).includes(s.name)||s.isDirectory()&&P.TRAVERSE_EXCLUDE_DIRS.includes(s.name))continue;const f=d.relative(i,n).replace(/\\/g,"/");if(s.isDirectory()){o.push({name:f,isDir:!0});continue}try{o.push({name:f,isDir:!1,fileProxyUrl:G(l,f,a),isLink:s.isSymbolicLink()})}catch(r){u(e,"WARN",`\u5904\u7406\u6587\u4EF6\u5931\u8D25: ${n}`,{error:r.message})}}return o}async function se(t,i,e,l,a){const o=[],m=await c.promises.readdir(t,{withFileTypes:!0});m.sort((s,n)=>s.isDirectory()&&!n.isDirectory()?-1:!s.isDirectory()&&n.isDirectory()?1:s.name.toLowerCase().localeCompare(n.name.toLowerCase()));for(const s of m){const n=d.join(t,s.name);if(!(s.name.startsWith(".")&&s.name!==".gitignore"||(P.CONTENT_TRAVERSE_EXCLUDE_FILES||[]).includes(s.name))&&!(s.isDirectory()&&P.TRAVERSE_EXCLUDE_DIRS.includes(s.name)))if(s.isDirectory()){const f=await se(n,i,e,l,a);if(f.length===0){const r=i||t,h=d.relative(r,n).replace(/\\/g,"/");o.push({name:h,isDir:!0})}else o.push(...f)}else try{const f=i||t,r=d.relative(f,n).replace(/\\/g,"/");o.push({name:r,isDir:!1,fileProxyUrl:G(l,r,a),isLink:s.isSymbolicLink()})}catch(f){u(e,"WARN",`\u5904\u7406\u6587\u4EF6\u5931\u8D25: ${n}`,{error:f.message})}}return o}function ne(t,i){const e=(i==null?"":String(i)).trim();if(!e||e==="."||e==="/")return d.resolve(t);const l=d.normalize(e).replace(/^[\/\\]+/,"");if(!l||l===".")return d.resolve(t);if(d.isAbsolute(e)||l.split(d.sep).includes(".."))throw new E("relativePath \u975E\u6CD5\uFF0C\u4E0D\u5141\u8BB8\u8D8A\u51FA\u76EE\u6807\u76EE\u5F55",{field:"relativePath",relativePath:i});const a=d.resolve(t),o=d.resolve(t,l);if(o!==a&&!o.startsWith(a+d.sep))throw new E("relativePath \u975E\u6CD5\uFF0C\u4E0D\u5141\u8BB8\u8D8A\u51FA\u76EE\u6807\u76EE\u5F55",{field:"relativePath",relativePath:i});return o}function oe(t,i){const e=(i==null?"":String(i)).trim();if(!e)return null;const l=d.resolve(t);let a;if(d.isAbsolute(e))a=d.resolve(e);else{const m=d.normalize(e).replace(/^[\/\\]+/,"");if(!m||m==="."||m.split(d.sep).includes(".."))return null;a=d.resolve(t,m)}if(a!==l&&!a.startsWith(l+d.sep))return null;const o=d.relative(l,a).replace(/\\/g,"/");return!o||o.startsWith("..")?null:{absPath:a,name:o}}function G(t,i,e){if(!t||!i)return null;const l=i.split("/").map(o=>encodeURIComponent(o)).join("/");let a=`${t}/${l}`;return e&&(a+=`?customTargetDir=${encodeURIComponent(e)}`),a}async function Se(t,i,e,l,a,o=null){if(!t)throw new E("userId \u4E0D\u80FD\u4E3A\u7A7A",{field:"userId"});if(!i)throw new E("cId \u4E0D\u80FD\u4E3A\u7A7A",{field:"cId"});if(!e||!String(e).trim())throw new E("filePath \u4E0D\u80FD\u4E3A\u7A7A",{field:"filePath"});const m=String(t),s=String(i),n=a&&a.trim()?a.trim():null,y=n||_(o,m,s);if(!c.existsSync(y))return{exists:!1};let f=oe(y,e);if(!f){const r=String(e).trim().replace(/^[\/\\]+/,"");r&&r!==String(e).trim()&&(f=oe(y,r))}if(!f)return{exists:!1};try{if(!(await c.promises.stat(f.absPath)).isFile())return{exists:!1}}catch{return{exists:!1}}return{exists:!0,name:f.name,fileProxyUrl:G(l,f.name,n)}}async function ae(t,i,e,l,a=""){const o=await c.promises.readdir(d.join(t,a),{withFileTypes:!0});let m=0;for(const s of o){const n=a?d.join(a,s.name):s.name,y=n.split(d.sep).filter(Boolean);if(y.some(w=>w.startsWith(".")))continue;const f=y[y.length-1];if(i.includes(f)||y.some(w=>e.includes(w)))continue;const r=d.join(t,n);let h;try{h=await c.promises.lstat(r)}catch(w){u(l,"WARN","Error occurred when getting file stats, skipping",{filePath:n.replace(/\\/g,"/"),error:w.message});continue}h.isSymbolicLink()||h.nlink>1||(h.isDirectory()?m+=await ae(t,i,e,l,n):h.isFile()&&(m+=h.size))}return m}async function Re(t,i,e,l,a,o,m=null){const s=Date.now(),n=`computer:${t}:${i}`,y=!(o===!1||o==="false");if(!t)throw new E("userId \u4E0D\u80FD\u4E3A\u7A7A",{field:"userId"});if(!i)throw new E("cId \u4E0D\u80FD\u4E3A\u7A7A",{field:"cId"});const f=String(t),r=String(i),h=l&&l.trim()?l.trim():null,w=h||_(m,f,r);if(!c.existsSync(w))return u(n,"INFO","Directory does not exist, returning empty list",{targetDir:w,userId:f,cId:r}),{files:[],recursive:y};const p=ne(w,a);if(!c.existsSync(p))return u(n,"INFO","List path does not exist, returning empty list",{targetDir:w,listDir:p,relativePath:a||"",userId:f,cId:r}),{files:[],recursive:y};if(!(await c.promises.stat(p)).isDirectory())throw new E("relativePath \u5FC5\u987B\u662F\u76EE\u5F55",{field:"relativePath",relativePath:a});u(n,"DEBUG","Start getting user file list",{targetDir:w,listDir:p,relativePath:a||"",recursive:y,userId:f,cId:r});try{const S=y?await se(p,w,n,e,h):await Ee(p,w,n,e,h);return u(n,"INFO","User file list obtained successfully",{fileCount:S.length,targetDir:w,listDir:p,relativePath:a||"",recursive:y,userId:f,cId:r,elapsedMs:Date.now()-s}),{files:S,recursive:y}}catch(S){throw S instanceof E?S:(u(n,"ERROR","Failed to get user file list",{targetDir:w,listDir:p,relativePath:a||"",recursive:y,userId:f,cId:r,error:S.message,elapsedMs:Date.now()-s}),new O(`Failed to get file list: ${S.message}`,{targetDir:w,originalError:S.message}))}}const Fe=5e3;function Q(t,i){const e=Number.parseInt(t,10);if(!Number.isFinite(e)||e<=0)throw new E(`${i} \u5FC5\u987B\u4E3A\u6B63\u6574\u6570`,{field:i,value:t});return e}function ce(t,i,e){return e?i.toLowerCase().includes(e)?!0:t.toLowerCase().includes(e):!1}function le(t,i,e){return!!(t.name.startsWith(".")&&t.name!==".gitignore"||i.includes(t.name)||t.isDirectory()&&e.includes(t.name))}function me(t,i,e,l){for(let a=i;a<t.length;a++)if(!le(t[a],e,l))return!0;return!1}function ue(t,i){return Date.now()-t>=i}async function Ie(t,i,e,l,a,o,m,s,n,y=null){const f=Date.now(),r=`computer:${t}:${i}`;if(!t)throw new E("userId \u4E0D\u80FD\u4E3A\u7A7A",{field:"userId"});if(!i)throw new E("cId \u4E0D\u80FD\u4E3A\u7A7A",{field:"cId"});const h=(l==null?"":String(l)).trim();if(!h)throw new E("kw \u4E0D\u80FD\u4E3A\u7A7A",{field:"kw"});const w=Q(m,"limit"),p=Q(s,"maxVisit"),g=Q(n,"timeoutMs"),S=h.toLowerCase(),D=String(t),R=String(i),v=a&&a.trim()?a.trim():null,F=v||_(y,D,R);if(!c.existsSync(F))return{files:[],truncated:!1,visited:0};const I=ne(F,o);if(!c.existsSync(I))return{files:[],truncated:!1,visited:0};if(!(await c.promises.stat(I)).isDirectory())throw new E("relativePath \u5FC5\u987B\u662F\u76EE\u5F55",{field:"relativePath",relativePath:o});const $=d.relative(F,I).replace(/\\/g,"/"),q=P.CONTENT_TRAVERSE_EXCLUDE_FILES||[],Z=P.TRAVERSE_EXCLUDE_DIRS||[],b=[],K=new Set;let A=0,Y=!1,C=!1;const L=[$],U=()=>A>=p||ue(f,g);for(;L.length>0;){if(U()){C=!0;break}if(b.length>=w){L.length>0&&(C=!0);break}const N=L.shift();if(K.has(N))continue;K.add(N);const M=N?d.join(F,N):F;let W;try{W=await c.promises.readdir(M,{withFileTypes:!0})}catch(k){u(r,"WARN","search readdir failed",{dirRel:N,error:k.message});continue}W.sort((k,x)=>k.isDirectory()&&!x.isDirectory()?-1:!k.isDirectory()&&x.isDirectory()?1:k.name.toLowerCase().localeCompare(x.name.toLowerCase()));const H=[];let B=!1,V=!1;for(let k=0;k<W.length;k++){const x=W[k];if(le(x,q,Z))continue;if(U()){C=!0,B=!0,V=!0;break}A+=1;const te=d.join(M,x.name),T=d.relative(F,te).replace(/\\/g,"/");if(x.isDirectory()){if(H.push(T),b.length<w&&ce(T,x.name,S)&&(b.push({name:T,isDir:!0}),b.length>=w)){B=!0,V=me(W,k+1,q,Z);break}continue}try{if(b.length<w&&ce(T,x.name,S)&&(b.push({name:T,isDir:!1,fileProxyUrl:G(e,T,v),isLink:x.isSymbolicLink()}),b.length>=w)){B=!0,V=me(W,k+1,q,Z);break}}catch(de){u(r,"WARN",`search \u5904\u7406\u6587\u4EF6\u5931\u8D25: ${te}`,{error:de.message})}}if(B||U()){(U()||V||H.length>0||L.length>0)&&(C=!0);continue}for(const k of H){if(U()){C=!0;break}if(L.length>=Fe){C=!0;break}if(A+L.length>=p){C=!0;break}K.has(k)||L.push(k)}}return b.sort((N,M)=>N.isDir!==M.isDir?N.isDir?-1:1:String(N.name).localeCompare(String(M.name))),Y=C||L.length>0||A>=p||ue(f,g),u(r,"INFO","File search completed",{kw:h,matchCount:b.length,visited:A,truncated:Y,relativePath:o||"",elapsedMs:Date.now()-f}),{files:b,truncated:Y,visited:A}}async function De(t,i,e,l,a=null){const o=Date.now(),m=`computer:${t}:${i}`;if(!t)throw new E("userId cannot be empty",{field:"userId"});if(!i)throw new E("cId cannot be empty",{field:"cId"});if(!Array.isArray(e))throw new E("files must be an array",{field:"files"});const s=String(t),n=String(i),y=l&&l.trim()?l.trim():null,f=y||_(a,s,n);c.existsSync(f)||c.mkdirSync(f,{recursive:!0});for(let r=0;r<e.length;r++){const h=e[r];if(!h||typeof h.operation!="string")throw new E(`files[${r}].operation cannot be empty`,{field:`files[${r}].operation`});if(!h.name||typeof h.name!="string")throw new E(`files[${r}].name cannot be empty`,{field:`files[${r}].name`});const w=h.operation.toLowerCase();if(!["create","delete","rename","modify"].includes(w))throw new E(`files[${r}].operation must be one of create, delete, rename or modify`,{field:`files[${r}].operation`});if(w==="rename"&&!h.renameFrom)throw new E(`files[${r}].renameFrom cannot be empty (rename operation requires)`,{field:`files[${r}].renameFrom`});if(w==="modify"&&h.isDir!==!0&&typeof h.contents!="string")throw new E(`files[${r}].contents must be a string (modify operation requires)`,{field:`files[${r}].contents`})}u(m,"DEBUG","Start updating user files",{userId:s,cId:n,filesCount:e.length});try{for(const r of e){const h=r.operation.toLowerCase(),w=r.name,p=d.normalize(w).replace(/^[\/\\]+/,""),g=d.join(f,p),S=d.resolve(g),D=d.resolve(f);if(!S.startsWith(D+d.sep)&&S!==D){u(m,"WARN","File path is not secure, skipping",{filePath:p,resolvedPath:S});continue}switch(h){case"create":{if(r.isDir===!0){if(c.existsSync(g)){if((await c.promises.stat(g)).isFile())throw new E("Cannot create directory, file with the same name already exists",{filePath:p});u(m,"INFO","Directory already exists, skipping creation",{filePath:p});break}await c.promises.mkdir(g,{recursive:!0}),u(m,"INFO","Directory created successfully",{filePath:p});break}if(c.existsSync(g)){if((await c.promises.stat(g)).isDirectory())throw new E("Cannot create file, directory with the same name already exists",{filePath:p});u(m,"INFO","File already exists, skipping creation",{filePath:p});break}await c.promises.mkdir(d.dirname(g),{recursive:!0});const R=r.contents||"";await c.promises.writeFile(g,R,"utf8"),u(m,"INFO","File created successfully",{filePath:p});break}case"delete":{c.existsSync(g)?(await c.promises.stat(g)).isDirectory()?(await c.promises.rm(g,{recursive:!0,force:!0}),u(m,"INFO","Directory deleted successfully",{filePath:p})):(await c.promises.unlink(g),u(m,"INFO","File deleted successfully",{filePath:p})):u(m,"WARN","The file or directory to be deleted does not exist",{filePath:p});break}case"rename":{const R=r.renameFrom;if(!R||typeof R!="string"){u(m,"WARN","Rename operation missing renameFrom",{filePath:p});break}const v=d.normalize(R).replace(/^[\/\\]+/,""),F=d.join(f,v),I=d.resolve(F);if(!I.startsWith(D+d.sep)&&I!==D){u(m,"WARN","Source path is not secure, skipping rename",{sourcePath:v,targetPath:p});break}if(c.existsSync(F)){const $=(await c.promises.stat(F)).isDirectory();await c.promises.mkdir(d.dirname(g),{recursive:!0}),await c.promises.rename(F,g),u(m,"INFO",$?"Directory renamed successfully":"File renamed successfully",{sourcePath:v,targetPath:p})}else u(m,"WARN","The file or directory to be renamed does not exist",{sourcePath:v});break}case"modify":{if(!c.existsSync(g)){u(m,"WARN","The file to be modified does not exist",{filePath:p});break}if((await c.promises.stat(g)).isDirectory()){u(m,"INFO","The target is a directory, skipping modification",{filePath:p});break}const v=typeof r.contents=="string"?r.contents:"";if(await c.promises.readFile(g,"utf8")===v){u(m,"INFO","File content has no changes, skipping write",{filePath:p});break}await c.promises.writeFile(g,v,"utf8"),u(m,"INFO","File modified successfully",{filePath:p});break}default:{u(m,"WARN","Unsupported operation type",{operation:h,filePath:p});break}}}return u(m,"INFO","User files updated successfully",{userId:s,cId:n,filesCount:e.length,elapsedMs:Date.now()-o}),{success:!0,message:"User files updated successfully",userId:s,cId:n,filesCount:e.length}}catch(r){throw u(m,"ERROR","User files updated failed",{userId:s,cId:n,error:r.message,elapsedMs:Date.now()-o}),new O(`User files updated failed: ${r.message}`,{userId:s,cId:n,originalError:r.message})}}async function ee(t,i,e,l,a,o=null){const m=Date.now(),s=`computer:${t}:${i}`;if(!t)throw new E("userId cannot be empty",{field:"userId"});if(!i)throw new E("cId cannot be empty",{field:"cId"});if(!e)throw new E("file cannot be empty",{field:"file"});if(!l||typeof l!="string")throw new E("filePath cannot be empty",{field:"filePath"});const n=String(t),y=String(i),f=a&&a.trim()?a.trim():null,r=f||_(o,n,y);c.existsSync(r)||c.mkdirSync(r,{recursive:!0});const h=d.normalize(l).replace(/^[\/\\]+/,""),w=d.join(r,h),p=d.resolve(w),g=d.resolve(r);if(!p.startsWith(g+d.sep)&&p!==g)throw new E("File path is not secure, cannot exceed user directory",{field:"filePath",providedPath:l,resolvedPath:p});try{if(await c.promises.mkdir(d.dirname(w),{recursive:!0}),e.buffer)await c.promises.writeFile(w,e.buffer);else if(typeof e.contents=="string")await c.promises.writeFile(w,e.contents,"utf8");else throw new E("File content format is incorrect",{field:"file",hasBuffer:!!e.buffer,hasContents:typeof e.contents});return u(s,"INFO","File uploaded successfully",{userId:n,cId:y,filePath:h,targetPath:p,fileSize:e.buffer?e.buffer.length:e.contents?e.contents.length:0,elapsedMs:Date.now()-m}),{success:!0,message:"File uploaded successfully",fileSize:e.buffer?e.buffer.length:e.contents?e.contents.length:0}}catch(S){throw u(s,"ERROR","File upload failed",{userId:n,cId:y,filePath:h,error:S.message,elapsedMs:Date.now()-m}),new O(`File upload failed: ${S.message}`,{userId:n,cId:y,filePath:h,originalError:S.message})}}async function ve(t,i,e,l,a,o=null){const m=Date.now(),s=`computer:${t}:${i}`;if(!t)throw new E("userId cannot be empty",{field:"userId"});if(!i)throw new E("cId cannot be empty",{field:"cId"});if(!Array.isArray(e))throw new E("files must be an array",{field:"files"});if(!Array.isArray(l))throw new E("filePaths must be an array",{field:"filePaths"});if(e.length!==l.length)throw new E(`File count (${e.length}) does not match path count (${l.length})`,{field:"filePaths"});u(s,"DEBUG","Start batch uploading files",{userId:t,cId:i,filesCount:e.length});const n=[];try{for(let r=0;r<e.length;r++){const h=e[r],w=l[r];if(!h){u(s,"WARN","Empty file object encountered in batch upload, skipping",{index:r,filePath:w}),n.push({success:!1,filePath:w,error:"Empty file object"});continue}if(!w||typeof w!="string"){u(s,"WARN","Invalid file path in batch upload, skipping",{index:r,originalname:h.originalname}),n.push({success:!1,filePath:w||"",originalname:h.originalname,error:"Invalid file path"});continue}try{const p=await ee(t,i,h,w,a,o);n.push({success:!0,filePath:w,originalname:h.originalname,...p})}catch(p){u(s,"ERROR","Single file upload failed in batch upload",{filePath:w,originalname:h.originalname,error:p.message}),n.push({success:!1,filePath:w,originalname:h.originalname,error:p.message})}}const y=n.filter(r=>r.success).length,f=n.filter(r=>!r.success).length;return u(s,"INFO","Batch upload files completed",{userId:t,cId:i,totalCount:e.length,successCount:y,failCount:f,elapsedMs:Date.now()-m}),{success:!0,message:"Batch upload completed",totalCount:e.length,successCount:y,failCount:f,results:n}}catch(y){throw u(s,"ERROR","Batch upload files failed",{userId:t,cId:i,error:y.message,elapsedMs:Date.now()-m}),new O(`Batch upload files failed: ${y.message}`,{userId:t,cId:i,originalError:y.message})}}async function ke(t,i,e,l=null){const a=Date.now(),o=`computer:${t}:${i}`,m=ie(l);if(!t)throw new E("userId cannot be empty",{field:"userId"});if(!i)throw new E("cId cannot be empty",{field:"cId"});if(!m&&!l?.workspacePath)throw new O("COMPUTER_WORKSPACE_DIR is not configured, cannot create zip");const s=String(t),n=String(i),y=e&&e.trim()?e.trim():null,f=y||_(l,s,n);if(!c.existsSync(f)){const S=`${s}_${n}.zip`;u(o,"WARN","Workspace directory does not exist, returning empty zip",{targetDir:f,userId:s,cId:n,zipFileName:S});const D=re("zip",{zlib:{level:9}});return D.append(null,{name:`${s}_${n}/`,type:"directory"}),D.on("warning",R=>{if(R.code==="ENOENT")u(o,"WARN","Encountered file problem when creating empty zip",{message:R.message,code:R.code});else throw u(o,"ERROR","Encountered warning when creating empty zip",{message:R.message,code:R.code}),R}),D.on("error",R=>{u(o,"ERROR","Failed to create empty zip",{message:R.message})}),{archive:D,zipFileName:S}}const r=`${s}_${n}.zip`;u(o,"DEBUG","Start creating workspace directory zip",{targetDir:f,zipFileName:r});const h=re("zip",{zlib:{level:9}}),w=P.CONTENT_TRAVERSE_EXCLUDE_FILES||[],p=P.TRAVERSE_EXCLUDE_DIRS||[],g=await ae(f,w,p,o);if(g>X){const S=X/1024/1024,D=(g/1024/1024).toFixed(2);throw u(o,"WARN","Download rejected due to oversized workspace",{targetDir:f,downloadableSize:g,maxSizeBytes:X}),new E(`Download failed: total file size ${D}MB exceeds limit ${S}MB`,{field:"downloadSize",downloadableSize:g,maxSizeBytes:X})}return h.directory(f,`${s}_${n}`,S=>{const D=S.name||"",R=D.split(/[\/\\]/).filter(Boolean);if(R.some(F=>F.startsWith(".")))return!1;const v=R[R.length-1];if(w.includes(v)||R.some(F=>p.includes(F)))return!1;try{const F=d.join(f,D),I=c.lstatSync(F);if(I.isSymbolicLink()||I.nlink>1)return!1}catch(F){return u(o,"WARN","Error occurred when detecting link file, skipping",{filePath:D,error:F.message}),!1}return S}),h.on("warning",S=>{if(S.code==="ENOENT")u(o,"WARN","Encountered file problem when creating zip",{message:S.message,code:S.code});else throw u(o,"ERROR","Encountered warning when creating zip",{message:S.message,code:S.code}),S}),h.on("error",S=>{u(o,"ERROR","Failed to create zip",{message:S.message,elapsedMs:Date.now()-a})}),h.on("end",()=>{u(o,"INFO","Workspace directory zip created successfully",{targetDir:f,zipFileName:r,elapsedMs:Date.now()-a})}),{archive:h,zipFileName:r}}async function be(t,i,e=200,l=null){const a=Date.now(),o=`computer:${t}:${i}`;if(!t)throw new E("userId cannot be empty",{field:"userId"});if(!i)throw new E("cId cannot be empty",{field:"cId"});const m=String(t),s=String(i),n=ge(l,m,s);if(!n)return u(o,"WARN","COMPUTER_WORKSPACE_DIR is not configured"),{success:!0,message:"Log workspace is not configured",logs:[],totalLines:0,startIndex:1,logFileName:null};if(!c.existsSync(n))return u(o,"DEBUG","Log directory does not exist",{logDir:n}),{success:!0,message:"Log directory does not exist",logs:[],totalLines:0,startIndex:1,logFileName:null};const f=(await c.promises.readdir(n,{withFileTypes:!0})).filter(I=>I.isFile()).map(I=>I.name);if(f.length===0)return u(o,"DEBUG","No log file found",{logDir:n}),{success:!0,message:"No log file found",logs:[],totalLines:0,startIndex:1,logFileName:null};const r=[];for(const I of f){const z=d.join(n,I),$=await c.promises.stat(z);r.push({name:I,fullPath:z,mtime:$.mtimeMs,size:$.size})}r.sort((I,z)=>z.mtime-I.mtime);const h=r[0],p=(await c.promises.readFile(h.fullPath,"utf8")).split(`
2
+ `).filter(I=>I.length>0),g=p.length,S=Math.max(1,e),D=Math.max(0,g-S),R=D+1,F=p.slice(D).map((I,z)=>({line:R+z,content:I}));return u(o,"DEBUG","Get latest logs",{fileName:h.name,fileSize:h.size,totalLines:g,returnedLines:F.length,elapsedMs:Date.now()-a}),{success:!0,message:"Get log successfully",logs:F,totalLines:g,startIndex:R,logFileName:h.name}}async function fe(t,i){try{const l=(await c.promises.readdir(t,{withFileTypes:!0})).filter(s=>!s.name.startsWith(".")&&s.name!=="node_modules");if(l.length!==1||!l[0].isDirectory())return;const a=d.join(t,l[0].name),o=d.join(t,`..tmp_${Date.now()}_${Math.round(Math.random()*1e6)}`);try{await c.promises.rename(a,o)}catch(s){if(s.code==="EXDEV")await c.promises.cp(a,o,{recursive:!0}),await c.promises.rm(a,{recursive:!0,force:!0});else throw s}const m=await c.promises.readdir(o);for(const s of m){const n=d.join(o,s),y=d.join(t,s);try{await c.promises.rename(n,y)}catch(f){if(f.code==="EXDEV")(await c.promises.lstat(n)).isDirectory()?await we(n,y):(await c.promises.copyFile(n,y),await c.promises.unlink(n));else throw f}}await c.promises.rm(o,{recursive:!0,force:!0}),u(i,"INFO","Removed single top-level directory from imported zip",{removedDir:l[0].name})}catch(e){u(i,"WARN","Failed to remove top-level directory from imported zip, continuing",{error:e.message})}}async function xe(t,i){if(!c.existsSync(t)){await c.promises.mkdir(t,{recursive:!0});return}const e=await c.promises.readdir(t,{withFileTypes:!0});for(const l of e){if(j.has(l.name))continue;const a=d.join(t,l.name);await c.promises.rm(a,{recursive:!0,force:!0}),u(i,"DEBUG","Removed workspace entry during import",{name:l.name})}}async function Ne(t,i,e){if(await c.promises.mkdir(i,{recursive:!0}),!c.existsSync(t)){await c.promises.mkdir(t,{recursive:!0});return}const l=await c.promises.readdir(t,{withFileTypes:!0});for(const a of l){if(j.has(a.name))continue;const o=d.join(t,a.name),m=d.join(i,a.name);await J(o,m),u(e,"DEBUG","Backed up workspace entry before import",{name:a.name})}}async function Pe(t,i,e){if(!c.existsSync(t))return;const l=await c.promises.readdir(t,{withFileTypes:!0});for(const a of l){if(j.has(a.name))continue;const o=d.join(t,a.name),m=d.join(i,a.name);await J(o,m)}u(e,"INFO","Workspace restored from backup after import failure")}async function ze(t,i,e){const l=await c.promises.readdir(t,{withFileTypes:!0}),a=[];try{for(const o of l){if(j.has(o.name)){u(e,"INFO","Skipping preserved entry from imported zip",{name:o.name});continue}const m=d.join(t,o.name),s=d.join(i,o.name);await J(m,s),a.push(o.name)}}catch(o){throw o.mergedEntries=a,o}}async function Ce(t,i,e,l,a=null){const o=Date.now(),m=`computer:${t}:${i}`,s=ie(a);if(!t)throw new E("userId cannot be empty",{field:"userId"});if(!i)throw new E("cId cannot be empty",{field:"cId"});if(!e||!e.path)throw new E("file is required",{field:"file"});if(!s&&!a?.workspacePath)throw new O("COMPUTER_WORKSPACE_DIR is not configured");if(d.extname(e.originalname||e.filename||"").toLowerCase()!==".zip")throw new E("Only zip files are supported",{field:"file",originalName:e.originalname});const y=String(t),f=String(i),r=l&&l.trim()?l.trim():_(a,y,f),h=d.join(r,".tmp"),w=d.join(h,`import_project_extract_${Date.now()}_${Math.round(Math.random()*1e6)}`);let p=null;u(m,"INFO","Start importing project from zip",{userId:y,cId:f,targetDir:r,fileName:e.originalname});try{await c.promises.mkdir(r,{recursive:!0}),await c.promises.mkdir(h,{recursive:!0}),await c.promises.mkdir(w,{recursive:!0}),await he(e.path,w),await fe(w,m),p=d.join(h,`import_backup_${Date.now()}_${Math.round(Math.random()*1e6)}`),await Ne(r,p,m);try{await ze(w,r,m)}catch(g){throw u(m,"ERROR","Merge imported project failed, restoring workspace",{error:g.message,mergedEntries:g.mergedEntries||[]}),await xe(r,m),await Pe(p,r,m),g}return c.existsSync(p)&&(await c.promises.rm(p,{recursive:!0,force:!0}),p=null),u(m,"INFO","Project imported successfully",{userId:y,cId:f,targetDir:r,elapsedMs:Date.now()-o}),{success:!0,message:"Project imported successfully",userId:y,cId:f,targetDir:r}}catch(g){throw u(m,"ERROR","Failed to import project",{userId:y,cId:f,targetDir:r,error:g.message,elapsedMs:Date.now()-o}),g instanceof E||g instanceof O||g instanceof pe?g:new O(`Failed to import project: ${g.message}`,{userId:y,cId:f,originalError:g.message})}finally{if(c.existsSync(w))try{await c.promises.rm(w,{recursive:!0,force:!0})}catch(g){u(m,"WARN","Failed to clean import extract directory",{extractRoot:w,error:g.message})}if(p&&c.existsSync(p))try{await c.promises.rm(p,{recursive:!0,force:!0})}catch(g){u(m,"WARN","Failed to clean import backup directory",{backupDir:p,error:g.message})}if(e?.path&&c.existsSync(e.path))try{await c.promises.unlink(e.path)}catch(g){u(m,"WARN","Failed to clean uploaded zip file",{tempZipPath:e.path,error:g.message})}}}async function Le(t,i,e,l,a,o=null){const m=`computer:${t}:${i}`;if(!e||typeof e!="string"||!e.trim())throw new E("fileName cannot be empty",{field:"fileName"});if(l!=null&&typeof l!="string")throw new E("content must be a string",{field:"content"});const s=e.trim(),n=l??"";return u(m,"INFO","Generate file from text content",{userId:t,cId:i,fileName:s,contentLength:n.length}),{...await ee(t,i,{contents:n,originalname:d.basename(s),size:Buffer.byteLength(n,"utf8")},s,a,o),message:"File generated successfully",fileName:s}}export{Re as getFileList,Se as resolveExistingFile,Ie as searchFiles,De as updateFiles,ee as uploadFile,ve as uploadFiles,Le as generateFile,ke as downloadAllFiles,be as getLatestLogs,Ce as importProject,j as IMPORT_PROJECT_PRESERVED_ENTRIES,fe as removeTopLevelDir};
@@ -1,14 +1,14 @@
1
- import n from"fs";import c from"path";import{exec as Ee}from"child_process";import je from"cross-spawn";import Ae from"archiver";import Q from"../../appConfig/index.js";import{extractZip as H}from"../common/zipUtils.js";import{ValidationError as d,SystemError as Z,FileError as I}from"../error/errorHandler.js";import{log as r}from"../log/logUtils.js";import{createPnpmNpmrc as Oe}from"../common/npmrcUtils.js";import{ensurePrimaryAgentDirs as he,syncAgents as de,linkWorkspaceToAgentStore as ke,isWorkspaceSkillsSymlinked as $e}from"../common/AgentWorkspaceUtils.js";import{ensureAgentStoreDirs as ye,tryAcquireAgentStoreLock as ue,releaseAgentStoreLock as ge,pruneAgentSkills as ze,installSkillDir as ie,replaceAgentsDir as Me,agentSkillExists as ve}from"../common/agentStoreUtils.js";import{writeAgentHookConfigs as Se}from"./hookConfigUtils.js";import{ensureWorkspaceDir as J,resolveWorkspaceDir as We,resolveWorkspaceRoot as se}from"./workspaceContext.js";function re(e){if(!e)return[];if(Array.isArray(e))return e.map(t=>typeof t=="string"?t.trim():t&&typeof t=="object"&&typeof t.url=="string"?t.url.trim():"").filter(Boolean);if(typeof e=="object")return Object.values(e).map(t=>typeof t=="string"?t.trim():"").filter(Boolean);if(typeof e=="string"){const t=e.trim();if(!t)return[];try{const i=JSON.parse(t);return re(i)}catch{}return[t]}return[]}function De(e){if(!e)return[];if(Array.isArray(e)){const t=[];for(const i of e)if(typeof i=="string"&&i.trim())t.push({name:null,url:i.trim()});else if(i&&typeof i=="object"){const s=typeof i.url=="string"?i.url.trim():"",a=i.name!=null?String(i.name).trim():"";s&&t.push({name:a||null,url:s})}return t}if(typeof e=="object")return Object.entries(e).map(([t,i])=>({name:String(t||"").trim()||null,url:typeof i=="string"?i.trim():""})).filter(t=>t.url);if(typeof e=="string"){const t=e.trim();if(!t)return[];try{return De(JSON.parse(t))}catch{return[{name:null,url:t}]}}return[]}function be(e){if(!e)return{};let t=e;if(typeof e=="string")try{t=JSON.parse(e)}catch{return{}}if(!t||typeof t!="object"||Array.isArray(t))return{};const i={};for(const[s,a]of Object.entries(t)){const o=String(s||"").trim(),p=typeof a=="string"?a.trim():"";o&&p&&(i[o]=p)}return i}function xe(e){if(!e)return[];if(Array.isArray(e))return e.map(t=>String(t||"").trim()).filter(Boolean);if(typeof e=="string"){const t=e.trim();if(!t)return[];try{const i=JSON.parse(t);if(Array.isArray(i))return i.map(s=>String(s||"").trim()).filter(Boolean)}catch{}return t.split(",").map(i=>i.trim()).filter(Boolean)}return[]}async function oe(e,t,i){let s;try{s=await fetch(e)}catch(u){throw new I(`Failed to download skill zip from url: ${e}`,{url:e,reason:u.message})}if(!s.ok)throw new I(`Failed to download skill zip from url: ${e}`,{url:e,status:s.status,statusText:s.statusText});const a=s.headers.get("content-type")||"";a&&!a.includes("zip")&&!a.includes("octet-stream")&&r(i,"WARN","Downloaded skill url content-type is not typical zip",{url:e,contentType:a});const o=await s.arrayBuffer(),p=Buffer.from(o);await n.promises.writeFile(t,p)}async function ee(e,t){const i=c.join(e,t);if(n.existsSync(i)&&(await n.promises.lstat(i)).isDirectory())return i;const s=await n.promises.readdir(e,{withFileTypes:!0});for(const a of s){if(!a.isDirectory())continue;const o=c.join(e,a.name),p=c.join(o,t);if(n.existsSync(p)&&(await n.promises.lstat(p)).isDirectory())return p}return null}async function kt(e){return ee(e,"skills")}const Te=".dynamic_add.lock";function Ce(e){const t=c.join(e,Te);return n.existsSync(t)&&n.statSync(t).isFile()}async function V(e){n.existsSync(e)&&await n.promises.rm(e,{recursive:!0,force:!0})}async function Y(e,t){try{await n.promises.rename(e,t)}catch(i){if(i.code==="EXDEV"){async function s(a,o){if((await n.promises.lstat(a)).isDirectory()){await n.promises.mkdir(o,{recursive:!0});const u=await n.promises.readdir(a);for(const l of u)await s(c.join(a,l),c.join(o,l))}else await n.promises.copyFile(a,o)}await s(e,t),await n.promises.rm(e,{recursive:!0,force:!0})}else throw i}}async function me(e){const i=(await n.promises.readdir(e,{withFileTypes:!0})).filter(a=>a.isDirectory()&&!a.name.startsWith("."));return i.find(a=>a.name==="skills")?(await n.promises.readdir(c.join(e,"skills"),{withFileTypes:!0})).filter(o=>o.isDirectory()&&!o.name.startsWith(".")).map(o=>({name:o.name,sourcePath:c.join(e,"skills",o.name)})):i.map(a=>({name:a.name,sourcePath:c.join(e,a.name)}))}const ne=8;async function ae(e,t,i){if(!e||e.length===0)return;const s=Math.max(1,Math.min(t,e.length));let a=0;await Promise.all(Array.from({length:s},async()=>{for(;;){const o=a++;if(o>=e.length)return;await i(e[o],o)}}))}async function Le(e){const{userId:t,cId:i,agentId:s,file:a,skillUrls:o,skillUrlMap:p,skillNames:u,updateSkillNames:l,mcpServersConfig:D,permissionsConfig:x,hooksConfig:f,hookScripts:y,service:F}=e,m=Date.now(),h=`computer:${t}:${i}`,L=be(p),_=De(o),z=xe(u),k=l!=null,M=new Set(xe(l)),b=[],g=[],R=new Set,O=new Set;let j=null,v=!1;if(!t)throw new d("userId cannot be empty",{field:"userId"});if(!i)throw new d("cId cannot be empty",{field:"cId"});if(!s)throw new d("agentId cannot be empty",{field:"agentId"});const T=se(F),S=await J(F,t,i,h),E=c.join(S,".tmp");await n.promises.mkdir(S,{recursive:!0}),await n.promises.mkdir(E,{recursive:!0});const{agentStorePath:N,skillsDir:C,agentsDir:te}=await ye(t,s,h,F),W=A=>!A||!k||M.has(A)?!0:!ve(C,A),q=async(A,P,U)=>{const K=c.join(E,`skill_url_${Date.now()}_${U}_${Math.round(Math.random()*1e6)}.zip`);b.push(K);const G=c.join(E,`skill_url_extract_${Date.now()}_${U}_${Math.round(Math.random()*1e6)}`);g.push(G),await n.promises.mkdir(G,{recursive:!0}),r(h,"INFO","Start download skill zip from url",{userId:t,cId:i,agentId:s,skillName:P,skillUrl:A}),await oe(A,K,h),await H(K,G);const B=await me(G);if(B.length===0){r(h,"WARN","No skill dir found in downloaded zip",{skillUrl:A,destName:P});return}let w=P?B.find($=>$.name===P):null;if(!w&&B.length===1&&(w=B[0]),P&&w){if(!W(P)){O.add(P);return}await ie(w.sourcePath,C,P,{asDynamic:!1}),R.add(P),r(h,"INFO","Skill from url updated to agent store",{userId:t,cId:i,agentId:s,skillUrl:A,skillName:P,zipDirName:w.name});return}for(const $ of B){if(!W($.name)){O.add($.name);continue}await ie($.sourcePath,C,$.name,{asDynamic:!1}),R.add($.name),r(h,"INFO","Skill from url updated to agent store",{userId:t,cId:i,agentId:s,skillUrl:A,skillName:$.name})}};await Se(S,{mcpServersConfig:D,hooksConfig:f,permissionsConfig:x,hookScripts:y},h);try{const A=await ue(N,h);if(!A.acquired)v=!0;else{j=A.lockPath;const U=Date.now();let K=null;if(a){if(!a.path)throw new d("Uploaded file has no valid path",{field:"file.path"});if(c.extname(a.originalname||a.filename||"").toLowerCase()!==".zip")throw new d("Only zip files are supported",{field:"file",originalName:a.originalname});const w=c.join(E,`skill_extract_${Date.now()}_${Math.round(Math.random()*1e6)}`);g.push(w),await n.promises.mkdir(w,{recursive:!0}),await H(a.path,w);const $=await ee(w,"skills");if($){const pe=(await n.promises.readdir($,{withFileTypes:!0})).filter(X=>X.isDirectory());await ae(pe,ne,async X=>{if(!W(X.name)){O.add(X.name);return}await ie(c.join($,X.name),C,X.name,{asDynamic:!1}),R.add(X.name)})}K=await ee(w,"agents")}const G=Object.entries(L);if(G.length>0){const B=[];for(const[w,$]of G){if(!W(w)){O.add(w),r(h,"INFO","Skip download skill zip (already in agent store)",{userId:t,cId:i,agentId:s,skillName:w,skillUrl:$});continue}B.push({skillName:w,skillUrl:$})}await ae(B,ne,async(w,$)=>{await q(w.skillUrl,w.skillName,$)})}else{const B=[];for(let w=0;w<_.length;w++){const{name:$,url:pe}=_[w];if($&&!W($)){O.add($);continue}B.push({skillUrl:pe,hintedName:$,index:w})}await ae(B,ne,async w=>{await q(w.skillUrl,w.hintedName,w.index)})}await Me(K,te),await ze(C,z,h),r(h,"INFO","Agent store resources prepared",{userId:t,cId:i,agentId:s,selectiveInstall:k,updatedSkills:Array.from(R),skippedSkills:Array.from(O),updateSkillNames:Array.from(M),keepSkillNames:z,skillUrlMapCount:G.length,prepareElapsedMs:Date.now()-U})}const P=await ke(S,C,te,h);return r(h,"INFO","Workspace created successfully (agent store)",{userId:t,cId:i,agentId:s,agentStorePath:N,updatedSkills:Array.from(R),skippedSkills:Array.from(O),skippedStoreUpdate:v,linkElapsedMs:P?.elapsedMs,elapsedMs:Date.now()-m}),{message:v?"Workspace linked to agent store (store update skipped, lock held)":"Workspace created successfully, linked to agent store",workspaceRoot:T,agentStorePath:N,updatedSkills:Array.from(R),skippedSkills:Array.from(O),skippedStoreUpdate:v}}catch(A){throw r(h,"ERROR","Failed to create workspace with agent store",{userId:t,cId:i,agentId:s,error:A.message,elapsedMs:Date.now()-m}),A instanceof d||A instanceof I||A instanceof Z?A:new Z(`Failed to create workspace: ${A.message}`,{userId:t,cId:i,agentId:s})}finally{await ge(j,h);const A=Date.now();for(const P of g)try{n.existsSync(P)&&await n.promises.rm(P,{recursive:!0,force:!0})}catch(U){r(h,"WARN","Failed to clean up temporary extracted zip",{error:U.message})}for(const P of b)try{n.existsSync(P)&&await n.promises.unlink(P)}catch(U){r(h,"WARN","Failed to clean up downloaded skill zip file",{downloadedZipPath:P,error:U.message})}try{a&&a.path&&n.existsSync(a.path)&&await n.promises.unlink(a.path)}catch(P){r(h,"WARN","Failed to clean up uploaded zip file",{tempZipPath:a?.path,error:P.message})}r(h,"INFO","Workspace temp cleanup completed",{userId:t,cId:i,agentId:s,elapsedMs:Date.now()-A,totalElapsedMs:Date.now()-m})}}async function Be(e,t,i,s,a,o,p,u,l,D,x,f,y=null){if(l!=null&&String(l).trim()!=="")return Le({userId:e,cId:t,agentId:l,file:i,skillUrls:s,skillUrlMap:f,skillNames:D,updateSkillNames:x,mcpServersConfig:a,permissionsConfig:o,hooksConfig:p,hookScripts:u,service:y});const F=Date.now(),m=`computer:${e}:${t}`,h=re(s),L=[],_=[],z=new Set;if(!e)throw new d("userId cannot be empty",{field:"userId"});if(!t)throw new d("cId cannot be empty",{field:"cId"});const k=se(y),M=await J(y,e,t,m),b=c.join(M,".tmp"),{skillsDir:g,agentsDir:R,agentTypes:O}=await he(M);n.existsSync(M)||await n.promises.mkdir(M,{recursive:!0});const j=c.join(b,`preserved_skills_${Date.now()}_${Math.round(Math.random()*1e6)}`);if(n.existsSync(g)){const E=(await n.promises.readdir(g,{withFileTypes:!0})).filter(N=>N.isDirectory()&&Ce(c.join(g,N.name)));if(E.length>0){await n.promises.mkdir(j,{recursive:!0});for(const N of E){const C=c.join(g,N.name),te=c.join(j,N.name);await Y(C,te)}r(m,"INFO","\u4FDD\u7559\u542B .dynamic_add.lock \u7684 skill",{preserved:E.map(N=>N.name)})}}if(await V(g),await V(R),await n.promises.mkdir(g,{recursive:!0}),await n.promises.mkdir(R,{recursive:!0}),n.existsSync(j)){const S=await n.promises.readdir(j,{withFileTypes:!0});for(const E of S)if(E.isDirectory()){const N=c.join(j,E.name),C=c.join(g,E.name);await Y(N,C)}await V(j)}const v=n.existsSync(g),T=n.existsSync(R);if(r(m,"INFO","Deleted old skills and agents directories completed",{userId:e,cId:t,targetSkillsDir:g,targetAgentsDir:R,agentTypes:O,skillsExists:v,agentsExists:T}),await Se(M,{mcpServersConfig:a,hooksConfig:p,permissionsConfig:o,hookScripts:u},m),!i&&h.length===0){const S=await de(M,m);return r(m,"INFO","Created workspace (no uploaded file, no skills and agents)",{userId:e,cId:t,workspaceRoot:k,skillsDir:null,agentsDir:null,agentTypes:O,syncAgentsElapsedMs:S?.elapsedMs,elapsedMs:Date.now()-F}),{message:"Workspace created (no uploaded file, no skills and agents)",workspaceRoot:k}}if(i){if(!i.path)throw new d("Uploaded file has no valid path",{field:"file.path"});if(c.extname(i.originalname||i.filename||"").toLowerCase()!==".zip")throw new d("Only zip files are supported",{field:"file",originalName:i.originalname})}r(m,"DEBUG","Start processing workspace resources",{userId:e,cId:t,workspaceRoot:k,hasUploadedZip:!!i,skillUrlsCount:h.length});try{n.existsSync(b)||await n.promises.mkdir(b,{recursive:!0});const S=[];if(i){const W=c.join(b,`skill_extract_${Date.now()}_${Math.round(Math.random()*1e6)}`);_.push(W),await n.promises.mkdir(W,{recursive:!0}),r(m,"DEBUG","Start extracting uploaded zip file",{extractRoot:W}),await H(i.path,W),r(m,"DEBUG","Uploaded zip file extracted successfully",{extractRoot:W});const q=await ee(W,"skills"),A=await ee(W,"agents");if(q){await n.promises.mkdir(g,{recursive:!0});const P=await n.promises.readdir(q,{withFileTypes:!0});for(const U of P){if(!U.isDirectory())continue;const K=c.join(q,U.name),G=c.join(g,U.name);n.existsSync(G)&&await V(G),await Y(K,G),z.add(U.name)}S.push("skills"),r(m,"INFO","skills updated to workspace",{userId:e,cId:t,workspaceRoot:k,targetSkillsDir:g,agentTypes:O})}else r(m,"INFO","skills directory not found in uploaded zip, skipping",{userId:e,cId:t,extractRoot:W});A?(await V(R),await Y(A,R),S.push("agents"),r(m,"INFO","agents updated to workspace",{userId:e,cId:t,workspaceRoot:k,targetAgentsDir:R,agentTypes:O})):r(m,"INFO","agents directory not found in uploaded zip, skipping",{userId:e,cId:t,extractRoot:W})}for(let W=0;W<h.length;W+=1){const q=h[W],A=c.join(b,`skill_url_${Date.now()}_${W}_${Math.round(Math.random()*1e6)}.zip`);L.push(A);const P=c.join(b,`skill_url_extract_${Date.now()}_${W}_${Math.round(Math.random()*1e6)}`);_.push(P),await n.promises.mkdir(P,{recursive:!0}),r(m,"INFO","Start download skill zip from url",{userId:e,cId:t,skillUrl:q}),await oe(q,A,m),r(m,"INFO","Skill zip downloaded, start extracting",{userId:e,cId:t,skillUrl:q,downloadedZipPath:A}),await H(A,P);const K=(await n.promises.readdir(P,{withFileTypes:!0})).filter(w=>w.isDirectory()&&!w.name.startsWith(".")),B=K.find(w=>w.name==="skills")?(await n.promises.readdir(c.join(P,"skills"),{withFileTypes:!0})).filter(w=>w.isDirectory()&&!w.name.startsWith(".")).map(w=>({name:w.name,sourcePath:c.join(P,"skills",w.name)})):K.map(w=>({name:w.name,sourcePath:c.join(P,w.name)}));if(B.length===0){r(m,"WARN","No skill directory found after extracting skill url zip",{userId:e,cId:t,skillUrl:q,extractRoot:P});continue}await n.promises.mkdir(g,{recursive:!0});for(const w of B){const $=c.join(g,w.name);n.existsSync($)&&await V($),await Y(w.sourcePath,$),z.add(w.name),r(m,"INFO","Skill from url updated to workspace",{userId:e,cId:t,skillUrl:q,skillName:w.name,destSkillPath:$})}S.includes("skills")||S.push("skills")}S.length===0&&r(m,"WARN","No valid skills or agents directories found",{userId:e,cId:t,hasUploadedZip:!!i,skillUrlsCount:h.length});const E=Array.from(z),N=S.length>0?`Workspace created successfully, ${S.join(" and ")} updated`:"Workspace created successfully (skills and agents directories not found)",C=Date.now()-F;r(m,"INFO","Workspace resources prepared",{userId:e,cId:t,updatedDirs:S,updatedSkills:E,agentTypes:O,prepareElapsedMs:C});const te=await de(M,m);return r(m,"INFO",N,{userId:e,cId:t,updatedDirs:S,updatedSkills:E,agentTypes:O,prepareElapsedMs:C,syncAgentsElapsedMs:te?.elapsedMs,elapsedMs:Date.now()-F}),{message:N,workspaceRoot:k,updatedSkills:E}}catch(S){throw r(m,"ERROR","Failed to process workspace resources",{userId:e,cId:t,error:S.message,elapsedMs:Date.now()-F}),S instanceof d||S instanceof I||S instanceof Z?S:new Z(`Failed to create workspace: ${S.message}`,{userId:e,cId:t})}finally{const S=Date.now();try{for(const E of _)n.existsSync(E)&&await n.promises.rm(E,{recursive:!0,force:!0})}catch(E){r(m,"WARN","Failed to clean up temporary extracted zip",{error:E.message})}for(const E of L)try{n.existsSync(E)&&await n.promises.unlink(E)}catch(N){r(m,"WARN","Failed to clean up downloaded skill zip file",{downloadedZipPath:E,error:N.message})}try{i&&i.path&&n.existsSync(i.path)&&await n.promises.unlink(i.path)}catch(E){r(m,"WARN","Failed to clean up uploaded zip file",{tempZipPath:i?.path,error:E.message})}r(m,"INFO","Workspace temp cleanup completed",{userId:e,cId:t,elapsedMs:Date.now()-S,totalElapsedMs:Date.now()-F})}}async function Ue(e,t,i,s,a,o=null){const p=Date.now(),u=`computer:${e}:${t}`,l=re(a),D=[],x=[],f=new Set;let y=null;if(!e)throw new d("userId cannot be empty",{field:"userId"});if(!t)throw new d("cId cannot be empty",{field:"cId"});if(!i)throw new d("agentId cannot be empty",{field:"agentId"});if(!s&&l.length===0)throw new d("file or skillUrls cannot both be empty",{field:"file|skillUrls"});const F=se(o),m=await J(o,e,t,u),h=c.join(m,".tmp");await n.promises.mkdir(m,{recursive:!0}),await n.promises.mkdir(h,{recursive:!0});const{agentStorePath:L,skillsDir:_,agentsDir:z}=await ye(e,i,u,o);try{let k=await ue(L,u);if(!k.acquired)for(let R=1;R<=5&&!k.acquired;R++)await new Promise(O=>setTimeout(O,200*R)),k=await ue(L,u);if(!k.acquired)throw new Z("Agent store is busy, please retry",{userId:e,cId:t,agentId:i});if(y=k.lockPath,s){if(!s.path)throw new d("Uploaded file has no valid path",{field:"file.path"});const R=c.join(h,`push_skill_extract_${Date.now()}_${Math.round(Math.random()*1e6)}`);x.push(R),await n.promises.mkdir(R,{recursive:!0}),await H(s.path,R);const O=await me(R),j=await ee(R,"skills"),v=j?(await n.promises.readdir(j,{withFileTypes:!0})).filter(T=>T.isDirectory()).map(T=>({name:T.name,sourcePath:c.join(j,T.name)})):O;await ae(v,ne,async T=>{await ie(T.sourcePath,_,T.name,{asDynamic:!0}),f.add(T.name)})}await ae(l,ne,async(R,O)=>{const j=c.join(h,`push_skill_url_${Date.now()}_${O}_${Math.round(Math.random()*1e6)}.zip`);D.push(j);const v=c.join(h,`push_skill_url_extract_${Date.now()}_${O}_${Math.round(Math.random()*1e6)}`);x.push(v),await n.promises.mkdir(v,{recursive:!0}),await oe(R,j,u),await H(j,v);const T=await me(v);for(const S of T)await ie(S.sourcePath,_,S.name,{asDynamic:!0}),f.add(S.name)});const M=await ke(m,_,z,u),b=Array.from(f),g=b.length>0?`Pushed ${b.length} skills to agent store: ${b.join(", ")}`:"No valid skill directories found in file or skillUrls";return r(u,"INFO",g,{userId:e,cId:t,agentId:i,updatedSkills:b,linkElapsedMs:M?.elapsedMs,elapsedMs:Date.now()-p}),{message:g,workspaceRoot:F,agentStorePath:L,updatedSkills:b}}catch(k){throw r(u,"ERROR","Failed to push skills to agent store",{userId:e,cId:t,agentId:i,error:k.message,elapsedMs:Date.now()-p}),k instanceof d||k instanceof I||k instanceof Z?k:new Z(`Failed to push skill to workspace: ${k.message}`,{userId:e,cId:t,agentId:i})}finally{await ge(y,u);for(const k of x)try{n.existsSync(k)&&await n.promises.rm(k,{recursive:!0,force:!0})}catch(M){r(u,"WARN","Failed to clean up temporary extracted zip",{error:M.message})}for(const k of D)try{n.existsSync(k)&&await n.promises.unlink(k)}catch(M){r(u,"WARN","Failed to clean up downloaded skill zip file",{downloadedZipPath:k,error:M.message})}try{s&&s.path&&n.existsSync(s.path)&&await n.promises.unlink(s.path)}catch(k){r(u,"WARN","Failed to clean up uploaded zip file",{tempZipPath:s?.path,error:k.message})}}}async function Ze(e,t,i,s,a,o=null){if(a!=null&&String(a).trim()!==""){let _=null;if(o?.isUserApp?_=We(o,e,t):Q.COMPUTER_WORKSPACE_DIR&&(_=c.join(Q.COMPUTER_WORKSPACE_DIR,String(e),String(t))),$e(_))return Ue(e,t,a,i,s,o);r(`computer:${e}:${t}`,"INFO","Push skills: agentId present but workspace not symlinked, use legacy path",{userId:e,cId:t,agentId:a,userWorkspaceRoot:_})}const u=Date.now(),l=`computer:${e}:${t}`,D=re(s),x=[],f=[];if(!e)throw new d("userId cannot be empty",{field:"userId"});if(!t)throw new d("cId cannot be empty",{field:"cId"});if(!i&&D.length===0)throw new d("file or skillUrls cannot both be empty",{field:"file|skillUrls"});if(i){if(!i.path)throw new d("Uploaded file has no valid path",{field:"file.path"});if(c.extname(i.originalname||i.filename||"").toLowerCase()!==".zip")throw new d("Only zip files are supported",{field:"file",originalName:i?.originalname})}const y=se(o),F=await J(o,e,t,l),m=c.join(F,".tmp"),{skillsDir:h,agentTypes:L}=await he(F);try{n.existsSync(F)||await n.promises.mkdir(F,{recursive:!0}),n.existsSync(h)||await n.promises.mkdir(h,{recursive:!0}),n.existsSync(m)||await n.promises.mkdir(m,{recursive:!0});const _=[],z=new Set;if(i){const g=c.join(m,`skill_push_${Date.now()}_${Math.round(Math.random()*1e6)}`);f.push(g),await n.promises.mkdir(g,{recursive:!0}),r(l,"DEBUG","Start extracting skill zip file",{extractRoot:g}),await H(i.path,g),r(l,"DEBUG","Skill zip file extracted successfully",{extractRoot:g});const R=await ee(g,"skills");if(!R)r(l,"WARN","skills directory not found in uploaded zip",{userId:e,cId:t,extractRoot:g});else{const j=(await n.promises.readdir(R,{withFileTypes:!0})).filter(v=>v.isDirectory()&&!v.name.startsWith("."));if(j.length===0)r(l,"WARN","skills directory in uploaded zip has no skill subdirectories",{userId:e,cId:t,skillsDir:R});else for(const v of j){const T=c.join(R,v.name),S=c.join(h,v.name);n.existsSync(S)&&await V(S),await Y(T,S),z.add(v.name),r(l,"INFO","skill pushed to workspace from uploaded zip",{userId:e,cId:t,skillName:v.name,destSkillPath:S})}}}for(let g=0;g<D.length;g+=1){const R=D[g],O=c.join(m,`skill_push_url_${Date.now()}_${g}_${Math.round(Math.random()*1e6)}.zip`);x.push(O);const j=c.join(m,`skill_push_url_extract_${Date.now()}_${g}_${Math.round(Math.random()*1e6)}`);f.push(j),await n.promises.mkdir(j,{recursive:!0}),r(l,"INFO","Start download skill zip for push from url",{userId:e,cId:t,skillUrl:R}),await oe(R,O,l),await H(O,j);const T=(await n.promises.readdir(j,{withFileTypes:!0})).filter(N=>N.isDirectory()&&!N.name.startsWith(".")),E=T.find(N=>N.name==="skills")?(await n.promises.readdir(c.join(j,"skills"),{withFileTypes:!0})).filter(N=>N.isDirectory()&&!N.name.startsWith(".")).map(N=>({name:N.name,sourcePath:c.join(j,"skills",N.name)})):T.map(N=>({name:N.name,sourcePath:c.join(j,N.name)}));if(E.length===0){r(l,"WARN","No skill directory found after extracting push skill url zip",{userId:e,cId:t,skillUrl:R,extractRoot:j});continue}for(const N of E){const C=c.join(h,N.name);n.existsSync(C)&&await V(C),await Y(N.sourcePath,C),z.add(N.name),r(l,"INFO","skill pushed to workspace from url zip",{userId:e,cId:t,skillUrl:R,skillName:N.name,destSkillPath:C})}}for(const g of z)_.push(g);const k=_.length>0?`Pushed ${_.length} skills: ${_.join(", ")}`:"No valid skill directories found in file or skillUrls",M=Date.now()-u;r(l,"INFO","Push skills resources prepared",{userId:e,cId:t,updatedSkills:_,agentTypes:L,prepareElapsedMs:M});const b=await de(F,l);return r(l,"INFO",k,{userId:e,cId:t,updatedSkills:_,agentTypes:L,prepareElapsedMs:M,syncAgentsElapsedMs:b?.elapsedMs,elapsedMs:Date.now()-u}),{message:k,workspaceRoot:y,updatedSkills:_}}catch(_){throw r(l,"ERROR","Failed to push skill to workspace",{userId:e,cId:t,error:_.message,elapsedMs:Date.now()-u}),_ instanceof d||_ instanceof I||_ instanceof Z?_:new Z(`Failed to push skill to workspace: ${_.message}`,{userId:e,cId:t})}finally{const _=Date.now();try{for(const z of f)n.existsSync(z)&&await n.promises.rm(z,{recursive:!0,force:!0})}catch(z){r(l,"WARN","Failed to clean up temporary extracted zip",{error:z.message})}for(const z of x)try{n.existsSync(z)&&await n.promises.unlink(z)}catch(k){r(l,"WARN","Failed to clean up downloaded skill zip file",{downloadedZipPath:z,error:k.message})}try{i&&i.path&&n.existsSync(i.path)&&await n.promises.unlink(i.path)}catch(z){r(l,"WARN","Failed to clean up uploaded zip file",{tempZipPath:i?.path,error:z.message})}r(l,"INFO","Push skills temp cleanup completed",{userId:e,cId:t,elapsedMs:Date.now()-_,totalElapsedMs:Date.now()-u})}}async function Ge(e,t,i,s,a=null){const o=Date.now(),p=`computer:${e}:${t}`;if(!e)throw new d("userId cannot be empty",{field:"userId"});if(!t)throw new d("cId cannot be empty",{field:"cId"});if(!i||!i.path)throw new d("file is required",{field:"file"});if(c.extname(i.originalname||i.filename||"").toLowerCase()!==".zip")throw new d("Only zip files are supported",{field:"file",originalName:i.originalname});const l=await J(a,e,t,p),D=c.join(l,".tmp"),x=c.join(D,`template_extract_${Date.now()}_${Math.round(Math.random()*1e6)}`);try{n.existsSync(l)||await n.promises.mkdir(l,{recursive:!0}),n.existsSync(D)||await n.promises.mkdir(D,{recursive:!0}),await n.promises.mkdir(x,{recursive:!0}),r(p,"DEBUG","Start extracting template zip file",{extractRoot:x}),await H(i.path,x),r(p,"DEBUG","Template zip file extracted successfully",{extractRoot:x});const f=await n.promises.readdir(x,{withFileTypes:!0});for(const y of f){const F=c.join(x,y.name),m=c.join(l,y.name);if(y.isDirectory())await Y(F,m);else{const h=c.dirname(m);n.existsSync(h)||await n.promises.mkdir(h,{recursive:!0}),await n.promises.copyFile(F,m)}}if(r(p,"INFO","Template files extracted to workspace",{userId:e,cId:t,targetDir:l,fileCount:f.length}),Q.GIT_ENABLED&&(s==="true"||s===!0)){const y=await import("../../service/gitService.js");await y.default.init({workspaceType:"taskAgent",userId:e,cId:t}),await y.default.commit({workspaceType:"taskAgent",userId:e,cId:t,message:"Initial commit"}),r(p,"INFO","Git init and initial commit done",{userId:e,cId:t})}return r(p,"INFO","Init project template completed",{userId:e,cId:t,targetDir:l,elapsedMs:Date.now()-o}),{message:"Project template initialized successfully",workspaceRoot:l}}catch(f){throw r(p,"ERROR","Failed to init project template",{userId:e,cId:t,error:f.message,elapsedMs:Date.now()-o}),f instanceof d||f instanceof I||f instanceof Z?f:new Z(`Failed to init project template: ${f.message}`,{userId:e,cId:t})}finally{try{n.existsSync(x)&&await n.promises.rm(x,{recursive:!0,force:!0})}catch(f){r(p,"WARN","Failed to clean up temporary extracted zip",{error:f.message})}try{i&&i.path&&n.existsSync(i.path)&&await n.promises.unlink(i.path)}catch(f){r(p,"WARN","Failed to clean up uploaded zip file",{tempZipPath:i?.path,error:f.message})}}}async function qe(e,t,i,s=null){if(!e)throw new d("userId cannot be empty",{field:"userId"});if(!t)throw new d("cId cannot be empty",{field:"cId"});if(!i||typeof i!="string"||!i.trim())throw new d("command cannot be empty",{field:"command"});const a=await J(s,e,t,"computer");if(!n.existsSync(a))throw new d("workspace directory does not exist",{field:"workDir",workDir:a});const o=`computer:${e}:${t}`;r(o,"INFO","Execute command in workspace",{userId:e,cId:t,workDir:a,command:i});const p=10*60*1e3,u={cwd:a,timeout:p,maxBuffer:50*1024*1024};return Q.BASH_PATH&&(u.shell=Q.BASH_PATH),new Promise((l,D)=>{Ee(i,u,(x,f,y)=>{const F=x?x.killed?-1:x.code||1:0,m=x&&x.killed?(y||"")+`
2
- Command timed out after ${p/1e3}s`:y||"";r(o,"INFO","Execute command completed",{userId:e,cId:t,exitCode:F,killed:x?x.killed:!1,stdoutLength:f?f.length:0,stderrLength:m.length}),l({stdout:f||"",stderr:m,exitCode:F})})})}async function Ke(e,t,i,s=null){const a=`computer:${e}:${t}`;if(!e)throw new d("userId cannot be empty",{field:"userId"});if(!t)throw new d("cId cannot be empty",{field:"cId"});const o=await J(s,e,t,a);if(!n.existsSync(o))throw new d("workspace directory does not exist",{field:"workDir",workDir:o});const p=Q.ZIP_WORKSPACE_EXCLUDE||[],u=Array.isArray(i)?i:[],l=new Set([...p,...u]),D=`${e}_${t}.zip`,x=Date.now();r(a,"INFO","Zip workspace request",{userId:e,cId:t,targetDir:o,excludeDirs:[...l]});const f=Ae("zip",{zlib:{level:9}});return f.directory(o,!1,y=>{const F=y.name||"";if(F.split(/[\/\\]/).filter(Boolean).some(h=>l.has(h)))return!1;try{const h=c.join(o,F);if(n.lstatSync(h).isSymbolicLink())return!1}catch{return!1}return y}),f.on("warning",y=>{y.code==="ENOENT"?r(a,"WARN","Encountered file problem when creating zip",{message:y.message,code:y.code}):r(a,"ERROR","Encountered warning when creating zip",{message:y.message,code:y.code})}),f.on("error",y=>{r(a,"ERROR","Failed to create zip",{message:y.message,elapsedMs:Date.now()-x})}),f.on("end",()=>{r(a,"INFO","Workspace zip created successfully",{targetDir:o,zipFileName:D,elapsedMs:Date.now()-x})}),{archive:f,zipFileName:D}}const fe=new Set([...Q.ZIP_WORKSPACE_EXCLUDE||[],"dist-packages"]);function le(e){let t;try{t=n.readdirSync(e,{withFileTypes:!0})}catch{return null}const i=c.join(e,"scripts","package-platforms.mjs");if(n.existsSync(i))return e;for(const s of t)if(s.isDirectory()&&!fe.has(s.name)){const a=le(c.join(e,s.name));if(a)return a}return null}function ce(e,t,i,s=10*60*1e3){return new Promise(a=>{const o=je(e,t,{...i,timeout:s});let p="",u="";o.stdout?.on("data",l=>{p+=l.toString()}),o.stderr?.on("data",l=>{u+=l.toString()}),o.on("error",l=>{a({stdout:p,stderr:u+`
3
- Failed to spawn ${e}: ${l.message}`,exitCode:1})}),o.on("close",(l,D)=>{a(l===null&&D?{stdout:p,stderr:u+`
4
- Process killed by signal ${D} (timeout after ${s/1e3}s)`,exitCode:-1}:{stdout:p,stderr:u,exitCode:l??1})})})}function Je(e){if(!e)return null;let t=e;t.endsWith(".tar.gz")?t=t.slice(0,-7):t.endsWith(".tar.bz2")?t=t.slice(0,-8):(t.endsWith(".tgz")||t.endsWith(".zip"))&&(t=t.slice(0,-4));const i=t.split("-");if(i.length<4)return null;let s=-1;for(let a=i.length-1;a>=2;a--)if(/^\d+\.\d+\.\d+$/.test(i[a])){s=a;break}return s<3?null:i.slice(2,s).join("-")}async function He(e,t,i,s,a=null){const o=`computer:${e}:${t}`;if(!e)throw new d("userId cannot be empty",{field:"userId"});if(!t)throw new d("cId cannot be empty",{field:"cId"});if(!i)throw new d("agentId cannot be empty",{field:"agentId"});if(!s)throw new d("version cannot be empty",{field:"version"});const p=await J(a,e,t,o);if(!n.existsSync(p))throw new d("workspace directory does not exist",{field:"workDir",workDir:p});r(o,"INFO","Build agent package request",{userId:e,cId:t,agentId:i,version:s});const u=le(p);if(!u)throw new d("package-platforms.mjs not found in workspace",{field:"script"});r(o,"INFO","Found package script",{projectDir:u});const l=await Pe(u,o);if(r(o,"INFO","pnpm install completed",{exitCode:l.exitCode,stderrLength:l.stderr.length}),l.exitCode!==0)throw r(o,"ERROR","pnpm install failed",{stderr:l.stderr}),new Z(`pnpm install failed: ${l.stderr}`);const D=c.join(u,"dist-packages"),x=["scripts/package-platforms.mjs",`agent-${i}`,s,D,"--print-artifacts"],f=await ce("node",x,{cwd:u,env:we()});if(r(o,"INFO","Build completed",{exitCode:f.exitCode,stdoutLength:f.stdout.length}),f.exitCode!==0)throw r(o,"ERROR","Build failed",{stderr:f.stderr}),new Z(`Build failed: ${f.stderr}`);const y=[".tar.gz",".tar.bz2",".zip",".tgz"],F=[],m=f.stdout.split(`
5
- `);for(const h of m){const L=h.trim();if(!L)continue;const _=L.toLowerCase();if(!y.some(g=>_.endsWith(g)))continue;const z=c.resolve(u,L);let k=c.relative(p,z);k=k.split(c.sep).join("/");const M=c.basename(L),b=Je(M);F.push({path:k,fileName:M,platform:b||""})}return r(o,"INFO","Build agent package completed",{artifactsCount:F.length,artifacts:F}),{artifacts:F}}async function Ve(e,t,i=null){const s=`computer:${e}:${t}`;if(!e)throw new d("userId cannot be empty",{field:"userId"});if(!t)throw new d("cId cannot be empty",{field:"cId"});const a=await J(i,e,t,s);if(!n.existsSync(a))return r(s,"WARN","Workspace not found, skip cleanup",{userId:e,cId:t}),{cleaned:!1};const o=le(a);if(!o)return r(s,"WARN","package-platforms.mjs not found, skip cleanup",{userId:e,cId:t}),{cleaned:!1};const p=c.join(o,"dist-packages");return n.existsSync(p)?(n.rmSync(p,{recursive:!0,force:!0}),r(s,"INFO","Cleaned dist-packages",{distPackagesDir:p}),{cleaned:!0}):(r(s,"INFO","dist-packages not found, nothing to clean",{distPackagesDir:p}),{cleaned:!1})}async function Ye(e,t,i=null){const s=`computer:${e}:${t}`;if(!e)throw new d("userId cannot be empty",{field:"userId"});if(!t)throw new d("cId cannot be empty",{field:"cId"});const a=i?.workspaceDir?i.workspaceDir:await J(i,e,t,s);return n.existsSync(a)?(await n.promises.rm(a,{recursive:!0,force:!0}),r(s,"INFO","Workspace deleted",{userId:e,cId:t,targetDir:a})):r(s,"WARN","Workspace not found, skip delete",{userId:e,cId:t,targetDir:a}),{deleted:!0}}function Ne(e){const t=c.join(e,"package.json");if(n.existsSync(t))return e;let i;try{i=n.readdirSync(e,{withFileTypes:!0})}catch{return null}for(const s of i)if(s.isDirectory()&&!fe.has(s.name)&&s.name!=="node_modules"){const a=Ne(c.join(e,s.name));if(a)return a}return null}const Re=["neverBuiltDependencies","onlyBuiltDependencies","ignoredBuiltDependencies"],Xe=["NPM_CONFIG_NEVER_BUILT_DEPENDENCIES","npm_config_never_built_dependencies","NPM_CONFIG_ONLY_BUILT_DEPENDENCIES","npm_config_only_built_dependencies","NPM_CONFIG_IGNORED_BUILT_DEPENDENCIES","npm_config_ignored_built_dependencies"];async function Qe(e,t){const i=c.join(e,"package.json");if(!n.existsSync(i))return;const s=await n.promises.readFile(i,"utf8");let a;try{a=JSON.parse(s)}catch{r(t,"WARN","Skip sanitizing package.json: invalid JSON",{projectDir:e});return}if(!a.pnpm||typeof a.pnpm!="object")return;const o=Re.filter(p=>p in a.pnpm);if(o.length!==0){for(const p of o)delete a.pnpm[p];Object.keys(a.pnpm).length===0&&delete a.pnpm,await n.promises.writeFile(i,`${JSON.stringify(a,null,2)}
6
- `,"utf8"),r(t,"INFO","Removed conflicting pnpm built dependency settings from package.json",{projectDir:e,removedKeys:o})}}async function Ie(e,t){const i=c.join(e,"pnpm-workspace.yaml");if(!n.existsSync(i))return;const a=(await n.promises.readFile(i,"utf8")).split(`
7
- `),o=[];let p=-1,u=[];for(const l of a){const D=l.trimStart(),x=l.length-D.length,f=Re.find(y=>new RegExp(`^${y}:\\s*(.*)$`).test(D));if(f){u.push(f);const y=D.slice(f.length+1).trim();(!y||y==="|"||y===">")&&(p=x);continue}if(p>=0){if(D===""||x>p)continue;p=-1}o.push(l)}u.length!==0&&(await n.promises.writeFile(i,o.join(`
8
- `),"utf8"),r(t,"INFO","Removed conflicting pnpm built dependency settings from pnpm-workspace.yaml",{projectDir:e,removedKeys:u}))}async function _e(e){if(!n.existsSync(e))return"";const t=await n.promises.readFile(e,"utf8"),i=t.split(`
9
- `).filter(s=>!/^\s*(never-built-dependencies|only-built-dependencies|ignored-built-dependencies)\b/.test(s)).join(`
10
- `);return i!==t&&await n.promises.writeFile(e,i,"utf8"),i}async function et(e,t){await Qe(e,t),await Ie(e,t),await _e(c.join(e,".npmrc"))}async function tt(e,t){await Oe(e,t),await et(e,t);const i=c.join(e,".npmrc");let s=await _e(i);const a=[];if(/dangerously-allow-all-builds\s*=/.test(s)||a.push("dangerously-allow-all-builds=true"),/production\s*=/.test(s)||a.push("production=false"),/confirm-modules-purge\s*=/.test(s)||a.push("confirm-modules-purge=false"),a.length>0){const o=s&&!s.endsWith(`
1
+ import n from"fs";import l from"path";import{exec as ve}from"child_process";import ze from"cross-spawn";import Me from"archiver";import ee from"../../appConfig/index.js";import{extractZip as Y}from"../common/zipUtils.js";import{ValidationError as w,SystemError as U,FileError as te}from"../error/errorHandler.js";import{log as o}from"../log/logUtils.js";import{createPnpmNpmrc as We}from"../common/npmrcUtils.js";import{ensurePrimaryAgentDirs as ye,syncAgents as fe,linkWorkspaceToAgentStore as ge,forceDirSymlink as Te,copyDirContents as Ce,isWorkspaceSkillsSymlinked as Le}from"../common/AgentWorkspaceUtils.js";import{getProjectStoreRoot as Be,getAgentStorePath as Se,readManifest as Ue,writeManifest as Ze,reverseRefs as Ge,ensureAgentStoreDirs as De,tryAcquireAgentStoreLock as de,releaseAgentStoreLock as xe,pruneAgentSkills as Ke,installSkillDir as ne,replaceAgentsDir as qe,agentSkillExists as Ve}from"../common/agentStoreUtils.js";import{writeAgentHookConfigs as Ne}from"./hookConfigUtils.js";import{ensureWorkspaceDir as J,resolveWorkspaceDir as Je,resolveWorkspaceRoot as re}from"./workspaceContext.js";function oe(e){if(!e)return[];if(Array.isArray(e))return e.map(t=>typeof t=="string"?t.trim():t&&typeof t=="object"&&typeof t.url=="string"?t.url.trim():"").filter(Boolean);if(typeof e=="object")return Object.values(e).map(t=>typeof t=="string"?t.trim():"").filter(Boolean);if(typeof e=="string"){const t=e.trim();if(!t)return[];try{const i=JSON.parse(t);return oe(i)}catch{}return[t]}return[]}async function je(e,t,i){try{return await Te(e,t,i),!0}catch(a){return o(i,"WARN","Skill view dir link unavailable, fallback to copy",{linkPath:e,targetDir:t,platform:process.platform,error:a.message}),!1}}async function He(e,t,i){const a=process.platform==="win32"?l.resolve(t):l.relative(l.dirname(e),t).split(l.sep).join("/");try{return await n.promises.symlink(a,e,"file"),!0}catch(s){return o(i,"WARN","Skill view file link unavailable, fallback to copy",{linkPath:e,targetFile:t,platform:process.platform,error:s.message}),!1}}async function Re(e,t,i,a,s,r={}){const p=Be(e,a);if(!p||!i)return!1;const{skillNames:m=null,subagentNames:c=null}=r,N=l.join(p,".view.lock");await n.promises.mkdir(p,{recursive:!0});const R=Date.now()+3e4;let h=!1;for(;Date.now()<R&&!h;)try{await n.promises.writeFile(N,String(Date.now()),{flag:"wx"}),h=!0}catch(g){if(g.code!=="EEXIST")throw g;try{const S=await n.promises.stat(N);if(Date.now()-S.mtimeMs>5*60*1e3){await n.promises.rm(N,{force:!0});continue}}catch{}await new Promise(S=>setTimeout(S,200))}if(!h)throw new U("Agent skill view is busy, please retry");try{const g=await Ue(p),S=g.agents[String(i)]||{skills:[],subagents:[]};m&&(S.skills=[...new Set(m.map(y=>String(y||"").trim()).filter(Boolean))]),c&&(S.subagents=[...new Set(c.map(y=>String(y||"").trim()).filter(Boolean))]),g.agents[String(i)]=S,await Ze(p,g);const f=[".agents",".claude",".opencode",".codex"],k=[];for(const y of f)for(const d of["skills","agents"]){const P=l.join(t,y,d);if(await n.promises.mkdir(l.dirname(P),{recursive:!0}),y===".agents")try{(await n.promises.lstat(P)).isSymbolicLink()&&await n.promises.rm(P,{recursive:!0,force:!0})}catch{}else{const z=l.join(t,".agents",d),D=process.platform==="win32"?l.resolve(z):"../.agents/"+d;let $=!1;try{$=(await n.promises.lstat(P)).isSymbolicLink()&&n.readlinkSync(P)===D}catch{}$||(await n.promises.rm(P,{recursive:!0,force:!0}),await je(P,z,s)||k.push([z,P]));continue}await n.promises.mkdir(P,{recursive:!0})}const L=async(y,d,P)=>{P==="skills"||(await n.promises.lstat(y)).isDirectory()?(k.length>0||!await je(d,y,s))&&await Ce(y,d):await He(d,y,s)||await n.promises.copyFile(y,d)},F=l.join(t,".agents");for(const y of["skills","agents"]){const P=Ge(g,y==="skills"?"skills":"subagents"),z=l.join(F,y),D=n.existsSync(z)?await n.promises.readdir(z):[],$=new Set(Object.keys(P));for(const j of D){const E=l.join(z,j);ce(E)||$.has(j)||(await n.promises.rm(E,{recursive:!0,force:!0}),o(s,"INFO","Skill view entry removed (no agent references it)",{kind:y,name:j}))}for(const[j,E]of Object.entries(P)){const O=l.join(z,j);if(ce(O))continue;const C=[...new Set([String(i),...E.map(String)])];let u=null;for(const W of C){const H=l.join(Se(e,W,a),y,j);if(n.existsSync(H)){u=H;break}}if(!u){await n.promises.rm(O,{recursive:!0,force:!0});continue}const _=process.platform==="win32"?l.resolve(u):l.relative(z,u).split(l.sep).join("/");let x=!1;try{x=!(await n.promises.lstat(O)).isSymbolicLink()||n.readlinkSync(O)!==_}catch{x=!0}x&&(await n.promises.rm(O,{recursive:!0,force:!0}),await L(u,O,y))}if(y==="skills"){const j=[...new Set([String(i),...Object.keys(g.agents||{})])];for(const E of j){const O=l.join(Se(e,E,a),"skills");let C=[];try{C=await n.promises.readdir(O,{withFileTypes:!0})}catch{continue}for(const u of C){if(!u.isDirectory())continue;const _=l.join(O,u.name);if(!ce(_))continue;const x=l.join(z,u.name);n.existsSync(x)||(await L(_,x,y),o(s,"INFO","Dynamic skill linked into view",{owner:E,name:u.name}))}}}}for(const[y,d]of k)await n.promises.rm(d,{recursive:!0,force:!0}),await n.promises.cp(y,d,{recursive:!0,dereference:!0,force:!0});return o(s,"INFO","Shared skill view synced",{userWorkspaceRoot:t,projectStoreRoot:p,agentId:i,skills:S.skills,subagents:S.subagents,copyFallbackDirs:k.length}),!0}finally{await n.promises.rm(N,{force:!0})}}function _e(e){if(!e)return[];if(Array.isArray(e)){const t=[];for(const i of e)if(typeof i=="string"&&i.trim())t.push({name:null,url:i.trim()});else if(i&&typeof i=="object"){const a=typeof i.url=="string"?i.url.trim():"",s=i.name!=null?String(i.name).trim():"";a&&t.push({name:s||null,url:a})}return t}if(typeof e=="object")return Object.entries(e).map(([t,i])=>({name:String(t||"").trim()||null,url:typeof i=="string"?i.trim():""})).filter(t=>t.url);if(typeof e=="string"){const t=e.trim();if(!t)return[];try{return _e(JSON.parse(t))}catch{return[{name:null,url:t}]}}return[]}function Ye(e){if(!e)return{};let t=e;if(typeof e=="string")try{t=JSON.parse(e)}catch{return{}}if(!t||typeof t!="object"||Array.isArray(t))return{};const i={};for(const[a,s]of Object.entries(t)){const r=String(a||"").trim(),p=typeof s=="string"?s.trim():"";r&&p&&(i[r]=p)}return i}function Pe(e){if(!e)return[];if(Array.isArray(e))return e.map(t=>String(t||"").trim()).filter(Boolean);if(typeof e=="string"){const t=e.trim();if(!t)return[];try{const i=JSON.parse(t);if(Array.isArray(i))return i.map(a=>String(a||"").trim()).filter(Boolean)}catch{}return t.split(",").map(i=>i.trim()).filter(Boolean)}return[]}async function le(e,t,i){let a;try{a=await fetch(e)}catch(m){throw new te(`Failed to download skill zip from url: ${e}`,{url:e,reason:m.message})}if(!a.ok)throw new te(`Failed to download skill zip from url: ${e}`,{url:e,status:a.status,statusText:a.statusText});const s=a.headers.get("content-type")||"";s&&!s.includes("zip")&&!s.includes("octet-stream")&&o(i,"WARN","Downloaded skill url content-type is not typical zip",{url:e,contentType:s});const r=await a.arrayBuffer(),p=Buffer.from(r);await n.promises.writeFile(t,p)}async function ie(e,t){const i=l.join(e,t);if(n.existsSync(i)&&(await n.promises.lstat(i)).isDirectory())return i;const a=await n.promises.readdir(e,{withFileTypes:!0});for(const s of a){if(!s.isDirectory())continue;const r=l.join(e,s.name),p=l.join(r,t);if(n.existsSync(p)&&(await n.promises.lstat(p)).isDirectory())return p}return null}async function Ft(e){return ie(e,"skills")}const Xe=".dynamic_add.lock";function ce(e){const t=l.join(e,Xe);return n.existsSync(t)&&n.statSync(t).isFile()}async function X(e){n.existsSync(e)&&await n.promises.rm(e,{recursive:!0,force:!0})}async function Q(e,t){try{await n.promises.rename(e,t)}catch(i){if(i.code==="EXDEV"){async function a(s,r){if((await n.promises.lstat(s)).isDirectory()){await n.promises.mkdir(r,{recursive:!0});const m=await n.promises.readdir(s);for(const c of m)await a(l.join(s,c),l.join(r,c))}else await n.promises.copyFile(s,r)}await a(e,t),await n.promises.rm(e,{recursive:!0,force:!0})}else throw i}}async function we(e){const i=(await n.promises.readdir(e,{withFileTypes:!0})).filter(s=>s.isDirectory()&&!s.name.startsWith("."));return i.find(s=>s.name==="skills")?(await n.promises.readdir(l.join(e,"skills"),{withFileTypes:!0})).filter(r=>r.isDirectory()&&!r.name.startsWith(".")).map(r=>({name:r.name,sourcePath:l.join(e,"skills",r.name)})):i.map(s=>({name:s.name,sourcePath:l.join(e,s.name)}))}const se=8;async function ae(e,t,i){if(!e||e.length===0)return;const a=Math.max(1,Math.min(t,e.length));let s=0;await Promise.all(Array.from({length:a},async()=>{for(;;){const r=s++;if(r>=e.length)return;await i(e[r],r)}}))}async function Qe(e){const{userId:t,cId:i,agentId:a,file:s,skillUrls:r,skillUrlMap:p,skillNames:m,updateSkillNames:c,mcpServersConfig:N,permissionsConfig:R,hooksConfig:h,hookScripts:g,service:S}=e,f=Date.now(),k=`computer:${t}:${i}`,L=Ye(p),F=_e(r),y=Pe(m),d=c!=null,P=new Set(Pe(c)),z=[],D=[],$=new Set,j=new Set;let E=null,O=!1;if(!t)throw new w("userId cannot be empty",{field:"userId"});if(!i)throw new w("cId cannot be empty",{field:"cId"});if(!a)throw new w("agentId cannot be empty",{field:"agentId"});const C=re(S),u=await J(S,t,i,k),_=l.join(u,".tmp");await n.promises.mkdir(u,{recursive:!0}),await n.promises.mkdir(_,{recursive:!0});const{agentStorePath:x,skillsDir:W,agentsDir:H}=await De(t,a,k,S),T=M=>!M||!d||P.has(M)?!0:!Ve(W,M),q=async(M,b,B)=>{const G=l.join(_,`skill_url_${Date.now()}_${B}_${Math.round(Math.random()*1e6)}.zip`);z.push(G);const Z=l.join(_,`skill_url_extract_${Date.now()}_${B}_${Math.round(Math.random()*1e6)}`);D.push(Z),await n.promises.mkdir(Z,{recursive:!0}),o(k,"INFO","Start download skill zip from url",{userId:t,cId:i,agentId:a,skillName:b,skillUrl:M}),await le(M,G,k),await Y(G,Z);const V=await we(Z);if(V.length===0){o(k,"WARN","No skill dir found in downloaded zip",{skillUrl:M,destName:b});return}let v=b?V.find(A=>A.name===b):null;if(!v&&V.length===1&&(v=V[0]),b&&v){if(!T(b)){j.add(b);return}await ne(v.sourcePath,W,b,{asDynamic:!1}),$.add(b),o(k,"INFO","Skill from url updated to agent store",{userId:t,cId:i,agentId:a,skillUrl:M,skillName:b,zipDirName:v.name});return}for(const A of V){if(!T(A.name)){j.add(A.name);continue}await ne(A.sourcePath,W,A.name,{asDynamic:!1}),$.add(A.name),o(k,"INFO","Skill from url updated to agent store",{userId:t,cId:i,agentId:a,skillUrl:M,skillName:A.name})}};await Ne(u,{mcpServersConfig:N,hooksConfig:h,permissionsConfig:R,hookScripts:g},k);try{const M=await de(x,k);if(!M.acquired)O=!0;else{E=M.lockPath;const G=Date.now();let Z=null;if(s){if(!s.path)throw new w("Uploaded file has no valid path",{field:"file.path"});if(l.extname(s.originalname||s.filename||"").toLowerCase()!==".zip")throw new w("Only zip files are supported",{field:"file",originalName:s.originalname});const A=l.join(_,`skill_extract_${Date.now()}_${Math.round(Math.random()*1e6)}`);D.push(A),await n.promises.mkdir(A,{recursive:!0}),await Y(s.path,A);const K=await ie(A,"skills");if(K){const me=(await n.promises.readdir(K,{withFileTypes:!0})).filter(I=>I.isDirectory());await ae(me,se,async I=>{if(!T(I.name)){j.add(I.name);return}await ne(l.join(K,I.name),W,I.name,{asDynamic:!1}),$.add(I.name)})}Z=await ie(A,"agents")}const V=Object.entries(L);if(V.length>0){const v=[];for(const[A,K]of V){if(!T(A)){j.add(A),o(k,"INFO","Skip download skill zip (already in agent store)",{userId:t,cId:i,agentId:a,skillName:A,skillUrl:K});continue}v.push({skillName:A,skillUrl:K})}await ae(v,se,async(A,K)=>{await q(A.skillUrl,A.skillName,K)})}else{const v=[];for(let A=0;A<F.length;A++){const{name:K,url:me}=F[A];if(K&&!T(K)){j.add(K);continue}v.push({skillUrl:me,hintedName:K,index:A})}await ae(v,se,async A=>{await q(A.skillUrl,A.hintedName,A.index)})}await qe(Z,H),await Ke(W,y,k),o(k,"INFO","Agent store resources prepared",{userId:t,cId:i,agentId:a,selectiveInstall:d,updatedSkills:Array.from($),skippedSkills:Array.from(j),updateSkillNames:Array.from(P),keepSkillNames:y,skillUrlMapCount:V.length,prepareElapsedMs:Date.now()-G})}let b=null,B=!1;if(S&&(S.isUserApp||S.isNormalProject)&&S.appId){let G=null;try{G=(await n.promises.readdir(H,{withFileTypes:!0})).map(Z=>Z.name)}catch{}B=await Re(t,u,a,S,k,{skillNames:m?y:null,subagentNames:G})}return B||(b=await ge(u,W,H,k)),o(k,"INFO","Workspace created successfully (agent store)",{userId:t,cId:i,agentId:a,agentStorePath:x,updatedSkills:Array.from($),skippedSkills:Array.from(j),skippedStoreUpdate:O,linkElapsedMs:b?.elapsedMs,elapsedMs:Date.now()-f}),{message:O?"Workspace linked to agent store (store update skipped, lock held)":"Workspace created successfully, linked to agent store",workspaceRoot:C,agentStorePath:x,updatedSkills:Array.from($),skippedSkills:Array.from(j),skippedStoreUpdate:O}}catch(M){throw o(k,"ERROR","Failed to create workspace with agent store",{userId:t,cId:i,agentId:a,error:M.message,elapsedMs:Date.now()-f}),M instanceof w||M instanceof te||M instanceof U?M:new U(`Failed to create workspace: ${M.message}`,{userId:t,cId:i,agentId:a})}finally{await xe(E,k);const M=Date.now();for(const b of D)try{n.existsSync(b)&&await n.promises.rm(b,{recursive:!0,force:!0})}catch(B){o(k,"WARN","Failed to clean up temporary extracted zip",{error:B.message})}for(const b of z)try{n.existsSync(b)&&await n.promises.unlink(b)}catch(B){o(k,"WARN","Failed to clean up downloaded skill zip file",{downloadedZipPath:b,error:B.message})}try{s&&s.path&&n.existsSync(s.path)&&await n.promises.unlink(s.path)}catch(b){o(k,"WARN","Failed to clean up uploaded zip file",{tempZipPath:s?.path,error:b.message})}o(k,"INFO","Workspace temp cleanup completed",{userId:t,cId:i,agentId:a,elapsedMs:Date.now()-M,totalElapsedMs:Date.now()-f})}}async function Ie(e,t,i,a,s,r,p,m,c,N,R,h,g=null){if(c!=null&&String(c).trim()!=="")return Qe({userId:e,cId:t,agentId:c,file:i,skillUrls:a,skillUrlMap:h,skillNames:N,updateSkillNames:R,mcpServersConfig:s,permissionsConfig:r,hooksConfig:p,hookScripts:m,service:g});const S=Date.now(),f=`computer:${e}:${t}`,k=oe(a),L=[],F=[],y=new Set;if(!e)throw new w("userId cannot be empty",{field:"userId"});if(!t)throw new w("cId cannot be empty",{field:"cId"});const d=re(g),P=await J(g,e,t,f),z=l.join(P,".tmp"),{skillsDir:D,agentsDir:$,agentTypes:j}=await ye(P);n.existsSync(P)||await n.promises.mkdir(P,{recursive:!0});const E=l.join(z,`preserved_skills_${Date.now()}_${Math.round(Math.random()*1e6)}`);if(n.existsSync(D)){const _=(await n.promises.readdir(D,{withFileTypes:!0})).filter(x=>x.isDirectory()&&ce(l.join(D,x.name)));if(_.length>0){await n.promises.mkdir(E,{recursive:!0});for(const x of _){const W=l.join(D,x.name),H=l.join(E,x.name);await Q(W,H)}o(f,"INFO","\u4FDD\u7559\u542B .dynamic_add.lock \u7684 skill",{preserved:_.map(x=>x.name)})}}if(await X(D),await X($),await n.promises.mkdir(D,{recursive:!0}),await n.promises.mkdir($,{recursive:!0}),n.existsSync(E)){const u=await n.promises.readdir(E,{withFileTypes:!0});for(const _ of u)if(_.isDirectory()){const x=l.join(E,_.name),W=l.join(D,_.name);await Q(x,W)}await X(E)}const O=n.existsSync(D),C=n.existsSync($);if(o(f,"INFO","Deleted old skills and agents directories completed",{userId:e,cId:t,targetSkillsDir:D,targetAgentsDir:$,agentTypes:j,skillsExists:O,agentsExists:C}),await Ne(P,{mcpServersConfig:s,hooksConfig:p,permissionsConfig:r,hookScripts:m},f),!i&&k.length===0){const u=await fe(P,f);return o(f,"INFO","Created workspace (no uploaded file, no skills and agents)",{userId:e,cId:t,workspaceRoot:d,skillsDir:null,agentsDir:null,agentTypes:j,syncAgentsElapsedMs:u?.elapsedMs,elapsedMs:Date.now()-S}),{message:"Workspace created (no uploaded file, no skills and agents)",workspaceRoot:d}}if(i){if(!i.path)throw new w("Uploaded file has no valid path",{field:"file.path"});if(l.extname(i.originalname||i.filename||"").toLowerCase()!==".zip")throw new w("Only zip files are supported",{field:"file",originalName:i.originalname})}o(f,"DEBUG","Start processing workspace resources",{userId:e,cId:t,workspaceRoot:d,hasUploadedZip:!!i,skillUrlsCount:k.length});try{n.existsSync(z)||await n.promises.mkdir(z,{recursive:!0});const u=[];if(i){const T=l.join(z,`skill_extract_${Date.now()}_${Math.round(Math.random()*1e6)}`);F.push(T),await n.promises.mkdir(T,{recursive:!0}),o(f,"DEBUG","Start extracting uploaded zip file",{extractRoot:T}),await Y(i.path,T),o(f,"DEBUG","Uploaded zip file extracted successfully",{extractRoot:T});const q=await ie(T,"skills"),M=await ie(T,"agents");if(q){await n.promises.mkdir(D,{recursive:!0});const b=await n.promises.readdir(q,{withFileTypes:!0});for(const B of b){if(!B.isDirectory())continue;const G=l.join(q,B.name),Z=l.join(D,B.name);n.existsSync(Z)&&await X(Z),await Q(G,Z),y.add(B.name)}u.push("skills"),o(f,"INFO","skills updated to workspace",{userId:e,cId:t,workspaceRoot:d,targetSkillsDir:D,agentTypes:j})}else o(f,"INFO","skills directory not found in uploaded zip, skipping",{userId:e,cId:t,extractRoot:T});M?(await X($),await Q(M,$),u.push("agents"),o(f,"INFO","agents updated to workspace",{userId:e,cId:t,workspaceRoot:d,targetAgentsDir:$,agentTypes:j})):o(f,"INFO","agents directory not found in uploaded zip, skipping",{userId:e,cId:t,extractRoot:T})}for(let T=0;T<k.length;T+=1){const q=k[T],M=l.join(z,`skill_url_${Date.now()}_${T}_${Math.round(Math.random()*1e6)}.zip`);L.push(M);const b=l.join(z,`skill_url_extract_${Date.now()}_${T}_${Math.round(Math.random()*1e6)}`);F.push(b),await n.promises.mkdir(b,{recursive:!0}),o(f,"INFO","Start download skill zip from url",{userId:e,cId:t,skillUrl:q}),await le(q,M,f),o(f,"INFO","Skill zip downloaded, start extracting",{userId:e,cId:t,skillUrl:q,downloadedZipPath:M}),await Y(M,b);const G=(await n.promises.readdir(b,{withFileTypes:!0})).filter(v=>v.isDirectory()&&!v.name.startsWith(".")),V=G.find(v=>v.name==="skills")?(await n.promises.readdir(l.join(b,"skills"),{withFileTypes:!0})).filter(v=>v.isDirectory()&&!v.name.startsWith(".")).map(v=>({name:v.name,sourcePath:l.join(b,"skills",v.name)})):G.map(v=>({name:v.name,sourcePath:l.join(b,v.name)}));if(V.length===0){o(f,"WARN","No skill directory found after extracting skill url zip",{userId:e,cId:t,skillUrl:q,extractRoot:b});continue}await n.promises.mkdir(D,{recursive:!0});for(const v of V){const A=l.join(D,v.name);n.existsSync(A)&&await X(A),await Q(v.sourcePath,A),y.add(v.name),o(f,"INFO","Skill from url updated to workspace",{userId:e,cId:t,skillUrl:q,skillName:v.name,destSkillPath:A})}u.includes("skills")||u.push("skills")}u.length===0&&o(f,"WARN","No valid skills or agents directories found",{userId:e,cId:t,hasUploadedZip:!!i,skillUrlsCount:k.length});const _=Array.from(y),x=u.length>0?`Workspace created successfully, ${u.join(" and ")} updated`:"Workspace created successfully (skills and agents directories not found)",W=Date.now()-S;o(f,"INFO","Workspace resources prepared",{userId:e,cId:t,updatedDirs:u,updatedSkills:_,agentTypes:j,prepareElapsedMs:W});const H=await fe(P,f);return o(f,"INFO",x,{userId:e,cId:t,updatedDirs:u,updatedSkills:_,agentTypes:j,prepareElapsedMs:W,syncAgentsElapsedMs:H?.elapsedMs,elapsedMs:Date.now()-S}),{message:x,workspaceRoot:d,updatedSkills:_}}catch(u){throw o(f,"ERROR","Failed to process workspace resources",{userId:e,cId:t,error:u.message,elapsedMs:Date.now()-S}),u instanceof w||u instanceof te||u instanceof U?u:new U(`Failed to create workspace: ${u.message}`,{userId:e,cId:t})}finally{const u=Date.now();try{for(const _ of F)n.existsSync(_)&&await n.promises.rm(_,{recursive:!0,force:!0})}catch(_){o(f,"WARN","Failed to clean up temporary extracted zip",{error:_.message})}for(const _ of L)try{n.existsSync(_)&&await n.promises.unlink(_)}catch(x){o(f,"WARN","Failed to clean up downloaded skill zip file",{downloadedZipPath:_,error:x.message})}try{i&&i.path&&n.existsSync(i.path)&&await n.promises.unlink(i.path)}catch(_){o(f,"WARN","Failed to clean up uploaded zip file",{tempZipPath:i?.path,error:_.message})}o(f,"INFO","Workspace temp cleanup completed",{userId:e,cId:t,elapsedMs:Date.now()-u,totalElapsedMs:Date.now()-S})}}async function Fe(e,t,i,a,s,r=null){const p=Date.now(),m=`computer:${e}:${t}`,c=oe(s),N=[],R=[],h=new Set;let g=null;if(!e)throw new w("userId cannot be empty",{field:"userId"});if(!t)throw new w("cId cannot be empty",{field:"cId"});if(!i)throw new w("agentId cannot be empty",{field:"agentId"});if(!a&&c.length===0)throw new w("file or skillUrls cannot both be empty",{field:"file|skillUrls"});const S=re(r),f=await J(r,e,t,m),k=l.join(f,".tmp");await n.promises.mkdir(f,{recursive:!0}),await n.promises.mkdir(k,{recursive:!0});const{agentStorePath:L,skillsDir:F,agentsDir:y}=await De(e,i,m,r);try{let d=await de(L,m);if(!d.acquired)for(let j=1;j<=5&&!d.acquired;j++)await new Promise(E=>setTimeout(E,200*j)),d=await de(L,m);if(!d.acquired)throw new U("Agent store is busy, please retry",{userId:e,cId:t,agentId:i});if(g=d.lockPath,a){if(!a.path)throw new w("Uploaded file has no valid path",{field:"file.path"});const j=l.join(k,`push_skill_extract_${Date.now()}_${Math.round(Math.random()*1e6)}`);R.push(j),await n.promises.mkdir(j,{recursive:!0}),await Y(a.path,j);const E=await we(j),O=await ie(j,"skills"),C=O?(await n.promises.readdir(O,{withFileTypes:!0})).filter(u=>u.isDirectory()).map(u=>({name:u.name,sourcePath:l.join(O,u.name)})):E;await ae(C,se,async u=>{await ne(u.sourcePath,F,u.name,{asDynamic:!0}),h.add(u.name)})}await ae(c,se,async(j,E)=>{const O=l.join(k,`push_skill_url_${Date.now()}_${E}_${Math.round(Math.random()*1e6)}.zip`);N.push(O);const C=l.join(k,`push_skill_url_extract_${Date.now()}_${E}_${Math.round(Math.random()*1e6)}`);R.push(C),await n.promises.mkdir(C,{recursive:!0}),await le(j,O,m),await Y(O,C);const u=await we(C);for(const _ of u)await ne(_.sourcePath,F,_.name,{asDynamic:!0}),h.add(_.name)});let P=null,z=!1;r&&(r.isUserApp||r.isNormalProject)&&r.appId&&(z=await Re(e,f,i,r,m,{})),z||(P=await ge(f,F,y,m));const D=Array.from(h),$=D.length>0?`Pushed ${D.length} skills to agent store: ${D.join(", ")}`:"No valid skill directories found in file or skillUrls";return o(m,"INFO",$,{userId:e,cId:t,agentId:i,updatedSkills:D,linkElapsedMs:P?.elapsedMs,elapsedMs:Date.now()-p}),{message:$,workspaceRoot:S,agentStorePath:L,updatedSkills:D}}catch(d){throw o(m,"ERROR","Failed to push skills to agent store",{userId:e,cId:t,agentId:i,error:d.message,elapsedMs:Date.now()-p}),d instanceof w||d instanceof te||d instanceof U?d:new U(`Failed to push skill to workspace: ${d.message}`,{userId:e,cId:t,agentId:i})}finally{await xe(g,m);for(const d of R)try{n.existsSync(d)&&await n.promises.rm(d,{recursive:!0,force:!0})}catch(P){o(m,"WARN","Failed to clean up temporary extracted zip",{error:P.message})}for(const d of N)try{n.existsSync(d)&&await n.promises.unlink(d)}catch(P){o(m,"WARN","Failed to clean up downloaded skill zip file",{downloadedZipPath:d,error:P.message})}try{a&&a.path&&n.existsSync(a.path)&&await n.promises.unlink(a.path)}catch(d){o(m,"WARN","Failed to clean up uploaded zip file",{tempZipPath:a?.path,error:d.message})}}}async function et(e,t,i,a,s,r=null){if(s!=null&&String(s).trim()!==""){if(r&&(r.isUserApp||r.isNormalProject)&&r.appId)return Fe(e,t,s,i,a,r);let F=null;if(r?.isUserApp?F=Je(r,e,t):ee.COMPUTER_WORKSPACE_DIR&&(F=l.join(ee.COMPUTER_WORKSPACE_DIR,String(e),String(t))),Le(F))return Fe(e,t,s,i,a,r);o(`computer:${e}:${t}`,"INFO","Push skills: agentId present but workspace not symlinked, use legacy path",{userId:e,cId:t,agentId:s,userWorkspaceRoot:F})}const m=Date.now(),c=`computer:${e}:${t}`,N=oe(a),R=[],h=[];if(!e)throw new w("userId cannot be empty",{field:"userId"});if(!t)throw new w("cId cannot be empty",{field:"cId"});if(!i&&N.length===0)throw new w("file or skillUrls cannot both be empty",{field:"file|skillUrls"});if(i){if(!i.path)throw new w("Uploaded file has no valid path",{field:"file.path"});if(l.extname(i.originalname||i.filename||"").toLowerCase()!==".zip")throw new w("Only zip files are supported",{field:"file",originalName:i?.originalname})}const g=re(r),S=await J(r,e,t,c),f=l.join(S,".tmp"),{skillsDir:k,agentTypes:L}=await ye(S);try{n.existsSync(S)||await n.promises.mkdir(S,{recursive:!0}),n.existsSync(k)||await n.promises.mkdir(k,{recursive:!0}),n.existsSync(f)||await n.promises.mkdir(f,{recursive:!0});const F=[],y=new Set;if(i){const D=l.join(f,`skill_push_${Date.now()}_${Math.round(Math.random()*1e6)}`);h.push(D),await n.promises.mkdir(D,{recursive:!0}),o(c,"DEBUG","Start extracting skill zip file",{extractRoot:D}),await Y(i.path,D),o(c,"DEBUG","Skill zip file extracted successfully",{extractRoot:D});const $=await ie(D,"skills");if(!$)o(c,"WARN","skills directory not found in uploaded zip",{userId:e,cId:t,extractRoot:D});else{const E=(await n.promises.readdir($,{withFileTypes:!0})).filter(O=>O.isDirectory()&&!O.name.startsWith("."));if(E.length===0)o(c,"WARN","skills directory in uploaded zip has no skill subdirectories",{userId:e,cId:t,skillsDir:$});else for(const O of E){const C=l.join($,O.name),u=l.join(k,O.name);n.existsSync(u)&&await X(u),await Q(C,u),y.add(O.name),o(c,"INFO","skill pushed to workspace from uploaded zip",{userId:e,cId:t,skillName:O.name,destSkillPath:u})}}}for(let D=0;D<N.length;D+=1){const $=N[D],j=l.join(f,`skill_push_url_${Date.now()}_${D}_${Math.round(Math.random()*1e6)}.zip`);R.push(j);const E=l.join(f,`skill_push_url_extract_${Date.now()}_${D}_${Math.round(Math.random()*1e6)}`);h.push(E),await n.promises.mkdir(E,{recursive:!0}),o(c,"INFO","Start download skill zip for push from url",{userId:e,cId:t,skillUrl:$}),await le($,j,c),await Y(j,E);const C=(await n.promises.readdir(E,{withFileTypes:!0})).filter(x=>x.isDirectory()&&!x.name.startsWith(".")),_=C.find(x=>x.name==="skills")?(await n.promises.readdir(l.join(E,"skills"),{withFileTypes:!0})).filter(x=>x.isDirectory()&&!x.name.startsWith(".")).map(x=>({name:x.name,sourcePath:l.join(E,"skills",x.name)})):C.map(x=>({name:x.name,sourcePath:l.join(E,x.name)}));if(_.length===0){o(c,"WARN","No skill directory found after extracting push skill url zip",{userId:e,cId:t,skillUrl:$,extractRoot:E});continue}for(const x of _){const W=l.join(k,x.name);n.existsSync(W)&&await X(W),await Q(x.sourcePath,W),y.add(x.name),o(c,"INFO","skill pushed to workspace from url zip",{userId:e,cId:t,skillUrl:$,skillName:x.name,destSkillPath:W})}}for(const D of y)F.push(D);const d=F.length>0?`Pushed ${F.length} skills: ${F.join(", ")}`:"No valid skill directories found in file or skillUrls",P=Date.now()-m;o(c,"INFO","Push skills resources prepared",{userId:e,cId:t,updatedSkills:F,agentTypes:L,prepareElapsedMs:P});const z=await fe(S,c);return o(c,"INFO",d,{userId:e,cId:t,updatedSkills:F,agentTypes:L,prepareElapsedMs:P,syncAgentsElapsedMs:z?.elapsedMs,elapsedMs:Date.now()-m}),{message:d,workspaceRoot:g,updatedSkills:F}}catch(F){throw o(c,"ERROR","Failed to push skill to workspace",{userId:e,cId:t,error:F.message,elapsedMs:Date.now()-m}),F instanceof w||F instanceof te||F instanceof U?F:new U(`Failed to push skill to workspace: ${F.message}`,{userId:e,cId:t})}finally{const F=Date.now();try{for(const y of h)n.existsSync(y)&&await n.promises.rm(y,{recursive:!0,force:!0})}catch(y){o(c,"WARN","Failed to clean up temporary extracted zip",{error:y.message})}for(const y of R)try{n.existsSync(y)&&await n.promises.unlink(y)}catch(d){o(c,"WARN","Failed to clean up downloaded skill zip file",{downloadedZipPath:y,error:d.message})}try{i&&i.path&&n.existsSync(i.path)&&await n.promises.unlink(i.path)}catch(y){o(c,"WARN","Failed to clean up uploaded zip file",{tempZipPath:i?.path,error:y.message})}o(c,"INFO","Push skills temp cleanup completed",{userId:e,cId:t,elapsedMs:Date.now()-F,totalElapsedMs:Date.now()-m})}}async function tt(e,t,i,a,s=null){const r=Date.now(),p=`computer:${e}:${t}`;if(!e)throw new w("userId cannot be empty",{field:"userId"});if(!t)throw new w("cId cannot be empty",{field:"cId"});if(!i||!i.path)throw new w("file is required",{field:"file"});if(l.extname(i.originalname||i.filename||"").toLowerCase()!==".zip")throw new w("Only zip files are supported",{field:"file",originalName:i.originalname});const c=await J(s,e,t,p),N=l.join(c,".tmp"),R=l.join(N,`template_extract_${Date.now()}_${Math.round(Math.random()*1e6)}`);try{n.existsSync(c)||await n.promises.mkdir(c,{recursive:!0}),n.existsSync(N)||await n.promises.mkdir(N,{recursive:!0}),await n.promises.mkdir(R,{recursive:!0}),o(p,"DEBUG","Start extracting template zip file",{extractRoot:R}),await Y(i.path,R),o(p,"DEBUG","Template zip file extracted successfully",{extractRoot:R});const h=await n.promises.readdir(R,{withFileTypes:!0});for(const g of h){const S=l.join(R,g.name),f=l.join(c,g.name);if(g.isDirectory())await Q(S,f);else{const k=l.dirname(f);n.existsSync(k)||await n.promises.mkdir(k,{recursive:!0}),await n.promises.copyFile(S,f)}}if(o(p,"INFO","Template files extracted to workspace",{userId:e,cId:t,targetDir:c,fileCount:h.length}),ee.GIT_ENABLED&&(a==="true"||a===!0)){const g=await import("../../service/gitService.js");await g.default.init({workspaceType:"taskAgent",userId:e,cId:t}),await g.default.commit({workspaceType:"taskAgent",userId:e,cId:t,message:"Initial commit"}),o(p,"INFO","Git init and initial commit done",{userId:e,cId:t})}return o(p,"INFO","Init project template completed",{userId:e,cId:t,targetDir:c,elapsedMs:Date.now()-r}),{message:"Project template initialized successfully",workspaceRoot:c}}catch(h){throw o(p,"ERROR","Failed to init project template",{userId:e,cId:t,error:h.message,elapsedMs:Date.now()-r}),h instanceof w||h instanceof te||h instanceof U?h:new U(`Failed to init project template: ${h.message}`,{userId:e,cId:t})}finally{try{n.existsSync(R)&&await n.promises.rm(R,{recursive:!0,force:!0})}catch(h){o(p,"WARN","Failed to clean up temporary extracted zip",{error:h.message})}try{i&&i.path&&n.existsSync(i.path)&&await n.promises.unlink(i.path)}catch(h){o(p,"WARN","Failed to clean up uploaded zip file",{tempZipPath:i?.path,error:h.message})}}}async function it(e,t,i,a=null){if(!e)throw new w("userId cannot be empty",{field:"userId"});if(!t)throw new w("cId cannot be empty",{field:"cId"});if(!i||typeof i!="string"||!i.trim())throw new w("command cannot be empty",{field:"command"});const s=await J(a,e,t,"computer");if(!n.existsSync(s))throw new w("workspace directory does not exist",{field:"workDir",workDir:s});const r=`computer:${e}:${t}`;o(r,"INFO","Execute command in workspace",{userId:e,cId:t,workDir:s,command:i});const p=10*60*1e3,m={cwd:s,timeout:p,maxBuffer:50*1024*1024};return ee.BASH_PATH&&(m.shell=ee.BASH_PATH),new Promise((c,N)=>{ve(i,m,(R,h,g)=>{const S=R?R.killed?-1:R.code||1:0,f=R&&R.killed?(g||"")+`
2
+ Command timed out after ${p/1e3}s`:g||"";o(r,"INFO","Execute command completed",{userId:e,cId:t,exitCode:S,killed:R?R.killed:!1,stdoutLength:h?h.length:0,stderrLength:f.length}),c({stdout:h||"",stderr:f,exitCode:S})})})}async function nt(e,t,i,a=null){const s=`computer:${e}:${t}`;if(!e)throw new w("userId cannot be empty",{field:"userId"});if(!t)throw new w("cId cannot be empty",{field:"cId"});const r=await J(a,e,t,s);if(!n.existsSync(r))throw new w("workspace directory does not exist",{field:"workDir",workDir:r});const p=ee.ZIP_WORKSPACE_EXCLUDE||[],m=Array.isArray(i)?i:[],c=new Set([...p,...m]),N=`${e}_${t}.zip`,R=Date.now();o(s,"INFO","Zip workspace request",{userId:e,cId:t,targetDir:r,excludeDirs:[...c]});const h=Me("zip",{zlib:{level:9}});return h.directory(r,!1,g=>{const S=g.name||"";if(S.split(/[\/\\]/).filter(Boolean).some(k=>c.has(k)))return!1;try{const k=l.join(r,S);if(n.lstatSync(k).isSymbolicLink())return!1}catch{return!1}return g}),h.on("warning",g=>{g.code==="ENOENT"?o(s,"WARN","Encountered file problem when creating zip",{message:g.message,code:g.code}):o(s,"ERROR","Encountered warning when creating zip",{message:g.message,code:g.code})}),h.on("error",g=>{o(s,"ERROR","Failed to create zip",{message:g.message,elapsedMs:Date.now()-R})}),h.on("end",()=>{o(s,"INFO","Workspace zip created successfully",{targetDir:r,zipFileName:N,elapsedMs:Date.now()-R})}),{archive:h,zipFileName:N}}const he=new Set([...ee.ZIP_WORKSPACE_EXCLUDE||[],"dist-packages"]);function pe(e){let t;try{t=n.readdirSync(e,{withFileTypes:!0})}catch{return null}const i=l.join(e,"scripts","package-platforms.mjs");if(n.existsSync(i))return e;for(const a of t)if(a.isDirectory()&&!he.has(a.name)){const s=pe(l.join(e,a.name));if(s)return s}return null}function ue(e,t,i,a=10*60*1e3){return new Promise(s=>{const r=ze(e,t,{...i,timeout:a});let p="",m="";r.stdout?.on("data",c=>{p+=c.toString()}),r.stderr?.on("data",c=>{m+=c.toString()}),r.on("error",c=>{s({stdout:p,stderr:m+`
3
+ Failed to spawn ${e}: ${c.message}`,exitCode:1})}),r.on("close",(c,N)=>{s(c===null&&N?{stdout:p,stderr:m+`
4
+ Process killed by signal ${N} (timeout after ${a/1e3}s)`,exitCode:-1}:{stdout:p,stderr:m,exitCode:c??1})})})}function st(e){if(!e)return null;let t=e;t.endsWith(".tar.gz")?t=t.slice(0,-7):t.endsWith(".tar.bz2")?t=t.slice(0,-8):(t.endsWith(".tgz")||t.endsWith(".zip"))&&(t=t.slice(0,-4));const i=t.split("-");if(i.length<4)return null;let a=-1;for(let s=i.length-1;s>=2;s--)if(/^\d+\.\d+\.\d+$/.test(i[s])){a=s;break}return a<3?null:i.slice(2,a).join("-")}async function at(e,t,i,a,s=null){const r=`computer:${e}:${t}`;if(!e)throw new w("userId cannot be empty",{field:"userId"});if(!t)throw new w("cId cannot be empty",{field:"cId"});if(!i)throw new w("agentId cannot be empty",{field:"agentId"});if(!a)throw new w("version cannot be empty",{field:"version"});const p=await J(s,e,t,r);if(!n.existsSync(p))throw new w("workspace directory does not exist",{field:"workDir",workDir:p});o(r,"INFO","Build agent package request",{userId:e,cId:t,agentId:i,version:a});const m=pe(p);if(!m)throw new w("package-platforms.mjs not found in workspace",{field:"script"});o(r,"INFO","Found package script",{projectDir:m});const c=await be(m,r);if(o(r,"INFO","pnpm install completed",{exitCode:c.exitCode,stderrLength:c.stderr.length}),c.exitCode!==0)throw o(r,"ERROR","pnpm install failed",{stderr:c.stderr}),new U(`pnpm install failed: ${c.stderr}`);const N=l.join(m,"dist-packages"),R=["scripts/package-platforms.mjs",`agent-${i}`,a,N,"--print-artifacts"],h=await ue("node",R,{cwd:m,env:ke()});if(o(r,"INFO","Build completed",{exitCode:h.exitCode,stdoutLength:h.stdout.length}),h.exitCode!==0)throw o(r,"ERROR","Build failed",{stderr:h.stderr}),new U(`Build failed: ${h.stderr}`);const g=[".tar.gz",".tar.bz2",".zip",".tgz"],S=[],f=h.stdout.split(`
5
+ `);for(const k of f){const L=k.trim();if(!L)continue;const F=L.toLowerCase();if(!g.some(D=>F.endsWith(D)))continue;const y=l.resolve(m,L);let d=l.relative(p,y);d=d.split(l.sep).join("/");const P=l.basename(L),z=st(P);S.push({path:d,fileName:P,platform:z||""})}return o(r,"INFO","Build agent package completed",{artifactsCount:S.length,artifacts:S}),{artifacts:S}}async function rt(e,t,i=null){const a=`computer:${e}:${t}`;if(!e)throw new w("userId cannot be empty",{field:"userId"});if(!t)throw new w("cId cannot be empty",{field:"cId"});const s=await J(i,e,t,a);if(!n.existsSync(s))return o(a,"WARN","Workspace not found, skip cleanup",{userId:e,cId:t}),{cleaned:!1};const r=pe(s);if(!r)return o(a,"WARN","package-platforms.mjs not found, skip cleanup",{userId:e,cId:t}),{cleaned:!1};const p=l.join(r,"dist-packages");return n.existsSync(p)?(n.rmSync(p,{recursive:!0,force:!0}),o(a,"INFO","Cleaned dist-packages",{distPackagesDir:p}),{cleaned:!0}):(o(a,"INFO","dist-packages not found, nothing to clean",{distPackagesDir:p}),{cleaned:!1})}async function ot(e,t,i=null){const a=`computer:${e}:${t}`;if(!e)throw new w("userId cannot be empty",{field:"userId"});if(!t)throw new w("cId cannot be empty",{field:"cId"});const s=i?.workspacePath?i.workspacePath:await J(i,e,t,a);return n.existsSync(s)?(await n.promises.rm(s,{recursive:!0,force:!0}),o(a,"INFO","Workspace deleted",{userId:e,cId:t,targetDir:s})):o(a,"WARN","Workspace not found, skip delete",{userId:e,cId:t,targetDir:s}),{deleted:!0}}function Ee(e){const t=l.join(e,"package.json");if(n.existsSync(t))return e;let i;try{i=n.readdirSync(e,{withFileTypes:!0})}catch{return null}for(const a of i)if(a.isDirectory()&&!he.has(a.name)&&a.name!=="node_modules"){const s=Ee(l.join(e,a.name));if(s)return s}return null}const Ae=["neverBuiltDependencies","onlyBuiltDependencies","ignoredBuiltDependencies"],lt=["NPM_CONFIG_NEVER_BUILT_DEPENDENCIES","npm_config_never_built_dependencies","NPM_CONFIG_ONLY_BUILT_DEPENDENCIES","npm_config_only_built_dependencies","NPM_CONFIG_IGNORED_BUILT_DEPENDENCIES","npm_config_ignored_built_dependencies"];async function ct(e,t){const i=l.join(e,"package.json");if(!n.existsSync(i))return;const a=await n.promises.readFile(i,"utf8");let s;try{s=JSON.parse(a)}catch{o(t,"WARN","Skip sanitizing package.json: invalid JSON",{projectDir:e});return}if(!s.pnpm||typeof s.pnpm!="object")return;const r=Ae.filter(p=>p in s.pnpm);if(r.length!==0){for(const p of r)delete s.pnpm[p];Object.keys(s.pnpm).length===0&&delete s.pnpm,await n.promises.writeFile(i,`${JSON.stringify(s,null,2)}
6
+ `,"utf8"),o(t,"INFO","Removed conflicting pnpm built dependency settings from package.json",{projectDir:e,removedKeys:r})}}async function pt(e,t){const i=l.join(e,"pnpm-workspace.yaml");if(!n.existsSync(i))return;const s=(await n.promises.readFile(i,"utf8")).split(`
7
+ `),r=[];let p=-1,m=[];for(const c of s){const N=c.trimStart(),R=c.length-N.length,h=Ae.find(g=>new RegExp(`^${g}:\\s*(.*)$`).test(N));if(h){m.push(h);const g=N.slice(h.length+1).trim();(!g||g==="|"||g===">")&&(p=R);continue}if(p>=0){if(N===""||R>p)continue;p=-1}r.push(c)}m.length!==0&&(await n.promises.writeFile(i,r.join(`
8
+ `),"utf8"),o(t,"INFO","Removed conflicting pnpm built dependency settings from pnpm-workspace.yaml",{projectDir:e,removedKeys:m}))}async function Oe(e){if(!n.existsSync(e))return"";const t=await n.promises.readFile(e,"utf8"),i=t.split(`
9
+ `).filter(a=>!/^\s*(never-built-dependencies|only-built-dependencies|ignored-built-dependencies)\b/.test(a)).join(`
10
+ `);return i!==t&&await n.promises.writeFile(e,i,"utf8"),i}async function ut(e,t){await ct(e,t),await pt(e,t),await Oe(l.join(e,".npmrc"))}async function mt(e,t){await We(e,t),await ut(e,t);const i=l.join(e,".npmrc");let a=await Oe(i);const s=[];if(/dangerously-allow-all-builds\s*=/.test(a)||s.push("dangerously-allow-all-builds=true"),/production\s*=/.test(a)||s.push("production=false"),/confirm-modules-purge\s*=/.test(a)||s.push("confirm-modules-purge=false"),s.length>0){const r=a&&!a.endsWith(`
11
11
  `)?`
12
- `:"";await n.promises.writeFile(i,`${s}${o}${a.join(`
12
+ `:"";await n.promises.writeFile(i,`${a}${r}${s.join(`
13
13
  `)}
14
- `,"utf8"),r(t,"INFO","Updated .npmrc for pnpm install",{projectDir:e,additions:a})}}function we(){const e={...process.env};delete e.CI,delete e.NPM_CONFIG_PRODUCTION,delete e.npm_config_production;for(const t of Xe)delete e[t];return e.NODE_ENV="development",e.NPM_CONFIG_PRODUCTION="false",e}async function Pe(e,t){await tt(e,t);const i=we();return ce("pnpm",["install","--prefer-offline","--config.production=false","--config.confirmModulesPurge=false","--config.dangerouslyAllowAllBuilds=true"],{cwd:e,env:i})}function Fe(e){const t=c.join(e,"pyproject.toml"),i=c.join(e,"requirements.txt");if(n.existsSync(t)||n.existsSync(i))return e;let s;try{s=n.readdirSync(e,{withFileTypes:!0})}catch{return null}for(const a of s)if(a.isDirectory()&&!fe.has(a.name)&&a.name!==".venv"&&a.name!=="venv"&&a.name!=="__pycache__"){const o=Fe(c.join(e,a.name));if(o)return o}return null}async function it(e,t,i,s=null){const a=`computer:${e}:${t}`,o=String(i||"").trim().toLowerCase();if(!e)throw new d("userId cannot be empty",{field:"userId"});if(!t)throw new d("cId cannot be empty",{field:"cId"});if(!o)throw new d("programmingLanguage cannot be empty",{field:"programmingLanguage"});const p=await J(s,e,t,a);if(!n.existsSync(p))throw new d("workspace directory does not exist",{field:"workDir",workDir:p});r(a,"INFO","Install project dependencies request",{userId:e,cId:t,programmingLanguage:o});const u=we();let l=null,D=null;if(o==="typescript"||o==="ts"){if(l=le(p)||Ne(p),!l)throw new d("TypeScript project not found in workspace",{field:"projectDir"});D=await Pe(l,a)}else if(o==="python"||o==="py"){if(l=Fe(p),!l)throw new d("Python project not found in workspace",{field:"projectDir"});const x=c.join(l,"pyproject.toml"),f=c.join(l,"requirements.txt");if(n.existsSync(x))D=await ce("pip",["install","-e","."],{cwd:l,env:u});else if(n.existsSync(f))D=await ce("pip",["install","-r","requirements.txt"],{cwd:l,env:u});else throw new d("No pyproject.toml or requirements.txt found in workspace",{field:"projectDir"})}else throw new d("Unsupported programmingLanguage",{field:"programmingLanguage",programmingLanguage:o});if(r(a,"INFO","Project dependencies install completed",{projectDir:l,programmingLanguage:o,exitCode:D.exitCode,stderrLength:D.stderr.length}),D.exitCode!==0)throw r(a,"ERROR","Project dependencies install failed",{projectDir:l,programmingLanguage:o,stderr:D.stderr}),new Z(`Project dependencies install failed: ${D.stderr||D.stdout}`);return{message:"Project dependencies installed successfully",projectDir:l,programmingLanguage:o}}export{Be as createWorkspace,Ze as pushSkillsToWorkspace,Ge as initProjectTemplate,qe as executeCommand,Ye as deleteWorkspace,Ke as zipWorkspace,He as buildAgentPackage,Ve as cleanupBuildArtifacts,it as installProjectDependencies};
14
+ `,"utf8"),o(t,"INFO","Updated .npmrc for pnpm install",{projectDir:e,additions:s})}}function ke(){const e={...process.env};delete e.CI,delete e.NPM_CONFIG_PRODUCTION,delete e.npm_config_production;for(const t of lt)delete e[t];return e.NODE_ENV="development",e.NPM_CONFIG_PRODUCTION="false",e}async function be(e,t){await mt(e,t);const i=ke();return ue("pnpm",["install","--prefer-offline","--config.production=false","--config.confirmModulesPurge=false","--config.dangerouslyAllowAllBuilds=true"],{cwd:e,env:i})}function $e(e){const t=l.join(e,"pyproject.toml"),i=l.join(e,"requirements.txt");if(n.existsSync(t)||n.existsSync(i))return e;let a;try{a=n.readdirSync(e,{withFileTypes:!0})}catch{return null}for(const s of a)if(s.isDirectory()&&!he.has(s.name)&&s.name!==".venv"&&s.name!=="venv"&&s.name!=="__pycache__"){const r=$e(l.join(e,s.name));if(r)return r}return null}async function ft(e,t,i,a=null){const s=`computer:${e}:${t}`,r=String(i||"").trim().toLowerCase();if(!e)throw new w("userId cannot be empty",{field:"userId"});if(!t)throw new w("cId cannot be empty",{field:"cId"});if(!r)throw new w("programmingLanguage cannot be empty",{field:"programmingLanguage"});const p=await J(a,e,t,s);if(!n.existsSync(p))throw new w("workspace directory does not exist",{field:"workDir",workDir:p});o(s,"INFO","Install project dependencies request",{userId:e,cId:t,programmingLanguage:r});const m=ke();let c=null,N=null;if(r==="typescript"||r==="ts"){if(c=pe(p)||Ee(p),!c)throw new w("TypeScript project not found in workspace",{field:"projectDir"});N=await be(c,s)}else if(r==="python"||r==="py"){if(c=$e(p),!c)throw new w("Python project not found in workspace",{field:"projectDir"});const R=l.join(c,"pyproject.toml"),h=l.join(c,"requirements.txt");if(n.existsSync(R))N=await ue("pip",["install","-e","."],{cwd:c,env:m});else if(n.existsSync(h))N=await ue("pip",["install","-r","requirements.txt"],{cwd:c,env:m});else throw new w("No pyproject.toml or requirements.txt found in workspace",{field:"projectDir"})}else throw new w("Unsupported programmingLanguage",{field:"programmingLanguage",programmingLanguage:r});if(o(s,"INFO","Project dependencies install completed",{projectDir:c,programmingLanguage:r,exitCode:N.exitCode,stderrLength:N.stderr.length}),N.exitCode!==0)throw o(s,"ERROR","Project dependencies install failed",{projectDir:c,programmingLanguage:r,stderr:N.stderr}),new U(`Project dependencies install failed: ${N.stderr||N.stdout}`);return{message:"Project dependencies installed successfully",projectDir:c,programmingLanguage:r}}export{Ie as createWorkspace,et as pushSkillsToWorkspace,tt as initProjectTemplate,it as executeCommand,ot as deleteWorkspace,nt as zipWorkspace,at as buildAgentPackage,rt as cleanupBuildArtifacts,ft as installProjectDependencies};
@@ -1 +1 @@
1
- import s from"fs";import c from"path";import a from"../../appConfig/index.js";import{log as w}from"../log/logUtils.js";import{ValidationError as i}from"../error/errorHandler.js";export const SERVICE_TYPE={USERAPP:"userapp",PAGEAPP:"pageApp",NORMAL_PROJECT:"normalProject",GENERAL_AGENT:"generalAgent"};export function resolveServiceContext(r){const t=r.headers&&typeof r.headers["x-service-type"]=="string"?r.headers["x-service-type"].trim():"",e=(r.body?.serviceType??r.query?.serviceType??"").toString().trim(),p=A=>{const l=A.toLowerCase();return l==="general"?SERVICE_TYPE.GENERAL_AGENT:Object.values(SERVICE_TYPE).find(E=>E.toLowerCase()===l)||""},n=p(t)||p(e)||SERVICE_TYPE.GENERAL_AGENT,o=typeof r.headers?.["x-app-id"]=="string"&&r.headers["x-app-id"].trim()||(r.body?.appId??r.query?.appId??"").toString().trim()||null||null,u=n===SERVICE_TYPE.USERAPP,P=n===SERVICE_TYPE.NORMAL_PROJECT;if(u&&!o)throw new i("appId is required for userapp workspace",{field:"appId"});if(P&&!o)throw new i("appId(projectId) is required for normalProject workspace",{field:"appId"});if(u&&!m(o))throw new i("appId contains illegal path segments",{field:"appId"});const d=typeof r.headers?.["x-workspace-path"]=="string"&&r.headers["x-workspace-path"].trim()||(r.body?.workspacePath??r.query?.workspacePath??"").toString().trim()||null,R=d?S(d):null;return{serviceType:n,appId:o,workspacePath:R,isUserApp:u,isNormalProject:P}}function m(r){return!!r&&!/[\/\\\0]/.test(r)&&r!=="."&&r!==".."}function S(r){let t=String(r).trim();if(t.length>512)throw new i("workspacePath length exceeds 512",{field:"workspacePath"});if(/[\0\x00-\x1f\x7f]/.test(t))throw new i("workspacePath contains illegal characters",{field:"workspacePath"});if(t=k(t),!t.startsWith("/")&&!/^[A-Za-z]:\/.*/.test(t))throw new i("workspacePath must be an absolute path (POSIX /a/b, Windows C:/a/b or UNC //server/share/a/b)",{field:"workspacePath"});if(t.split("/").some(e=>e==="."||e===".."))throw new i("workspacePath must not contain dot segments",{field:"workspacePath"});return t}function k(r){const t=r.startsWith("//")||r.startsWith("\\\\");let e=r.replace(/\\/g,"/").replace(/\/{2,}/g,"/");return t&&(e="/"+e),/^[a-z]:\//.test(e)&&(e=e.charAt(0).toUpperCase()+e.slice(1)),e}function h(r,t){if(!r)throw new i(`${t} configuration does not exist`,{field:t});return r}export function resolveWorkspaceRoot(r){return r?.isUserApp?h(a.USERAPP_WORKSPACE_DIR,"USERAPP_WORKSPACE_DIR"):a.COMPUTER_WORKSPACE_DIR}export function resolveWorkspaceDir(r,t,e){return r?.workspacePath?r.workspacePath:r?.isUserApp?c.join(resolveWorkspaceRoot(r),String(r.appId)):r?.isNormalProject?c.join(a.COMPUTER_WORKSPACE_DIR,String(t),"NormalProject",String(r.appId)):c.join(a.COMPUTER_WORKSPACE_DIR,String(t),String(e))}export async function ensureWorkspaceDir(r,t,e,p="computer"){if(r?.workspacePath){const o=r.workspacePath;return s.existsSync(o)||(await s.promises.mkdir(o,{recursive:!0}),w(p,"INFO","Created bound workspace directory",{boundDir:o})),o}const n=resolveWorkspaceRoot(r);if(!n)throw new i("COMPUTER_WORKSPACE_DIR configuration does not exist",{field:"COMPUTER_WORKSPACE_DIR"});if(s.existsSync(n)||(await s.promises.mkdir(n,{recursive:!0}),w(p,"INFO","Created user workspace root directory",{workspaceRoot:n})),r?.isUserApp){const o=c.join(n,String(r.appId));if(!s.existsSync(o))throw new i(`userapp workspace mount does not exist: ${o}`,{field:"USERAPP_WORKSPACE_DIR"});return o}const f=resolveWorkspaceDir(r,t,e);return s.existsSync(f)||await s.promises.mkdir(f,{recursive:!0}),f}export function resolveLogDir(r,t,e){return r?.isUserApp?h(a.USERAPP_LOG_DIR,"USERAPP_LOG_DIR"):a.COMPUTER_WORKSPACE_DIR?c.join(resolveWorkspaceDir(r,t,e),".logs"):null}
1
+ import n from"fs";import c from"path";import a from"../../appConfig/index.js";import{log as w}from"../log/logUtils.js";import{ValidationError as i}from"../error/errorHandler.js";export const SERVICE_TYPE={USERAPP:"userapp",PAGEAPP:"pageApp",NORMAL_PROJECT:"normalProject",TASK_AGENT:"taskAgent"};export function resolveServiceContext(r){const t=r.headers&&typeof r.headers["x-service-type"]=="string"?r.headers["x-service-type"].trim():"",e=(r.body?.serviceType??r.query?.serviceType??"").toString().trim(),p=R=>{const A=R.toLowerCase();return Object.values(SERVICE_TYPE).find(m=>m.toLowerCase()===A)||""},s=p(t)||p(e)||SERVICE_TYPE.TASK_AGENT,o=typeof r.headers?.["x-app-id"]=="string"&&r.headers["x-app-id"].trim()||(r.body?.appId??r.query?.appId??"").toString().trim()||null||null,u=s===SERVICE_TYPE.USERAPP,P=s===SERVICE_TYPE.NORMAL_PROJECT;if(u&&!o)throw new i("appId is required for userapp workspace",{field:"appId"});if(P&&!o)throw new i("appId(projectId) is required for normalProject workspace",{field:"appId"});if(u&&!S(o))throw new i("appId contains illegal path segments",{field:"appId"});const d=typeof r.headers?.["x-workspace-path"]=="string"&&r.headers["x-workspace-path"].trim()||(r.body?.workspacePath??r.query?.workspacePath??"").toString().trim()||null,h=d?k(d):null;return{serviceType:s,appId:o,workspacePath:h,isUserApp:u,isNormalProject:P}}function S(r){return!!r&&!/[\/\\\0]/.test(r)&&r!=="."&&r!==".."}function k(r){let t=String(r).trim();if(t.length>512)throw new i("workspacePath length exceeds 512",{field:"workspacePath"});if(/[\0\x00-\x1f\x7f]/.test(t))throw new i("workspacePath contains illegal characters",{field:"workspacePath"});if(t=E(t),!t.startsWith("/")&&!/^[A-Za-z]:\/.*/.test(t))throw new i("workspacePath must be an absolute path (POSIX /a/b, Windows C:/a/b or UNC //server/share/a/b)",{field:"workspacePath"});if(t.split("/").some(e=>e==="."||e===".."))throw new i("workspacePath must not contain dot segments",{field:"workspacePath"});return t}function E(r){const t=r.startsWith("//")||r.startsWith("\\\\");let e=r.replace(/\\/g,"/").replace(/\/{2,}/g,"/");return t&&(e="/"+e),/^[a-z]:\//.test(e)&&(e=e.charAt(0).toUpperCase()+e.slice(1)),e}function l(r,t){if(!r)throw new i(`${t} configuration does not exist`,{field:t});return r}export function resolveWorkspaceRoot(r){return r?.isUserApp?l(a.USERAPP_WORKSPACE_DIR,"USERAPP_WORKSPACE_DIR"):a.COMPUTER_WORKSPACE_DIR}export function resolveWorkspaceDir(r,t,e){return r?.workspacePath?r.workspacePath:r?.isUserApp?c.join(resolveWorkspaceRoot(r),String(r.appId)):r?.isNormalProject?c.join(a.COMPUTER_WORKSPACE_DIR,String(t),"NormalProject",String(r.appId)):c.join(a.COMPUTER_WORKSPACE_DIR,String(t),String(e))}export async function ensureWorkspaceDir(r,t,e,p="computer"){if(r?.workspacePath){const o=r.workspacePath;return n.existsSync(o)||(await n.promises.mkdir(o,{recursive:!0}),w(p,"INFO","Created bound workspace directory",{boundDir:o})),o}const s=resolveWorkspaceRoot(r);if(!s)throw new i("COMPUTER_WORKSPACE_DIR configuration does not exist",{field:"COMPUTER_WORKSPACE_DIR"});if(n.existsSync(s)||(await n.promises.mkdir(s,{recursive:!0}),w(p,"INFO","Created user workspace root directory",{workspaceRoot:s})),r?.isUserApp){const o=c.join(s,String(r.appId));if(!n.existsSync(o))throw new i(`userapp workspace mount does not exist: ${o}`,{field:"USERAPP_WORKSPACE_DIR"});return o}const f=resolveWorkspaceDir(r,t,e);return n.existsSync(f)||await n.promises.mkdir(f,{recursive:!0}),f}export function resolveLogDir(r,t,e){return r?.isUserApp?l(a.USERAPP_LOG_DIR,"USERAPP_LOG_DIR"):a.COMPUTER_WORKSPACE_DIR?c.join(resolveWorkspaceDir(r,t,e),".logs"):null}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuwax-file-server",
3
3
  "displayName": "nuwax-file-server",
4
- "version": "1.4.4",
4
+ "version": "1.4.5",
5
5
  "description": "Cross-platform file service deployment tool with start/stop/restart CLI commands",
6
6
  "type": "module",
7
7
  "main": "index.js",