clay-server 2.26.1-beta.1 → 2.27.0-beta.2

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.
@@ -0,0 +1,94 @@
1
+ var fs = require("fs");
2
+ var path = require("path");
3
+ var crypto = require("crypto");
4
+
5
+ /**
6
+ * Attach image handling to a project context.
7
+ *
8
+ * ctx fields:
9
+ * cwd, slug
10
+ */
11
+ function attachImage(ctx) {
12
+ var cwd = ctx.cwd;
13
+ var slug = ctx.slug;
14
+
15
+ // --- Chat image storage ---
16
+ var _imgConfig = require("./config");
17
+ var _imgUtils = require("./utils");
18
+ var _imagesBaseDir = path.join(_imgConfig.CONFIG_DIR, "images");
19
+ var _imagesEncodedCwd = _imgUtils.encodeCwd(cwd);
20
+ var imagesDir = path.join(_imagesBaseDir, _imagesEncodedCwd);
21
+
22
+ // Convert imageRefs in history entries to images with URLs for the client
23
+ function hydrateImageRefs(entry) {
24
+ if (!entry) return entry;
25
+ // Hydrate context_preview: convert screenshotFile to screenshotUrl
26
+ if (entry.type === "context_preview" && entry.tab && entry.tab.screenshotFile) {
27
+ var hydrated = {};
28
+ for (var k in entry) hydrated[k] = entry[k];
29
+ hydrated.tab = {};
30
+ for (var tk in entry.tab) hydrated.tab[tk] = entry.tab[tk];
31
+ hydrated.tab.screenshotUrl = "/p/" + slug + "/images/" + entry.tab.screenshotFile;
32
+ delete hydrated.tab.screenshotFile;
33
+ return hydrated;
34
+ }
35
+ if (!entry.imageRefs) return entry;
36
+ if (entry.type !== "user_message" && entry.type !== "mention_user") return entry;
37
+ var images = [];
38
+ for (var ri = 0; ri < entry.imageRefs.length; ri++) {
39
+ var ref = entry.imageRefs[ri];
40
+ images.push({ mediaType: ref.mediaType, url: "/p/" + slug + "/images/" + ref.file });
41
+ }
42
+ var hydrated = {};
43
+ for (var k2 in entry) {
44
+ if (k2 !== "imageRefs") hydrated[k2] = entry[k2];
45
+ }
46
+ hydrated.images = images;
47
+ return hydrated;
48
+ }
49
+
50
+ function saveImageFile(mediaType, base64data, ownerLinuxUser) {
51
+ try { fs.mkdirSync(imagesDir, { recursive: true }); } catch (e) {}
52
+ var ext = mediaType === "image/png" ? ".png" : mediaType === "image/gif" ? ".gif" : mediaType === "image/webp" ? ".webp" : ".jpg";
53
+ var hash = crypto.createHash("sha256").update(base64data).digest("hex").substring(0, 16);
54
+ var fileName = Date.now() + "-" + hash + ext;
55
+ var filePath = path.join(imagesDir, fileName);
56
+ try {
57
+ fs.writeFileSync(filePath, Buffer.from(base64data, "base64"));
58
+ if (process.platform !== "win32") {
59
+ // 644 so all local users can read (needed for git, copy, etc.)
60
+ try { fs.chmodSync(filePath, 0o644); } catch (e) {}
61
+ // In OS-user mode the daemon runs as root, so chown the file
62
+ // (and parent dirs) to the session owner to avoid permission issues.
63
+ if (ownerLinuxUser) {
64
+ try {
65
+ var osUsersMod = require("./os-users");
66
+ var uid = osUsersMod.getLinuxUserUid(ownerLinuxUser);
67
+ if (uid != null) {
68
+ require("child_process").execSync("chown " + uid + " " + JSON.stringify(filePath));
69
+ // Also fix parent dirs if root-owned
70
+ try {
71
+ var dirStat = fs.statSync(imagesDir);
72
+ if (dirStat.uid !== uid) {
73
+ require("child_process").execSync("chown " + uid + " " + JSON.stringify(imagesDir));
74
+ }
75
+ } catch (e2) {}
76
+ }
77
+ } catch (e) {}
78
+ }
79
+ }
80
+ return fileName;
81
+ } catch (e) {
82
+ console.error("[images] Failed to save image:", e.message);
83
+ return null;
84
+ }
85
+ }
86
+
87
+ return {
88
+ imagesDir: imagesDir,
89
+ hydrateImageRefs: hydrateImageRefs,
90
+ saveImageFile: saveImageFile,
91
+ };
92
+ }
93
+
94
+ module.exports = { attachImage: attachImage };
@@ -0,0 +1,161 @@
1
+ var fs = require("fs");
2
+ var path = require("path");
3
+
4
+ /**
5
+ * Attach knowledge file management to a project context.
6
+ *
7
+ * ctx fields:
8
+ * cwd, isMate, sendTo, matesModule, projectOwnerId
9
+ */
10
+ function attachKnowledge(ctx) {
11
+ var cwd = ctx.cwd;
12
+ var isMate = ctx.isMate;
13
+ var sendTo = ctx.sendTo;
14
+ var matesModule = ctx.matesModule;
15
+ var getProjectOwnerId = ctx.getProjectOwnerId;
16
+
17
+ function listKnowledgeFiles() {
18
+ var knowledgeDir = path.join(cwd, "knowledge");
19
+ var files = [];
20
+ try {
21
+ var entries = fs.readdirSync(knowledgeDir);
22
+ for (var ki = 0; ki < entries.length; ki++) {
23
+ if (entries[ki] === "session-digests.jsonl") continue;
24
+ if (entries[ki] === "sticky-notes.md") continue;
25
+ if (entries[ki] === "memory-summary.md") continue;
26
+ if (entries[ki].endsWith(".md") || entries[ki].endsWith(".jsonl")) {
27
+ var stat = fs.statSync(path.join(knowledgeDir, entries[ki]));
28
+ files.push({ name: entries[ki], size: stat.size, mtime: stat.mtimeMs, common: false });
29
+ }
30
+ }
31
+ } catch (e) { /* dir may not exist */ }
32
+ files.sort(function (a, b) { return b.mtime - a.mtime; });
33
+
34
+ if (isMate) {
35
+ var mateCtx = matesModule.buildMateCtx(getProjectOwnerId());
36
+ var thisMateId = path.basename(cwd);
37
+ for (var pi = 0; pi < files.length; pi++) {
38
+ files[pi].promoted = matesModule.isPromoted(mateCtx, thisMateId, files[pi].name);
39
+ }
40
+ var commonFiles = matesModule.getCommonKnowledgeForMate(mateCtx, thisMateId);
41
+ for (var ci = 0; ci < commonFiles.length; ci++) {
42
+ if (commonFiles[ci].ownMateId !== thisMateId) {
43
+ files.push(commonFiles[ci]);
44
+ }
45
+ }
46
+ }
47
+ return files;
48
+ }
49
+
50
+ function listKnowledgeFilesBasic() {
51
+ var knowledgeDir = path.join(cwd, "knowledge");
52
+ var files = [];
53
+ try {
54
+ var entries = fs.readdirSync(knowledgeDir);
55
+ for (var ki = 0; ki < entries.length; ki++) {
56
+ if (entries[ki].endsWith(".md") || entries[ki].endsWith(".jsonl")) {
57
+ var stat = fs.statSync(path.join(knowledgeDir, entries[ki]));
58
+ files.push({ name: entries[ki], size: stat.size, mtime: stat.mtimeMs });
59
+ }
60
+ }
61
+ } catch (e) {}
62
+ files.sort(function (a, b) { return b.mtime - a.mtime; });
63
+ if (isMate) {
64
+ var mateCtx = matesModule.buildMateCtx(getProjectOwnerId());
65
+ var thisMateId = path.basename(cwd);
66
+ for (var pi = 0; pi < files.length; pi++) {
67
+ files[pi].common = false;
68
+ files[pi].promoted = matesModule.isPromoted(mateCtx, thisMateId, files[pi].name);
69
+ }
70
+ var commonFiles = matesModule.getCommonKnowledgeForMate(mateCtx, thisMateId);
71
+ for (var ci = 0; ci < commonFiles.length; ci++) {
72
+ if (commonFiles[ci].ownMateId !== thisMateId) files.push(commonFiles[ci]);
73
+ }
74
+ }
75
+ return files;
76
+ }
77
+
78
+ function handleKnowledgeMessage(ws, msg) {
79
+ if (msg.type === "knowledge_list") {
80
+ sendTo(ws, { type: "knowledge_list", files: listKnowledgeFiles() });
81
+ return true;
82
+ }
83
+
84
+ if (msg.type === "knowledge_read") {
85
+ if (!msg.name) return true;
86
+ var safeName = path.basename(msg.name);
87
+ if (msg.common && msg.ownMateId && isMate) {
88
+ var mateCtx = matesModule.buildMateCtx(getProjectOwnerId());
89
+ try {
90
+ var content = matesModule.readCommonKnowledgeFile(mateCtx, msg.ownMateId, safeName);
91
+ sendTo(ws, { type: "knowledge_content", name: safeName, content: content, common: true, ownMateId: msg.ownMateId });
92
+ } catch (e) {
93
+ sendTo(ws, { type: "knowledge_content", name: safeName, content: "", error: "File not found", common: true });
94
+ }
95
+ } else {
96
+ var filePath = path.join(cwd, "knowledge", safeName);
97
+ try {
98
+ var content = fs.readFileSync(filePath, "utf8");
99
+ sendTo(ws, { type: "knowledge_content", name: safeName, content: content });
100
+ } catch (e) {
101
+ sendTo(ws, { type: "knowledge_content", name: safeName, content: "", error: "File not found" });
102
+ }
103
+ }
104
+ return true;
105
+ }
106
+
107
+ if (msg.type === "knowledge_save") {
108
+ if (!msg.name || typeof msg.content !== "string") return true;
109
+ var safeName = path.basename(msg.name);
110
+ if (!safeName.endsWith(".md") && !safeName.endsWith(".jsonl")) safeName += ".md";
111
+ var knowledgeDir = path.join(cwd, "knowledge");
112
+ fs.mkdirSync(knowledgeDir, { recursive: true });
113
+ fs.writeFileSync(path.join(knowledgeDir, safeName), msg.content);
114
+ sendTo(ws, { type: "knowledge_saved", name: safeName });
115
+ sendTo(ws, { type: "knowledge_list", files: listKnowledgeFilesBasic() });
116
+ return true;
117
+ }
118
+
119
+ if (msg.type === "knowledge_delete") {
120
+ if (!msg.name) return true;
121
+ var safeName = path.basename(msg.name);
122
+ var filePath = path.join(cwd, "knowledge", safeName);
123
+ try { fs.unlinkSync(filePath); } catch (e) {}
124
+ sendTo(ws, { type: "knowledge_deleted", name: safeName });
125
+ sendTo(ws, { type: "knowledge_list", files: listKnowledgeFilesBasic() });
126
+ return true;
127
+ }
128
+
129
+ if (msg.type === "knowledge_promote") {
130
+ if (!isMate || !msg.name) return true;
131
+ var safeName = path.basename(msg.name);
132
+ var mateCtx = matesModule.buildMateCtx(getProjectOwnerId());
133
+ var thisMateId = path.basename(cwd);
134
+ var mate = matesModule.getMate(mateCtx, thisMateId);
135
+ var mateName = (mate && mate.name) || null;
136
+ matesModule.promoteKnowledge(mateCtx, thisMateId, mateName, safeName);
137
+ sendTo(ws, { type: "knowledge_promoted", name: safeName });
138
+ sendTo(ws, { type: "knowledge_list", files: listKnowledgeFiles() });
139
+ return true;
140
+ }
141
+
142
+ if (msg.type === "knowledge_depromote") {
143
+ if (!isMate || !msg.name) return true;
144
+ var safeName = path.basename(msg.name);
145
+ var mateCtx = matesModule.buildMateCtx(getProjectOwnerId());
146
+ var thisMateId = path.basename(cwd);
147
+ matesModule.depromoteKnowledge(mateCtx, thisMateId, safeName);
148
+ sendTo(ws, { type: "knowledge_depromoted", name: safeName });
149
+ sendTo(ws, { type: "knowledge_list", files: listKnowledgeFiles() });
150
+ return true;
151
+ }
152
+
153
+ return false;
154
+ }
155
+
156
+ return {
157
+ handleKnowledgeMessage: handleKnowledgeMessage,
158
+ };
159
+ }
160
+
161
+ module.exports = { attachKnowledge: attachKnowledge };