nuwax-file-server 1.3.1 → 1.3.3-beta.1

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 O(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)}}O(r);const R={NODE_ENV:r,PORT:parseInt(process.env.PORT),DEPLOYMENT_MODE:process.env.DEPLOYMENT_MODE||"docker-compose",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,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",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 R;
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 O(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)}}O(r);const R={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,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",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 R;
@@ -0,0 +1,73 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ export const UNSUPPORTED_EVENTS = [
5
+ "PermissionRequest",
6
+ "SubagentStart",
7
+ "SubagentStop",
8
+ "TeammateIdle",
9
+ "TaskCompleted",
10
+ "ConfigChange",
11
+ "WorktreeCreate",
12
+ "WorktreeRemove",
13
+ "InstructionsLoaded",
14
+ ];
15
+ export const SUPPORTED_EVENTS = [
16
+ "PreToolUse",
17
+ "PostToolUse",
18
+ "PostToolUseFailure",
19
+ "SessionStart",
20
+ "SessionEnd",
21
+ "Stop",
22
+ "UserPromptSubmit",
23
+ "Notification",
24
+ "PreCompact",
25
+ ];
26
+ function readSettingsFile(filePath) {
27
+ try {
28
+ const content = readFileSync(filePath, "utf-8");
29
+ return JSON.parse(content);
30
+ }
31
+ catch {
32
+ return null;
33
+ }
34
+ }
35
+ function mergeHooksConfig(base, override) {
36
+ const result = { ...base };
37
+ for (const [event, matchers] of Object.entries(override)) {
38
+ const key = event;
39
+ const existing = result[key] ?? [];
40
+ result[key] = [...existing, ...(matchers ?? [])];
41
+ }
42
+ return result;
43
+ }
44
+ export function loadConfig(cwd = process.cwd(), { homeDir = homedir() } = {}) {
45
+ const globalPath = join(homeDir, ".claude", "settings.json");
46
+ const projectPath = join(cwd, ".claude", "settings.json");
47
+ const localPath = join(cwd, ".claude", "settings.local.json");
48
+ const globalSettings = readSettingsFile(globalPath);
49
+ const projectSettings = readSettingsFile(projectPath);
50
+ const localSettings = readSettingsFile(localPath);
51
+ // Merge: global -> project -> local (later entries are appended / extend)
52
+ let merged = {};
53
+ if (globalSettings?.hooks)
54
+ merged = mergeHooksConfig(merged, globalSettings.hooks);
55
+ if (projectSettings?.hooks)
56
+ merged = mergeHooksConfig(merged, projectSettings.hooks);
57
+ if (localSettings?.hooks)
58
+ merged = mergeHooksConfig(merged, localSettings.hooks);
59
+ // disableAllHooks: local takes precedence, then project, then global
60
+ const disableAllHooks = localSettings?.disableAllHooks ??
61
+ projectSettings?.disableAllHooks ??
62
+ globalSettings?.disableAllHooks ??
63
+ false;
64
+ return { hooks: merged, disableAllHooks };
65
+ }
66
+ export function warnUnsupportedEvents(hooks, warn = console.warn) {
67
+ for (const event of UNSUPPORTED_EVENTS) {
68
+ if (hooks[event] && hooks[event].length > 0) {
69
+ warn(`[opencode-hooks-api] Unsupported event "${event}" is configured but will not fire. Reason: no OpenCode equivalent.`);
70
+ }
71
+ }
72
+ }
73
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1,50 @@
1
+ export function buildToolHookInput(eventName, event, cwd) {
2
+ return {
3
+ session_id: event.sessionId,
4
+ cwd,
5
+ hook_event_name: eventName,
6
+ tool_name: event.tool,
7
+ tool_input: event.args,
8
+ };
9
+ }
10
+ export function buildSessionHookInput(eventName, event, cwd) {
11
+ return {
12
+ session_id: event.sessionId,
13
+ cwd,
14
+ hook_event_name: eventName,
15
+ };
16
+ }
17
+ /**
18
+ * Detect if a tool execution resulted in failure (best-effort).
19
+ * Used to decide between PostToolUse and PostToolUseFailure.
20
+ */
21
+ export function detectToolFailure(event) {
22
+ // Explicit error flag
23
+ if (event.error === true || (typeof event.error === "string" && event.error.length > 0)) {
24
+ return true;
25
+ }
26
+ const output = typeof event.output === "string" ? event.output : JSON.stringify(event.output ?? "");
27
+ if (event.tool === "bash") {
28
+ // Check for bash error patterns
29
+ const errorPatterns = [
30
+ /exit code [1-9]/i,
31
+ /exited with code [1-9]/i,
32
+ /command failed/i,
33
+ /error:/i,
34
+ /\bfailed\b/i,
35
+ /\bFAILED\b/,
36
+ ];
37
+ return errorPatterns.some((p) => p.test(output));
38
+ }
39
+ // For other tools, check output for generic error indicators
40
+ const genericErrorPatterns = [/error/i, /failed/i, /exception/i];
41
+ return genericErrorPatterns.some((p) => p.test(output));
42
+ }
43
+ /**
44
+ * Map OpenCode tool.execute.after event to Claude hook event name.
45
+ * Returns PostToolUseFailure if failure detected, else PostToolUse.
46
+ */
47
+ export function mapToolAfterEvent(event) {
48
+ return detectToolFailure(event) ? "PostToolUseFailure" : "PostToolUse";
49
+ }
50
+ //# sourceMappingURL=events.js.map
@@ -0,0 +1,177 @@
1
+ import { spawn } from "node:child_process";
2
+ // ---------------------------------------------------------------------------
3
+ // Helpers
4
+ // ---------------------------------------------------------------------------
5
+ function parseHookOutput(text) {
6
+ const trimmed = text.trim();
7
+ if (!trimmed)
8
+ return null;
9
+ try {
10
+ return JSON.parse(trimmed);
11
+ }
12
+ catch {
13
+ return null;
14
+ }
15
+ }
16
+ function outputToResult(output) {
17
+ if (!output)
18
+ return { action: "allow" };
19
+ // Block conditions: explicit decision, continue=false, or ok=false
20
+ if (output.decision === "block" || output.continue === false || output.ok === false) {
21
+ return {
22
+ action: "block",
23
+ reason: output.stopReason ?? output.reason ?? "Blocked by hook",
24
+ updatedInput: output.hookSpecificOutput?.updatedInput,
25
+ additionalContext: output.hookSpecificOutput?.additionalContext,
26
+ };
27
+ }
28
+ return {
29
+ action: "allow",
30
+ updatedInput: output.hookSpecificOutput?.updatedInput,
31
+ additionalContext: output.hookSpecificOutput?.additionalContext,
32
+ };
33
+ }
34
+ function interpolateEnvVars(value) {
35
+ return value.replace(/\$([A-Z_][A-Z0-9_]*)/g, (_, name) => process.env[name] ?? "");
36
+ }
37
+ // ---------------------------------------------------------------------------
38
+ // Command handler
39
+ // ---------------------------------------------------------------------------
40
+ async function runCommand(handler, input) {
41
+ const timeoutMs = (handler.timeout ?? 600) * 1000;
42
+ const inputJson = JSON.stringify(input);
43
+ return new Promise((resolve) => {
44
+ let settled = false;
45
+ const proc = spawn("bash", ["-c", handler.command], {
46
+ env: {
47
+ ...process.env,
48
+ OPENCODE_PROJECT_DIR: input.cwd,
49
+ OPENCODE_SESSION_ID: input.session_id,
50
+ OPENCODE_HOOK_EVENT: input.hook_event_name,
51
+ },
52
+ });
53
+ const stdoutChunks = [];
54
+ const stderrChunks = [];
55
+ proc.stdout.on("data", (chunk) => stdoutChunks.push(chunk));
56
+ proc.stderr.on("data", (chunk) => stderrChunks.push(chunk));
57
+ const timer = setTimeout(() => {
58
+ if (!settled) {
59
+ settled = true;
60
+ proc.kill("SIGKILL");
61
+ resolve({ action: "error", reason: `Command timed out after ${handler.timeout ?? 600}s` });
62
+ }
63
+ }, timeoutMs);
64
+ proc.on("close", (code) => {
65
+ if (settled)
66
+ return;
67
+ settled = true;
68
+ clearTimeout(timer);
69
+ const stdout = Buffer.concat(stdoutChunks).toString("utf-8");
70
+ const stderr = Buffer.concat(stderrChunks).toString("utf-8").trim();
71
+ if (code === 0) {
72
+ const output = parseHookOutput(stdout);
73
+ resolve(outputToResult(output));
74
+ }
75
+ else if (code === 2) {
76
+ resolve({ action: "block", reason: stderr || "Blocked by hook" });
77
+ }
78
+ else {
79
+ // Non-zero, non-2: non-blocking error
80
+ const reason = stderr || `Command exited with code ${code}`;
81
+ console.error(`[opencode-hooks-api] Command hook error (exit ${code}): ${reason}`);
82
+ resolve({ action: "error", reason });
83
+ }
84
+ });
85
+ proc.on("error", (err) => {
86
+ if (settled)
87
+ return;
88
+ settled = true;
89
+ clearTimeout(timer);
90
+ console.error(`[opencode-hooks-api] Failed to spawn command: ${err.message}`);
91
+ resolve({ action: "error", reason: err.message });
92
+ });
93
+ // Write input JSON to stdin then close it
94
+ proc.stdin.write(inputJson, () => {
95
+ proc.stdin.end();
96
+ });
97
+ });
98
+ }
99
+ // ---------------------------------------------------------------------------
100
+ // HTTP handler
101
+ // ---------------------------------------------------------------------------
102
+ async function runHttp(handler, input) {
103
+ const timeoutMs = (handler.timeout ?? 600) * 1000;
104
+ const body = JSON.stringify(input);
105
+ const headers = { "Content-Type": "application/json" };
106
+ if (handler.headers) {
107
+ for (const [key, value] of Object.entries(handler.headers)) {
108
+ headers[key] = interpolateEnvVars(value);
109
+ }
110
+ }
111
+ try {
112
+ const response = await fetch(handler.url, {
113
+ method: "POST",
114
+ headers,
115
+ body,
116
+ signal: AbortSignal.timeout(timeoutMs),
117
+ });
118
+ if (!response.ok) {
119
+ const reason = `HTTP ${response.status} ${response.statusText}`;
120
+ console.error(`[opencode-hooks-api] HTTP hook error: ${reason}`);
121
+ return { action: "error", reason };
122
+ }
123
+ const text = await response.text();
124
+ const output = parseHookOutput(text);
125
+ return outputToResult(output);
126
+ }
127
+ catch (err) {
128
+ const message = err instanceof Error ? err.message : String(err);
129
+ console.error(`[opencode-hooks-api] HTTP hook fetch error: ${message}`);
130
+ return { action: "error", reason: message };
131
+ }
132
+ }
133
+ // ---------------------------------------------------------------------------
134
+ // Prompt / Agent handler
135
+ // ---------------------------------------------------------------------------
136
+ async function runPrompt(handler, input, client) {
137
+ if (!client || typeof client.session !== "object") {
138
+ return { action: "error", reason: "OpenCode SDK client not available for prompt/agent hook" };
139
+ }
140
+ const sdkClient = client;
141
+ const timeoutMs = (handler.timeout ?? (handler.type === "agent" ? 60 : 30)) * 1000;
142
+ const promptText = handler.prompt.replace(/\$ARGUMENTS/g, JSON.stringify(input));
143
+ try {
144
+ const result = await sdkClient.session.prompt({ prompt: promptText, timeout: timeoutMs });
145
+ const output = parseHookOutput(result.text);
146
+ return outputToResult(output);
147
+ }
148
+ catch (err) {
149
+ const message = err instanceof Error ? err.message : String(err);
150
+ console.error(`[opencode-hooks-api] Prompt/agent hook error: ${message}`);
151
+ return { action: "error", reason: message };
152
+ }
153
+ }
154
+ // ---------------------------------------------------------------------------
155
+ // Public API
156
+ // ---------------------------------------------------------------------------
157
+ export async function executeHook(handler, input, client) {
158
+ switch (handler.type) {
159
+ case "command":
160
+ return runCommand(handler, input);
161
+ case "http":
162
+ return runHttp(handler, input);
163
+ case "prompt":
164
+ return runPrompt(handler, input, client);
165
+ case "agent":
166
+ return runPrompt(handler, input, client);
167
+ default: {
168
+ // Exhaustiveness guard — TypeScript should never reach here
169
+ const _exhaustive = handler;
170
+ void _exhaustive;
171
+ return { action: "skip", reason: "Unknown handler type" };
172
+ }
173
+ }
174
+ }
175
+ // Re-export helpers for testing
176
+ export { parseHookOutput, outputToResult, interpolateEnvVars };
177
+ //# sourceMappingURL=executor.js.map
@@ -0,0 +1,214 @@
1
+ import { loadConfig, warnUnsupportedEvents } from "./config.js";
2
+ import { matchHook } from "./matcher.js";
3
+ import { executeHook } from "./executor.js";
4
+ import { buildToolHookInput, buildSessionHookInput, mapToolAfterEvent, } from "./events.js";
5
+ const LOG_PREFIX = "[opencode-hooks-api]";
6
+ async function runHooksForEvent(eventName, matchers, toolName, input, client) {
7
+ if (!matchers || matchers.length === 0)
8
+ return [];
9
+ const promises = [];
10
+ for (const matcher of matchers) {
11
+ if (!matchHook(matcher.matcher, toolName))
12
+ continue;
13
+ for (const handler of matcher.hooks) {
14
+ promises.push(executeHook(handler, input, client).catch((err) => {
15
+ const message = err instanceof Error ? err.message : String(err);
16
+ console.warn(`${LOG_PREFIX} Hook error (${eventName}):`, message);
17
+ return { action: "error", reason: message };
18
+ }));
19
+ }
20
+ }
21
+ return Promise.all(promises);
22
+ }
23
+ function findBlockResult(results) {
24
+ return results.find((r) => r.action === "block");
25
+ }
26
+ function findUpdatedInput(results) {
27
+ for (const r of results) {
28
+ if (r.updatedInput)
29
+ return r.updatedInput;
30
+ }
31
+ return undefined;
32
+ }
33
+ function logAdditionalContext(results, eventName) {
34
+ for (const r of results) {
35
+ if (r.additionalContext) {
36
+ console.log(`${LOG_PREFIX} [${eventName}] additionalContext: ${r.additionalContext}`);
37
+ }
38
+ }
39
+ }
40
+ function extractSessionId(properties) {
41
+ // session.idle / session.compacted → properties.sessionID
42
+ if (typeof properties.sessionID === "string")
43
+ return properties.sessionID;
44
+ // session.created / session.updated / session.deleted → properties.info.id
45
+ if (properties.info &&
46
+ typeof properties.info === "object" &&
47
+ "id" in properties.info &&
48
+ typeof properties.info.id === "string") {
49
+ return properties.info.id;
50
+ }
51
+ return "";
52
+ }
53
+ async function runSlashCommandHooks(eventName, matchers, sessionId, directory, client) {
54
+ if (!matchers || matchers.length === 0)
55
+ return;
56
+ const input = {
57
+ session_id: sessionId,
58
+ cwd: directory,
59
+ hook_event_name: eventName,
60
+ };
61
+ process.env.OPENCODE_SESSION_ID = sessionId;
62
+ process.env.OPENCODE_HOOK_EVENT = eventName;
63
+ const results = await runHooksForEvent(eventName, matchers, undefined, input, client);
64
+ const block = findBlockResult(results);
65
+ if (block) {
66
+ console.log(`${LOG_PREFIX} [${eventName}] Hook requested block: ${block.reason ?? "no reason"}`);
67
+ }
68
+ logAdditionalContext(results, eventName);
69
+ }
70
+ // ---------------------------------------------------------------------------
71
+ // Plugin entry point
72
+ // ---------------------------------------------------------------------------
73
+ export const HooksPlugin = async ({ directory, client }) => {
74
+ const { hooks, disableAllHooks } = loadConfig(directory);
75
+ if (disableAllHooks) {
76
+ console.log(`${LOG_PREFIX} All hooks disabled (disableAllHooks=true)`);
77
+ return {};
78
+ }
79
+ warnUnsupportedEvents(hooks);
80
+ process.env.OPENCODE_PROJECT_DIR = directory;
81
+ const toolBefore = async (input, output) => {
82
+ const eventName = "PreToolUse";
83
+ const matchers = hooks[eventName];
84
+ if (!matchers || matchers.length === 0)
85
+ return;
86
+ const ocEvent = {
87
+ sessionId: input.sessionID,
88
+ tool: input.tool,
89
+ args: output.args ?? {},
90
+ };
91
+ const hookInput = buildToolHookInput(eventName, ocEvent, directory);
92
+ process.env.OPENCODE_SESSION_ID = input.sessionID;
93
+ process.env.OPENCODE_HOOK_EVENT = eventName;
94
+ const results = await runHooksForEvent(eventName, matchers, input.tool, hookInput, client);
95
+ const block = findBlockResult(results);
96
+ if (block) {
97
+ throw new Error(block.reason ?? "Blocked by PreToolUse hook");
98
+ }
99
+ const updatedInput = findUpdatedInput(results);
100
+ if (updatedInput) {
101
+ Object.assign(output.args, updatedInput);
102
+ }
103
+ logAdditionalContext(results, eventName);
104
+ };
105
+ const toolAfter = async (input, output) => {
106
+ const ocEvent = {
107
+ sessionId: input.sessionID,
108
+ tool: input.tool,
109
+ args: input.args ?? {},
110
+ output: output.output,
111
+ };
112
+ const eventName = mapToolAfterEvent(ocEvent);
113
+ const matchers = hooks[eventName];
114
+ if (!matchers || matchers.length === 0)
115
+ return;
116
+ const hookInput = buildToolHookInput(eventName, ocEvent, directory);
117
+ process.env.OPENCODE_SESSION_ID = input.sessionID;
118
+ process.env.OPENCODE_HOOK_EVENT = eventName;
119
+ const results = await runHooksForEvent(eventName, matchers, input.tool, hookInput, client);
120
+ const block = findBlockResult(results);
121
+ if (block) {
122
+ console.log(`${LOG_PREFIX} [${eventName}] Hook requested block: ${block.reason ?? "no reason"}`);
123
+ }
124
+ logAdditionalContext(results, eventName);
125
+ };
126
+ const onEvent = async ({ event, }) => {
127
+ try {
128
+ switch (event.type) {
129
+ // ---- Session lifecycle ----
130
+ case "session.created": {
131
+ const matchers = hooks["SessionStart"];
132
+ if (!matchers || matchers.length === 0)
133
+ return;
134
+ const sessionId = extractSessionId(event.properties);
135
+ const ocEvent = { sessionId };
136
+ const hookInput = buildSessionHookInput("SessionStart", ocEvent, directory);
137
+ process.env.OPENCODE_SESSION_ID = sessionId;
138
+ process.env.OPENCODE_HOOK_EVENT = "SessionStart";
139
+ const results = await runHooksForEvent("SessionStart", matchers, undefined, hookInput, client);
140
+ const block = findBlockResult(results);
141
+ if (block) {
142
+ console.log(`${LOG_PREFIX} [SessionStart] Hook requested block: ${block.reason ?? "no reason"}`);
143
+ }
144
+ logAdditionalContext(results, "SessionStart");
145
+ break;
146
+ }
147
+ case "session.deleted": {
148
+ const matchers = hooks["SessionEnd"];
149
+ if (!matchers || matchers.length === 0)
150
+ return;
151
+ const sessionId = extractSessionId(event.properties);
152
+ const ocEvent = { sessionId };
153
+ const hookInput = buildSessionHookInput("SessionEnd", ocEvent, directory);
154
+ process.env.OPENCODE_SESSION_ID = sessionId;
155
+ process.env.OPENCODE_HOOK_EVENT = "SessionEnd";
156
+ const results = await runHooksForEvent("SessionEnd", matchers, undefined, hookInput, client);
157
+ const block = findBlockResult(results);
158
+ if (block) {
159
+ console.log(`${LOG_PREFIX} [SessionEnd] Hook requested block: ${block.reason ?? "no reason"}`);
160
+ }
161
+ logAdditionalContext(results, "SessionEnd");
162
+ break;
163
+ }
164
+ case "session.idle": {
165
+ const matchers = hooks["Stop"];
166
+ if (!matchers || matchers.length === 0)
167
+ return;
168
+ const sessionId = extractSessionId(event.properties);
169
+ const ocEvent = { sessionId };
170
+ const hookInput = buildSessionHookInput("Stop", ocEvent, directory);
171
+ process.env.OPENCODE_SESSION_ID = sessionId;
172
+ process.env.OPENCODE_HOOK_EVENT = "Stop";
173
+ const results = await runHooksForEvent("Stop", matchers, undefined, hookInput, client);
174
+ const block = findBlockResult(results);
175
+ if (block) {
176
+ console.log(`${LOG_PREFIX} [Stop] Hook requested block: ${block.reason ?? "no reason"}`);
177
+ }
178
+ logAdditionalContext(results, "Stop");
179
+ break;
180
+ }
181
+ // ---- Slash command stubs ----
182
+ case "command.executed": {
183
+ const props = event.properties;
184
+ const commandName = props.name ?? "";
185
+ const sessionId = props.sessionID ?? "";
186
+ if (commandName === "hook-prompt") {
187
+ await runSlashCommandHooks("UserPromptSubmit", hooks["UserPromptSubmit"], sessionId, directory, client);
188
+ }
189
+ else if (commandName === "hook-notify") {
190
+ await runSlashCommandHooks("Notification", hooks["Notification"], sessionId, directory, client);
191
+ }
192
+ else if (commandName === "hook-precompact") {
193
+ await runSlashCommandHooks("PreCompact", hooks["PreCompact"], sessionId, directory, client);
194
+ }
195
+ break;
196
+ }
197
+ default:
198
+ // Ignore all other events
199
+ break;
200
+ }
201
+ }
202
+ catch (err) {
203
+ const message = err instanceof Error ? err.message : String(err);
204
+ console.warn(`${LOG_PREFIX} Unhandled error in event handler (${event.type}):`, message);
205
+ }
206
+ };
207
+ return {
208
+ "tool.execute.before": toolBefore,
209
+ "tool.execute.after": toolAfter,
210
+ event: onEvent,
211
+ };
212
+ };
213
+ export default HooksPlugin;
214
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Tests whether a hook should fire based on its matcher pattern.
3
+ *
4
+ * @param matcher - The regex pattern string from hook config (optional)
5
+ * @param value - The value to test against (e.g. tool name). If undefined, always matches.
6
+ * @returns true if the hook should fire
7
+ */
8
+ export function matchHook(matcher, value) {
9
+ // If no value to test against, always match (events with no matcher concept)
10
+ if (value === undefined)
11
+ return true;
12
+ // Empty string, "*", or undefined matcher = match everything
13
+ if (!matcher || matcher === "*")
14
+ return true;
15
+ try {
16
+ const regex = new RegExp(matcher, "i");
17
+ return regex.test(value);
18
+ }
19
+ catch {
20
+ // Invalid regex: treat as no match to avoid crashing
21
+ return false;
22
+ }
23
+ }
24
+ //# sourceMappingURL=matcher.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,36 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ const ENV_SCRIPT = path.join(".claude", "hooks", "platform-env.sh");
5
+ const ENV_MARKER = "platform-env.sh";
6
+
7
+ /**
8
+ * OpenCode 专用:在 tool.execute.before 直改 bash command,不经过 updatedInput,与用户 PreToolUse hook 解耦。
9
+ */
10
+ export default async function PlatformEnvPlugin({ directory }) {
11
+ const envFile = path.join(directory, ENV_SCRIPT);
12
+
13
+ return {
14
+ "tool.execute.before": async (input, output) => {
15
+ if (input.tool !== "bash") {
16
+ return;
17
+ }
18
+
19
+ const command = output.args?.command;
20
+ if (typeof command !== "string" || !command.trim()) {
21
+ return;
22
+ }
23
+
24
+ if (command.includes(ENV_MARKER)) {
25
+ return;
26
+ }
27
+
28
+ if (!fs.existsSync(envFile)) {
29
+ return;
30
+ }
31
+
32
+ const quotedEnvFile = `'${envFile.replace(/'/g, `'\\''`)}'`;
33
+ output.args.command = `set -a && source ${quotedEnvFile} && set +a && ${command}`;
34
+ },
35
+ };
36
+ }
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.3.1",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.3.3-beta.1",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 z from"express";import k from"multer";import f from"fs";import w from"path";import{ValidationError as C,asyncHandler as d}from"../utils/error/errorHandler.js";import{log as u}from"../utils/log/logUtils.js";import F from"../appConfig/index.js";import{createWorkspace as N,pushSkillsToWorkspace as S,initProjectTemplate as b,installProjectDependencies as A,executeCommand as j,deleteWorkspace as O,zipWorkspace as D,buildAgentPackage as E,cleanupBuildArtifacts as P}from"../utils/computer/computerUtils.js";import{getFileList as U,updateFiles as T,uploadFile as R,uploadFiles as x,downloadAllFiles as v,getLatestLogs as H}from"../utils/computer/computerFileUtils.js";const g=z.Router(),h=k({storage:k.diskStorage({destination:(t,a,s)=>{try{const e=F.COMPUTER_WORKSPACE_DIR;if(!e)return s(new Error("COMPUTER_WORKSPACE_DIR is not configured, cannot determine upload temporary directory"));const o=t.body?.userId||"unknown",n=t.body?.cId||"unknown",i=w.join(e,String(o),String(n),".tmp");f.existsSync(i)||f.mkdirSync(i,{recursive:!0}),s(null,i)}catch(e){s(e)}},filename:(t,a,s)=>{const e=w.extname(a.originalname)||".zip",o=w.basename(a.originalname,e),n=`${Date.now()}_${Math.round(Math.random()*1e6)}`;s(null,`${o}_${n}${e}`)}}),limits:{fileSize:F.UPLOAD_MAX_FILE_SIZE_BYTES}});function _(t,a,s,e){if(t&&t.name==="MulterError"||t&&(t.name==="ValidationError"||t instanceof C))return e(t);e(t)}const q=[{path:"/create-workspace",method:"post",middleware:h.single("file"),handler:d(async(t,a)=>{const{userId:s,cId:e}=t.body||{},o=t.file||null,n=`computer:${s}:${e}`;u(n,"INFO","Create workspace request",{userId:s,cId:e,hasFile:!!o,fileName:o?.originalname,fileSize:o?.size});const i=await N(s,e,o);a.status(200).json({success:!0,...i})})},{path:"/create-workspace-v2",method:"post",middleware:h.single("file"),handler:d(async(t,a)=>{const{userId:s,cId:e,skillUrls:o,mcpServersConfig:n,hooksConfig:i,permissionsConfig:r,hookScripts:l}=t.body||{},c=t.file||null,y=`computer:${s}:${e}`;let m=o;if(typeof o=="string")try{const $=JSON.parse(o);m=Array.isArray($)?$:[o]}catch{m=[o]}let p=l;if(typeof l=="string")try{p=JSON.parse(l)}catch{p=null}u(y,"INFO","Create workspace v2 request",{userId:s,cId:e,hasFile:!!c,fileName:c?.originalname,fileSize:c?.size,skillUrlsCount:Array.isArray(m)?m.length:0,hasMcpServersConfig:!!n,hasHooksConfig:!!i,hasPermissionsConfig:!!r,hookScriptsCount:Array.isArray(p)?p.length:0});const I=await N(s,e,c,m,n,r,i,p);a.status(200).json({success:!0,...I})})},{path:"/push-skills-to-workspace",method:"post",middleware:h.single("file"),handler:d(async(t,a)=>{const{userId:s,cId:e}=t.body||{},o=t.file||null,n=`computer:${s}:${e}`;u(n,"INFO","\u63A8\u9001\u6280\u80FD\u5230\u5DE5\u4F5C\u7A7A\u95F4\u8BF7\u6C42",{userId:s,cId:e,hasFile:!!o,fileName:o?.originalname,fileSize:o?.size});const i=await S(s,e,o);a.status(200).json({success:!0,...i})})},{path:"/push-skills-to-workspace-v2",method:"post",middleware:h.single("file"),handler:d(async(t,a)=>{const{userId:s,cId:e,skillUrls:o}=t.body||{},n=t.file||null,i=`computer:${s}:${e}`;let r=o;if(typeof o=="string")try{const c=JSON.parse(o);r=Array.isArray(c)?c:[o]}catch{r=[o]}u(i,"INFO","\u63A8\u9001\u6280\u80FD\u5230\u5DE5\u4F5C\u7A7A\u95F4\u8BF7\u6C42(v2)",{userId:s,cId:e,hasFile:!!n,fileName:n?.originalname,fileSize:n?.size,skillUrlsCount:Array.isArray(r)?r.length:0});const l=await S(s,e,n,r);a.status(200).json({success:!0,...l})})},{path:"/get-file-list",method:"get",handler:d(async(t,a)=>{const{userId:s,cId:e,proxyPath:o,customTargetDir:n}=t.query,i=await U(s,e,o,n);a.status(200).json({success:!0,...i})})},{path:"/files-update",method:"post",handler:d(async(t,a)=>{const{userId:s,cId:e,files:o,customTargetDir:n}=t.body||{},i=`computer:${s}:${e}`;u(i,"INFO","Files update",{userId:s,cId:e,filesCount:o?o.length:0}),o&&Array.isArray(o)&&o.forEach(l=>{if(l&&typeof l.contents=="string"&&l.contents)try{l.contents=decodeURIComponent(l.contents)}catch(c){u(i,"WARN","Decode file content failed",{fileName:l.name,error:c.message})}});const r=await T(s,e,o,n);a.status(200).json(r)})},{path:"/upload-file",method:"post",middleware:h.single("file"),handler:d(async(t,a)=>{const{userId:s,cId:e,filePath:o,customTargetDir:n}=t.body||{},i=t.file,r=`computer:${s}:${e}`;u(r,"INFO","Upload single file",{userId:s,cId:e,filePath:o});const c={buffer:await f.promises.readFile(i.path),originalname:i.originalname,mimetype:i.mimetype,size:i.size};try{const y=await R(s,e,c,o,n);a.status(200).json(y)}finally{f.existsSync(i.path)&&await f.promises.unlink(i.path)}})},{path:"/upload-files",method:"post",middleware:h.array("files"),handler:d(async(t,a)=>{const{userId:s,cId:e,filePaths:o,customTargetDir:n}=t.body||{},i=t.files||[],r=`computer:${s}:${e}`,l=Array.isArray(o)?o:typeof o=="string"?[o]:o;u(r,"INFO","Batch upload files request",{userId:s,cId:e,filesCount:i.length,filePathsCount:Array.isArray(l)?l.length:0});const c=[],y=[];try{for(const p of i){y.push(p.path);const I=await f.promises.readFile(p.path);c.push({buffer:I,originalname:p.originalname,mimetype:p.mimetype,size:p.size})}const m=await x(s,e,c,l,n);a.status(200).json(m)}finally{for(const m of y)if(f.existsSync(m))try{await f.promises.unlink(m)}catch(p){u(r,"WARN","Clean temporary file failed",{tempPath:m,error:p.message})}}})},{path:"/install-project",method:"post",handler:d(async(t,a)=>{const{userId:s,cId:e,programmingLanguage:o}=t.body||{},n=`computer:${s}:${e}`;u(n,"INFO","Install project request",{userId:s,cId:e,programmingLanguage:o});const i=await A(s,e,o);a.status(200).json({success:!0,...i})})},{path:"/init-project-template",method:"post",middleware:h.single("file"),handler:d(async(t,a)=>{const{userId:s,cId:e,enableGit:o}=t.body||{},n=t.file||null,i=`computer:${s}:${e}`;u(i,"INFO","Init project template request",{userId:s,cId:e,hasFile:!!n,fileName:n?.originalname,fileSize:n?.size,enableGit:o});const r=await b(s,e,n,o);a.status(200).json({success:!0,...r})})},{path:"/download-all-files",method:"get",handler:d(async(t,a)=>{const{userId:s,cId:e,customTargetDir:o}=t.query||{},n=`computer:${s}:${e}`;u(n,"INFO","Download all files request",{userId:s,cId:e});const{archive:i,zipFileName:r}=await v(s,e,o);a.setHeader("Content-Type","application/zip");const l=encodeURIComponent(r);a.setHeader("Content-Disposition",`attachment; filename="${l}"; filename*=UTF-8''${l}`),i.on("error",c=>{a.destroy(c)}),i.pipe(a),i.finalize()})},{path:"/execute-command",method:"post",handler:d(async(t,a)=>{const{userId:s,cId:e,command:o}=t.body||{},n=`computer:${s}:${e}`;u(n,"INFO","Execute command request",{userId:s,cId:e,command:o});const i=await j(s,e,o);a.status(200).json({success:!0,...i})})},{path:"/delete-workspace",method:"post",handler:d(async(t,a)=>{const{userId:s,cId:e}=t.body||{},o=`computer:${s}:${e}`;u(o,"INFO","Delete workspace request",{userId:s,cId:e});const n=await O(s,e);a.status(200).json({success:!0,...n})})},{path:"/get-logs",method:"get",handler:d(async(t,a)=>{const{userId:s,cId:e,tailLines:o}=t.query,n=o?parseInt(o,10):200,i=Number.isFinite(n)&&n>0?n:200,r=await H(s,e,i);a.status(200).json(r)})},{path:"/zip-workspace",method:"post",handler:d(async(t,a)=>{const{userId:s,cId:e,excludeDirs:o}=t.body||{},n=`computer:${s}:${e}`;u(n,"INFO","Zip workspace request",{userId:s,cId:e,hasExcludeDirs:!!o});const{archive:i,zipFileName:r}=await D(s,e,o||null);a.setHeader("Content-Type","application/zip");const l=encodeURIComponent(r);a.setHeader("Content-Disposition",`attachment; filename="${l}"; filename*=UTF-8''${l}`),i.on("error",c=>{a.destroy(c)}),i.pipe(a),i.finalize()})},{path:"/build-agent-package",method:"post",handler:d(async(t,a)=>{const{userId:s,cId:e,agentId:o,version:n}=t.body||{},i=`computer:${s}:${e}`;u(i,"INFO","Build agent package request",{userId:s,cId:e,agentId:o,version:n});const r=await E(s,e,o,n);a.status(200).json({success:!0,...r})})},{path:"/cleanup-build-artifacts",method:"post",handler:d(async(t,a)=>{const{userId:s,cId:e}=t.body||{},o=`computer:${s}:${e}`;u(o,"INFO","Cleanup build artifacts request",{userId:s,cId:e});const n=await P(s,e);a.status(200).json({success:!0,...n})})}];q.forEach(t=>{if(t.middleware)g[t.method](t.path,t.middleware,t.handler);else if(t.customHandler){const a=[];t.handler&&a.push(t.handler),t.decodeMiddleware&&a.push(t.decodeMiddleware),a.push(_),a.push(t.customHandler),g[t.method](t.path,...a)}else g[t.method](t.path,t.handler)});export default g;
1
+ import b from"express";import k from"multer";import f from"fs";import w from"path";import{ValidationError as F,asyncHandler as d}from"../utils/error/errorHandler.js";import{log as u}from"../utils/log/logUtils.js";import N from"../appConfig/index.js";import{createWorkspace as z,pushSkillsToWorkspace as S,initProjectTemplate as j,installProjectDependencies as C,executeCommand as A,deleteWorkspace as O,zipWorkspace as D,buildAgentPackage as P,cleanupBuildArtifacts as E}from"../utils/computer/computerUtils.js";import{getFileList as T,updateFiles as U,uploadFile as R,uploadFiles as x,downloadAllFiles as q,getLatestLogs as v,importProject as H}from"../utils/computer/computerFileUtils.js";const y=b.Router(),h=k({storage:k.diskStorage({destination:(s,a,t)=>{try{const e=N.COMPUTER_WORKSPACE_DIR;if(!e)return t(new Error("COMPUTER_WORKSPACE_DIR is not configured, cannot determine upload temporary directory"));const o=s.body?.userId||"unknown",i=s.body?.cId||"unknown",n=w.join(e,String(o),String(i),".tmp");f.existsSync(n)||f.mkdirSync(n,{recursive:!0}),t(null,n)}catch(e){t(e)}},filename:(s,a,t)=>{const e=w.extname(a.originalname)||".zip",o=w.basename(a.originalname,e),i=`${Date.now()}_${Math.round(Math.random()*1e6)}`;t(null,`${o}_${i}${e}`)}}),limits:{fileSize:N.UPLOAD_MAX_FILE_SIZE_BYTES}});function _(s,a,t,e){if(s&&s.name==="MulterError"||s&&(s.name==="ValidationError"||s instanceof F))return e(s);e(s)}const M=[{path:"/create-workspace",method:"post",middleware:h.single("file"),handler:d(async(s,a)=>{const{userId:t,cId:e}=s.body||{},o=s.file||null,i=`computer:${t}:${e}`;u(i,"INFO","Create workspace request",{userId:t,cId:e,hasFile:!!o,fileName:o?.originalname,fileSize:o?.size});const n=await z(t,e,o);a.status(200).json({success:!0,...n})})},{path:"/create-workspace-v2",method:"post",middleware:h.single("file"),handler:d(async(s,a)=>{const{userId:t,cId:e,skillUrls:o,mcpServersConfig:i,hooksConfig:n,permissionsConfig:r,hookScripts:l}=s.body||{},c=s.file||null,g=`computer:${t}:${e}`;let m=o;if(typeof o=="string")try{const $=JSON.parse(o);m=Array.isArray($)?$:[o]}catch{m=[o]}let p=l;if(typeof l=="string")try{p=JSON.parse(l)}catch{p=null}u(g,"INFO","Create workspace v2 request",{userId:t,cId:e,hasFile:!!c,fileName:c?.originalname,fileSize:c?.size,skillUrlsCount:Array.isArray(m)?m.length:0,hasMcpServersConfig:!!i,hasHooksConfig:!!n,hasPermissionsConfig:!!r,hookScriptsCount:Array.isArray(p)?p.length:0});const I=await z(t,e,c,m,i,r,n,p);a.status(200).json({success:!0,...I})})},{path:"/push-skills-to-workspace",method:"post",middleware:h.single("file"),handler:d(async(s,a)=>{const{userId:t,cId:e}=s.body||{},o=s.file||null,i=`computer:${t}:${e}`;u(i,"INFO","\u63A8\u9001\u6280\u80FD\u5230\u5DE5\u4F5C\u7A7A\u95F4\u8BF7\u6C42",{userId:t,cId:e,hasFile:!!o,fileName:o?.originalname,fileSize:o?.size});const n=await S(t,e,o);a.status(200).json({success:!0,...n})})},{path:"/push-skills-to-workspace-v2",method:"post",middleware:h.single("file"),handler:d(async(s,a)=>{const{userId:t,cId:e,skillUrls:o}=s.body||{},i=s.file||null,n=`computer:${t}:${e}`;let r=o;if(typeof o=="string")try{const c=JSON.parse(o);r=Array.isArray(c)?c:[o]}catch{r=[o]}u(n,"INFO","\u63A8\u9001\u6280\u80FD\u5230\u5DE5\u4F5C\u7A7A\u95F4\u8BF7\u6C42(v2)",{userId:t,cId:e,hasFile:!!i,fileName:i?.originalname,fileSize:i?.size,skillUrlsCount:Array.isArray(r)?r.length:0});const l=await S(t,e,i,r);a.status(200).json({success:!0,...l})})},{path:"/get-file-list",method:"get",handler:d(async(s,a)=>{const{userId:t,cId:e,proxyPath:o,customTargetDir:i}=s.query,n=await T(t,e,o,i);a.status(200).json({success:!0,...n})})},{path:"/files-update",method:"post",handler:d(async(s,a)=>{const{userId:t,cId:e,files:o,customTargetDir:i}=s.body||{},n=`computer:${t}:${e}`;u(n,"INFO","Files update",{userId:t,cId:e,filesCount:o?o.length:0}),o&&Array.isArray(o)&&o.forEach(l=>{if(l&&typeof l.contents=="string"&&l.contents)try{l.contents=decodeURIComponent(l.contents)}catch(c){u(n,"WARN","Decode file content failed",{fileName:l.name,error:c.message})}});const r=await U(t,e,o,i);a.status(200).json(r)})},{path:"/upload-file",method:"post",middleware:h.single("file"),handler:d(async(s,a)=>{const{userId:t,cId:e,filePath:o,customTargetDir:i}=s.body||{},n=s.file,r=`computer:${t}:${e}`;u(r,"INFO","Upload single file",{userId:t,cId:e,filePath:o});const c={buffer:await f.promises.readFile(n.path),originalname:n.originalname,mimetype:n.mimetype,size:n.size};try{const g=await R(t,e,c,o,i);a.status(200).json(g)}finally{f.existsSync(n.path)&&await f.promises.unlink(n.path)}})},{path:"/import-project",method:"post",middleware:h.single("file"),handler:d(async(s,a)=>{const{userId:t,cId:e,customTargetDir:o}=s.body||{},i=s.file,n=`computer:${t}:${e}`;if(u(n,"INFO","Import project request",{userId:t,cId:e,fileName:i?.originalname,fileSize:i?.size}),!i)throw new F("file is required",{field:"file"});const r=await H(t,e,i,o);a.status(200).json(r)})},{path:"/upload-files",method:"post",middleware:h.array("files"),handler:d(async(s,a)=>{const{userId:t,cId:e,filePaths:o,customTargetDir:i}=s.body||{},n=s.files||[],r=`computer:${t}:${e}`,l=Array.isArray(o)?o:typeof o=="string"?[o]:o;u(r,"INFO","Batch upload files request",{userId:t,cId:e,filesCount:n.length,filePathsCount:Array.isArray(l)?l.length:0});const c=[],g=[];try{for(const p of n){g.push(p.path);const I=await f.promises.readFile(p.path);c.push({buffer:I,originalname:p.originalname,mimetype:p.mimetype,size:p.size})}const m=await x(t,e,c,l,i);a.status(200).json(m)}finally{for(const m of g)if(f.existsSync(m))try{await f.promises.unlink(m)}catch(p){u(r,"WARN","Clean temporary file failed",{tempPath:m,error:p.message})}}})},{path:"/install-project",method:"post",handler:d(async(s,a)=>{const{userId:t,cId:e,programmingLanguage:o}=s.body||{},i=`computer:${t}:${e}`;u(i,"INFO","Install project request",{userId:t,cId:e,programmingLanguage:o});const n=await C(t,e,o);a.status(200).json({success:!0,...n})})},{path:"/init-project-template",method:"post",middleware:h.single("file"),handler:d(async(s,a)=>{const{userId:t,cId:e,enableGit:o}=s.body||{},i=s.file||null,n=`computer:${t}:${e}`;u(n,"INFO","Init project template request",{userId:t,cId:e,hasFile:!!i,fileName:i?.originalname,fileSize:i?.size,enableGit:o});const r=await j(t,e,i,o);a.status(200).json({success:!0,...r})})},{path:"/download-all-files",method:"get",handler:d(async(s,a)=>{const{userId:t,cId:e,customTargetDir:o}=s.query||{},i=`computer:${t}:${e}`;u(i,"INFO","Download all files request",{userId:t,cId:e});const{archive:n,zipFileName:r}=await q(t,e,o);a.setHeader("Content-Type","application/zip");const l=encodeURIComponent(r);a.setHeader("Content-Disposition",`attachment; filename="${l}"; filename*=UTF-8''${l}`),n.on("error",c=>{a.destroy(c)}),n.pipe(a),n.finalize()})},{path:"/execute-command",method:"post",handler:d(async(s,a)=>{const{userId:t,cId:e,command:o}=s.body||{},i=`computer:${t}:${e}`;u(i,"INFO","Execute command request",{userId:t,cId:e,command:o});const n=await A(t,e,o);a.status(200).json({success:!0,...n})})},{path:"/delete-workspace",method:"post",handler:d(async(s,a)=>{const{userId:t,cId:e}=s.body||{},o=`computer:${t}:${e}`;u(o,"INFO","Delete workspace request",{userId:t,cId:e});const i=await O(t,e);a.status(200).json({success:!0,...i})})},{path:"/get-logs",method:"get",handler:d(async(s,a)=>{const{userId:t,cId:e,tailLines:o}=s.query,i=o?parseInt(o,10):200,n=Number.isFinite(i)&&i>0?i:200,r=await v(t,e,n);a.status(200).json(r)})},{path:"/zip-workspace",method:"post",handler:d(async(s,a)=>{const{userId:t,cId:e,excludeDirs:o}=s.body||{},i=`computer:${t}:${e}`;u(i,"INFO","Zip workspace request",{userId:t,cId:e,hasExcludeDirs:!!o});const{archive:n,zipFileName:r}=await D(t,e,o||null);a.setHeader("Content-Type","application/zip");const l=encodeURIComponent(r);a.setHeader("Content-Disposition",`attachment; filename="${l}"; filename*=UTF-8''${l}`),n.on("error",c=>{a.destroy(c)}),n.pipe(a),n.finalize()})},{path:"/build-agent-package",method:"post",handler:d(async(s,a)=>{const{userId:t,cId:e,agentId:o,version:i}=s.body||{},n=`computer:${t}:${e}`;u(n,"INFO","Build agent package request",{userId:t,cId:e,agentId:o,version:i});const r=await P(t,e,o,i);a.status(200).json({success:!0,...r})})},{path:"/cleanup-build-artifacts",method:"post",handler:d(async(s,a)=>{const{userId:t,cId:e}=s.body||{},o=`computer:${t}:${e}`;u(o,"INFO","Cleanup build artifacts request",{userId:t,cId:e});const i=await E(t,e);a.status(200).json({success:!0,...i})})}];M.forEach(s=>{if(s.middleware)y[s.method](s.path,s.middleware,s.handler);else if(s.customHandler){const a=[];s.handler&&a.push(s.handler),s.decodeMiddleware&&a.push(s.decodeMiddleware),a.push(_),a.push(s.customHandler),y[s.method](s.path,...a)}else y[s.method](s.path,s.handler)});export default y;
@@ -0,0 +1 @@
1
+ export function shouldUseFastRestart({fastRestartEnabled:o,nodeModulesExists:e}){return!!o&&!!e}
@@ -1 +1 @@
1
- import v from"path";import c from"fs";import{log as t}from"../log/logUtils.js";import{BusinessError as f,FileError as g,ResourceError as S}from"../error/errorHandler.js";import{isProjectStarting as w,addStartingProject as p,removeStartingProject as P,startDev_NonBlocking as N}from"./processManager.js";import h from"../error/errorCodes.js";import{stopDevServer as y}from"./stopDevUtils.js";import{removeNodeModules as F}from"../buildDependency/dependencyManager.js";import{createPnpmNpmrc as d}from"../common/npmrcUtils.js";import{copyNodeModulesFromCache as O}from"../common/templateCacheUtils.js";import{extractIsolationContext as R,resolveProjectPath as E}from"../common/projectPathUtils.js";async function x(r,e){t(e,"INFO","Start restarting development server",{projectId:e,requestId:r.requestId});const l=R(r?.query||{}),s=E(e,l),o=v.join(s,"package.json");if(!c.existsSync(o))throw t(e,"WARN","Project missing package.json file",{projectId:e,requestId:r.requestId}),new S("Project missing package.json file",{projectId:e,projectPath:s});let a;try{a=JSON.parse(c.readFileSync(o,"utf8"))}catch(i){throw new g("package.json file format error",{projectId:e,jsonFilePath:o,originalError:i.message})}const n=a.scripts.dev;if(!n)throw t(e,"WARN","Project missing dev script",{projectId:e,requestId:r.requestId}),new f("Project missing dev script",{projectId:e});if(w(e))throw new f("\u8BE5\u9879\u76EE\u6B63\u5728\u91CD\u542F\u4E2D\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5",{projectId:e,code:h.PROJECT_STARTING});p(e);try{const i=r.query.pid;await y(r,e,i,{strict:!1,waitForStop:!0}),t(e,"INFO","Remove node_modules and restore from cache",{projectId:e,requestId:r.requestId}),await F(s,e),await O(s,e),t(e,"INFO","Ensure .npmrc with copy mode",{projectId:e,requestId:r.requestId}),await d(s,e),t(e,"INFO","Start starting dev server",{projectId:e,requestId:r.requestId});const{pid:m,port:u}=await N({req:r,projectId:e,projectPath:s,devScript:n});return t(e,"INFO","Dev server restart completed",{projectId:e,pid:m,port:u,requestId:r.requestId}),{success:!0,message:"Development server restart successfully",projectId:e,pid:m,port:u}}finally{P(e)}}export{x as restartDevServer};
1
+ import c from"path";import i from"fs";import{log as r}from"../log/logUtils.js";import{BusinessError as v,FileError as F,ResourceError as p}from"../error/errorHandler.js";import{isProjectStarting as R,addStartingProject as w,removeStartingProject as P,startDev_NonBlocking as h}from"./processManager.js";import y from"../error/errorCodes.js";import{stopDevServer as N}from"./stopDevUtils.js";import{removeNodeModules as E}from"../buildDependency/dependencyManager.js";import{createPnpmNpmrc as g}from"../common/npmrcUtils.js";import{copyNodeModulesFromCache as x}from"../common/templateCacheUtils.js";import O from"../../appConfig/index.js";import{shouldUseFastRestart as _}from"./restartDevPredicate.js";import{extractIsolationContext as D,resolveProjectPath as k}from"../common/projectPathUtils.js";async function q(e,t){r(t,"INFO","Start restarting development server",{projectId:t,requestId:e.requestId});const S=D(e?.query||{}),s=k(t,S),o=c.join(s,"package.json");if(!i.existsSync(o))throw r(t,"WARN","Project missing package.json file",{projectId:t,requestId:e.requestId}),new p("Project missing package.json file",{projectId:t,projectPath:s});let n;try{n=JSON.parse(i.readFileSync(o,"utf8"))}catch(a){throw new F("package.json file format error",{projectId:t,jsonFilePath:o,originalError:a.message})}const m=n.scripts.dev;if(!m)throw r(t,"WARN","Project missing dev script",{projectId:t,requestId:e.requestId}),new v("Project missing dev script",{projectId:t});if(R(t))throw new v("\u8BE5\u9879\u76EE\u6B63\u5728\u91CD\u542F\u4E2D\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5",{projectId:t,code:y.PROJECT_STARTING});w(t);try{const a=e.query.pid;await N(e,t,a,{strict:!1,waitForStop:!0});const d=c.join(s,"node_modules");let l=!1;try{l=i.existsSync(d)}catch{}_({fastRestartEnabled:O.FAST_RESTART_ENABLED,nodeModulesExists:l})?(r(t,"INFO","Fast restart: preserve node_modules + lockfile, rely on pnpm incremental install",{projectId:t,requestId:e.requestId,fastRestart:!0}),await g(s,t)):(r(t,"INFO","Full restart: remove node_modules and restore from cache",{projectId:t,requestId:e.requestId,fastRestart:!1}),await E(s,t),await x(s,t),await g(s,t)),r(t,"INFO","Start starting dev server",{projectId:t,requestId:e.requestId});const{pid:u,port:f}=await h({req:e,projectId:t,projectPath:s,devScript:m});return r(t,"INFO","Dev server restart completed",{projectId:t,pid:u,port:f,requestId:e.requestId}),{success:!0,message:"Development server restart successfully",projectId:t,pid:u,port:f}}finally{P(t)}}export{q as restartDevServer};
@@ -0,0 +1 @@
1
+ import e from"fs";import n from"path";import{FileError as m}from"../error/errorHandler.js";function u(r,i){const t=String(i||"").replace(/\\/g,"/");if(!t||t.startsWith("/")||/^[a-zA-Z]:/.test(t))throw new m(`Unsafe zip entry path: ${i}`,{entryName:i});const s=t.replace(/^\/+/,"").split("/").filter(Boolean);if(s.some(c=>c===".."))throw new m(`Unsafe zip entry path: ${i}`,{entryName:i});const o=n.resolve(r,...s),p=n.resolve(r);if(o!==p&&!o.startsWith(p+n.sep))throw new m(`Unsafe zip entry path: ${i}`,{entryName:i,targetPath:o,basePath:p});return o}async function w(r,i){try{await e.promises.rename(r,i)}catch(t){if(t.code==="EXDEV"){async function a(s,o){if((await e.promises.lstat(s)).isDirectory()){await e.promises.mkdir(o,{recursive:!0});const c=await e.promises.readdir(s);for(const f of c)await a(n.join(s,f),n.join(o,f))}else await e.promises.copyFile(s,o)}await a(r,i),await e.promises.rm(r,{recursive:!0,force:!0})}else throw t}}async function l(r,i){if(!e.existsSync(r))return;if(e.existsSync(i)&&await e.promises.rm(i,{recursive:!0,force:!0}),(await e.promises.lstat(r)).isDirectory()){await w(r,i);return}await e.promises.mkdir(n.dirname(i),{recursive:!0});try{await e.promises.rename(r,i)}catch(a){if(a.code==="EXDEV")await e.promises.copyFile(r,i),await e.promises.unlink(r);else throw a}}export{u as assertSafeZipEntryPath,w as moveDirectory,l as movePath};
@@ -1 +1 @@
1
- import l from"fs";import E from"path";import h from"yauzl";import{FileError as g}from"../error/errorHandler.js";function k(c,i){return new Promise((d,r)=>{h.open(c,{lazyEntries:!0},(m,e)=>{if(m)return r(new g(`\u65E0\u6CD5\u6253\u5F00\u538B\u7F29\u5305: ${m.message}`,{zipPath:c,originalError:m.message}));e.readEntry(),e.on("entry",n=>{if(/\/$/.test(n.fileName)){const t=E.join(i,n.fileName);try{l.mkdirSync(t,{recursive:!0})}catch(s){console.warn("[extractZip] mkdir for directory entry failed, skip this dir",{dirPath:t,code:s.code,message:s.message}),e.readEntry();return}e.readEntry()}else e.openReadStream(n,(t,s)=>{if(t)return r(new g(`Failed to read zip entry: ${t.message}`,{entry:n.fileName,originalError:t.message}));const a=E.join(i,n.fileName),u=E.dirname(a);try{l.mkdirSync(u,{recursive:!0})}catch(o){console.warn("[extractZip] mkdir for file entry failed, skip this file",{dirPath:u,filePath:a,code:o.code,message:o.message}),e.readEntry();return}try{if(l.existsSync(a)&&l.statSync(a).isDirectory()){e.readEntry();return}}catch(o){return r(new g(`Failed to check file status: ${o.message}`,{filePath:a,originalError:o.message}))}const f=l.createWriteStream(a);s.pipe(f),f.on("close",()=>{e.readEntry()}),f.on("error",o=>{r(new g(`Failed to write file: ${o.message}`,{filePath:a,originalError:o.message}))})})}),e.on("end",()=>{d()}),e.on("error",n=>{r(new g(`Error occurred during unzip: ${n.message}`,{zipPath:c,originalError:n.message}))})})})}const y=new Map,w=50;function S(c){return new Promise(i=>{try{const d=l.statSync(c),r=`${c}:${d.mtimeMs}:${d.size}`;if(y.has(r))return i(y.get(r));h.open(c,{lazyEntries:!0},(m,e)=>{if(m)return i(null);const n=new Map;e.readEntry(),e.on("entry",t=>{const s=t.fileName.replace(/\\/g,"/");/\/$/.test(t.fileName)||n.set(s,t),e.readEntry()}),e.on("end",()=>{e.close(),y.size>=w&&Array.from(y.keys()).slice(0,Math.floor(w/2)).forEach(s=>y.delete(s)),y.set(r,n),i(n)}),e.on("error",()=>{e.close(),i(null)})})}catch{i(null)}})}function $(c,i,d){return new Promise(async r=>{try{const m=await S(c);if(!m)return r(!1);const e=i.replace(/^[\/\\]+/,"").replace(/\\/g,"/");if(!m.has(e))return r(!1);h.open(c,{lazyEntries:!1},(n,t)=>{if(n)return r(!1);let s=null;for(const a of t.entries)if(a.fileName.replace(/\\/g,"/")===e&&!/\/$/.test(a.fileName)){s=a;break}if(!s)return t.close(),r(!1);t.openReadStream(s,(a,u)=>{if(a||!u)return t.close(),r(!1);const f=E.dirname(d);try{l.mkdirSync(f,{recursive:!0})}catch{return t.close(),r(!1)}const o=l.createWriteStream(d);u.pipe(o),o.on("close",()=>{t.close(),r(!0)}),o.on("error",()=>{t.close(),r(!1)})})})}catch{r(!1)}})}export{k as extractZip,$ as extractSingleFileFromZip};
1
+ import u from"fs";import w from"path";import h from"yauzl";import{FileError as E}from"../error/errorHandler.js";import{assertSafeZipEntryPath as S}from"./fileSystemUtils.js";function $(o,c){return new Promise((d,e)=>{h.open(o,{lazyEntries:!0},(i,r)=>{if(i)return e(new E(`\u65E0\u6CD5\u6253\u5F00\u538B\u7F29\u5305: ${i.message}`,{zipPath:o,originalError:i.message}));r.readEntry(),r.on("entry",a=>{let t;try{t=S(c,a.fileName)}catch(n){return r.close(),e(n)}if(/\/$/.test(a.fileName)){try{u.mkdirSync(t,{recursive:!0})}catch(n){console.warn("[extractZip] mkdir for directory entry failed, skip this dir",{dirPath:t,code:n.code,message:n.message}),r.readEntry();return}r.readEntry()}else r.openReadStream(a,(n,y)=>{if(n)return e(new E(`Failed to read zip entry: ${n.message}`,{entry:a.fileName,originalError:n.message}));const s=t,g=w.dirname(s);try{u.mkdirSync(g,{recursive:!0})}catch(m){console.warn("[extractZip] mkdir for file entry failed, skip this file",{dirPath:g,filePath:s,code:m.code,message:m.message}),r.readEntry();return}try{if(u.existsSync(s)&&u.statSync(s).isDirectory()){r.readEntry();return}}catch(m){return e(new E(`Failed to check file status: ${m.message}`,{filePath:s,originalError:m.message}))}const l=u.createWriteStream(s);y.pipe(l),l.on("close",()=>{r.readEntry()}),l.on("error",m=>{e(new E(`Failed to write file: ${m.message}`,{filePath:s,originalError:m.message}))})})}),r.on("end",()=>{d()}),r.on("error",a=>{e(new E(`Error occurred during unzip: ${a.message}`,{zipPath:o,originalError:a.message}))})})})}const f=new Map,k=50;function x(o){return new Promise(c=>{try{const d=u.statSync(o),e=`${o}:${d.mtimeMs}:${d.size}`;if(f.has(e))return c(f.get(e));h.open(o,{lazyEntries:!0},(i,r)=>{if(i)return c(null);const a=new Map;r.readEntry(),r.on("entry",t=>{const n=t.fileName.replace(/\\/g,"/");/\/$/.test(t.fileName)||a.set(n,t),r.readEntry()}),r.on("end",()=>{r.close(),f.size>=k&&Array.from(f.keys()).slice(0,Math.floor(k/2)).forEach(n=>f.delete(n)),f.set(e,a),c(a)}),r.on("error",()=>{r.close(),c(null)})})}catch{c(null)}})}function p(o,c,d){return new Promise(async e=>{try{const i=await x(o);if(!i)return e(!1);const r=c.replace(/^[\/\\]+/,"").replace(/\\/g,"/");if(!i.has(r))return e(!1);h.open(o,{lazyEntries:!1},(a,t)=>{if(a)return e(!1);let n=null;for(const y of t.entries)if(y.fileName.replace(/\\/g,"/")===r&&!/\/$/.test(y.fileName)){n=y;break}if(!n)return t.close(),e(!1);t.openReadStream(n,(y,s)=>{if(y||!s)return t.close(),e(!1);const g=w.dirname(d);try{u.mkdirSync(g,{recursive:!0})}catch{return t.close(),e(!1)}const l=u.createWriteStream(d);s.pipe(l),l.on("close",()=>{t.close(),e(!0)}),l.on("error",()=>{t.close(),e(!1)})})})}catch{e(!1)}})}export{$ as extractZip,p as extractSingleFileFromZip};
@@ -1,2 +1,2 @@
1
- import m from"fs";import h from"path";import C from"archiver";import P from"../../appConfig/index.js";import{log as s}from"../log/logUtils.js";import{ValidationError as y,SystemError as z}from"../error/errorHandler.js";const _=100*1024*1024,O=P.DOWNLOAD_MAX_FILE_SIZE_BYTES||_;async function x(d,u,n,g,f){const r=[],w=await m.promises.readdir(d,{withFileTypes:!0});w.sort((i,t)=>i.isDirectory()&&!t.isDirectory()?-1:!i.isDirectory()&&t.isDirectory()?1:i.name.toLowerCase().localeCompare(t.name.toLowerCase()));for(const i of w){const t=h.join(d,i.name);if(!(i.name.startsWith(".")&&i.name!==".gitignore"||(P.CONTENT_TRAVERSE_EXCLUDE_FILES||[]).includes(i.name))&&!(i.isDirectory()&&P.TRAVERSE_EXCLUDE_DIRS.includes(i.name)))if(i.isDirectory()){const e=await x(t,u,n,g,f);if(e.length===0){const c=u||d,l=h.relative(c,t).replace(/\\/g,"/");r.push({name:l,isDir:!0})}else r.push(...e)}else try{const e=u||d,c=h.relative(e,t).replace(/\\/g,"/"),l=i.isSymbolicLink();let o=null;if(g){const R=c.split("/").map(E=>encodeURIComponent(E)).join("/");o=`${g}/${R}`,f&&(o+=`?customTargetDir=${encodeURIComponent(f)}`)}const a={name:c,isDir:!1,fileProxyUrl:o,isLink:l};r.push(a)}catch(e){s(n,"WARN",`\u5904\u7406\u6587\u4EF6\u5931\u8D25: ${t}`,{error:e.message})}}return r}async function A(d,u,n,g,f=""){const r=await m.promises.readdir(h.join(d,f),{withFileTypes:!0});let w=0;for(const i of r){const t=f?h.join(f,i.name):i.name,p=t.split(h.sep).filter(Boolean);if(p.some(o=>o.startsWith(".")))continue;const e=p[p.length-1];if(u.includes(e)||p.some(o=>n.includes(o)))continue;const c=h.join(d,t);let l;try{l=await m.promises.lstat(c)}catch(o){s(g,"WARN","Error occurred when getting file stats, skipping",{filePath:t.replace(/\\/g,"/"),error:o.message});continue}l.isSymbolicLink()||l.nlink>1||(l.isDirectory()?w+=await A(d,u,n,g,t):l.isFile()&&(w+=l.size))}return w}async function L(d,u,n,g){const f=Date.now(),r=`computer:${d}:${u}`,w=P.COMPUTER_WORKSPACE_DIR;if(!d)throw new y("userId \u4E0D\u80FD\u4E3A\u7A7A",{field:"userId"});if(!u)throw new y("cId \u4E0D\u80FD\u4E3A\u7A7A",{field:"cId"});const i=String(d),t=String(u),p=g&&g.trim()?g:h.join(w,i,t);if(!m.existsSync(p))return s(r,"INFO","Directory does not exist, returning empty list",{targetDir:p,userId:i,cId:t}),{files:[]};s(r,"DEBUG","Start getting user file list",{targetDir:p,userId:i,cId:t});try{const e=await x(p,p,r,n,g);return s(r,"INFO","User file list obtained successfully",{fileCount:e.length,targetDir:p,userId:i,cId:t,elapsedMs:Date.now()-f}),{files:e}}catch(e){throw s(r,"ERROR","Failed to get user file list",{targetDir:p,userId:i,cId:t,error:e.message,elapsedMs:Date.now()-f}),new z(`Failed to get file list: ${e.message}`,{targetDir:p,originalError:e.message})}}async function W(d,u,n,g){const f=Date.now(),r=`computer:${d}:${u}`,w=P.COMPUTER_WORKSPACE_DIR;if(!d)throw new y("userId cannot be empty",{field:"userId"});if(!u)throw new y("cId cannot be empty",{field:"cId"});if(!Array.isArray(n))throw new y("files must be an array",{field:"files"});const i=String(d),t=String(u),p=g&&g.trim()?g:h.join(w,i,t);m.existsSync(p)||m.mkdirSync(p,{recursive:!0});for(let e=0;e<n.length;e++){const c=n[e];if(!c||typeof c.operation!="string")throw new y(`files[${e}].operation cannot be empty`,{field:`files[${e}].operation`});if(!c.name||typeof c.name!="string")throw new y(`files[${e}].name cannot be empty`,{field:`files[${e}].name`});const l=c.operation.toLowerCase();if(!["create","delete","rename","modify"].includes(l))throw new y(`files[${e}].operation must be one of create, delete, rename or modify`,{field:`files[${e}].operation`});if(l==="rename"&&!c.renameFrom)throw new y(`files[${e}].renameFrom cannot be empty (rename operation requires)`,{field:`files[${e}].renameFrom`});if(l==="modify"&&c.isDir!==!0&&typeof c.contents!="string")throw new y(`files[${e}].contents must be a string (modify operation requires)`,{field:`files[${e}].contents`})}s(r,"DEBUG","Start updating user files",{userId:i,cId:t,filesCount:n.length});try{for(const e of n){const c=e.operation.toLowerCase(),l=e.name,o=h.normalize(l).replace(/^[\/\\]+/,""),a=h.join(p,o),R=h.resolve(a),E=h.resolve(p);if(!R.startsWith(E+h.sep)&&R!==E){s(r,"WARN","File path is not secure, skipping",{filePath:o,resolvedPath:R});continue}switch(c){case"create":{if(e.isDir===!0){if(m.existsSync(a)){if((await m.promises.stat(a)).isFile())throw new y("Cannot create directory, file with the same name already exists",{filePath:o});s(r,"INFO","Directory already exists, skipping creation",{filePath:o});break}await m.promises.mkdir(a,{recursive:!0}),s(r,"INFO","Directory created successfully",{filePath:o});break}if(m.existsSync(a)){if((await m.promises.stat(a)).isDirectory())throw new y("Cannot create file, directory with the same name already exists",{filePath:o});s(r,"INFO","File already exists, skipping creation",{filePath:o});break}await m.promises.mkdir(h.dirname(a),{recursive:!0});const S=e.contents||"";await m.promises.writeFile(a,S,"utf8"),s(r,"INFO","File created successfully",{filePath:o});break}case"delete":{m.existsSync(a)?(await m.promises.stat(a)).isDirectory()?(await m.promises.rm(a,{recursive:!0,force:!0}),s(r,"INFO","Directory deleted successfully",{filePath:o})):(await m.promises.unlink(a),s(r,"INFO","File deleted successfully",{filePath:o})):s(r,"WARN","The file or directory to be deleted does not exist",{filePath:o});break}case"rename":{const S=e.renameFrom;if(!S||typeof S!="string"){s(r,"WARN","Rename operation missing renameFrom",{filePath:o});break}const I=h.normalize(S).replace(/^[\/\\]+/,""),D=h.join(p,I),N=h.resolve(D);if(!N.startsWith(E+h.sep)&&N!==E){s(r,"WARN","Source path is not secure, skipping rename",{sourcePath:I,targetPath:o});break}if(m.existsSync(D)){const b=(await m.promises.stat(D)).isDirectory();await m.promises.mkdir(h.dirname(a),{recursive:!0}),await m.promises.rename(D,a),s(r,"INFO",b?"Directory renamed successfully":"File renamed successfully",{sourcePath:I,targetPath:o})}else s(r,"WARN","The file or directory to be renamed does not exist",{sourcePath:I});break}case"modify":{if(!m.existsSync(a)){s(r,"WARN","The file to be modified does not exist",{filePath:o});break}if((await m.promises.stat(a)).isDirectory()){s(r,"INFO","The target is a directory, skipping modification",{filePath:o});break}const I=typeof e.contents=="string"?e.contents:"";if(await m.promises.readFile(a,"utf8")===I){s(r,"INFO","File content has no changes, skipping write",{filePath:o});break}await m.promises.writeFile(a,I,"utf8"),s(r,"INFO","File modified successfully",{filePath:o});break}default:{s(r,"WARN","Unsupported operation type",{operation:c,filePath:o});break}}}return s(r,"INFO","User files updated successfully",{userId:i,cId:t,filesCount:n.length,elapsedMs:Date.now()-f}),{success:!0,message:"User files updated successfully",userId:i,cId:t,filesCount:n.length}}catch(e){throw s(r,"ERROR","User files updated failed",{userId:i,cId:t,error:e.message,elapsedMs:Date.now()-f}),new z(`User files updated failed: ${e.message}`,{userId:i,cId:t,originalError:e.message})}}async function $(d,u,n,g,f){const r=Date.now(),w=`computer:${d}:${u}`,i=P.COMPUTER_WORKSPACE_DIR;if(!d)throw new y("userId cannot be empty",{field:"userId"});if(!u)throw new y("cId cannot be empty",{field:"cId"});if(!n)throw new y("file cannot be empty",{field:"file"});if(!g||typeof g!="string")throw new y("filePath cannot be empty",{field:"filePath"});const t=String(d),p=String(u),e=f&&f.trim()?f:h.join(i,t,p);m.existsSync(e)||m.mkdirSync(e,{recursive:!0});const c=h.normalize(g).replace(/^[\/\\]+/,""),l=h.join(e,c),o=h.resolve(l),a=h.resolve(e);if(!o.startsWith(a+h.sep)&&o!==a)throw new y("File path is not secure, cannot exceed user directory",{field:"filePath",providedPath:g,resolvedPath:o});try{if(await m.promises.mkdir(h.dirname(l),{recursive:!0}),n.buffer)await m.promises.writeFile(l,n.buffer);else if(typeof n.contents=="string")await m.promises.writeFile(l,n.contents,"utf8");else throw new y("File content format is incorrect",{field:"file",hasBuffer:!!n.buffer,hasContents:typeof n.contents});return s(w,"INFO","File uploaded successfully",{userId:t,cId:p,filePath:c,targetPath:o,fileSize:n.buffer?n.buffer.length:n.contents?n.contents.length:0,elapsedMs:Date.now()-r}),{success:!0,message:"File uploaded successfully",fileSize:n.buffer?n.buffer.length:n.contents?n.contents.length:0}}catch(R){throw s(w,"ERROR","File upload failed",{userId:t,cId:p,filePath:c,error:R.message,elapsedMs:Date.now()-r}),new z(`File upload failed: ${R.message}`,{userId:t,cId:p,filePath:c,originalError:R.message})}}async function v(d,u,n,g,f){const r=Date.now(),w=`computer:${d}:${u}`;if(!d)throw new y("userId cannot be empty",{field:"userId"});if(!u)throw new y("cId cannot be empty",{field:"cId"});if(!Array.isArray(n))throw new y("files must be an array",{field:"files"});if(!Array.isArray(g))throw new y("filePaths must be an array",{field:"filePaths"});if(n.length!==g.length)throw new y(`File count (${n.length}) does not match path count (${g.length})`,{field:"filePaths"});s(w,"DEBUG","Start batch uploading files",{userId:d,cId:u,filesCount:n.length});const i=[];try{for(let e=0;e<n.length;e++){const c=n[e],l=g[e];if(!c){s(w,"WARN","Empty file object encountered in batch upload, skipping",{index:e,filePath:l}),i.push({success:!1,filePath:l,error:"Empty file object"});continue}if(!l||typeof l!="string"){s(w,"WARN","Invalid file path in batch upload, skipping",{index:e,originalname:c.originalname}),i.push({success:!1,filePath:l||"",originalname:c.originalname,error:"Invalid file path"});continue}try{const o=await $(d,u,c,l,f);i.push({success:!0,filePath:l,originalname:c.originalname,...o})}catch(o){s(w,"ERROR","Single file upload failed in batch upload",{filePath:l,originalname:c.originalname,error:o.message}),i.push({success:!1,filePath:l,originalname:c.originalname,error:o.message})}}const t=i.filter(e=>e.success).length,p=i.filter(e=>!e.success).length;return s(w,"INFO","Batch upload files completed",{userId:d,cId:u,totalCount:n.length,successCount:t,failCount:p,elapsedMs:Date.now()-r}),{success:!0,message:"Batch upload completed",totalCount:n.length,successCount:t,failCount:p,results:i}}catch(t){throw s(w,"ERROR","Batch upload files failed",{userId:d,cId:u,error:t.message,elapsedMs:Date.now()-r}),new z(`Batch upload files failed: ${t.message}`,{userId:d,cId:u,originalError:t.message})}}async function U(d,u,n){const g=Date.now(),f=`computer:${d}:${u}`,r=P.COMPUTER_WORKSPACE_DIR;if(!d)throw new y("userId cannot be empty",{field:"userId"});if(!u)throw new y("cId cannot be empty",{field:"cId"});if(!r)throw new z("COMPUTER_WORKSPACE_DIR is not configured, cannot create zip");const w=String(d),i=String(u),t=n&&n.trim()?n:h.join(r,w,i);if(!m.existsSync(t)){const a=`${w}_${i}.zip`;s(f,"WARN","Workspace directory does not exist, returning empty zip",{targetDir:t,userId:w,cId:i,zipFileName:a});const R=C("zip",{zlib:{level:9}});return R.append(null,{name:`${w}_${i}/`,type:"directory"}),R.on("warning",E=>{if(E.code==="ENOENT")s(f,"WARN","Encountered file problem when creating empty zip",{message:E.message,code:E.code});else throw s(f,"ERROR","Encountered warning when creating empty zip",{message:E.message,code:E.code}),E}),R.on("error",E=>{s(f,"ERROR","Failed to create empty zip",{message:E.message})}),{archive:R,zipFileName:a}}const p=`${w}_${i}.zip`;s(f,"DEBUG","Start creating workspace directory zip",{targetDir:t,zipFileName:p});const e=C("zip",{zlib:{level:9}}),c=P.CONTENT_TRAVERSE_EXCLUDE_FILES||[],l=P.TRAVERSE_EXCLUDE_DIRS||[],o=await A(t,c,l,f);if(o>O){const a=O/1024/1024,R=(o/1024/1024).toFixed(2);throw s(f,"WARN","Download rejected due to oversized workspace",{targetDir:t,downloadableSize:o,maxSizeBytes:O}),new y(`Download failed: total file size ${R}MB exceeds limit ${a}MB`,{field:"downloadSize",downloadableSize:o,maxSizeBytes:O})}return e.directory(t,`${w}_${i}`,a=>{const R=a.name||"",E=R.split(/[\/\\]/).filter(Boolean);if(E.some(I=>I.startsWith(".")))return!1;const S=E[E.length-1];if(c.includes(S)||E.some(I=>l.includes(I)))return!1;try{const I=h.join(t,R),D=m.lstatSync(I);if(D.isSymbolicLink()||D.nlink>1)return!1}catch(I){return s(f,"WARN","Error occurred when detecting link file, skipping",{filePath:R,error:I.message}),!1}return a}),e.on("warning",a=>{if(a.code==="ENOENT")s(f,"WARN","Encountered file problem when creating zip",{message:a.message,code:a.code});else throw s(f,"ERROR","Encountered warning when creating zip",{message:a.message,code:a.code}),a}),e.on("error",a=>{s(f,"ERROR","Failed to create zip",{message:a.message,elapsedMs:Date.now()-g})}),e.on("end",()=>{s(f,"INFO","Workspace directory zip created successfully",{targetDir:t,zipFileName:p,elapsedMs:Date.now()-g})}),{archive:e,zipFileName:p}}async function M(d,u,n=200){const g=Date.now(),f=`computer:${d}:${u}`;if(!d)throw new y("userId cannot be empty",{field:"userId"});if(!u)throw new y("cId cannot be empty",{field:"cId"});const r=String(d),w=String(u),i=P.COMPUTER_WORKSPACE_DIR;if(!i)return s(f,"WARN","COMPUTER_WORKSPACE_DIR is not configured"),{success:!0,message:"Log workspace is not configured",logs:[],totalLines:0,startIndex:1,logFileName:null};const t=h.join(i,r,w,".logs");if(!m.existsSync(t))return s(f,"DEBUG","Log directory does not exist",{logDir:t}),{success:!0,message:"Log directory does not exist",logs:[],totalLines:0,startIndex:1,logFileName:null};const e=(await m.promises.readdir(t,{withFileTypes:!0})).filter(F=>F.isFile()).map(F=>F.name);if(e.length===0)return s(f,"DEBUG","No log file found",{logDir:t}),{success:!0,message:"No log file found",logs:[],totalLines:0,startIndex:1,logFileName:null};const c=[];for(const F of e){const b=h.join(t,F),k=await m.promises.stat(b);c.push({name:F,fullPath:b,mtime:k.mtimeMs,size:k.size})}c.sort((F,b)=>b.mtime-F.mtime);const l=c[0],a=(await m.promises.readFile(l.fullPath,"utf8")).split(`
2
- `).filter(F=>F.length>0),R=a.length,E=Math.max(1,n),S=Math.max(0,R-E),I=S+1,N=a.slice(S).map((F,b)=>({line:I+b,content:F}));return s(f,"DEBUG","Get latest logs",{fileName:l.name,fileSize:l.size,totalLines:R,returnedLines:N.length,elapsedMs:Date.now()-g}),{success:!0,message:"Get log successfully",logs:N,totalLines:R,startIndex:I,logFileName:l.name}}export{L as getFileList,W as updateFiles,$ as uploadFile,v as uploadFiles,U as downloadAllFiles,M as getLatestLogs};
1
+ import i from"fs";import p from"path";import _ from"archiver";import P from"../../appConfig/index.js";import{log as n}from"../log/logUtils.js";import{ValidationError as y,SystemError as N,FileError as j}from"../error/errorHandler.js";import{extractZip as M}from"../common/zipUtils.js";import{moveDirectory as L,movePath as x}from"../common/fileSystemUtils.js";const b=new Set([".git",".agents",".claude",".codex",".opencode",".tmp",".logs"]),U=100*1024*1024,v=P.DOWNLOAD_MAX_FILE_SIZE_BYTES||U;async function C(o,d,r,f,l){const t=[],g=await i.promises.readdir(o,{withFileTypes:!0});g.sort((c,s)=>c.isDirectory()&&!s.isDirectory()?-1:!c.isDirectory()&&s.isDirectory()?1:c.name.toLowerCase().localeCompare(s.name.toLowerCase()));for(const c of g){const s=p.join(o,c.name);if(!(c.name.startsWith(".")&&c.name!==".gitignore"||(P.CONTENT_TRAVERSE_EXCLUDE_FILES||[]).includes(c.name))&&!(c.isDirectory()&&P.TRAVERSE_EXCLUDE_DIRS.includes(c.name)))if(c.isDirectory()){const e=await C(s,d,r,f,l);if(e.length===0){const w=d||o,u=p.relative(w,s).replace(/\\/g,"/");t.push({name:u,isDir:!0})}else t.push(...e)}else try{const e=d||o,w=p.relative(e,s).replace(/\\/g,"/"),u=c.isSymbolicLink();let m=null;if(f){const E=w.split("/").map(R=>encodeURIComponent(R)).join("/");m=`${f}/${E}`,l&&(m+=`?customTargetDir=${encodeURIComponent(l)}`)}const a={name:w,isDir:!1,fileProxyUrl:m,isLink:u};t.push(a)}catch(e){n(r,"WARN",`\u5904\u7406\u6587\u4EF6\u5931\u8D25: ${s}`,{error:e.message})}}return t}async function $(o,d,r,f,l=""){const t=await i.promises.readdir(p.join(o,l),{withFileTypes:!0});let g=0;for(const c of t){const s=l?p.join(l,c.name):c.name,h=s.split(p.sep).filter(Boolean);if(h.some(m=>m.startsWith(".")))continue;const e=h[h.length-1];if(d.includes(e)||h.some(m=>r.includes(m)))continue;const w=p.join(o,s);let u;try{u=await i.promises.lstat(w)}catch(m){n(f,"WARN","Error occurred when getting file stats, skipping",{filePath:s.replace(/\\/g,"/"),error:m.message});continue}u.isSymbolicLink()||u.nlink>1||(u.isDirectory()?g+=await $(o,d,r,f,s):u.isFile()&&(g+=u.size))}return g}async function T(o,d,r,f){const l=Date.now(),t=`computer:${o}:${d}`,g=P.COMPUTER_WORKSPACE_DIR;if(!o)throw new y("userId \u4E0D\u80FD\u4E3A\u7A7A",{field:"userId"});if(!d)throw new y("cId \u4E0D\u80FD\u4E3A\u7A7A",{field:"cId"});const c=String(o),s=String(d),h=f&&f.trim()?f:p.join(g,c,s);if(!i.existsSync(h))return n(t,"INFO","Directory does not exist, returning empty list",{targetDir:h,userId:c,cId:s}),{files:[]};n(t,"DEBUG","Start getting user file list",{targetDir:h,userId:c,cId:s});try{const e=await C(h,h,t,r,f);return n(t,"INFO","User file list obtained successfully",{fileCount:e.length,targetDir:h,userId:c,cId:s,elapsedMs:Date.now()-l}),{files:e}}catch(e){throw n(t,"ERROR","Failed to get user file list",{targetDir:h,userId:c,cId:s,error:e.message,elapsedMs:Date.now()-l}),new N(`Failed to get file list: ${e.message}`,{targetDir:h,originalError:e.message})}}async function B(o,d,r,f){const l=Date.now(),t=`computer:${o}:${d}`,g=P.COMPUTER_WORKSPACE_DIR;if(!o)throw new y("userId cannot be empty",{field:"userId"});if(!d)throw new y("cId cannot be empty",{field:"cId"});if(!Array.isArray(r))throw new y("files must be an array",{field:"files"});const c=String(o),s=String(d),h=f&&f.trim()?f:p.join(g,c,s);i.existsSync(h)||i.mkdirSync(h,{recursive:!0});for(let e=0;e<r.length;e++){const w=r[e];if(!w||typeof w.operation!="string")throw new y(`files[${e}].operation cannot be empty`,{field:`files[${e}].operation`});if(!w.name||typeof w.name!="string")throw new y(`files[${e}].name cannot be empty`,{field:`files[${e}].name`});const u=w.operation.toLowerCase();if(!["create","delete","rename","modify"].includes(u))throw new y(`files[${e}].operation must be one of create, delete, rename or modify`,{field:`files[${e}].operation`});if(u==="rename"&&!w.renameFrom)throw new y(`files[${e}].renameFrom cannot be empty (rename operation requires)`,{field:`files[${e}].renameFrom`});if(u==="modify"&&w.isDir!==!0&&typeof w.contents!="string")throw new y(`files[${e}].contents must be a string (modify operation requires)`,{field:`files[${e}].contents`})}n(t,"DEBUG","Start updating user files",{userId:c,cId:s,filesCount:r.length});try{for(const e of r){const w=e.operation.toLowerCase(),u=e.name,m=p.normalize(u).replace(/^[\/\\]+/,""),a=p.join(h,m),E=p.resolve(a),R=p.resolve(h);if(!E.startsWith(R+p.sep)&&E!==R){n(t,"WARN","File path is not secure, skipping",{filePath:m,resolvedPath:E});continue}switch(w){case"create":{if(e.isDir===!0){if(i.existsSync(a)){if((await i.promises.stat(a)).isFile())throw new y("Cannot create directory, file with the same name already exists",{filePath:m});n(t,"INFO","Directory already exists, skipping creation",{filePath:m});break}await i.promises.mkdir(a,{recursive:!0}),n(t,"INFO","Directory created successfully",{filePath:m});break}if(i.existsSync(a)){if((await i.promises.stat(a)).isDirectory())throw new y("Cannot create file, directory with the same name already exists",{filePath:m});n(t,"INFO","File already exists, skipping creation",{filePath:m});break}await i.promises.mkdir(p.dirname(a),{recursive:!0});const S=e.contents||"";await i.promises.writeFile(a,S,"utf8"),n(t,"INFO","File created successfully",{filePath:m});break}case"delete":{i.existsSync(a)?(await i.promises.stat(a)).isDirectory()?(await i.promises.rm(a,{recursive:!0,force:!0}),n(t,"INFO","Directory deleted successfully",{filePath:m})):(await i.promises.unlink(a),n(t,"INFO","File deleted successfully",{filePath:m})):n(t,"WARN","The file or directory to be deleted does not exist",{filePath:m});break}case"rename":{const S=e.renameFrom;if(!S||typeof S!="string"){n(t,"WARN","Rename operation missing renameFrom",{filePath:m});break}const I=p.normalize(S).replace(/^[\/\\]+/,""),D=p.join(h,I),O=p.resolve(D);if(!O.startsWith(R+p.sep)&&O!==R){n(t,"WARN","Source path is not secure, skipping rename",{sourcePath:I,targetPath:m});break}if(i.existsSync(D)){const k=(await i.promises.stat(D)).isDirectory();await i.promises.mkdir(p.dirname(a),{recursive:!0}),await i.promises.rename(D,a),n(t,"INFO",k?"Directory renamed successfully":"File renamed successfully",{sourcePath:I,targetPath:m})}else n(t,"WARN","The file or directory to be renamed does not exist",{sourcePath:I});break}case"modify":{if(!i.existsSync(a)){n(t,"WARN","The file to be modified does not exist",{filePath:m});break}if((await i.promises.stat(a)).isDirectory()){n(t,"INFO","The target is a directory, skipping modification",{filePath:m});break}const I=typeof e.contents=="string"?e.contents:"";if(await i.promises.readFile(a,"utf8")===I){n(t,"INFO","File content has no changes, skipping write",{filePath:m});break}await i.promises.writeFile(a,I,"utf8"),n(t,"INFO","File modified successfully",{filePath:m});break}default:{n(t,"WARN","Unsupported operation type",{operation:w,filePath:m});break}}}return n(t,"INFO","User files updated successfully",{userId:c,cId:s,filesCount:r.length,elapsedMs:Date.now()-l}),{success:!0,message:"User files updated successfully",userId:c,cId:s,filesCount:r.length}}catch(e){throw n(t,"ERROR","User files updated failed",{userId:c,cId:s,error:e.message,elapsedMs:Date.now()-l}),new N(`User files updated failed: ${e.message}`,{userId:c,cId:s,originalError:e.message})}}async function A(o,d,r,f,l){const t=Date.now(),g=`computer:${o}:${d}`,c=P.COMPUTER_WORKSPACE_DIR;if(!o)throw new y("userId cannot be empty",{field:"userId"});if(!d)throw new y("cId cannot be empty",{field:"cId"});if(!r)throw new y("file cannot be empty",{field:"file"});if(!f||typeof f!="string")throw new y("filePath cannot be empty",{field:"filePath"});const s=String(o),h=String(d),e=l&&l.trim()?l:p.join(c,s,h);i.existsSync(e)||i.mkdirSync(e,{recursive:!0});const w=p.normalize(f).replace(/^[\/\\]+/,""),u=p.join(e,w),m=p.resolve(u),a=p.resolve(e);if(!m.startsWith(a+p.sep)&&m!==a)throw new y("File path is not secure, cannot exceed user directory",{field:"filePath",providedPath:f,resolvedPath:m});try{if(await i.promises.mkdir(p.dirname(u),{recursive:!0}),r.buffer)await i.promises.writeFile(u,r.buffer);else if(typeof r.contents=="string")await i.promises.writeFile(u,r.contents,"utf8");else throw new y("File content format is incorrect",{field:"file",hasBuffer:!!r.buffer,hasContents:typeof r.contents});return n(g,"INFO","File uploaded successfully",{userId:s,cId:h,filePath:w,targetPath:m,fileSize:r.buffer?r.buffer.length:r.contents?r.contents.length:0,elapsedMs:Date.now()-t}),{success:!0,message:"File uploaded successfully",fileSize:r.buffer?r.buffer.length:r.contents?r.contents.length:0}}catch(E){throw n(g,"ERROR","File upload failed",{userId:s,cId:h,filePath:w,error:E.message,elapsedMs:Date.now()-t}),new N(`File upload failed: ${E.message}`,{userId:s,cId:h,filePath:w,originalError:E.message})}}async function G(o,d,r,f,l){const t=Date.now(),g=`computer:${o}:${d}`;if(!o)throw new y("userId cannot be empty",{field:"userId"});if(!d)throw new y("cId cannot be empty",{field:"cId"});if(!Array.isArray(r))throw new y("files must be an array",{field:"files"});if(!Array.isArray(f))throw new y("filePaths must be an array",{field:"filePaths"});if(r.length!==f.length)throw new y(`File count (${r.length}) does not match path count (${f.length})`,{field:"filePaths"});n(g,"DEBUG","Start batch uploading files",{userId:o,cId:d,filesCount:r.length});const c=[];try{for(let e=0;e<r.length;e++){const w=r[e],u=f[e];if(!w){n(g,"WARN","Empty file object encountered in batch upload, skipping",{index:e,filePath:u}),c.push({success:!1,filePath:u,error:"Empty file object"});continue}if(!u||typeof u!="string"){n(g,"WARN","Invalid file path in batch upload, skipping",{index:e,originalname:w.originalname}),c.push({success:!1,filePath:u||"",originalname:w.originalname,error:"Invalid file path"});continue}try{const m=await A(o,d,w,u,l);c.push({success:!0,filePath:u,originalname:w.originalname,...m})}catch(m){n(g,"ERROR","Single file upload failed in batch upload",{filePath:u,originalname:w.originalname,error:m.message}),c.push({success:!1,filePath:u,originalname:w.originalname,error:m.message})}}const s=c.filter(e=>e.success).length,h=c.filter(e=>!e.success).length;return n(g,"INFO","Batch upload files completed",{userId:o,cId:d,totalCount:r.length,successCount:s,failCount:h,elapsedMs:Date.now()-t}),{success:!0,message:"Batch upload completed",totalCount:r.length,successCount:s,failCount:h,results:c}}catch(s){throw n(g,"ERROR","Batch upload files failed",{userId:o,cId:d,error:s.message,elapsedMs:Date.now()-t}),new N(`Batch upload files failed: ${s.message}`,{userId:o,cId:d,originalError:s.message})}}async function K(o,d,r){const f=Date.now(),l=`computer:${o}:${d}`,t=P.COMPUTER_WORKSPACE_DIR;if(!o)throw new y("userId cannot be empty",{field:"userId"});if(!d)throw new y("cId cannot be empty",{field:"cId"});if(!t)throw new N("COMPUTER_WORKSPACE_DIR is not configured, cannot create zip");const g=String(o),c=String(d),s=r&&r.trim()?r:p.join(t,g,c);if(!i.existsSync(s)){const a=`${g}_${c}.zip`;n(l,"WARN","Workspace directory does not exist, returning empty zip",{targetDir:s,userId:g,cId:c,zipFileName:a});const E=_("zip",{zlib:{level:9}});return E.append(null,{name:`${g}_${c}/`,type:"directory"}),E.on("warning",R=>{if(R.code==="ENOENT")n(l,"WARN","Encountered file problem when creating empty zip",{message:R.message,code:R.code});else throw n(l,"ERROR","Encountered warning when creating empty zip",{message:R.message,code:R.code}),R}),E.on("error",R=>{n(l,"ERROR","Failed to create empty zip",{message:R.message})}),{archive:E,zipFileName:a}}const h=`${g}_${c}.zip`;n(l,"DEBUG","Start creating workspace directory zip",{targetDir:s,zipFileName:h});const e=_("zip",{zlib:{level:9}}),w=P.CONTENT_TRAVERSE_EXCLUDE_FILES||[],u=P.TRAVERSE_EXCLUDE_DIRS||[],m=await $(s,w,u,l);if(m>v){const a=v/1024/1024,E=(m/1024/1024).toFixed(2);throw n(l,"WARN","Download rejected due to oversized workspace",{targetDir:s,downloadableSize:m,maxSizeBytes:v}),new y(`Download failed: total file size ${E}MB exceeds limit ${a}MB`,{field:"downloadSize",downloadableSize:m,maxSizeBytes:v})}return e.directory(s,`${g}_${c}`,a=>{const E=a.name||"",R=E.split(/[\/\\]/).filter(Boolean);if(R.some(I=>I.startsWith(".")))return!1;const S=R[R.length-1];if(w.includes(S)||R.some(I=>u.includes(I)))return!1;try{const I=p.join(s,E),D=i.lstatSync(I);if(D.isSymbolicLink()||D.nlink>1)return!1}catch(I){return n(l,"WARN","Error occurred when detecting link file, skipping",{filePath:E,error:I.message}),!1}return a}),e.on("warning",a=>{if(a.code==="ENOENT")n(l,"WARN","Encountered file problem when creating zip",{message:a.message,code:a.code});else throw n(l,"ERROR","Encountered warning when creating zip",{message:a.message,code:a.code}),a}),e.on("error",a=>{n(l,"ERROR","Failed to create zip",{message:a.message,elapsedMs:Date.now()-f})}),e.on("end",()=>{n(l,"INFO","Workspace directory zip created successfully",{targetDir:s,zipFileName:h,elapsedMs:Date.now()-f})}),{archive:e,zipFileName:h}}async function X(o,d,r=200){const f=Date.now(),l=`computer:${o}:${d}`;if(!o)throw new y("userId cannot be empty",{field:"userId"});if(!d)throw new y("cId cannot be empty",{field:"cId"});const t=String(o),g=String(d),c=P.COMPUTER_WORKSPACE_DIR;if(!c)return n(l,"WARN","COMPUTER_WORKSPACE_DIR is not configured"),{success:!0,message:"Log workspace is not configured",logs:[],totalLines:0,startIndex:1,logFileName:null};const s=p.join(c,t,g,".logs");if(!i.existsSync(s))return n(l,"DEBUG","Log directory does not exist",{logDir:s}),{success:!0,message:"Log directory does not exist",logs:[],totalLines:0,startIndex:1,logFileName:null};const e=(await i.promises.readdir(s,{withFileTypes:!0})).filter(F=>F.isFile()).map(F=>F.name);if(e.length===0)return n(l,"DEBUG","No log file found",{logDir:s}),{success:!0,message:"No log file found",logs:[],totalLines:0,startIndex:1,logFileName:null};const w=[];for(const F of e){const k=p.join(s,F),z=await i.promises.stat(k);w.push({name:F,fullPath:k,mtime:z.mtimeMs,size:z.size})}w.sort((F,k)=>k.mtime-F.mtime);const u=w[0],a=(await i.promises.readFile(u.fullPath,"utf8")).split(`
2
+ `).filter(F=>F.length>0),E=a.length,R=Math.max(1,r),S=Math.max(0,E-R),I=S+1,O=a.slice(S).map((F,k)=>({line:I+k,content:F}));return n(l,"DEBUG","Get latest logs",{fileName:u.name,fileSize:u.size,totalLines:E,returnedLines:O.length,elapsedMs:Date.now()-f}),{success:!0,message:"Get log successfully",logs:O,totalLines:E,startIndex:I,logFileName:u.name}}async function W(o,d){try{const f=(await i.promises.readdir(o,{withFileTypes:!0})).filter(c=>!c.name.startsWith(".")&&c.name!=="node_modules");if(f.length!==1||!f[0].isDirectory())return;const l=p.join(o,f[0].name),t=p.join(o,`..tmp_${Date.now()}_${Math.round(Math.random()*1e6)}`);try{await i.promises.rename(l,t)}catch(c){if(c.code==="EXDEV")await i.promises.cp(l,t,{recursive:!0}),await i.promises.rm(l,{recursive:!0,force:!0});else throw c}const g=await i.promises.readdir(t);for(const c of g){const s=p.join(t,c),h=p.join(o,c);try{await i.promises.rename(s,h)}catch(e){if(e.code==="EXDEV")(await i.promises.lstat(s)).isDirectory()?await L(s,h):(await i.promises.copyFile(s,h),await i.promises.unlink(s));else throw e}}await i.promises.rm(t,{recursive:!0,force:!0}),n(d,"INFO","Removed single top-level directory from imported zip",{removedDir:f[0].name})}catch(r){n(d,"WARN","Failed to remove top-level directory from imported zip, continuing",{error:r.message})}}async function V(o,d){if(!i.existsSync(o)){await i.promises.mkdir(o,{recursive:!0});return}const r=await i.promises.readdir(o,{withFileTypes:!0});for(const f of r){if(b.has(f.name))continue;const l=p.join(o,f.name);await i.promises.rm(l,{recursive:!0,force:!0}),n(d,"DEBUG","Removed workspace entry during import",{name:f.name})}}async function Z(o,d,r){if(await i.promises.mkdir(d,{recursive:!0}),!i.existsSync(o)){await i.promises.mkdir(o,{recursive:!0});return}const f=await i.promises.readdir(o,{withFileTypes:!0});for(const l of f){if(b.has(l.name))continue;const t=p.join(o,l.name),g=p.join(d,l.name);await x(t,g),n(r,"DEBUG","Backed up workspace entry before import",{name:l.name})}}async function q(o,d,r){if(!i.existsSync(o))return;const f=await i.promises.readdir(o,{withFileTypes:!0});for(const l of f){if(b.has(l.name))continue;const t=p.join(o,l.name),g=p.join(d,l.name);await x(t,g)}n(r,"INFO","Workspace restored from backup after import failure")}async function Y(o,d,r){const f=await i.promises.readdir(o,{withFileTypes:!0}),l=[];try{for(const t of f){if(b.has(t.name)){n(r,"INFO","Skipping preserved entry from imported zip",{name:t.name});continue}const g=p.join(o,t.name),c=p.join(d,t.name);await x(g,c),l.push(t.name)}}catch(t){throw t.mergedEntries=l,t}}async function J(o,d,r,f){const l=Date.now(),t=`computer:${o}:${d}`,g=P.COMPUTER_WORKSPACE_DIR;if(!o)throw new y("userId cannot be empty",{field:"userId"});if(!d)throw new y("cId cannot be empty",{field:"cId"});if(!r||!r.path)throw new y("file is required",{field:"file"});if(!g)throw new N("COMPUTER_WORKSPACE_DIR is not configured");if(p.extname(r.originalname||r.filename||"").toLowerCase()!==".zip")throw new y("Only zip files are supported",{field:"file",originalName:r.originalname});const s=String(o),h=String(d),e=f&&f.trim()?f.trim():p.join(g,s,h),w=p.join(e,".tmp"),u=p.join(w,`import_project_extract_${Date.now()}_${Math.round(Math.random()*1e6)}`);let m=null;n(t,"INFO","Start importing project from zip",{userId:s,cId:h,targetDir:e,fileName:r.originalname});try{await i.promises.mkdir(e,{recursive:!0}),await i.promises.mkdir(w,{recursive:!0}),await i.promises.mkdir(u,{recursive:!0}),await M(r.path,u),await W(u,t),m=p.join(w,`import_backup_${Date.now()}_${Math.round(Math.random()*1e6)}`),await Z(e,m,t);try{await Y(u,e,t)}catch(a){throw n(t,"ERROR","Merge imported project failed, restoring workspace",{error:a.message,mergedEntries:a.mergedEntries||[]}),await V(e,t),await q(m,e,t),a}return i.existsSync(m)&&(await i.promises.rm(m,{recursive:!0,force:!0}),m=null),n(t,"INFO","Project imported successfully",{userId:s,cId:h,targetDir:e,elapsedMs:Date.now()-l}),{success:!0,message:"Project imported successfully",userId:s,cId:h,targetDir:e}}catch(a){throw n(t,"ERROR","Failed to import project",{userId:s,cId:h,targetDir:e,error:a.message,elapsedMs:Date.now()-l}),a instanceof y||a instanceof N||a instanceof j?a:new N(`Failed to import project: ${a.message}`,{userId:s,cId:h,originalError:a.message})}finally{if(i.existsSync(u))try{await i.promises.rm(u,{recursive:!0,force:!0})}catch(a){n(t,"WARN","Failed to clean import extract directory",{extractRoot:u,error:a.message})}if(m&&i.existsSync(m))try{await i.promises.rm(m,{recursive:!0,force:!0})}catch(a){n(t,"WARN","Failed to clean import backup directory",{backupDir:m,error:a.message})}if(r?.path&&i.existsSync(r.path))try{await i.promises.unlink(r.path)}catch(a){n(t,"WARN","Failed to clean uploaded zip file",{tempZipPath:r.path,error:a.message})}}}export{T as getFileList,B as updateFiles,A as uploadFile,G as uploadFiles,K as downloadAllFiles,X as getLatestLogs,J as importProject,b as IMPORT_PROJECT_PRESERVED_ENTRIES,W as removeTopLevelDir};
@@ -1,14 +1,14 @@
1
- import i from"fs";import r from"path";import{exec as le}from"child_process";import pe from"cross-spawn";import de from"archiver";import B from"../../appConfig/index.js";import{extractZip as G}from"../common/zipUtils.js";import{ValidationError as g,SystemError as A,FileError as Z}from"../error/errorHandler.js";import{log as c}from"../log/logUtils.js";import{createPnpmNpmrc as me}from"../common/npmrcUtils.js";import{ensurePrimaryAgentDirs as te,syncAgents as X}from"../common/AgentWorkspaceUtils.js";function ie(e){if(!e)return[];if(Array.isArray(e))return e.map(t=>typeof t=="string"?t.trim():"").filter(Boolean);if(typeof e=="string"){const t=e.trim();if(!t)return[];try{const n=JSON.parse(t);if(Array.isArray(n))return n.map(o=>typeof o=="string"?o.trim():"").filter(Boolean)}catch{}return[t]}return[]}async function ne(e,t,n){let o;try{o=await fetch(e)}catch(y){throw new Z(`Failed to download skill zip from url: ${e}`,{url:e,reason:y.message})}if(!o.ok)throw new Z(`Failed to download skill zip from url: ${e}`,{url:e,status:o.status,statusText:o.statusText});const s=o.headers.get("content-type")||"";s&&!s.includes("zip")&&!s.includes("octet-stream")&&c(n,"WARN","Downloaded skill url content-type is not typical zip",{url:e,contentType:s});const a=await o.arrayBuffer(),m=Buffer.from(a);await i.promises.writeFile(t,m)}async function $(e="computer"){const t=B.COMPUTER_WORKSPACE_DIR;if(!t)throw new g("COMPUTER_WORKSPACE_DIR configuration does not exist",{field:"COMPUTER_WORKSPACE_DIR"});return i.existsSync(t)||(await i.promises.mkdir(t,{recursive:!0}),c(e,"INFO","Created user workspace root directory",{workspaceRoot:t})),t}async function H(e,t){const n=r.join(e,t);if(i.existsSync(n)&&(await i.promises.lstat(n)).isDirectory())return n;const o=await i.promises.readdir(e,{withFileTypes:!0});for(const s of o){if(!s.isDirectory())continue;const a=r.join(e,s.name),m=r.join(a,t);if(i.existsSync(m)&&(await i.promises.lstat(m)).isDirectory())return m}return null}async function Ue(e){return H(e,"skills")}const fe=".dynamic_add.lock";function ue(e){const t=r.join(e,fe);return i.existsSync(t)&&i.statSync(t).isFile()}async function C(e){i.existsSync(e)&&await i.promises.rm(e,{recursive:!0,force:!0})}async function T(e,t){try{await i.promises.rename(e,t)}catch(n){if(n.code==="EXDEV"){async function o(s,a){if((await i.promises.lstat(s)).isDirectory()){await i.promises.mkdir(a,{recursive:!0});const y=await i.promises.readdir(s);for(const l of y)await o(r.join(s,l),r.join(a,l))}else await i.promises.copyFile(s,a)}await o(e,t),await i.promises.rm(e,{recursive:!0,force:!0})}else throw n}}async function we(e,t,n,o,s,a){const m=t&&t.trim(),y=n&&n.trim(),l=o&&o.trim(),p=Array.isArray(s)&&s.length>0;if(!m&&!y&&!l&&!p)return;const h=r.join(e,".claude");try{if(i.existsSync(h)||await i.promises.mkdir(h,{recursive:!0}),m)try{const f={mcpServers:JSON.parse(t)},S=r.join(e,".mcp.json");await i.promises.writeFile(S,JSON.stringify(f,null,2),"utf-8"),c(a,"INFO","Written .mcp.json to workspace root")}catch(f){c(a,"WARN","Failed to parse/write mcpServersConfig, skipping",{error:f.message})}const d={};if(y)try{d.hooks=JSON.parse(n)}catch(f){c(a,"WARN","Failed to parse hooksConfig, skipping",{error:f.message})}if(l)try{d.permissions=JSON.parse(o)}catch(f){c(a,"WARN","Failed to parse permissionsConfig, skipping",{error:f.message})}if(Object.keys(d).length>0){const f=r.join(h,"settings.json");await i.promises.writeFile(f,JSON.stringify(d,null,2),"utf-8"),c(a,"INFO","Written .claude/settings.json",{keys:Object.keys(d)})}if(p){const f=r.join(h,"hooks");i.existsSync(f)||await i.promises.mkdir(f,{recursive:!0});for(const S of s){if(!S||!S.path||!S.content)continue;const w=r.normalize(S.path);if(w.startsWith("..")||r.isAbsolute(w)){c(a,"WARN","Hook script path contains traversal, skipping",{path:S.path});continue}const x=r.join(h,w),j=r.dirname(x);i.existsSync(j)||await i.promises.mkdir(j,{recursive:!0}),await i.promises.writeFile(x,S.content,"utf-8"),await i.promises.chmod(x,493),c(a,"INFO","Written hook script",{path:S.path})}}}catch(d){c(a,"WARN","Failed to write .claude/settings or .mcp.json or hook scripts, not blocking workspace creation",{error:d.message})}}async function he(e,t,n,o,s,a,m,y){const l=Date.now(),p=`computer:${e}:${t}`,h=ie(o),d=[],f=[],S=new Set;if(!e)throw new g("userId cannot be empty",{field:"userId"});if(!t)throw new g("cId cannot be empty",{field:"cId"});const w=await $(p),x=r.join(w,String(e),String(t),".tmp"),j=r.join(w,String(e),String(t)),{skillsDir:k,agentsDir:D,agentTypes:_}=await te(j);i.existsSync(j)||await i.promises.mkdir(j,{recursive:!0});const N=r.join(x,`preserved_skills_${Date.now()}_${Math.round(Math.random()*1e6)}`);if(i.existsSync(k)){const F=(await i.promises.readdir(k,{withFileTypes:!0})).filter(O=>O.isDirectory()&&ue(r.join(k,O.name)));if(F.length>0){await i.promises.mkdir(N,{recursive:!0});for(const O of F){const R=r.join(k,O.name),v=r.join(N,O.name);await T(R,v)}c(p,"INFO","\u4FDD\u7559\u542B .dynamic_add.lock \u7684 skill",{preserved:F.map(O=>O.name)})}}if(await C(k),await C(D),await i.promises.mkdir(k,{recursive:!0}),await i.promises.mkdir(D,{recursive:!0}),i.existsSync(N)){const u=await i.promises.readdir(N,{withFileTypes:!0});for(const F of u)if(F.isDirectory()){const O=r.join(N,F.name),R=r.join(k,F.name);await T(O,R)}await C(N)}const E=i.existsSync(k),W=i.existsSync(D);c(p,"INFO","Deleted old skills and agents directories completed",{userId:e,cId:t,targetSkillsDir:k,targetAgentsDir:D,agentTypes:_,skillsExists:E,agentsExists:W});const b=r.join(j,".mcp.json");i.existsSync(b)&&await i.promises.unlink(b);const L=r.join(j,".claude","settings.json");if(i.existsSync(L)&&await i.promises.unlink(L),await we(j,s,m,a,y,p),!n&&h.length===0)return await X(j),c(p,"INFO","Created workspace (no uploaded file, no skills and agents)",{userId:e,cId:t,workspaceRoot:w,skillsDir:null,agentsDir:null,agentTypes:_,elapsedMs:Date.now()-l}),{message:"Workspace created (no uploaded file, no skills and agents)",workspaceRoot:w};if(n){if(!n.path)throw new g("Uploaded file has no valid path",{field:"file.path"});if(r.extname(n.originalname||n.filename||"").toLowerCase()!==".zip")throw new g("Only zip files are supported",{field:"file",originalName:n.originalname})}c(p,"DEBUG","Start processing workspace resources",{userId:e,cId:t,workspaceRoot:w,hasUploadedZip:!!n,skillUrlsCount:h.length});try{i.existsSync(x)||await i.promises.mkdir(x,{recursive:!0});const u=[];if(n){const R=r.join(x,`skill_extract_${Date.now()}_${Math.round(Math.random()*1e6)}`);f.push(R),await i.promises.mkdir(R,{recursive:!0}),c(p,"DEBUG","Start extracting uploaded zip file",{extractRoot:R}),await G(n.path,R),c(p,"DEBUG","Uploaded zip file extracted successfully",{extractRoot:R});const v=await H(R,"skills"),M=await H(R,"agents");if(v){await i.promises.mkdir(k,{recursive:!0});const z=await i.promises.readdir(v,{withFileTypes:!0});for(const U of z){if(!U.isDirectory())continue;const J=r.join(v,U.name),K=r.join(k,U.name);i.existsSync(K)&&await C(K),await T(J,K),S.add(U.name)}u.push("skills"),c(p,"INFO","skills updated to workspace",{userId:e,cId:t,workspaceRoot:w,targetSkillsDir:k,agentTypes:_})}else c(p,"INFO","skills directory not found in uploaded zip, skipping",{userId:e,cId:t,extractRoot:R});M?(await C(D),await T(M,D),u.push("agents"),c(p,"INFO","agents updated to workspace",{userId:e,cId:t,workspaceRoot:w,targetAgentsDir:D,agentTypes:_})):c(p,"INFO","agents directory not found in uploaded zip, skipping",{userId:e,cId:t,extractRoot:R})}for(let R=0;R<h.length;R+=1){const v=h[R],M=r.join(x,`skill_url_${Date.now()}_${R}_${Math.round(Math.random()*1e6)}.zip`);d.push(M);const z=r.join(x,`skill_url_extract_${Date.now()}_${R}_${Math.round(Math.random()*1e6)}`);f.push(z),await i.promises.mkdir(z,{recursive:!0}),c(p,"INFO","Start download skill zip from url",{userId:e,cId:t,skillUrl:v}),await ne(v,M,p),c(p,"INFO","Skill zip downloaded, start extracting",{userId:e,cId:t,skillUrl:v,downloadedZipPath:M}),await G(M,z);const J=(await i.promises.readdir(z,{withFileTypes:!0})).filter(P=>P.isDirectory()&&!P.name.startsWith(".")),ee=J.find(P=>P.name==="skills")?(await i.promises.readdir(r.join(z,"skills"),{withFileTypes:!0})).filter(P=>P.isDirectory()&&!P.name.startsWith(".")).map(P=>({name:P.name,sourcePath:r.join(z,"skills",P.name)})):J.map(P=>({name:P.name,sourcePath:r.join(z,P.name)}));if(ee.length===0){c(p,"WARN","No skill directory found after extracting skill url zip",{userId:e,cId:t,skillUrl:v,extractRoot:z});continue}await i.promises.mkdir(k,{recursive:!0});for(const P of ee){const q=r.join(k,P.name);i.existsSync(q)&&await C(q),await T(P.sourcePath,q),S.add(P.name),c(p,"INFO","Skill from url updated to workspace",{userId:e,cId:t,skillUrl:v,skillName:P.name,destSkillPath:q})}u.includes("skills")||u.push("skills")}u.length===0&&c(p,"WARN","No valid skills or agents directories found",{userId:e,cId:t,hasUploadedZip:!!n,skillUrlsCount:h.length});const F=Array.from(S),O=u.length>0?`Workspace created successfully, ${u.join(" and ")} updated`:"Workspace created successfully (skills and agents directories not found)";return c(p,"INFO",O,{userId:e,cId:t,updatedDirs:u,updatedSkills:F,agentTypes:_,elapsedMs:Date.now()-l}),await X(j),{message:O,workspaceRoot:w,updatedSkills:F}}catch(u){throw c(p,"ERROR","Failed to process workspace resources",{userId:e,cId:t,error:u.message,elapsedMs:Date.now()-l}),u instanceof g||u instanceof Z||u instanceof A?u:new A(`Failed to create workspace: ${u.message}`,{userId:e,cId:t})}finally{try{for(const u of f)i.existsSync(u)&&await i.promises.rm(u,{recursive:!0,force:!0})}catch(u){c(p,"WARN","Failed to clean up temporary extracted zip",{error:u.message})}for(const u of d)try{i.existsSync(u)&&await i.promises.unlink(u)}catch(F){c(p,"WARN","Failed to clean up downloaded skill zip file",{downloadedZipPath:u,error:F.message})}try{n&&n.path&&i.existsSync(n.path)&&await i.promises.unlink(n.path)}catch(u){c(p,"WARN","Failed to clean up uploaded zip file",{tempZipPath:n?.path,error:u.message})}}}async function ge(e,t,n,o){const s=Date.now(),a=`computer:${e}:${t}`,m=ie(o),y=[],l=[];if(!e)throw new g("userId cannot be empty",{field:"userId"});if(!t)throw new g("cId cannot be empty",{field:"cId"});if(!n&&m.length===0)throw new g("file or skillUrls cannot both be empty",{field:"file|skillUrls"});if(n){if(!n.path)throw new g("Uploaded file has no valid path",{field:"file.path"});if(r.extname(n.originalname||n.filename||"").toLowerCase()!==".zip")throw new g("Only zip files are supported",{field:"file",originalName:n?.originalname})}const p=await $(a),h=r.join(p,String(e),String(t),".tmp"),d=r.join(p,String(e),String(t)),{skillsDir:f,agentTypes:S}=await te(d);try{i.existsSync(d)||await i.promises.mkdir(d,{recursive:!0}),i.existsSync(f)||await i.promises.mkdir(f,{recursive:!0}),i.existsSync(h)||await i.promises.mkdir(h,{recursive:!0});const w=[],x=new Set;if(n){const k=r.join(h,`skill_push_${Date.now()}_${Math.round(Math.random()*1e6)}`);l.push(k),await i.promises.mkdir(k,{recursive:!0}),c(a,"DEBUG","Start extracting skill zip file",{extractRoot:k}),await G(n.path,k),c(a,"DEBUG","Skill zip file extracted successfully",{extractRoot:k});const D=await H(k,"skills");if(!D)c(a,"WARN","skills directory not found in uploaded zip",{userId:e,cId:t,extractRoot:k});else{const N=(await i.promises.readdir(D,{withFileTypes:!0})).filter(E=>E.isDirectory()&&!E.name.startsWith("."));if(N.length===0)c(a,"WARN","skills directory in uploaded zip has no skill subdirectories",{userId:e,cId:t,skillsDir:D});else for(const E of N){const W=r.join(D,E.name),b=r.join(f,E.name);i.existsSync(b)&&await C(b),await T(W,b),x.add(E.name),c(a,"INFO","skill pushed to workspace from uploaded zip",{userId:e,cId:t,skillName:E.name,destSkillPath:b})}}}for(let k=0;k<m.length;k+=1){const D=m[k],_=r.join(h,`skill_push_url_${Date.now()}_${k}_${Math.round(Math.random()*1e6)}.zip`);y.push(_);const N=r.join(h,`skill_push_url_extract_${Date.now()}_${k}_${Math.round(Math.random()*1e6)}`);l.push(N),await i.promises.mkdir(N,{recursive:!0}),c(a,"INFO","Start download skill zip for push from url",{userId:e,cId:t,skillUrl:D}),await ne(D,_,a),await G(_,N);const W=(await i.promises.readdir(N,{withFileTypes:!0})).filter(u=>u.isDirectory()&&!u.name.startsWith(".")),L=W.find(u=>u.name==="skills")?(await i.promises.readdir(r.join(N,"skills"),{withFileTypes:!0})).filter(u=>u.isDirectory()&&!u.name.startsWith(".")).map(u=>({name:u.name,sourcePath:r.join(N,"skills",u.name)})):W.map(u=>({name:u.name,sourcePath:r.join(N,u.name)}));if(L.length===0){c(a,"WARN","No skill directory found after extracting push skill url zip",{userId:e,cId:t,skillUrl:D,extractRoot:N});continue}for(const u of L){const F=r.join(f,u.name);i.existsSync(F)&&await C(F),await T(u.sourcePath,F),x.add(u.name),c(a,"INFO","skill pushed to workspace from url zip",{userId:e,cId:t,skillUrl:D,skillName:u.name,destSkillPath:F})}}for(const k of x)w.push(k);const j=w.length>0?`Pushed ${w.length} skills: ${w.join(", ")}`:"No valid skill directories found in file or skillUrls";return c(a,"INFO",j,{userId:e,cId:t,updatedSkills:w,agentTypes:S,elapsedMs:Date.now()-s}),await X(d),{message:j,workspaceRoot:p,updatedSkills:w}}catch(w){throw c(a,"ERROR","Failed to push skill to workspace",{userId:e,cId:t,error:w.message,elapsedMs:Date.now()-s}),w instanceof g||w instanceof Z||w instanceof A?w:new A(`Failed to push skill to workspace: ${w.message}`,{userId:e,cId:t})}finally{try{for(const w of l)i.existsSync(w)&&await i.promises.rm(w,{recursive:!0,force:!0})}catch(w){c(a,"WARN","Failed to clean up temporary extracted zip",{error:w.message})}for(const w of y)try{i.existsSync(w)&&await i.promises.unlink(w)}catch(x){c(a,"WARN","Failed to clean up downloaded skill zip file",{downloadedZipPath:w,error:x.message})}try{n&&n.path&&i.existsSync(n.path)&&await i.promises.unlink(n.path)}catch(w){c(a,"WARN","Failed to clean up uploaded zip file",{tempZipPath:n?.path,error:w.message})}}}async function ye(e,t,n,o){const s=Date.now(),a=`computer:${e}:${t}`;if(!e)throw new g("userId cannot be empty",{field:"userId"});if(!t)throw new g("cId cannot be empty",{field:"cId"});if(!n||!n.path)throw new g("file is required",{field:"file"});if(r.extname(n.originalname||n.filename||"").toLowerCase()!==".zip")throw new g("Only zip files are supported",{field:"file",originalName:n.originalname});const y=await $(a),l=r.join(y,String(e),String(t)),p=r.join(l,".tmp"),h=r.join(p,`template_extract_${Date.now()}_${Math.round(Math.random()*1e6)}`);try{i.existsSync(l)||await i.promises.mkdir(l,{recursive:!0}),i.existsSync(p)||await i.promises.mkdir(p,{recursive:!0}),await i.promises.mkdir(h,{recursive:!0}),c(a,"DEBUG","Start extracting template zip file",{extractRoot:h}),await G(n.path,h),c(a,"DEBUG","Template zip file extracted successfully",{extractRoot:h});const d=await i.promises.readdir(h,{withFileTypes:!0});for(const f of d){const S=r.join(h,f.name),w=r.join(l,f.name);if(f.isDirectory())await T(S,w);else{const x=r.dirname(w);i.existsSync(x)||await i.promises.mkdir(x,{recursive:!0}),await i.promises.copyFile(S,w)}}if(c(a,"INFO","Template files extracted to workspace",{userId:e,cId:t,targetDir:l,fileCount:d.length}),B.GIT_ENABLED&&(o==="true"||o===!0)){const f=await import("../../service/gitService.js");await f.default.init({workspaceType:"taskAgent",userId:e,cId:t}),await f.default.commit({workspaceType:"taskAgent",userId:e,cId:t,message:"Initial commit"}),c(a,"INFO","Git init and initial commit done",{userId:e,cId:t})}return c(a,"INFO","Init project template completed",{userId:e,cId:t,targetDir:l,elapsedMs:Date.now()-s}),{message:"Project template initialized successfully",workspaceRoot:l}}catch(d){throw c(a,"ERROR","Failed to init project template",{userId:e,cId:t,error:d.message,elapsedMs:Date.now()-s}),d instanceof g||d instanceof Z||d instanceof A?d:new A(`Failed to init project template: ${d.message}`,{userId:e,cId:t})}finally{try{i.existsSync(h)&&await i.promises.rm(h,{recursive:!0,force:!0})}catch(d){c(a,"WARN","Failed to clean up temporary extracted zip",{error:d.message})}try{n&&n.path&&i.existsSync(n.path)&&await i.promises.unlink(n.path)}catch(d){c(a,"WARN","Failed to clean up uploaded zip file",{tempZipPath:n?.path,error:d.message})}}}async function ke(e,t,n){if(!e)throw new g("userId cannot be empty",{field:"userId"});if(!t)throw new g("cId cannot be empty",{field:"cId"});if(!n||typeof n!="string"||!n.trim())throw new g("command cannot be empty",{field:"command"});const o=await $("computer"),s=r.join(o,String(e),String(t));if(!i.existsSync(s))throw new g("workspace directory does not exist",{field:"workDir",workDir:s});const a=`computer:${e}:${t}`;c(a,"INFO","Execute command in workspace",{userId:e,cId:t,workDir:s,command:n});const m=10*60*1e3,y={cwd:s,timeout:m,maxBuffer:50*1024*1024};return B.BASH_PATH&&(y.shell=B.BASH_PATH),new Promise((l,p)=>{le(n,y,(h,d,f)=>{const S=h?h.killed?-1:h.code||1:0,w=h&&h.killed?(f||"")+`
2
- Command timed out after ${m/1e3}s`:f||"";c(a,"INFO","Execute command completed",{userId:e,cId:t,exitCode:S,killed:h?h.killed:!1,stdoutLength:d?d.length:0,stderrLength:w.length}),l({stdout:d||"",stderr:w,exitCode:S})})})}async function Se(e,t,n){const o=`computer:${e}:${t}`;if(!e)throw new g("userId cannot be empty",{field:"userId"});if(!t)throw new g("cId cannot be empty",{field:"cId"});const s=await $(o),a=r.join(s,String(e),String(t));if(!i.existsSync(a))throw new g("workspace directory does not exist",{field:"workDir",workDir:a});const m=B.ZIP_WORKSPACE_EXCLUDE||[],y=Array.isArray(n)?n:[],l=new Set([...m,...y]),p=`${e}_${t}.zip`,h=Date.now();c(o,"INFO","Zip workspace request",{userId:e,cId:t,targetDir:a,excludeDirs:[...l]});const d=de("zip",{zlib:{level:9}});return d.directory(a,!1,f=>{const S=f.name||"";if(S.split(/[\/\\]/).filter(Boolean).some(x=>l.has(x)))return!1;try{const x=r.join(a,S);if(i.lstatSync(x).isSymbolicLink())return!1}catch{return!1}return f}),d.on("warning",f=>{f.code==="ENOENT"?c(o,"WARN","Encountered file problem when creating zip",{message:f.message,code:f.code}):c(o,"ERROR","Encountered warning when creating zip",{message:f.message,code:f.code})}),d.on("error",f=>{c(o,"ERROR","Failed to create zip",{message:f.message,elapsedMs:Date.now()-h})}),d.on("end",()=>{c(o,"INFO","Workspace zip created successfully",{targetDir:a,zipFileName:p,elapsedMs:Date.now()-h})}),{archive:d,zipFileName:p}}const Q=new Set([...B.ZIP_WORKSPACE_EXCLUDE||[],"dist-packages"]);function V(e){let t;try{t=i.readdirSync(e,{withFileTypes:!0})}catch{return null}const n=r.join(e,"scripts","package-platforms.mjs");if(i.existsSync(n))return e;for(const o of t)if(o.isDirectory()&&!Q.has(o.name)){const s=V(r.join(e,o.name));if(s)return s}return null}function Y(e,t,n,o=10*60*1e3){return new Promise(s=>{const a=pe(e,t,{...n,timeout:o});let m="",y="";a.stdout?.on("data",l=>{m+=l.toString()}),a.stderr?.on("data",l=>{y+=l.toString()}),a.on("error",l=>{s({stdout:m,stderr:y+`
3
- Failed to spawn ${e}: ${l.message}`,exitCode:1})}),a.on("close",(l,p)=>{s(l===null&&p?{stdout:m,stderr:y+`
4
- Process killed by signal ${p} (timeout after ${o/1e3}s)`,exitCode:-1}:{stdout:m,stderr:y,exitCode:l??1})})})}function xe(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 n=t.split("-");if(n.length<4)return null;let o=-1;for(let s=n.length-1;s>=2;s--)if(/^\d+\.\d+\.\d+$/.test(n[s])){o=s;break}return o<3?null:n.slice(2,o).join("-")}async function Ne(e,t,n,o){const s=`computer:${e}:${t}`;if(!e)throw new g("userId cannot be empty",{field:"userId"});if(!t)throw new g("cId cannot be empty",{field:"cId"});if(!n)throw new g("agentId cannot be empty",{field:"agentId"});if(!o)throw new g("version cannot be empty",{field:"version"});const a=await $(s),m=r.join(a,String(e),String(t));if(!i.existsSync(m))throw new g("workspace directory does not exist",{field:"workDir",workDir:m});c(s,"INFO","Build agent package request",{userId:e,cId:t,agentId:n,version:o});const y=V(m);if(!y)throw new g("package-platforms.mjs not found in workspace",{field:"script"});c(s,"INFO","Found package script",{projectDir:y});const l=await ae(y,s);if(c(s,"INFO","pnpm install completed",{exitCode:l.exitCode,stderrLength:l.stderr.length}),l.exitCode!==0)throw c(s,"ERROR","pnpm install failed",{stderr:l.stderr}),new A(`pnpm install failed: ${l.stderr}`);const p=r.join(y,"dist-packages"),h=["scripts/package-platforms.mjs",`agent-${n}`,o,p,"--print-artifacts"],d=await Y("node",h,{cwd:y,env:I()});if(c(s,"INFO","Build completed",{exitCode:d.exitCode,stdoutLength:d.stdout.length}),d.exitCode!==0)throw c(s,"ERROR","Build failed",{stderr:d.stderr}),new A(`Build failed: ${d.stderr}`);const f=[".tar.gz",".tar.bz2",".zip",".tgz"],S=[],w=d.stdout.split(`
5
- `);for(const x of w){const j=x.trim();if(!j)continue;const k=j.toLowerCase();if(!f.some(W=>k.endsWith(W)))continue;const D=r.resolve(y,j);let _=r.relative(m,D);_=_.split(r.sep).join("/");const N=r.basename(j),E=xe(N);S.push({path:_,fileName:N,platform:E||""})}return c(s,"INFO","Build agent package completed",{artifactsCount:S.length,artifacts:S}),{artifacts:S}}async function je(e,t){const n=`computer:${e}:${t}`;if(!e)throw new g("userId cannot be empty",{field:"userId"});if(!t)throw new g("cId cannot be empty",{field:"cId"});const o=await $(n),s=r.join(o,String(e),String(t));if(!i.existsSync(s))return c(n,"WARN","Workspace not found, skip cleanup",{userId:e,cId:t}),{cleaned:!1};const a=V(s);if(!a)return c(n,"WARN","package-platforms.mjs not found, skip cleanup",{userId:e,cId:t}),{cleaned:!1};const m=r.join(a,"dist-packages");return i.existsSync(m)?(i.rmSync(m,{recursive:!0,force:!0}),c(n,"INFO","Cleaned dist-packages",{distPackagesDir:m}),{cleaned:!0}):(c(n,"INFO","dist-packages not found, nothing to clean",{distPackagesDir:m}),{cleaned:!1})}async function De(e,t){const n=`computer:${e}:${t}`;if(!e)throw new g("userId cannot be empty",{field:"userId"});if(!t)throw new g("cId cannot be empty",{field:"cId"});const o=await $(n),s=r.join(o,String(e),String(t));return i.existsSync(s)?(await i.promises.rm(s,{recursive:!0,force:!0}),c(n,"INFO","Workspace deleted",{userId:e,cId:t,targetDir:s})):c(n,"WARN","Workspace not found, skip delete",{userId:e,cId:t,targetDir:s}),{deleted:!0}}function se(e){const t=r.join(e,"package.json");if(i.existsSync(t))return e;let n;try{n=i.readdirSync(e,{withFileTypes:!0})}catch{return null}for(const o of n)if(o.isDirectory()&&!Q.has(o.name)&&o.name!=="node_modules"){const s=se(r.join(e,o.name));if(s)return s}return null}const oe=["neverBuiltDependencies","onlyBuiltDependencies","ignoredBuiltDependencies"],Re=["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 Pe(e,t){const n=r.join(e,"package.json");if(!i.existsSync(n))return;const o=await i.promises.readFile(n,"utf8");let s;try{s=JSON.parse(o)}catch{c(t,"WARN","Skip sanitizing package.json: invalid JSON",{projectDir:e});return}if(!s.pnpm||typeof s.pnpm!="object")return;const a=oe.filter(m=>m in s.pnpm);if(a.length!==0){for(const m of a)delete s.pnpm[m];Object.keys(s.pnpm).length===0&&delete s.pnpm,await i.promises.writeFile(n,`${JSON.stringify(s,null,2)}
6
- `,"utf8"),c(t,"INFO","Removed conflicting pnpm built dependency settings from package.json",{projectDir:e,removedKeys:a})}}async function Fe(e,t){const n=r.join(e,"pnpm-workspace.yaml");if(!i.existsSync(n))return;const s=(await i.promises.readFile(n,"utf8")).split(`
7
- `),a=[];let m=-1,y=[];for(const l of s){const p=l.trimStart(),h=l.length-p.length,d=oe.find(f=>new RegExp(`^${f}:\\s*(.*)$`).test(p));if(d){y.push(d);const f=p.slice(d.length+1).trim();(!f||f==="|"||f===">")&&(m=h);continue}if(m>=0){if(p===""||h>m)continue;m=-1}a.push(l)}y.length!==0&&(await i.promises.writeFile(n,a.join(`
8
- `),"utf8"),c(t,"INFO","Removed conflicting pnpm built dependency settings from pnpm-workspace.yaml",{projectDir:e,removedKeys:y}))}async function re(e){if(!i.existsSync(e))return"";const t=await i.promises.readFile(e,"utf8"),n=t.split(`
1
+ import n from"fs";import l from"path";import{exec as ae}from"child_process";import le from"cross-spawn";import ce from"archiver";import M from"../../appConfig/index.js";import{extractZip as L}from"../common/zipUtils.js";import{ValidationError as u,SystemError as $,FileError as U}from"../error/errorHandler.js";import{log as r}from"../log/logUtils.js";import{createPnpmNpmrc as pe}from"../common/npmrcUtils.js";import{ensurePrimaryAgentDirs as I,syncAgents as V}from"../common/AgentWorkspaceUtils.js";import{writeAgentHookConfigs as de}from"./hookConfigUtils.js";function ee(e){if(!e)return[];if(Array.isArray(e))return 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);if(Array.isArray(i))return i.map(o=>typeof o=="string"?o.trim():"").filter(Boolean)}catch{}return[t]}return[]}async function te(e,t,i){let o;try{o=await fetch(e)}catch(y){throw new U(`Failed to download skill zip from url: ${e}`,{url:e,reason:y.message})}if(!o.ok)throw new U(`Failed to download skill zip from url: ${e}`,{url:e,status:o.status,statusText:o.statusText});const s=o.headers.get("content-type")||"";s&&!s.includes("zip")&&!s.includes("octet-stream")&&r(i,"WARN","Downloaded skill url content-type is not typical zip",{url:e,contentType:s});const a=await o.arrayBuffer(),d=Buffer.from(a);await n.promises.writeFile(t,d)}async function A(e="computer"){const t=M.COMPUTER_WORKSPACE_DIR;if(!t)throw new u("COMPUTER_WORKSPACE_DIR configuration does not exist",{field:"COMPUTER_WORKSPACE_DIR"});return n.existsSync(t)||(await n.promises.mkdir(t,{recursive:!0}),r(e,"INFO","Created user workspace root directory",{workspaceRoot:t})),t}async function q(e,t){const i=l.join(e,t);if(n.existsSync(i)&&(await n.promises.lstat(i)).isDirectory())return i;const o=await n.promises.readdir(e,{withFileTypes:!0});for(const s of o){if(!s.isDirectory())continue;const a=l.join(e,s.name),d=l.join(a,t);if(n.existsSync(d)&&(await n.promises.lstat(d)).isDirectory())return d}return null}async function Le(e){return q(e,"skills")}const me=".dynamic_add.lock";function fe(e){const t=l.join(e,me);return n.existsSync(t)&&n.statSync(t).isFile()}async function b(e){n.existsSync(e)&&await n.promises.rm(e,{recursive:!0,force:!0})}async function T(e,t){try{await n.promises.rename(e,t)}catch(i){if(i.code==="EXDEV"){async function o(s,a){if((await n.promises.lstat(s)).isDirectory()){await n.promises.mkdir(a,{recursive:!0});const y=await n.promises.readdir(s);for(const c of y)await o(l.join(s,c),l.join(a,c))}else await n.promises.copyFile(s,a)}await o(e,t),await n.promises.rm(e,{recursive:!0,force:!0})}else throw i}}async function ue(e,t,i,o,s,a,d,y){const c=Date.now(),p=`computer:${e}:${t}`,w=ee(o),m=[],g=[],R=new Set;if(!e)throw new u("userId cannot be empty",{field:"userId"});if(!t)throw new u("cId cannot be empty",{field:"cId"});const f=await A(p),D=l.join(f,String(e),String(t),".tmp"),E=l.join(f,String(e),String(t)),{skillsDir:k,agentsDir:_,agentTypes:F}=await I(E);n.existsSync(E)||await n.promises.mkdir(E,{recursive:!0});const N=l.join(D,`preserved_skills_${Date.now()}_${Math.round(Math.random()*1e6)}`);if(n.existsSync(k)){const P=(await n.promises.readdir(k,{withFileTypes:!0})).filter(S=>S.isDirectory()&&fe(l.join(k,S.name)));if(P.length>0){await n.promises.mkdir(N,{recursive:!0});for(const S of P){const x=l.join(k,S.name),z=l.join(N,S.name);await T(x,z)}r(p,"INFO","\u4FDD\u7559\u542B .dynamic_add.lock \u7684 skill",{preserved:P.map(S=>S.name)})}}if(await b(k),await b(_),await n.promises.mkdir(k,{recursive:!0}),await n.promises.mkdir(_,{recursive:!0}),n.existsSync(N)){const h=await n.promises.readdir(N,{withFileTypes:!0});for(const P of h)if(P.isDirectory()){const S=l.join(N,P.name),x=l.join(k,P.name);await T(S,x)}await b(N)}const O=n.existsSync(k),C=n.existsSync(_);if(r(p,"INFO","Deleted old skills and agents directories completed",{userId:e,cId:t,targetSkillsDir:k,targetAgentsDir:_,agentTypes:F,skillsExists:O,agentsExists:C}),await de(E,{mcpServersConfig:s,hooksConfig:d,permissionsConfig:a,hookScripts:y},p),!i&&w.length===0)return await V(E),r(p,"INFO","Created workspace (no uploaded file, no skills and agents)",{userId:e,cId:t,workspaceRoot:f,skillsDir:null,agentsDir:null,agentTypes:F,elapsedMs:Date.now()-c}),{message:"Workspace created (no uploaded file, no skills and agents)",workspaceRoot:f};if(i){if(!i.path)throw new u("Uploaded file has no valid path",{field:"file.path"});if(l.extname(i.originalname||i.filename||"").toLowerCase()!==".zip")throw new u("Only zip files are supported",{field:"file",originalName:i.originalname})}r(p,"DEBUG","Start processing workspace resources",{userId:e,cId:t,workspaceRoot:f,hasUploadedZip:!!i,skillUrlsCount:w.length});try{n.existsSync(D)||await n.promises.mkdir(D,{recursive:!0});const h=[];if(i){const x=l.join(D,`skill_extract_${Date.now()}_${Math.round(Math.random()*1e6)}`);g.push(x),await n.promises.mkdir(x,{recursive:!0}),r(p,"DEBUG","Start extracting uploaded zip file",{extractRoot:x}),await L(i.path,x),r(p,"DEBUG","Uploaded zip file extracted successfully",{extractRoot:x});const z=await q(x,"skills"),W=await q(x,"agents");if(z){await n.promises.mkdir(k,{recursive:!0});const v=await n.promises.readdir(z,{withFileTypes:!0});for(const B of v){if(!B.isDirectory())continue;const G=l.join(z,B.name),Z=l.join(k,B.name);n.existsSync(Z)&&await b(Z),await T(G,Z),R.add(B.name)}h.push("skills"),r(p,"INFO","skills updated to workspace",{userId:e,cId:t,workspaceRoot:f,targetSkillsDir:k,agentTypes:F})}else r(p,"INFO","skills directory not found in uploaded zip, skipping",{userId:e,cId:t,extractRoot:x});W?(await b(_),await T(W,_),h.push("agents"),r(p,"INFO","agents updated to workspace",{userId:e,cId:t,workspaceRoot:f,targetAgentsDir:_,agentTypes:F})):r(p,"INFO","agents directory not found in uploaded zip, skipping",{userId:e,cId:t,extractRoot:x})}for(let x=0;x<w.length;x+=1){const z=w[x],W=l.join(D,`skill_url_${Date.now()}_${x}_${Math.round(Math.random()*1e6)}.zip`);m.push(W);const v=l.join(D,`skill_url_extract_${Date.now()}_${x}_${Math.round(Math.random()*1e6)}`);g.push(v),await n.promises.mkdir(v,{recursive:!0}),r(p,"INFO","Start download skill zip from url",{userId:e,cId:t,skillUrl:z}),await te(z,W,p),r(p,"INFO","Skill zip downloaded, start extracting",{userId:e,cId:t,skillUrl:z,downloadedZipPath:W}),await L(W,v);const G=(await n.promises.readdir(v,{withFileTypes:!0})).filter(j=>j.isDirectory()&&!j.name.startsWith(".")),Q=G.find(j=>j.name==="skills")?(await n.promises.readdir(l.join(v,"skills"),{withFileTypes:!0})).filter(j=>j.isDirectory()&&!j.name.startsWith(".")).map(j=>({name:j.name,sourcePath:l.join(v,"skills",j.name)})):G.map(j=>({name:j.name,sourcePath:l.join(v,j.name)}));if(Q.length===0){r(p,"WARN","No skill directory found after extracting skill url zip",{userId:e,cId:t,skillUrl:z,extractRoot:v});continue}await n.promises.mkdir(k,{recursive:!0});for(const j of Q){const K=l.join(k,j.name);n.existsSync(K)&&await b(K),await T(j.sourcePath,K),R.add(j.name),r(p,"INFO","Skill from url updated to workspace",{userId:e,cId:t,skillUrl:z,skillName:j.name,destSkillPath:K})}h.includes("skills")||h.push("skills")}h.length===0&&r(p,"WARN","No valid skills or agents directories found",{userId:e,cId:t,hasUploadedZip:!!i,skillUrlsCount:w.length});const P=Array.from(R),S=h.length>0?`Workspace created successfully, ${h.join(" and ")} updated`:"Workspace created successfully (skills and agents directories not found)";return r(p,"INFO",S,{userId:e,cId:t,updatedDirs:h,updatedSkills:P,agentTypes:F,elapsedMs:Date.now()-c}),await V(E),{message:S,workspaceRoot:f,updatedSkills:P}}catch(h){throw r(p,"ERROR","Failed to process workspace resources",{userId:e,cId:t,error:h.message,elapsedMs:Date.now()-c}),h instanceof u||h instanceof U||h instanceof $?h:new $(`Failed to create workspace: ${h.message}`,{userId:e,cId:t})}finally{try{for(const h of g)n.existsSync(h)&&await n.promises.rm(h,{recursive:!0,force:!0})}catch(h){r(p,"WARN","Failed to clean up temporary extracted zip",{error:h.message})}for(const h of m)try{n.existsSync(h)&&await n.promises.unlink(h)}catch(P){r(p,"WARN","Failed to clean up downloaded skill zip file",{downloadedZipPath:h,error:P.message})}try{i&&i.path&&n.existsSync(i.path)&&await n.promises.unlink(i.path)}catch(h){r(p,"WARN","Failed to clean up uploaded zip file",{tempZipPath:i?.path,error:h.message})}}}async function we(e,t,i,o){const s=Date.now(),a=`computer:${e}:${t}`,d=ee(o),y=[],c=[];if(!e)throw new u("userId cannot be empty",{field:"userId"});if(!t)throw new u("cId cannot be empty",{field:"cId"});if(!i&&d.length===0)throw new u("file or skillUrls cannot both be empty",{field:"file|skillUrls"});if(i){if(!i.path)throw new u("Uploaded file has no valid path",{field:"file.path"});if(l.extname(i.originalname||i.filename||"").toLowerCase()!==".zip")throw new u("Only zip files are supported",{field:"file",originalName:i?.originalname})}const p=await A(a),w=l.join(p,String(e),String(t),".tmp"),m=l.join(p,String(e),String(t)),{skillsDir:g,agentTypes:R}=await I(m);try{n.existsSync(m)||await n.promises.mkdir(m,{recursive:!0}),n.existsSync(g)||await n.promises.mkdir(g,{recursive:!0}),n.existsSync(w)||await n.promises.mkdir(w,{recursive:!0});const f=[],D=new Set;if(i){const k=l.join(w,`skill_push_${Date.now()}_${Math.round(Math.random()*1e6)}`);c.push(k),await n.promises.mkdir(k,{recursive:!0}),r(a,"DEBUG","Start extracting skill zip file",{extractRoot:k}),await L(i.path,k),r(a,"DEBUG","Skill zip file extracted successfully",{extractRoot:k});const _=await q(k,"skills");if(!_)r(a,"WARN","skills directory not found in uploaded zip",{userId:e,cId:t,extractRoot:k});else{const N=(await n.promises.readdir(_,{withFileTypes:!0})).filter(O=>O.isDirectory()&&!O.name.startsWith("."));if(N.length===0)r(a,"WARN","skills directory in uploaded zip has no skill subdirectories",{userId:e,cId:t,skillsDir:_});else for(const O of N){const C=l.join(_,O.name),h=l.join(g,O.name);n.existsSync(h)&&await b(h),await T(C,h),D.add(O.name),r(a,"INFO","skill pushed to workspace from uploaded zip",{userId:e,cId:t,skillName:O.name,destSkillPath:h})}}}for(let k=0;k<d.length;k+=1){const _=d[k],F=l.join(w,`skill_push_url_${Date.now()}_${k}_${Math.round(Math.random()*1e6)}.zip`);y.push(F);const N=l.join(w,`skill_push_url_extract_${Date.now()}_${k}_${Math.round(Math.random()*1e6)}`);c.push(N),await n.promises.mkdir(N,{recursive:!0}),r(a,"INFO","Start download skill zip for push from url",{userId:e,cId:t,skillUrl:_}),await te(_,F,a),await L(F,N);const C=(await n.promises.readdir(N,{withFileTypes:!0})).filter(S=>S.isDirectory()&&!S.name.startsWith(".")),P=C.find(S=>S.name==="skills")?(await n.promises.readdir(l.join(N,"skills"),{withFileTypes:!0})).filter(S=>S.isDirectory()&&!S.name.startsWith(".")).map(S=>({name:S.name,sourcePath:l.join(N,"skills",S.name)})):C.map(S=>({name:S.name,sourcePath:l.join(N,S.name)}));if(P.length===0){r(a,"WARN","No skill directory found after extracting push skill url zip",{userId:e,cId:t,skillUrl:_,extractRoot:N});continue}for(const S of P){const x=l.join(g,S.name);n.existsSync(x)&&await b(x),await T(S.sourcePath,x),D.add(S.name),r(a,"INFO","skill pushed to workspace from url zip",{userId:e,cId:t,skillUrl:_,skillName:S.name,destSkillPath:x})}}for(const k of D)f.push(k);const E=f.length>0?`Pushed ${f.length} skills: ${f.join(", ")}`:"No valid skill directories found in file or skillUrls";return r(a,"INFO",E,{userId:e,cId:t,updatedSkills:f,agentTypes:R,elapsedMs:Date.now()-s}),await V(m),{message:E,workspaceRoot:p,updatedSkills:f}}catch(f){throw r(a,"ERROR","Failed to push skill to workspace",{userId:e,cId:t,error:f.message,elapsedMs:Date.now()-s}),f instanceof u||f instanceof U||f instanceof $?f:new $(`Failed to push skill to workspace: ${f.message}`,{userId:e,cId:t})}finally{try{for(const f of c)n.existsSync(f)&&await n.promises.rm(f,{recursive:!0,force:!0})}catch(f){r(a,"WARN","Failed to clean up temporary extracted zip",{error:f.message})}for(const f of y)try{n.existsSync(f)&&await n.promises.unlink(f)}catch(D){r(a,"WARN","Failed to clean up downloaded skill zip file",{downloadedZipPath:f,error:D.message})}try{i&&i.path&&n.existsSync(i.path)&&await n.promises.unlink(i.path)}catch(f){r(a,"WARN","Failed to clean up uploaded zip file",{tempZipPath:i?.path,error:f.message})}}}async function ge(e,t,i,o){const s=Date.now(),a=`computer:${e}:${t}`;if(!e)throw new u("userId cannot be empty",{field:"userId"});if(!t)throw new u("cId cannot be empty",{field:"cId"});if(!i||!i.path)throw new u("file is required",{field:"file"});if(l.extname(i.originalname||i.filename||"").toLowerCase()!==".zip")throw new u("Only zip files are supported",{field:"file",originalName:i.originalname});const y=await A(a),c=l.join(y,String(e),String(t)),p=l.join(c,".tmp"),w=l.join(p,`template_extract_${Date.now()}_${Math.round(Math.random()*1e6)}`);try{n.existsSync(c)||await n.promises.mkdir(c,{recursive:!0}),n.existsSync(p)||await n.promises.mkdir(p,{recursive:!0}),await n.promises.mkdir(w,{recursive:!0}),r(a,"DEBUG","Start extracting template zip file",{extractRoot:w}),await L(i.path,w),r(a,"DEBUG","Template zip file extracted successfully",{extractRoot:w});const m=await n.promises.readdir(w,{withFileTypes:!0});for(const g of m){const R=l.join(w,g.name),f=l.join(c,g.name);if(g.isDirectory())await T(R,f);else{const D=l.dirname(f);n.existsSync(D)||await n.promises.mkdir(D,{recursive:!0}),await n.promises.copyFile(R,f)}}if(r(a,"INFO","Template files extracted to workspace",{userId:e,cId:t,targetDir:c,fileCount:m.length}),M.GIT_ENABLED&&(o==="true"||o===!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"}),r(a,"INFO","Git init and initial commit done",{userId:e,cId:t})}return r(a,"INFO","Init project template completed",{userId:e,cId:t,targetDir:c,elapsedMs:Date.now()-s}),{message:"Project template initialized successfully",workspaceRoot:c}}catch(m){throw r(a,"ERROR","Failed to init project template",{userId:e,cId:t,error:m.message,elapsedMs:Date.now()-s}),m instanceof u||m instanceof U||m instanceof $?m:new $(`Failed to init project template: ${m.message}`,{userId:e,cId:t})}finally{try{n.existsSync(w)&&await n.promises.rm(w,{recursive:!0,force:!0})}catch(m){r(a,"WARN","Failed to clean up temporary extracted zip",{error:m.message})}try{i&&i.path&&n.existsSync(i.path)&&await n.promises.unlink(i.path)}catch(m){r(a,"WARN","Failed to clean up uploaded zip file",{tempZipPath:i?.path,error:m.message})}}}async function he(e,t,i){if(!e)throw new u("userId cannot be empty",{field:"userId"});if(!t)throw new u("cId cannot be empty",{field:"cId"});if(!i||typeof i!="string"||!i.trim())throw new u("command cannot be empty",{field:"command"});const o=await A("computer"),s=l.join(o,String(e),String(t));if(!n.existsSync(s))throw new u("workspace directory does not exist",{field:"workDir",workDir:s});const a=`computer:${e}:${t}`;r(a,"INFO","Execute command in workspace",{userId:e,cId:t,workDir:s,command:i});const d=10*60*1e3,y={cwd:s,timeout:d,maxBuffer:50*1024*1024};return M.BASH_PATH&&(y.shell=M.BASH_PATH),new Promise((c,p)=>{ae(i,y,(w,m,g)=>{const R=w?w.killed?-1:w.code||1:0,f=w&&w.killed?(g||"")+`
2
+ Command timed out after ${d/1e3}s`:g||"";r(a,"INFO","Execute command completed",{userId:e,cId:t,exitCode:R,killed:w?w.killed:!1,stdoutLength:m?m.length:0,stderrLength:f.length}),c({stdout:m||"",stderr:f,exitCode:R})})})}async function ye(e,t,i){const o=`computer:${e}:${t}`;if(!e)throw new u("userId cannot be empty",{field:"userId"});if(!t)throw new u("cId cannot be empty",{field:"cId"});const s=await A(o),a=l.join(s,String(e),String(t));if(!n.existsSync(a))throw new u("workspace directory does not exist",{field:"workDir",workDir:a});const d=M.ZIP_WORKSPACE_EXCLUDE||[],y=Array.isArray(i)?i:[],c=new Set([...d,...y]),p=`${e}_${t}.zip`,w=Date.now();r(o,"INFO","Zip workspace request",{userId:e,cId:t,targetDir:a,excludeDirs:[...c]});const m=ce("zip",{zlib:{level:9}});return m.directory(a,!1,g=>{const R=g.name||"";if(R.split(/[\/\\]/).filter(Boolean).some(D=>c.has(D)))return!1;try{const D=l.join(a,R);if(n.lstatSync(D).isSymbolicLink())return!1}catch{return!1}return g}),m.on("warning",g=>{g.code==="ENOENT"?r(o,"WARN","Encountered file problem when creating zip",{message:g.message,code:g.code}):r(o,"ERROR","Encountered warning when creating zip",{message:g.message,code:g.code})}),m.on("error",g=>{r(o,"ERROR","Failed to create zip",{message:g.message,elapsedMs:Date.now()-w})}),m.on("end",()=>{r(o,"INFO","Workspace zip created successfully",{targetDir:a,zipFileName:p,elapsedMs:Date.now()-w})}),{archive:m,zipFileName:p}}const Y=new Set([...M.ZIP_WORKSPACE_EXCLUDE||[],"dist-packages"]);function J(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 o of t)if(o.isDirectory()&&!Y.has(o.name)){const s=J(l.join(e,o.name));if(s)return s}return null}function H(e,t,i,o=10*60*1e3){return new Promise(s=>{const a=le(e,t,{...i,timeout:o});let d="",y="";a.stdout?.on("data",c=>{d+=c.toString()}),a.stderr?.on("data",c=>{y+=c.toString()}),a.on("error",c=>{s({stdout:d,stderr:y+`
3
+ Failed to spawn ${e}: ${c.message}`,exitCode:1})}),a.on("close",(c,p)=>{s(c===null&&p?{stdout:d,stderr:y+`
4
+ Process killed by signal ${p} (timeout after ${o/1e3}s)`,exitCode:-1}:{stdout:d,stderr:y,exitCode:c??1})})})}function ke(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 o=-1;for(let s=i.length-1;s>=2;s--)if(/^\d+\.\d+\.\d+$/.test(i[s])){o=s;break}return o<3?null:i.slice(2,o).join("-")}async function Se(e,t,i,o){const s=`computer:${e}:${t}`;if(!e)throw new u("userId cannot be empty",{field:"userId"});if(!t)throw new u("cId cannot be empty",{field:"cId"});if(!i)throw new u("agentId cannot be empty",{field:"agentId"});if(!o)throw new u("version cannot be empty",{field:"version"});const a=await A(s),d=l.join(a,String(e),String(t));if(!n.existsSync(d))throw new u("workspace directory does not exist",{field:"workDir",workDir:d});r(s,"INFO","Build agent package request",{userId:e,cId:t,agentId:i,version:o});const y=J(d);if(!y)throw new u("package-platforms.mjs not found in workspace",{field:"script"});r(s,"INFO","Found package script",{projectDir:y});const c=await oe(y,s);if(r(s,"INFO","pnpm install completed",{exitCode:c.exitCode,stderrLength:c.stderr.length}),c.exitCode!==0)throw r(s,"ERROR","pnpm install failed",{stderr:c.stderr}),new $(`pnpm install failed: ${c.stderr}`);const p=l.join(y,"dist-packages"),w=["scripts/package-platforms.mjs",`agent-${i}`,o,p,"--print-artifacts"],m=await H("node",w,{cwd:y,env:X()});if(r(s,"INFO","Build completed",{exitCode:m.exitCode,stdoutLength:m.stdout.length}),m.exitCode!==0)throw r(s,"ERROR","Build failed",{stderr:m.stderr}),new $(`Build failed: ${m.stderr}`);const g=[".tar.gz",".tar.bz2",".zip",".tgz"],R=[],f=m.stdout.split(`
5
+ `);for(const D of f){const E=D.trim();if(!E)continue;const k=E.toLowerCase();if(!g.some(C=>k.endsWith(C)))continue;const _=l.resolve(y,E);let F=l.relative(d,_);F=F.split(l.sep).join("/");const N=l.basename(E),O=ke(N);R.push({path:F,fileName:N,platform:O||""})}return r(s,"INFO","Build agent package completed",{artifactsCount:R.length,artifacts:R}),{artifacts:R}}async function xe(e,t){const i=`computer:${e}:${t}`;if(!e)throw new u("userId cannot be empty",{field:"userId"});if(!t)throw new u("cId cannot be empty",{field:"cId"});const o=await A(i),s=l.join(o,String(e),String(t));if(!n.existsSync(s))return r(i,"WARN","Workspace not found, skip cleanup",{userId:e,cId:t}),{cleaned:!1};const a=J(s);if(!a)return r(i,"WARN","package-platforms.mjs not found, skip cleanup",{userId:e,cId:t}),{cleaned:!1};const d=l.join(a,"dist-packages");return n.existsSync(d)?(n.rmSync(d,{recursive:!0,force:!0}),r(i,"INFO","Cleaned dist-packages",{distPackagesDir:d}),{cleaned:!0}):(r(i,"INFO","dist-packages not found, nothing to clean",{distPackagesDir:d}),{cleaned:!1})}async function De(e,t){const i=`computer:${e}:${t}`;if(!e)throw new u("userId cannot be empty",{field:"userId"});if(!t)throw new u("cId cannot be empty",{field:"cId"});const o=await A(i),s=l.join(o,String(e),String(t));return n.existsSync(s)?(await n.promises.rm(s,{recursive:!0,force:!0}),r(i,"INFO","Workspace deleted",{userId:e,cId:t,targetDir:s})):r(i,"WARN","Workspace not found, skip delete",{userId:e,cId:t,targetDir:s}),{deleted:!0}}function ie(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 o of i)if(o.isDirectory()&&!Y.has(o.name)&&o.name!=="node_modules"){const s=ie(l.join(e,o.name));if(s)return s}return null}const ne=["neverBuiltDependencies","onlyBuiltDependencies","ignoredBuiltDependencies"],Ne=["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 Re(e,t){const i=l.join(e,"package.json");if(!n.existsSync(i))return;const o=await n.promises.readFile(i,"utf8");let s;try{s=JSON.parse(o)}catch{r(t,"WARN","Skip sanitizing package.json: invalid JSON",{projectDir:e});return}if(!s.pnpm||typeof s.pnpm!="object")return;const a=ne.filter(d=>d in s.pnpm);if(a.length!==0){for(const d of a)delete s.pnpm[d];Object.keys(s.pnpm).length===0&&delete s.pnpm,await n.promises.writeFile(i,`${JSON.stringify(s,null,2)}
6
+ `,"utf8"),r(t,"INFO","Removed conflicting pnpm built dependency settings from package.json",{projectDir:e,removedKeys:a})}}async function _e(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
+ `),a=[];let d=-1,y=[];for(const c of s){const p=c.trimStart(),w=c.length-p.length,m=ne.find(g=>new RegExp(`^${g}:\\s*(.*)$`).test(p));if(m){y.push(m);const g=p.slice(m.length+1).trim();(!g||g==="|"||g===">")&&(d=w);continue}if(d>=0){if(p===""||w>d)continue;d=-1}a.push(c)}y.length!==0&&(await n.promises.writeFile(i,a.join(`
8
+ `),"utf8"),r(t,"INFO","Removed conflicting pnpm built dependency settings from pnpm-workspace.yaml",{projectDir:e,removedKeys:y}))}async function se(e){if(!n.existsSync(e))return"";const t=await n.promises.readFile(e,"utf8"),i=t.split(`
9
9
  `).filter(o=>!/^\s*(never-built-dependencies|only-built-dependencies|ignored-built-dependencies)\b/.test(o)).join(`
10
- `);return n!==t&&await i.promises.writeFile(e,n,"utf8"),n}async function _e(e,t){await Pe(e,t),await Fe(e,t),await re(r.join(e,".npmrc"))}async function Ee(e,t){await me(e,t),await _e(e,t);const n=r.join(e,".npmrc");let o=await re(n);const s=[];if(/dangerously-allow-all-builds\s*=/.test(o)||s.push("dangerously-allow-all-builds=true"),/production\s*=/.test(o)||s.push("production=false"),/confirm-modules-purge\s*=/.test(o)||s.push("confirm-modules-purge=false"),s.length>0){const a=o&&!o.endsWith(`
10
+ `);return i!==t&&await n.promises.writeFile(e,i,"utf8"),i}async function je(e,t){await Re(e,t),await _e(e,t),await se(l.join(e,".npmrc"))}async function Pe(e,t){await pe(e,t),await je(e,t);const i=l.join(e,".npmrc");let o=await se(i);const s=[];if(/dangerously-allow-all-builds\s*=/.test(o)||s.push("dangerously-allow-all-builds=true"),/production\s*=/.test(o)||s.push("production=false"),/confirm-modules-purge\s*=/.test(o)||s.push("confirm-modules-purge=false"),s.length>0){const a=o&&!o.endsWith(`
11
11
  `)?`
12
- `:"";await i.promises.writeFile(n,`${o}${a}${s.join(`
12
+ `:"";await n.promises.writeFile(i,`${o}${a}${s.join(`
13
13
  `)}
14
- `,"utf8"),c(t,"INFO","Updated .npmrc for pnpm install",{projectDir:e,additions:s})}}function I(){const e={...process.env};delete e.CI,delete e.NPM_CONFIG_PRODUCTION,delete e.npm_config_production;for(const t of Re)delete e[t];return e.NODE_ENV="development",e.NPM_CONFIG_PRODUCTION="false",e}async function ae(e,t){await Ee(e,t);const n=I();return Y("pnpm",["install","--prefer-offline","--config.production=false","--config.confirmModulesPurge=false","--config.dangerouslyAllowAllBuilds=true"],{cwd:e,env:n})}function ce(e){const t=r.join(e,"pyproject.toml"),n=r.join(e,"requirements.txt");if(i.existsSync(t)||i.existsSync(n))return e;let o;try{o=i.readdirSync(e,{withFileTypes:!0})}catch{return null}for(const s of o)if(s.isDirectory()&&!Q.has(s.name)&&s.name!==".venv"&&s.name!=="venv"&&s.name!=="__pycache__"){const a=ce(r.join(e,s.name));if(a)return a}return null}async function Oe(e,t,n){const o=`computer:${e}:${t}`,s=String(n||"").trim().toLowerCase();if(!e)throw new g("userId cannot be empty",{field:"userId"});if(!t)throw new g("cId cannot be empty",{field:"cId"});if(!s)throw new g("programmingLanguage cannot be empty",{field:"programmingLanguage"});const a=await $(o),m=r.join(a,String(e),String(t));if(!i.existsSync(m))throw new g("workspace directory does not exist",{field:"workDir",workDir:m});c(o,"INFO","Install project dependencies request",{userId:e,cId:t,programmingLanguage:s});const y=I();let l=null,p=null;if(s==="typescript"||s==="ts"){if(l=V(m)||se(m),!l)throw new g("TypeScript project not found in workspace",{field:"projectDir"});p=await ae(l,o)}else if(s==="python"||s==="py"){if(l=ce(m),!l)throw new g("Python project not found in workspace",{field:"projectDir"});const h=r.join(l,"pyproject.toml"),d=r.join(l,"requirements.txt");if(i.existsSync(h))p=await Y("pip",["install","-e","."],{cwd:l,env:y});else if(i.existsSync(d))p=await Y("pip",["install","-r","requirements.txt"],{cwd:l,env:y});else throw new g("No pyproject.toml or requirements.txt found in workspace",{field:"projectDir"})}else throw new g("Unsupported programmingLanguage",{field:"programmingLanguage",programmingLanguage:s});if(c(o,"INFO","Project dependencies install completed",{projectDir:l,programmingLanguage:s,exitCode:p.exitCode,stderrLength:p.stderr.length}),p.exitCode!==0)throw c(o,"ERROR","Project dependencies install failed",{projectDir:l,programmingLanguage:s,stderr:p.stderr}),new A(`Project dependencies install failed: ${p.stderr||p.stdout}`);return{message:"Project dependencies installed successfully",projectDir:l,programmingLanguage:s}}export{he as createWorkspace,ge as pushSkillsToWorkspace,ye as initProjectTemplate,ke as executeCommand,De as deleteWorkspace,Se as zipWorkspace,Ne as buildAgentPackage,je as cleanupBuildArtifacts,Oe as installProjectDependencies};
14
+ `,"utf8"),r(t,"INFO","Updated .npmrc for pnpm install",{projectDir:e,additions:s})}}function X(){const e={...process.env};delete e.CI,delete e.NPM_CONFIG_PRODUCTION,delete e.npm_config_production;for(const t of Ne)delete e[t];return e.NODE_ENV="development",e.NPM_CONFIG_PRODUCTION="false",e}async function oe(e,t){await Pe(e,t);const i=X();return H("pnpm",["install","--prefer-offline","--config.production=false","--config.confirmModulesPurge=false","--config.dangerouslyAllowAllBuilds=true"],{cwd:e,env:i})}function re(e){const t=l.join(e,"pyproject.toml"),i=l.join(e,"requirements.txt");if(n.existsSync(t)||n.existsSync(i))return e;let o;try{o=n.readdirSync(e,{withFileTypes:!0})}catch{return null}for(const s of o)if(s.isDirectory()&&!Y.has(s.name)&&s.name!==".venv"&&s.name!=="venv"&&s.name!=="__pycache__"){const a=re(l.join(e,s.name));if(a)return a}return null}async function Ee(e,t,i){const o=`computer:${e}:${t}`,s=String(i||"").trim().toLowerCase();if(!e)throw new u("userId cannot be empty",{field:"userId"});if(!t)throw new u("cId cannot be empty",{field:"cId"});if(!s)throw new u("programmingLanguage cannot be empty",{field:"programmingLanguage"});const a=await A(o),d=l.join(a,String(e),String(t));if(!n.existsSync(d))throw new u("workspace directory does not exist",{field:"workDir",workDir:d});r(o,"INFO","Install project dependencies request",{userId:e,cId:t,programmingLanguage:s});const y=X();let c=null,p=null;if(s==="typescript"||s==="ts"){if(c=J(d)||ie(d),!c)throw new u("TypeScript project not found in workspace",{field:"projectDir"});p=await oe(c,o)}else if(s==="python"||s==="py"){if(c=re(d),!c)throw new u("Python project not found in workspace",{field:"projectDir"});const w=l.join(c,"pyproject.toml"),m=l.join(c,"requirements.txt");if(n.existsSync(w))p=await H("pip",["install","-e","."],{cwd:c,env:y});else if(n.existsSync(m))p=await H("pip",["install","-r","requirements.txt"],{cwd:c,env:y});else throw new u("No pyproject.toml or requirements.txt found in workspace",{field:"projectDir"})}else throw new u("Unsupported programmingLanguage",{field:"programmingLanguage",programmingLanguage:s});if(r(o,"INFO","Project dependencies install completed",{projectDir:c,programmingLanguage:s,exitCode:p.exitCode,stderrLength:p.stderr.length}),p.exitCode!==0)throw r(o,"ERROR","Project dependencies install failed",{projectDir:c,programmingLanguage:s,stderr:p.stderr}),new $(`Project dependencies install failed: ${p.stderr||p.stdout}`);return{message:"Project dependencies installed successfully",projectDir:c,programmingLanguage:s}}export{ue as createWorkspace,we as pushSkillsToWorkspace,ge as initProjectTemplate,he as executeCommand,De as deleteWorkspace,ye as zipWorkspace,Se as buildAgentPackage,xe as cleanupBuildArtifacts,Ee as installProjectDependencies};
@@ -0,0 +1,6 @@
1
+ import i from"fs";import s from"path";import{fileURLToPath as V}from"url";import{log as l}from"../log/logUtils.js";const $=s.dirname(V(import.meta.url)),K=s.join($,"../../assets/opencode-hooks-plugin"),X=s.join($,"../../assets/opencode-platform-env-plugin"),T=new Set(["SessionStart","SubagentStart","SubagentStop","PreToolUse","PermissionRequest","PostToolUse","PreCompact","PostCompact","UserPromptSubmit","Stop"]),x="opencode-hooks-plugin.js",O="opencode-hooks-plugin",v="opencode-platform-env-plugin.js",Y="hooks/platform-env.sh";function M(e,t=30){const n=Number(e);return Number.isFinite(n)&&n>0?n:t}function Z(e){if(!e)return null;if(typeof e=="object"&&!Array.isArray(e))return e;if(typeof e=="string"){const t=e.trim();if(!t)return null;try{const n=JSON.parse(t);return typeof n=="object"&&n!==null&&!Array.isArray(n)?n:null}catch{return null}}return null}function D(e){if(!e||typeof e!="object"||Array.isArray(e))return null;const t={};for(const[n,c]of Object.entries(e)){if(!Array.isArray(c))continue;const r=[];for(const o of c){if(!o||typeof o!="object")continue;const u=[],a=Array.isArray(o.hooks)?o.hooks:[];for(const f of a){const m=Z(f);m&&u.push(m)}u.length!==0&&r.push({...o,hooks:u})}r.length>0&&(t[n]=r)}return Object.keys(t).length>0?t:null}function q(e){return F(e).hooksMap}function F(e){if(e==null||!String(e).trim())return{attempted:!1,hooksMap:null,error:null};try{const t=JSON.parse(e);return{attempted:!0,hooksMap:D(t),error:null}}catch(t){return{attempted:!0,hooksMap:null,error:t instanceof Error?t.message:String(t)}}}function B(e){return`'${String(e).replace(/'/g,"'\\''")}'`}function U(e){return String(e).replace(/\$([a-zA-Z_][a-zA-Z0-9_]*)/g,"${$1}")}const Q=/git rev-parse --show-toplevel/;function L(e){const t=e.trim();return!t||Q.test(t)||/[|;&`<>$()]/.test(t)||/^(bash|sh|zsh|dash|python3?|node|npm|npx|curl|wget|echo|env|cd|export|test|\[)\s+/i.test(t)?!1:!!(t.startsWith("/")||t.includes("/")||/\.(sh|bash|py|js|mjs|cjs|ts|pl|rb)$/i.test(t)||/^[\w.-]+\.sh$/i.test(t))}function G(e){const t=e.trim();if(!L(t))return t;if(t.startsWith("/"))return`bash "${t.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`;const n=t.replace(/^\.\//,""),c=".codex/hooks/";if(n.startsWith(c)){const o=n.slice(c.length);if(o&&!o.includes("/"))return E(o)}return/^[\w.-]+\.sh$/i.test(n)?E(n):`bash "$(git rev-parse --show-toplevel 2>/dev/null || pwd)/${n.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`}function ee(e){if(!e||typeof e!="object")return['-H "Content-Type: application/json"'];const t=[],n=Object.entries(e);n.some(([r])=>r.toLowerCase()==="content-type")||t.push('-H "Content-Type: application/json"');for(const[r,o]of n){if(o==null)continue;const u=String(r).replace(/\\/g,"\\\\").replace(/"/g,'\\"'),a=U(o).replace(/\\/g,"\\\\").replace(/"/g,'\\"');t.push(`-H "${u}: ${a}"`)}return t}function z(e,t,n){return`#!/usr/bin/env bash
2
+ set -euo pipefail
3
+ curl -fsS -X POST ${ee(n).join(" ")} --data-binary @- --max-time ${t} ${B(e)}
4
+ `}function E(e){return`bash "$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.codex/hooks/${e}"`}async function N(e,t,n={}){const c=s.dirname(e);await i.promises.mkdir(c,{recursive:!0});const r=s.join(c,`.${s.basename(e)}.${process.pid}.${Date.now()}.tmp`);await i.promises.writeFile(r,t,"utf-8"),n.mode!=null&&await i.promises.chmod(r,n.mode),await i.promises.rename(r,e)}async function C(e,t){await N(e,`${JSON.stringify(t,null,2)}
5
+ `)}async function J(e,t,n){const c={};let r=0;for(const[o,u]of Object.entries(e)){if(!T.has(o)){l(n,"WARN","Skipping unsupported Codex hook event",{eventName:o});continue}const a=[];for(const f of u){if(!f||typeof f!="object")continue;const m=[],j=Array.isArray(f.hooks)?f.hooks:[];for(const p of j){if(!p||typeof p!="object")continue;const g=p.type;if(g==="command"&&typeof p.command=="string"&&p.command.trim()){m.push({...p,type:"command",command:G(p.command)});continue}if(g==="http"&&typeof p.url=="string"&&p.url.trim()){const y=M(p.timeout,30),d=`http-hook-${r++}.sh`,P=s.join(t,d),b=p.headers&&typeof p.headers=="object"&&!Array.isArray(p.headers)?p.headers:void 0;await i.promises.mkdir(t,{recursive:!0}),await N(P,z(p.url.trim(),y,b),{mode:493}),m.push({type:"command",command:E(d),timeout:y,...p.statusMessage?{statusMessage:p.statusMessage}:{}});continue}if(g==="prompt"||g==="agent"){l(n,"WARN","Codex skips non-command hook handler",{eventName:o,type:g});continue}l(n,"WARN","Codex skips unknown hook handler",{eventName:o,type:g})}m.length>0&&a.push({...f,hooks:m})}a.length>0&&(c[o]=a)}return Object.keys(c).length>0?c:null}async function W(e,t){const n=s.join(K,"dist");if(!i.existsSync(n))return l(t,"WARN","opencode-hooks-plugin assets missing, skipping OpenCode plugin install",{sourceDistDir:n}),!1;await i.promises.mkdir(e,{recursive:!0});const c=s.join(e,O,"dist");await i.promises.mkdir(c,{recursive:!0});const r=await i.promises.readdir(n);for(const a of r)a.endsWith(".js")&&await i.promises.copyFile(s.join(n,a),s.join(c,a));const o=s.join(e,x),u=`export { default } from "./${O}/dist/index.js";
6
+ `;return await N(o,u),l(t,"INFO","Installed opencode-hooks-plugin into .opencode/plugins",{entryFile:x}),!0}function I(e){return Array.isArray(e)?e.some(t=>t&&t.path===Y):!1}async function _(e,t){const n=s.join(X,"platform-env-plugin.js");return i.existsSync(n)?(await i.promises.mkdir(e,{recursive:!0}),await i.promises.copyFile(n,s.join(e,v)),l(t,"INFO","Installed opencode-platform-env-plugin into .opencode/plugins",{entryFile:v}),!0):(l(t,"WARN","opencode-platform-env-plugin assets missing, skipping install",{sourceFile:n}),!1)}async function te(e){if(!i.existsSync(e))return{settings:{},corrupt:!1};try{const t=await i.promises.readFile(e,"utf-8"),n=JSON.parse(t);return n&&typeof n=="object"&&!Array.isArray(n)?{settings:n,corrupt:!1}:{settings:{},corrupt:!0}}catch{return{settings:{},corrupt:!0}}}async function ne(e,t,n){if(!Array.isArray(t)||t.length===0)return;const c=s.join(e,"hooks");await i.promises.mkdir(c,{recursive:!0});for(const r of t){if(!r||!r.path||r.content==null)continue;const o=s.normalize(r.path);if(o.startsWith("..")||s.isAbsolute(o)){l(n,"WARN","Hook script path contains traversal, skipping",{path:r.path});continue}const u=s.join(e,o),a=s.dirname(u);await i.promises.mkdir(a,{recursive:!0}),await N(u,r.content,{mode:493}),l(n,"INFO","Written hook script",{path:r.path})}}async function R(e){const t=[s.join(e,".codex","hooks.json"),s.join(e,".codex","hooks"),s.join(e,".opencode","plugins",x),s.join(e,".opencode","plugins",O),s.join(e,".opencode","plugins",v),s.join(e,".claude","hooks")];for(const n of t)i.existsSync(n)&&await i.promises.rm(n,{recursive:!0,force:!0})}async function oe(e){const t=[s.join(e,".mcp.json"),s.join(e,".claude","settings.json")];for(const n of t)i.existsSync(n)&&await i.promises.rm(n,{recursive:!0,force:!0});await R(e)}async function se(e,t,n,c=!1){const r=s.join(e,".tmp",`hook-staging-${Date.now()}-${process.pid}`),o=s.join(r,"codex"),u=s.join(o,"hooks"),a=s.join(r,"opencode","plugins");await i.promises.mkdir(u,{recursive:!0});const f=await J(t,u,n),m=await W(a,n),j=c?await _(a,n):!1;return{stagingRoot:r,codexStagingRoot:o,codexHooks:f,pluginInstalled:m,platformEnvPluginInstalled:j}}async function ie(e,t,n){const c=s.join(e,".codex"),r=s.join(c,"hooks"),o=s.join(e,".opencode","plugins");await R(e),await i.promises.mkdir(c,{recursive:!0});const u=s.join(t.codexStagingRoot,"hooks");if(i.existsSync(u)&&await i.promises.rename(u,r),t.codexHooks&&(await C(s.join(c,"hooks.json"),{hooks:t.codexHooks}),l(n,"INFO","Written .codex/hooks.json",{events:Object.keys(t.codexHooks)})),t.pluginInstalled){await i.promises.mkdir(o,{recursive:!0});const a=s.join(t.stagingRoot,"opencode","plugins",x),f=s.join(t.stagingRoot,"opencode","plugins",O);i.existsSync(a)&&await i.promises.copyFile(a,s.join(o,x)),i.existsSync(f)&&await i.promises.cp(f,s.join(o,O),{recursive:!0,force:!0})}if(t.platformEnvPluginInstalled){await i.promises.mkdir(o,{recursive:!0});const a=s.join(t.stagingRoot,"opencode","plugins",v);i.existsSync(a)&&await i.promises.copyFile(a,s.join(o,v))}}async function re(e,{mcpServersConfig:t,hooksConfig:n,permissionsConfig:c,hookScripts:r},o){const u=!!(t&&t.trim()),a=F(n),f=!!(c&&c.trim()),m=Array.isArray(r)&&r.length>0;a.attempted&&a.error&&l(o,"ERROR","Invalid hooksConfig, keeping existing hook configs",{error:a.error});let j=null,p=!1;if(u)try{j=JSON.parse(t),p=!0}catch(h){l(o,"WARN","Failed to parse mcpServersConfig, keeping existing .mcp.json",{error:h.message})}let g=null,y=!1;if(f)try{g=JSON.parse(c),y=!0}catch(h){l(o,"WARN","Failed to parse permissionsConfig, keeping existing permissions",{error:h.message})}const d=a.attempted&&!a.error,P=m,b=I(r);if(!p&&!d&&!y&&!P)return;const H=s.join(e,".claude"),k=s.join(H,"settings.json");let w=null;try{if(await i.promises.mkdir(H,{recursive:!0}),d&&a.hooksMap&&(w=await se(e,a.hooksMap,o,b)),p&&(await C(s.join(e,".mcp.json"),{mcpServers:j}),l(o,"INFO","Written .mcp.json to workspace root")),d&&a.hooksMap&&w?await ie(e,w,o):d&&!a.hooksMap&&await R(e),d||y){const{settings:h,corrupt:S}=await te(k);if(S&&!d)l(o,"WARN","Corrupt .claude/settings.json, skipping settings update",{settingsPath:k});else{y&&S&&l(o,"WARN","Corrupt .claude/settings.json, rewriting hooks without preserving old fields",{settingsPath:k});const A=S?{}:{...h};d&&(a.hooksMap?A.hooks=a.hooksMap:delete A.hooks),y&&!S&&(A.permissions=g),Object.keys(A).length>0?(await C(k,A),l(o,"INFO","Written .claude/settings.json",{keys:Object.keys(A)})):i.existsSync(k)&&await i.promises.rm(k,{force:!0})}}if(P){const h=s.join(H,"hooks");i.existsSync(h)&&await i.promises.rm(h,{recursive:!0,force:!0}),await ne(H,r,o);const S=s.join(e,".opencode","plugins");b&&await _(S,o)}}catch(h){l(o,"ERROR","Failed to write agent hook configs, keeping previous files when possible",{error:h.message})}finally{w?.stagingRoot&&i.existsSync(w.stagingRoot)&&await i.promises.rm(w.stagingRoot,{recursive:!0,force:!0})}}export{q as parseHooksConfigString,F as parseHooksConfigWithStatus,D as normalizeHooksMap,J as transformHooksForCodex,z as buildHttpWrapperScript,E as buildCodexHookCommand,G as normalizeCodexCommand,L as isLikelyScriptPath,U as toBashEnvExpandable,M as parseTimeoutSeconds,N as writeFileAtomic,C as writeJsonFileAtomic,W as installOpencodeHooksPlugin,_ as installOpencodePlatformEnvPlugin,I as hasPlatformEnvScript,oe as clearAgentHookConfigs,R as clearHookArtifacts,re as writeAgentHookConfigs,T as CODEX_HOOK_EVENTS};
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.3.1",
4
+ "version": "1.3.3-beta.1",
5
5
  "description": "Cross-platform file service deployment tool with start/stop/restart CLI commands",
6
6
  "type": "module",
7
7
  "main": "index.js",