@cryptiklemur/lattice 5.4.3 → 5.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/README.md +114 -0
  2. package/dist/client/assets/{angular-html-BMCIs4B9.js → angular-html-B_LrO4d_.js} +1 -1
  3. package/dist/client/assets/{angular-ts-Dl9U67Xi.js → angular-ts-DG2-AEn3.js} +1 -1
  4. package/dist/client/assets/{apl-Dc2Jp1EP.js → apl-D9AV-2KJ.js} +1 -1
  5. package/dist/client/assets/{astro-KOvSEh6g.js → astro-D55z-bYO.js} +1 -1
  6. package/dist/client/assets/{blade-Bp9mbJ5K.js → blade-Bs-tcMis.js} +1 -1
  7. package/dist/client/assets/{c-C0ETB1bm.js → c-CAs-oCwZ.js} +1 -1
  8. package/dist/client/assets/{cobol-DE5VmQJX.js → cobol-0B2WKsi0.js} +1 -1
  9. package/dist/client/assets/{coffee-COzJhfRU.js → coffee-BLi0y75x.js} +1 -1
  10. package/dist/client/assets/{cpp-jzhs79Ii.js → cpp-CTXpwK-o.js} +1 -1
  11. package/dist/client/assets/{crystal-Czf3Etrk.js → crystal-B_OPrNIv.js} +1 -1
  12. package/dist/client/assets/{css-Cf45eU4M.js → css-C9nlz4Z2.js} +1 -1
  13. package/dist/client/assets/{dist-Bk-EVR9B.js → dist-CzUlrFrL.js} +2 -2
  14. package/dist/client/assets/{edge-BmKSsYuX.js → edge-DE3SfmPJ.js} +1 -1
  15. package/dist/client/assets/{elixir-Ds7FEpTU.js → elixir-BS_1fIPf.js} +1 -1
  16. package/dist/client/assets/{elm-C_AmF7eK.js → elm-CrfkxZpb.js} +1 -1
  17. package/dist/client/assets/{erb-DevlC2ei.js → erb-B6Ab2Y07.js} +1 -1
  18. package/dist/client/assets/{git-rebase-CpJCeeHA.js → git-rebase-RkfMNUtm.js} +1 -1
  19. package/dist/client/assets/{glimmer-js-CFH-Kzxa.js → glimmer-js-CBA96B3V.js} +1 -1
  20. package/dist/client/assets/{glimmer-ts-BkiQ1tjj.js → glimmer-ts-WPYNrOak.js} +1 -1
  21. package/dist/client/assets/{glsl-DLuvkGQM.js → glsl-BVPbkPwC.js} +1 -1
  22. package/dist/client/assets/{graphql-CYjDXA-l.js → graphql-CplPyf-J.js} +1 -1
  23. package/dist/client/assets/{hack-CDfrJ_Sc.js → hack-Bqnr1D44.js} +1 -1
  24. package/dist/client/assets/{haml-C4YjrCNF.js → haml-Bk9H5oqC.js} +1 -1
  25. package/dist/client/assets/{handlebars-WIT13u08.js → handlebars-Dho-7JSJ.js} +1 -1
  26. package/dist/client/assets/{html-ntqTOVvn.js → html-Dx1dAJ5K.js} +1 -1
  27. package/dist/client/assets/{html-derivative-B3rc20Rp.js → html-derivative-DMEkbacz.js} +1 -1
  28. package/dist/client/assets/{http-jTU2ko3B.js → http-yzZRblhh.js} +1 -1
  29. package/dist/client/assets/{hurl-D2HM7b2B.js → hurl-DwA-puXE.js} +1 -1
  30. package/dist/client/assets/{index-CZNra7P3.js → index-BV9isded.js} +321 -88
  31. package/dist/client/assets/{index-DCKRhnC1.css → index-DcwXjiH0.css} +1 -1
  32. package/dist/client/assets/{java-BofMW2-d.js → java-da4cNL_S.js} +1 -1
  33. package/dist/client/assets/{javascript-DHlF2jGd.js → javascript-BRBtsTGZ.js} +1 -1
  34. package/dist/client/assets/{jinja-BXkUuA_p.js → jinja-KYkTqvLF.js} +1 -1
  35. package/dist/client/assets/{jison-CYSiwIU-.js → jison-CpJZ2qu3.js} +1 -1
  36. package/dist/client/assets/{json-5cpQWmWq.js → json-1ZKXsOcj.js} +1 -1
  37. package/dist/client/assets/{jsx-D8kfeKVb.js → jsx-Ci3IutCK.js} +1 -1
  38. package/dist/client/assets/{julia-BkW_r2Xd.js → julia-8BIB33ff.js} +1 -1
  39. package/dist/client/assets/{just-Dz61xACw.js → just-BlxIBqAl.js} +1 -1
  40. package/dist/client/assets/{latex-gqtZNh_M.js → latex-Dt6aIDIN.js} +1 -1
  41. package/dist/client/assets/{liquid-KcpeevnQ.js → liquid-rNU0f2fO.js} +1 -1
  42. package/dist/client/assets/{lua-C3Vw3Kyi.js → lua-9O59duWh.js} +1 -1
  43. package/dist/client/assets/{marko-DVaxNS7b.js → marko-cSK6Q4M9.js} +1 -1
  44. package/dist/client/assets/{mdc-DRm1pTBY.js → mdc-DD0DONjK.js} +1 -1
  45. package/dist/client/assets/{nginx-DYudX1m0.js → nginx-Bfzt7qPl.js} +1 -1
  46. package/dist/client/assets/{nim-DejMEf9z.js → nim-DOD72OhS.js} +1 -1
  47. package/dist/client/assets/{perl-D09qXnjW.js → perl-1cg42R6O.js} +1 -1
  48. package/dist/client/assets/{php-BrfQzrDQ.js → php-DfG0eJE0.js} +1 -1
  49. package/dist/client/assets/{pug-DPbk8XD2.js → pug-BJ4ktrjh.js} +1 -1
  50. package/dist/client/assets/{qml-BCh8MHc-.js → qml-BwGhenQ4.js} +1 -1
  51. package/dist/client/assets/{r-DCcyjvCV.js → r-DSKy_gZ9.js} +1 -1
  52. package/dist/client/assets/{razor-CHeljxcn.js → razor-morGoSMw.js} +1 -1
  53. package/dist/client/assets/{regexp-DpLv_uhV.js → regexp-DSQzd7Gw.js} +1 -1
  54. package/dist/client/assets/{rst-BmAGX4li.js → rst-CQ4DB61w.js} +1 -1
  55. package/dist/client/assets/{ruby-C28IDakj.js → ruby-dcUoNsdX.js} +1 -1
  56. package/dist/client/assets/{sas-Dq78Tt13.js → sas-BckOIwmR.js} +1 -1
  57. package/dist/client/assets/{scss-CCq8md_z.js → scss-CIDQomQQ.js} +1 -1
  58. package/dist/client/assets/{shellscript-Cl_slHLv.js → shellscript-DscXCB_p.js} +1 -1
  59. package/dist/client/assets/{shellsession-D50FTgCo.js → shellsession-C9EjZURj.js} +1 -1
  60. package/dist/client/assets/{soy-Da12pRro.js → soy-CVzyKHIE.js} +1 -1
  61. package/dist/client/assets/{sql-DRKhJcrs.js → sql-Ddk-vRG3.js} +1 -1
  62. package/dist/client/assets/{stata-E06HAz87.js → stata-BGl5cxb8.js} +1 -1
  63. package/dist/client/assets/{surrealql-CTaVrdiv.js → surrealql-3IZgL_Ze.js} +1 -1
  64. package/dist/client/assets/{svelte-j0H5d0BS.js → svelte-BRM0niF5.js} +1 -1
  65. package/dist/client/assets/{templ-BXfOHYWo.js → templ-qbgb30u-.js} +1 -1
  66. package/dist/client/assets/{tex-CGBWhBxV.js → tex-Ct_-hpJo.js} +1 -1
  67. package/dist/client/assets/{ts-tags-C-diQc0f.js → ts-tags-C85B12Rc.js} +1 -1
  68. package/dist/client/assets/{tsx-CUBPow91.js → tsx-d401eVG9.js} +1 -1
  69. package/dist/client/assets/{twig-BYoQufij.js → twig-CEvQ_MDP.js} +1 -1
  70. package/dist/client/assets/{typescript-CAiUPCi4.js → typescript-y2NNcyKH.js} +1 -1
  71. package/dist/client/assets/{vue-CEKqQRtx.js → vue-CiTZbygT.js} +1 -1
  72. package/dist/client/assets/{vue-html-DSRnbzbs.js → vue-html-B4nYUWKJ.js} +1 -1
  73. package/dist/client/assets/{vue-vine-PFaCZbOl.js → vue-vine-DdfbYtGV.js} +1 -1
  74. package/dist/client/assets/{xml-CpFeNxWK.js → xml-1B2HtnyO.js} +1 -1
  75. package/dist/client/assets/{xsl-DOlnqqgg.js → xsl-DoRYSDxo.js} +1 -1
  76. package/dist/client/assets/{yaml-Bv-3IQB_.js → yaml-DpbRFfH9.js} +1 -1
  77. package/dist/client/index.html +2 -2
  78. package/dist/client/sw.js +1 -1
  79. package/dist/server/daemon.js +75 -26
  80. package/dist/server/features/brainstorm.js +267 -0
  81. package/dist/server/handlers/attachment.js +66 -29
  82. package/dist/server/handlers/brainstorm.js +33 -0
  83. package/dist/server/handlers/fs.js +61 -72
  84. package/dist/server/handlers/memory.js +37 -52
  85. package/dist/server/handlers/mesh.js +4 -1
  86. package/dist/server/handlers/session.js +47 -58
  87. package/dist/server/mesh/connector.js +85 -1
  88. package/dist/server/project/file-browser.js +14 -14
  89. package/dist/server/project/session.js +98 -14
  90. package/dist/server/ws/broadcast.js +60 -0
  91. package/package.json +1 -1
@@ -0,0 +1,267 @@
1
+ import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, statSync, watch } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { loadConfig } from "../config.js";
4
+ import { broadcast } from "../ws/broadcast.js";
5
+ import { log } from "../logger.js";
6
+ var activeBrainstorms = {};
7
+ var watchers = [];
8
+ var debounceTimers = {};
9
+ function debounce(key, fn, ms) {
10
+ if (debounceTimers[key]) {
11
+ clearTimeout(debounceTimers[key]);
12
+ }
13
+ debounceTimers[key] = setTimeout(function () {
14
+ delete debounceTimers[key];
15
+ fn();
16
+ }, ms);
17
+ }
18
+ function getMostRecentSessionDir(brainstormDir) {
19
+ if (!existsSync(brainstormDir)) {
20
+ return null;
21
+ }
22
+ var entries = [];
23
+ try {
24
+ var names = readdirSync(brainstormDir);
25
+ for (var i = 0; i < names.length; i++) {
26
+ var fullPath = join(brainstormDir, names[i]);
27
+ try {
28
+ var stat = statSync(fullPath);
29
+ if (stat.isDirectory()) {
30
+ entries.push({ name: names[i], mtime: stat.mtimeMs });
31
+ }
32
+ }
33
+ catch {
34
+ // skip
35
+ }
36
+ }
37
+ }
38
+ catch {
39
+ return null;
40
+ }
41
+ if (entries.length === 0) {
42
+ return null;
43
+ }
44
+ entries.sort(function (a, b) {
45
+ return b.mtime - a.mtime;
46
+ });
47
+ return join(brainstormDir, entries[0].name);
48
+ }
49
+ function isWaitingFile(filename) {
50
+ return filename === "waiting.html" || filename.startsWith("waiting");
51
+ }
52
+ function handleContentFile(projectSlug, contentDir, sessionDir, filename) {
53
+ if (!filename.endsWith(".html")) {
54
+ return;
55
+ }
56
+ if (isWaitingFile(filename)) {
57
+ log.server("[brainstorm] waiting file detected for project %s", projectSlug);
58
+ delete activeBrainstorms[projectSlug];
59
+ broadcast({ type: "brainstorm:cleared" });
60
+ return;
61
+ }
62
+ var filePath = join(contentDir, filename);
63
+ if (!existsSync(filePath)) {
64
+ return;
65
+ }
66
+ var html;
67
+ try {
68
+ html = readFileSync(filePath, "utf-8");
69
+ }
70
+ catch (err) {
71
+ log.server("[brainstorm] failed to read %s: %s", filePath, err);
72
+ return;
73
+ }
74
+ log.server("[brainstorm] broadcasting content %s for project %s", filename, projectSlug);
75
+ activeBrainstorms[projectSlug] = { html, filename, sessionDir };
76
+ broadcast({ type: "brainstorm:content", html, filename, sessionDir });
77
+ }
78
+ function handleStateFile(projectSlug, filename) {
79
+ if (filename === "server-stopped") {
80
+ log.server("[brainstorm] server-stopped detected for project %s", projectSlug);
81
+ delete activeBrainstorms[projectSlug];
82
+ broadcast({ type: "brainstorm:cleared" });
83
+ }
84
+ }
85
+ function watchSessionDir(projectSlug, sessionDir) {
86
+ var contentDir = join(sessionDir, "content");
87
+ var stateDir = join(sessionDir, "state");
88
+ if (!existsSync(contentDir)) {
89
+ mkdirSync(contentDir, { recursive: true });
90
+ }
91
+ if (!existsSync(stateDir)) {
92
+ mkdirSync(stateDir, { recursive: true });
93
+ }
94
+ var existingFiles = [];
95
+ try {
96
+ existingFiles = readdirSync(contentDir);
97
+ }
98
+ catch {
99
+ // ok
100
+ }
101
+ for (var i = 0; i < existingFiles.length; i++) {
102
+ var fname = existingFiles[i];
103
+ if (fname.endsWith(".html") && !isWaitingFile(fname)) {
104
+ handleContentFile(projectSlug, contentDir, sessionDir, fname);
105
+ }
106
+ }
107
+ try {
108
+ var contentWatcher = watch(contentDir, function (eventType, filename) {
109
+ if (!filename) {
110
+ return;
111
+ }
112
+ debounce("content:" + sessionDir + ":" + filename, function () {
113
+ handleContentFile(projectSlug, contentDir, sessionDir, filename);
114
+ }, 50);
115
+ });
116
+ watchers.push(contentWatcher);
117
+ }
118
+ catch (err) {
119
+ log.server("[brainstorm] failed to watch content dir %s: %s", contentDir, err);
120
+ }
121
+ try {
122
+ var stateWatcher = watch(stateDir, function (eventType, filename) {
123
+ if (!filename) {
124
+ return;
125
+ }
126
+ debounce("state:" + sessionDir + ":" + filename, function () {
127
+ handleStateFile(projectSlug, filename);
128
+ }, 50);
129
+ });
130
+ watchers.push(stateWatcher);
131
+ }
132
+ catch (err) {
133
+ log.server("[brainstorm] failed to watch state dir %s: %s", stateDir, err);
134
+ }
135
+ }
136
+ function watchBrainstormDir(projectSlug, brainstormDir) {
137
+ var recentSession = getMostRecentSessionDir(brainstormDir);
138
+ if (recentSession) {
139
+ watchSessionDir(projectSlug, recentSession);
140
+ }
141
+ try {
142
+ var dirWatcher = watch(brainstormDir, function (eventType, filename) {
143
+ if (!filename) {
144
+ return;
145
+ }
146
+ debounce("brainstorm:" + brainstormDir + ":" + filename, function () {
147
+ var candidate = join(brainstormDir, filename);
148
+ try {
149
+ var stat = statSync(candidate);
150
+ if (stat.isDirectory()) {
151
+ log.server("[brainstorm] new session dir detected: %s", candidate);
152
+ watchSessionDir(projectSlug, candidate);
153
+ }
154
+ }
155
+ catch {
156
+ // entry may have been removed
157
+ }
158
+ }, 50);
159
+ });
160
+ watchers.push(dirWatcher);
161
+ }
162
+ catch (err) {
163
+ log.server("[brainstorm] failed to watch brainstorm dir %s: %s", brainstormDir, err);
164
+ }
165
+ }
166
+ function watchProjectRoot(projectSlug, projectPath) {
167
+ var superpowersDir = join(projectPath, ".superpowers");
168
+ var brainstormDir = join(superpowersDir, "brainstorm");
169
+ if (existsSync(brainstormDir)) {
170
+ watchBrainstormDir(projectSlug, brainstormDir);
171
+ return;
172
+ }
173
+ if (existsSync(superpowersDir)) {
174
+ watchForBrainstormDir(projectSlug, superpowersDir, brainstormDir);
175
+ return;
176
+ }
177
+ try {
178
+ var rootWatcher = watch(projectPath, function (eventType, filename) {
179
+ if (filename !== ".superpowers") {
180
+ return;
181
+ }
182
+ if (existsSync(superpowersDir)) {
183
+ rootWatcher.close();
184
+ watchForBrainstormDir(projectSlug, superpowersDir, brainstormDir);
185
+ }
186
+ });
187
+ watchers.push(rootWatcher);
188
+ }
189
+ catch (err) {
190
+ log.server("[brainstorm] failed to watch project root %s: %s", projectPath, err);
191
+ }
192
+ }
193
+ function watchForBrainstormDir(projectSlug, superpowersDir, brainstormDir) {
194
+ if (existsSync(brainstormDir)) {
195
+ watchBrainstormDir(projectSlug, brainstormDir);
196
+ return;
197
+ }
198
+ try {
199
+ var spWatcher = watch(superpowersDir, function (eventType, filename) {
200
+ if (filename !== "brainstorm") {
201
+ return;
202
+ }
203
+ if (existsSync(brainstormDir)) {
204
+ spWatcher.close();
205
+ watchBrainstormDir(projectSlug, brainstormDir);
206
+ }
207
+ });
208
+ watchers.push(spWatcher);
209
+ }
210
+ catch (err) {
211
+ log.server("[brainstorm] failed to watch .superpowers dir %s: %s", superpowersDir, err);
212
+ }
213
+ }
214
+ export function startBrainstormWatchers() {
215
+ var config = loadConfig();
216
+ var projects = config.projects;
217
+ for (var i = 0; i < projects.length; i++) {
218
+ var project = projects[i];
219
+ if (!project.path || !project.slug) {
220
+ continue;
221
+ }
222
+ if (!existsSync(project.path)) {
223
+ continue;
224
+ }
225
+ log.server("[brainstorm] setting up watcher for project %s at %s", project.slug, project.path);
226
+ watchProjectRoot(project.slug, project.path);
227
+ }
228
+ }
229
+ export function stopBrainstormWatchers() {
230
+ for (var i = 0; i < watchers.length; i++) {
231
+ try {
232
+ watchers[i].close();
233
+ }
234
+ catch {
235
+ // ignore
236
+ }
237
+ }
238
+ watchers = [];
239
+ activeBrainstorms = {};
240
+ var keys = Object.keys(debounceTimers);
241
+ for (var i = 0; i < keys.length; i++) {
242
+ clearTimeout(debounceTimers[keys[i]]);
243
+ }
244
+ debounceTimers = {};
245
+ }
246
+ export function getActiveBrainstorm(projectSlug) {
247
+ return activeBrainstorms[projectSlug] || null;
248
+ }
249
+ export function getAnyActiveBrainstorm() {
250
+ var keys = Object.keys(activeBrainstorms);
251
+ if (keys.length === 0)
252
+ return null;
253
+ return activeBrainstorms[keys[0]];
254
+ }
255
+ export function writeBrainstormEvent(sessionDir, event) {
256
+ var stateDir = join(sessionDir, "state");
257
+ if (!existsSync(stateDir)) {
258
+ mkdirSync(stateDir, { recursive: true });
259
+ }
260
+ var eventsFile = join(stateDir, "events");
261
+ try {
262
+ appendFileSync(eventsFile, JSON.stringify(event) + "\n", "utf-8");
263
+ }
264
+ catch (err) {
265
+ log.server("[brainstorm] failed to write event to %s: %s", eventsFile, err);
266
+ }
267
+ }
@@ -1,5 +1,10 @@
1
+ import { createWriteStream, createReadStream } from "node:fs";
2
+ import { unlink, mkdtemp } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { tmpdir } from "node:os";
1
5
  import { registerHandler } from "../ws/router.js";
2
6
  import { sendTo } from "../ws/broadcast.js";
7
+ import { log } from "../logger.js";
3
8
  var MAX_ATTACHMENT_SIZE = 10 * 1024 * 1024;
4
9
  var stores = new Map();
5
10
  var completed = new Map();
@@ -21,22 +26,35 @@ function getClientCompleted(clientId) {
21
26
  }
22
27
  return store;
23
28
  }
24
- registerHandler("attachment", function (clientId, message) {
29
+ async function cleanupPending(pending) {
30
+ try {
31
+ pending.writeStream.destroy();
32
+ await unlink(pending.tempPath).catch(function () { });
33
+ await unlink(pending.tempDir).catch(function () { });
34
+ }
35
+ catch { }
36
+ }
37
+ registerHandler("attachment", async function (clientId, message) {
25
38
  if (message.type === "attachment:chunk") {
26
39
  var msg = message;
27
40
  var store = getClientStore(clientId);
28
41
  var pending = store.get(msg.attachmentId);
29
42
  if (!pending) {
43
+ var tempDir = await mkdtemp(join(tmpdir(), "lattice-attach-"));
44
+ var tempPath = join(tempDir, "data.bin");
30
45
  pending = {
31
- chunks: new Map(),
46
+ tempDir: tempDir,
47
+ tempPath: tempPath,
48
+ writeStream: createWriteStream(tempPath),
32
49
  totalChunks: msg.totalChunks,
33
50
  receivedCount: 0,
34
51
  totalBytes: 0,
35
52
  createdAt: Date.now(),
53
+ chunksSeen: new Set(),
36
54
  };
37
55
  store.set(msg.attachmentId, pending);
38
56
  }
39
- if (pending.chunks.has(msg.chunkIndex)) {
57
+ if (pending.chunksSeen.has(msg.chunkIndex)) {
40
58
  sendTo(clientId, {
41
59
  type: "attachment:error",
42
60
  attachmentId: msg.attachmentId,
@@ -46,6 +64,7 @@ registerHandler("attachment", function (clientId, message) {
46
64
  }
47
65
  var chunkBuffer = Buffer.from(msg.data, "base64");
48
66
  if (pending.totalBytes + chunkBuffer.length > MAX_ATTACHMENT_SIZE) {
67
+ await cleanupPending(pending);
49
68
  store.delete(msg.attachmentId);
50
69
  sendTo(clientId, {
51
70
  type: "attachment:error",
@@ -54,7 +73,8 @@ registerHandler("attachment", function (clientId, message) {
54
73
  });
55
74
  return;
56
75
  }
57
- pending.chunks.set(msg.chunkIndex, chunkBuffer);
76
+ pending.writeStream.write(chunkBuffer);
77
+ pending.chunksSeen.add(msg.chunkIndex);
58
78
  pending.receivedCount++;
59
79
  pending.totalBytes += chunkBuffer.length;
60
80
  sendTo(clientId, {
@@ -85,36 +105,46 @@ registerHandler("attachment", function (clientId, message) {
85
105
  });
86
106
  return;
87
107
  }
88
- var buffers = [];
89
- for (var ci = 0; ci < completePending.totalChunks; ci++) {
90
- var chunk = completePending.chunks.get(ci);
91
- if (!chunk) {
92
- sendTo(clientId, {
93
- type: "attachment:error",
94
- attachmentId: completeMsg.attachmentId,
95
- error: "Missing chunk at index " + ci,
96
- });
97
- return;
98
- }
99
- buffers.push(chunk);
108
+ await new Promise(function (resolve) {
109
+ completePending.writeStream.end(function () { resolve(); });
110
+ });
111
+ try {
112
+ var assembled = await readTempFile(completePending.tempPath);
113
+ var isText = completeMsg.attachmentType === "paste" || isTextMimeType(completeMsg.mimeType);
114
+ var content = isText ? assembled.toString("utf-8") : assembled.toString("base64");
115
+ var attachment = {
116
+ type: completeMsg.attachmentType,
117
+ name: completeMsg.name,
118
+ content,
119
+ mimeType: completeMsg.mimeType,
120
+ size: completeMsg.size,
121
+ lineCount: completeMsg.lineCount,
122
+ };
123
+ var finishedStore = getClientCompleted(clientId);
124
+ finishedStore.set(completeMsg.attachmentId, attachment);
125
+ }
126
+ catch (err) {
127
+ log.ws("Failed to read assembled attachment: %O", err);
128
+ sendTo(clientId, {
129
+ type: "attachment:error",
130
+ attachmentId: completeMsg.attachmentId,
131
+ error: "Failed to assemble attachment",
132
+ });
100
133
  }
101
- var assembled = Buffer.concat(buffers);
102
- var isText = completeMsg.attachmentType === "paste" || isTextMimeType(completeMsg.mimeType);
103
- var content = isText ? assembled.toString("utf-8") : assembled.toString("base64");
104
- var attachment = {
105
- type: completeMsg.attachmentType,
106
- name: completeMsg.name,
107
- content,
108
- mimeType: completeMsg.mimeType,
109
- size: completeMsg.size,
110
- lineCount: completeMsg.lineCount,
111
- };
112
- var finishedStore = getClientCompleted(clientId);
113
- finishedStore.set(completeMsg.attachmentId, attachment);
134
+ await cleanupPending(completePending);
114
135
  completeStore.delete(completeMsg.attachmentId);
115
136
  return;
116
137
  }
117
138
  });
139
+ function readTempFile(path) {
140
+ return new Promise(function (resolve, reject) {
141
+ var chunks = [];
142
+ var stream = createReadStream(path);
143
+ stream.on("data", function (chunk) { chunks.push(chunk); });
144
+ stream.on("end", function () { resolve(Buffer.concat(chunks)); });
145
+ stream.on("error", reject);
146
+ });
147
+ }
118
148
  function isTextMimeType(mime) {
119
149
  if (mime.startsWith("text/"))
120
150
  return true;
@@ -142,6 +172,12 @@ export function getAttachments(clientId, ids) {
142
172
  return result;
143
173
  }
144
174
  export function cleanupClient(clientId) {
175
+ var clientStore = stores.get(clientId);
176
+ if (clientStore) {
177
+ for (var [, pending] of clientStore) {
178
+ void cleanupPending(pending);
179
+ }
180
+ }
145
181
  stores.delete(clientId);
146
182
  completed.delete(clientId);
147
183
  }
@@ -150,6 +186,7 @@ var ttlCleanupInterval = setInterval(function () {
150
186
  stores.forEach(function (store) {
151
187
  store.forEach(function (pending, id) {
152
188
  if (now - pending.createdAt > TTL_MS) {
189
+ void cleanupPending(pending);
153
190
  store.delete(id);
154
191
  }
155
192
  });
@@ -0,0 +1,33 @@
1
+ import { registerHandler } from "../ws/router.js";
2
+ import { sendTo } from "../ws/broadcast.js";
3
+ import { getActiveBrainstorm, getAnyActiveBrainstorm, writeBrainstormEvent } from "../features/brainstorm.js";
4
+ import { getActiveProjectForClient } from "./fs.js";
5
+ registerHandler("brainstorm", function (clientId, message) {
6
+ if (message.type === "brainstorm:select") {
7
+ var selectMsg = message;
8
+ writeBrainstormEvent(selectMsg.sessionDir, {
9
+ type: "click",
10
+ choice: selectMsg.choice,
11
+ text: selectMsg.text,
12
+ timestamp: Date.now(),
13
+ });
14
+ return;
15
+ }
16
+ if (message.type === "brainstorm:status_request") {
17
+ var projectSlug = getActiveProjectForClient(clientId);
18
+ var active = projectSlug ? getActiveBrainstorm(projectSlug) : getAnyActiveBrainstorm();
19
+ if (active) {
20
+ sendTo(clientId, {
21
+ type: "brainstorm:status",
22
+ active: true,
23
+ html: active.html,
24
+ filename: active.filename,
25
+ sessionDir: active.sessionDir,
26
+ });
27
+ }
28
+ else {
29
+ sendTo(clientId, { type: "brainstorm:status", active: false });
30
+ }
31
+ return;
32
+ }
33
+ });