lib/project-filesystem.js
Rama main · réplica de lectura del repositorio original.
var fs = require("fs");
var path = require("path");
var attachRequestAccess = require("./project-request-access").attachRequestAccess;
var attachFileHistory = require("./project-file-history").attachFileHistory;
var resolveFilePath = require("./project-file-path").resolveFilePath;
var fileError = require("./project-file-path").fileError;
/**
* Attach filesystem-related message handlers to a project context.
*
* ctx fields:
* cwd, slug, osUsers
* sm (session manager)
* send, sendTo
* getOsUserInfoForWs (function)
* startFileWatch, stopFileWatch, startDirWatch (from _fileWatch)
* usersModule, fsAsUser
* validateEnvString, onEnvironmentChanged (functions)
* opts (for onGetProjectEnv, onSetProjectEnv, onGetSharedEnv, onSetSharedEnv callbacks)
* IGNORED_DIRS, BINARY_EXTS, IMAGE_EXTS, FS_MAX_SIZE (constants)
*/
function attachFilesystem(ctx) {
var cwd = ctx.cwd;
var slug = ctx.slug;
var sendTo = ctx.sendTo;
var startFileWatch = ctx.startFileWatch;
var stopFileWatch = ctx.stopFileWatch;
var startDirWatch = ctx.startDirWatch;
var fsAsUser = ctx.fsAsUser;
var validateEnvString = ctx.validateEnvString;
var onEnvironmentChanged = ctx.onEnvironmentChanged || function () {};
var opts = ctx.opts;
var IGNORED_DIRS = ctx.IGNORED_DIRS;
var BINARY_EXTS = ctx.BINARY_EXTS;
var IMAGE_EXTS = ctx.IMAGE_EXTS;
var FS_MAX_SIZE = ctx.FS_MAX_SIZE;
var access = ctx.requestAccess || attachRequestAccess(ctx);
var fileHistory = attachFileHistory(ctx, access);
var operations = require("./project-file-operations").attachFileOperations(ctx);
function handleFilesystemMessage(ws, msg) {
var fileScope;
var fileTarget;
// Resolve live authority before every operation; never trust message roles.
if (/^fs_/.test(msg.type) && msg.type !== "fs_unwatch") {
try {
if (msg.instanceId && (msg.projectSlug !== slug || (msg.accountId || null) !== (ws._clayUser && ws._clayUser.id || null))) throw new Error('File pane context changed');
fileScope = access.fileScope(ws);
if (/^fs_(list|read|write|watch)$/.test(msg.type)) {
fileTarget = resolveFilePath(cwd, msg.type === "fs_list" ? msg.path || "." : msg.path, fileScope);
}
} catch (e) {
var denied = fileError(e);
sendTo(ws, { type: msg.type + "_result", path: msg.path, instanceId: msg.instanceId, requestId: msg.requestId, projectSlug: msg.projectSlug, sessionId: msg.sessionId, accountId: msg.accountId, ok: false, error: denied.message, errorCode: denied.code });
return true;
}
}
// --- fs_list ---
if (msg.type === "fs_list") {
try {
sendTo(ws, { type: 'fs_list_result', path: msg.path || '.', entries: operations.list(fileScope, msg.path || '.') });
startDirWatch(ws, msg.path || '.');
} catch (e) {
sendTo(ws, { type: 'fs_list_result', path: msg.path, entries: [], error: fileError(e).message, errorCode: fileError(e).code });
}
return true;
}
// --- fs_search ---
if (msg.type === "fs_search") {
var query = (msg.query || "").trim().toLowerCase();
if (!query) {
sendTo(ws, { type: "fs_search_result", query: msg.query, entries: [] });
return true;
}
try {
var searchResults = [];
var MAX_RESULTS = 50;
var searchUserInfo = access.osIdentity(ws);
function walkDir(dir, relPrefix) {
if (searchResults.length >= MAX_RESULTS) return;
var items;
try {
if (searchUserInfo) {
items = fsAsUser("list", { dir: dir }, searchUserInfo);
} else {
items = fs.readdirSync(dir, { withFileTypes: true }).map(function (d) {
return { name: d.name, isDir: d.isDirectory() };
});
}
} catch (e) { return; }
for (var i = 0; i < items.length; i++) {
if (searchResults.length >= MAX_RESULTS) return;
var it = items[i];
if (it.isDir && IGNORED_DIRS.has(it.name)) continue;
var rel = relPrefix ? relPrefix + "/" + it.name : it.name;
if (it.name.toLowerCase().indexOf(query) !== -1) {
searchResults.push({ name: it.name, type: it.isDir ? "dir" : "file", path: rel });
}
if (it.isDir) {
walkDir(path.join(dir, it.name), rel);
}
}
}
walkDir(cwd, "");
sendTo(ws, { type: "fs_search_result", query: msg.query, entries: searchResults });
} catch (e) {
sendTo(ws, { type: "fs_search_result", query: msg.query, entries: [], error: fileError(e).message, errorCode: fileError(e).code });
}
return true;
}
// --- fs_read ---
if (msg.type === "fs_read") {
var metadata = { type: 'fs_read_result', path: msg.path, instanceId: msg.instanceId, requestId: msg.requestId, projectSlug: msg.projectSlug, sessionId: msg.sessionId, accountId: msg.accountId };
try {
var readResult = operations.read(fileScope, msg.path);
try { var fileSnapshot = operations.snapshot(fileScope, msg.path); if (fileSnapshot.hash === require('crypto').createHash('sha256').update(readResult.content).digest('hex')) readResult.controlRevision = require('./clay-control-target-projection').digest(fileSnapshot); } catch (ignored) {}
sendTo(ws, Object.assign(metadata, readResult));
}
catch (e) { sendTo(ws, Object.assign(metadata, { error: fileError(e).message, errorCode: fileError(e).code })); }
return true;
}
if (msg.type === 'fs_write') {
try { sendTo(ws, Object.assign({ type: 'fs_write_result', path: msg.path }, operations.write(fileScope, msg.path, msg.content || ''))); }
catch (e) { sendTo(ws, { type: 'fs_write_result', path: msg.path, ok: false, error: fileError(e).message, errorCode: fileError(e).code }); }
return true;
}
// --- Project settings permission gate ---
if (msg.type === "get_project_env" || msg.type === "set_project_env" ||
msg.type === "get_shared_env" || msg.type === "set_shared_env" ||
msg.type === "transfer_project_owner") {
var globalSetting = /^(get_shared_env|set_shared_env)$/.test(msg.type);
var targetSlug = msg.slug || slug;
if (!access.hasPermission(ws, "projectSettings") ||
(globalSetting ? !access.isAdmin(ws) : !access.canAccessProject(ws, targetSlug))) {
sendTo(ws, { type: globalSetting ? (msg.type === 'get_shared_env' ? 'shared_env_result' : 'set_shared_env_result') : (msg.type === 'get_project_env' ? 'project_env_result' : msg.type === 'set_project_env' ? 'set_project_env_result' : 'error'), slug: targetSlug, requestId: msg.requestId, ok: false, error: 'Project settings access is not permitted', text: "Project settings access is not permitted" });
return true;
}
}
// --- Project environment variables ---
if (msg.type === "get_project_env") {
if (typeof opts.onGetProjectEnv !== "function") { sendTo(ws, { type: "project_env_result", slug: targetSlug, requestId: msg.requestId, error: "Project environment is unavailable in this runtime." }); return true; }
var envrc = "";
if (typeof opts.onGetProjectEnv === "function") {
var envResult;
try { envResult = opts.onGetProjectEnv(targetSlug); }
catch (error) { sendTo(ws, { type: "project_env_result", slug: targetSlug, requestId: msg.requestId, error: error.message }); return true; }
envrc = envResult.envrc || "";
}
sendTo(ws, { type: "project_env_result", slug: targetSlug, requestId: msg.requestId, envrc: envrc });
return true;
}
if (msg.type === "set_project_env") {
if (typeof opts.onSetProjectEnv === "function") {
var envError = validateEnvString(msg.envrc || "");
if (envError) {
sendTo(ws, { type: "set_project_env_result", ok: false, slug: targetSlug, requestId: msg.requestId, error: envError });
return true;
}
var setResult;
try { setResult = opts.onSetProjectEnv(targetSlug, msg.envrc || ""); }
catch (error) { sendTo(ws, { type: "set_project_env_result", slug: targetSlug, requestId: msg.requestId, ok: false, error: error.message }); return true; }
if (setResult.ok && !setResult.runtimeRefreshed) onEnvironmentChanged();
sendTo(ws, Object.assign({}, setResult, { type: "set_project_env_result", slug: targetSlug, requestId: msg.requestId, timing: setResult.ok ? "Applies to newly created coding-agent processes. Active processes keep their current environment." : undefined }));
} else {
sendTo(ws, { type: "set_project_env_result", slug: targetSlug, requestId: msg.requestId, ok: false, error: "Not supported" });
}
return true;
}
// --- Shared environment variables ---
if (msg.type === "get_shared_env") {
var sharedEnvrc = "";
if (typeof opts.onGetSharedEnv === "function") {
try {
var sharedResult = opts.onGetSharedEnv();
sharedEnvrc = sharedResult.envrc || "";
} catch (error) { sendTo(ws, { type: "shared_env_result", error: error.message }); return true; }
}
if (typeof opts.onGetSharedEnv !== "function") { sendTo(ws, { type: "shared_env_result", error: "Shared environment is unavailable in this runtime." }); return true; }
sendTo(ws, { type: "shared_env_result", envrc: sharedEnvrc });
return true;
}
if (msg.type === "set_shared_env") {
if (typeof opts.onSetSharedEnv === "function") {
var sharedEnvError = validateEnvString(msg.envrc || "");
if (sharedEnvError) {
sendTo(ws, { type: "set_shared_env_result", ok: false, error: sharedEnvError });
return true;
}
var sharedSetResult;
try { sharedSetResult = opts.onSetSharedEnv(msg.envrc || ""); }
catch (error) { sendTo(ws, { type: "set_shared_env_result", ok: false, error: error.message }); return true; }
if (sharedSetResult.ok) onEnvironmentChanged();
sendTo(ws, { type: "set_shared_env_result", ok: sharedSetResult.ok, error: sharedSetResult.error, timing: sharedSetResult.ok ? "Applies to newly created coding-agent processes. Active processes keep their current environment." : undefined });
} else {
sendTo(ws, { type: "set_shared_env_result", ok: false, error: "Not supported" });
}
return true;
}
// --- File watcher ---
if (msg.type === "fs_watch") {
if (msg.path) startFileWatch(ws, msg.path, msg.instanceId ? { instanceId: msg.instanceId, requestId: msg.requestId } : null);
return true;
}
if (msg.type === "fs_unwatch") {
stopFileWatch(ws, msg.instanceId ? { instanceId: msg.instanceId, requestId: msg.requestId } : { instanceId: undefined, requestId: undefined });
return true;
}
if (fileHistory.handleFileHistory(ws, msg)) return true;
return false;
}
return {
handleFilesystemMessage: handleFilesystemMessage,
productOperations: operations,
};
}
module.exports = { attachFilesystem: attachFilesystem };