crewly 1.20.327 → 1.20.330

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 (78) hide show
  1. package/config/skills/agent/core/publish-app/SKILL.md +9 -0
  2. package/config/skills/agent/core/reply-channel/SKILL.md +19 -0
  3. package/config/skills/agent/core/reply-channel/execute.sh +38 -14
  4. package/config/skills/agent/core/reply-channel/execute.test.sh +18 -0
  5. package/config/skills/agent/core/send-file/SKILL.md +5 -0
  6. package/config/skills/agent/core/slack-post/SKILL.md +1 -0
  7. package/config/skills/agent/core/slack-post/execute.sh +20 -4
  8. package/dist/backend/backend/src/constants.d.ts +35 -0
  9. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  10. package/dist/backend/backend/src/constants.js +42 -0
  11. package/dist/backend/backend/src/constants.js.map +1 -1
  12. package/dist/backend/backend/src/controllers/chat-v2/chat-v2.controller.d.ts.map +1 -1
  13. package/dist/backend/backend/src/controllers/chat-v2/chat-v2.controller.js +31 -1
  14. package/dist/backend/backend/src/controllers/chat-v2/chat-v2.controller.js.map +1 -1
  15. package/dist/backend/backend/src/controllers/slack/slack.controller.d.ts.map +1 -1
  16. package/dist/backend/backend/src/controllers/slack/slack.controller.js +16 -2
  17. package/dist/backend/backend/src/controllers/slack/slack.controller.js.map +1 -1
  18. package/dist/backend/backend/src/index.d.ts.map +1 -1
  19. package/dist/backend/backend/src/index.js +17 -0
  20. package/dist/backend/backend/src/index.js.map +1 -1
  21. package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
  22. package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js +4 -0
  23. package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
  24. package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts +7 -0
  25. package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts.map +1 -1
  26. package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.js +13 -2
  27. package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.js.map +1 -1
  28. package/dist/backend/backend/src/services/decisions/recurring-change-handoff.d.ts +10 -0
  29. package/dist/backend/backend/src/services/decisions/recurring-change-handoff.d.ts.map +1 -1
  30. package/dist/backend/backend/src/services/decisions/recurring-change-handoff.js +17 -2
  31. package/dist/backend/backend/src/services/decisions/recurring-change-handoff.js.map +1 -1
  32. package/dist/backend/backend/src/services/event-bus/event-to-workitem-bridge.service.d.ts +11 -0
  33. package/dist/backend/backend/src/services/event-bus/event-to-workitem-bridge.service.d.ts.map +1 -1
  34. package/dist/backend/backend/src/services/event-bus/event-to-workitem-bridge.service.js +41 -3
  35. package/dist/backend/backend/src/services/event-bus/event-to-workitem-bridge.service.js.map +1 -1
  36. package/dist/backend/backend/src/services/file-handoff/file-handoff.inbound.d.ts +35 -0
  37. package/dist/backend/backend/src/services/file-handoff/file-handoff.inbound.d.ts.map +1 -0
  38. package/dist/backend/backend/src/services/file-handoff/file-handoff.inbound.js +45 -0
  39. package/dist/backend/backend/src/services/file-handoff/file-handoff.inbound.js.map +1 -0
  40. package/dist/backend/backend/src/services/file-handoff/file-handoff.outbound.d.ts +44 -0
  41. package/dist/backend/backend/src/services/file-handoff/file-handoff.outbound.d.ts.map +1 -0
  42. package/dist/backend/backend/src/services/file-handoff/file-handoff.outbound.js +50 -0
  43. package/dist/backend/backend/src/services/file-handoff/file-handoff.outbound.js.map +1 -0
  44. package/dist/backend/backend/src/services/file-handoff/file-handoff.service.d.ts +224 -0
  45. package/dist/backend/backend/src/services/file-handoff/file-handoff.service.d.ts.map +1 -0
  46. package/dist/backend/backend/src/services/file-handoff/file-handoff.service.js +454 -0
  47. package/dist/backend/backend/src/services/file-handoff/file-handoff.service.js.map +1 -0
  48. package/dist/backend/backend/src/services/file-handoff/file-handoff.wiring.d.ts +42 -0
  49. package/dist/backend/backend/src/services/file-handoff/file-handoff.wiring.d.ts.map +1 -0
  50. package/dist/backend/backend/src/services/file-handoff/file-handoff.wiring.js +92 -0
  51. package/dist/backend/backend/src/services/file-handoff/file-handoff.wiring.js.map +1 -0
  52. package/dist/backend/backend/src/services/project-tickets/ticket-tracking.d.ts +14 -1
  53. package/dist/backend/backend/src/services/project-tickets/ticket-tracking.d.ts.map +1 -1
  54. package/dist/backend/backend/src/services/project-tickets/ticket-tracking.js +26 -2
  55. package/dist/backend/backend/src/services/project-tickets/ticket-tracking.js.map +1 -1
  56. package/dist/backend/backend/src/services/slack/slack.service.d.ts.map +1 -1
  57. package/dist/backend/backend/src/services/slack/slack.service.js +16 -6
  58. package/dist/backend/backend/src/services/slack/slack.service.js.map +1 -1
  59. package/dist/backend/build-info.json +2 -2
  60. package/dist/cli/backend/src/constants.d.ts +35 -0
  61. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  62. package/dist/cli/backend/src/constants.js +42 -0
  63. package/dist/cli/backend/src/constants.js.map +1 -1
  64. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
  65. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js +4 -0
  66. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
  67. package/dist/cli/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts +7 -0
  68. package/dist/cli/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts.map +1 -1
  69. package/dist/cli/backend/src/services/chat-v2/chat-v2.dispatcher.service.js +13 -2
  70. package/dist/cli/backend/src/services/chat-v2/chat-v2.dispatcher.service.js.map +1 -1
  71. package/dist/cli/backend/src/services/project-tickets/ticket-tracking.d.ts +14 -1
  72. package/dist/cli/backend/src/services/project-tickets/ticket-tracking.d.ts.map +1 -1
  73. package/dist/cli/backend/src/services/project-tickets/ticket-tracking.js +26 -2
  74. package/dist/cli/backend/src/services/project-tickets/ticket-tracking.js.map +1 -1
  75. package/dist/cli/backend/src/services/slack/slack.service.d.ts.map +1 -1
  76. package/dist/cli/backend/src/services/slack/slack.service.js +16 -6
  77. package/dist/cli/backend/src/services/slack/slack.service.js.map +1 -1
  78. package/package.json +1 -1
@@ -0,0 +1,454 @@
1
+ /**
2
+ * Automatic cross-machine file hand-off.
3
+ *
4
+ * Agents on different machines cannot open each other's local paths. This
5
+ * service moves the file through Cloud's file-handoff relay (the same one the
6
+ * `send-file` / `receive-file` skills use) so nobody has to paste an
7
+ * instruction by hand:
8
+ *
9
+ * - Sender: {@link FileHandoffService.attachToMessage} uploads the files an
10
+ * agent attached (`--file`) or mentioned by path, and appends one compact
11
+ * marker line per file to the message text.
12
+ * - Receiver: {@link FileHandoffService.expandInbound} finds the markers in
13
+ * a delivered message, downloads each file into the project inbox, and
14
+ * rewrites the marker to the local path.
15
+ *
16
+ * Marker format (readable in Slack): `📎 report.pdf (2.1 MB) · crewly-file:hf_xxx`.
17
+ *
18
+ * @module services/file-handoff/file-handoff.service
19
+ */
20
+ import * as fs from 'fs';
21
+ import * as http from 'http';
22
+ import * as https from 'https';
23
+ import * as os from 'os';
24
+ import * as path from 'path';
25
+ import { FILE_HANDOFF_CONSTANTS as C } from '../../constants.js';
26
+ import { redactSecretsWithCount } from '../../utils/secret-redactor.js';
27
+ // ---------------------------------------------------------------------------
28
+ // Pure helpers
29
+ // ---------------------------------------------------------------------------
30
+ const MARKER_ID_RE = new RegExp(`${escapeRe(C.MARKER_PREFIX)}(${C.ID_PATTERN})`, 'g');
31
+ const MARKER_TOKEN_RE = new RegExp(`(?:${escapeRe(C.MARKER_GLYPH)}[^\\n]*?·\\s*)?${escapeRe(C.MARKER_PREFIX)}(${C.ID_PATTERN})`, 'g');
32
+ const KIB = 1024;
33
+ const SIZE_UNITS = ['B', 'KB', 'MB', 'GB'];
34
+ /** Characters that end a path written in prose */
35
+ const PATH_TRAILING_PUNCT = /[.,;:!?)\]}>'"`]+$/;
36
+ /** Absolute or `./relative` paths in free text */
37
+ const PATH_IN_TEXT_RE = /(?:^|[\s"'`(<[])((?:\/|\.\/)[^\s"'`)>\]]+)/g;
38
+ function escapeRe(s) {
39
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
40
+ }
41
+ /**
42
+ * Human size, e.g. `2.1 MB`.
43
+ *
44
+ * @param bytes - Size in bytes
45
+ * @returns Short size string
46
+ */
47
+ export function formatSize(bytes) {
48
+ let value = bytes;
49
+ let unit = 0;
50
+ while (value >= KIB && unit < SIZE_UNITS.length - 1) {
51
+ value /= KIB;
52
+ unit += 1;
53
+ }
54
+ const text = unit === 0 || value >= 10 ? String(Math.round(value)) : value.toFixed(1);
55
+ return `${text} ${SIZE_UNITS[unit]}`;
56
+ }
57
+ /**
58
+ * The marker line for one uploaded file.
59
+ *
60
+ * @param fileName - Name the receiver sees
61
+ * @param sizeBytes - File size
62
+ * @param handoffId - Cloud handoff id
63
+ * @returns `📎 report.pdf (2.1 MB) · crewly-file:hf_xxx`
64
+ */
65
+ export function formatMarker(fileName, sizeBytes, handoffId) {
66
+ const safeName = fileName.replace(/[\r\n]+/g, ' ').trim() || 'file';
67
+ return `${C.MARKER_GLYPH} ${safeName} (${formatSize(sizeBytes)}) · ${C.MARKER_PREFIX}${handoffId}`;
68
+ }
69
+ /**
70
+ * Handoff ids named by markers in a text, first occurrence order, no repeats.
71
+ *
72
+ * @param text - Message text
73
+ * @returns Handoff ids
74
+ */
75
+ export function findMarkerIds(text) {
76
+ const ids = [];
77
+ for (const m of text.matchAll(MARKER_ID_RE)) {
78
+ if (!ids.includes(m[1]))
79
+ ids.push(m[1]);
80
+ }
81
+ return ids;
82
+ }
83
+ /**
84
+ * Absolute (`/x/y`) and `./relative` paths written in a text.
85
+ *
86
+ * @param text - Message text
87
+ * @returns Candidate paths in order, without duplicates
88
+ */
89
+ export function extractPathCandidates(text) {
90
+ const found = [];
91
+ for (const m of text.matchAll(PATH_IN_TEXT_RE)) {
92
+ const p = m[1].replace(PATH_TRAILING_PUNCT, '');
93
+ if (p.length > 2 && !found.includes(p))
94
+ found.push(p);
95
+ }
96
+ return found;
97
+ }
98
+ /**
99
+ * Whether a file name or path is one that must never leave the machine
100
+ * (`.env*`, keys, credentials, anything under `.ssh` / `.aws` / `.git`).
101
+ *
102
+ * @param file - File path
103
+ * @returns True for an obvious secret
104
+ */
105
+ export function isSecretPath(file) {
106
+ const parts = file.split(path.sep).filter(Boolean).map((s) => s.toLowerCase());
107
+ const name = parts[parts.length - 1] ?? '';
108
+ if (C.SECRET_NAME_PATTERNS.some((re) => re.test(name)))
109
+ return true;
110
+ return parts.slice(0, -1).some((d) => C.SECRET_DIR_NAMES.includes(d));
111
+ }
112
+ function within(child, parent) {
113
+ return child === parent || child.startsWith(parent.endsWith(path.sep) ? parent : parent + path.sep);
114
+ }
115
+ function real(p) {
116
+ try {
117
+ return fs.realpathSync(p);
118
+ }
119
+ catch {
120
+ return null;
121
+ }
122
+ }
123
+ /** A safe local file name (last segment, no leading dots or control characters). */
124
+ function safeName(name) {
125
+ // eslint-disable-next-line no-control-regex
126
+ const base = name.replace(/\\/g, '/').split('/').pop().replace(/[\u0000-\u001f]/g, '').replace(/^[.\s]+/, '').trim();
127
+ return base || 'file';
128
+ }
129
+ // ---------------------------------------------------------------------------
130
+ // Network (streamed from / to disk, never buffered)
131
+ // ---------------------------------------------------------------------------
132
+ /** Default {@link PutFileFn}: a streamed `PUT` with an exact Content-Length. */
133
+ export const putFileStreamed = (target, file, sizeBytes, timeoutMs) => new Promise((resolve, reject) => {
134
+ const url = new URL(target.url);
135
+ const lib = url.protocol === 'http:' ? http : https;
136
+ const req = lib.request(url, { method: 'PUT', headers: { ...target.headers, 'Content-Length': String(sizeBytes) }, timeout: timeoutMs }, (res) => {
137
+ res.resume();
138
+ res.on('end', () => (res.statusCode === 200 ? resolve() : reject(new Error(`upload failed (HTTP ${res.statusCode ?? 0})`))));
139
+ });
140
+ req.on('timeout', () => req.destroy(new Error('upload timed out')));
141
+ req.on('error', reject);
142
+ const src = fs.createReadStream(file);
143
+ src.on('error', (e) => req.destroy(e));
144
+ src.pipe(req);
145
+ });
146
+ /** Default {@link GetFileFn}: a streamed `GET` written to `dest`. */
147
+ export const getFileStreamed = (url, dest, timeoutMs) => new Promise((resolve, reject) => {
148
+ const target = new URL(url);
149
+ const lib = target.protocol === 'http:' ? http : https;
150
+ const req = lib.get(target, { timeout: timeoutMs }, (res) => {
151
+ if (res.statusCode !== 200) {
152
+ res.resume();
153
+ reject(new Error(`download failed (HTTP ${res.statusCode ?? 0})`));
154
+ return;
155
+ }
156
+ const out = fs.createWriteStream(dest);
157
+ res.pipe(out);
158
+ out.on('finish', () => out.close(() => resolve()));
159
+ out.on('error', reject);
160
+ res.on('error', reject);
161
+ });
162
+ req.on('timeout', () => req.destroy(new Error('download timed out')));
163
+ req.on('error', reject);
164
+ });
165
+ // ---------------------------------------------------------------------------
166
+ // Service
167
+ // ---------------------------------------------------------------------------
168
+ /**
169
+ * Uploads files for outgoing messages and downloads them for incoming ones.
170
+ */
171
+ export class FileHandoffService {
172
+ deps;
173
+ putFile;
174
+ getFile;
175
+ constructor(deps) {
176
+ this.deps = deps;
177
+ this.putFile = deps.putFile ?? putFileStreamed;
178
+ this.getFile = deps.getFile ?? getFileStreamed;
179
+ }
180
+ log(level, message, meta) {
181
+ this.deps.log?.(level, message, meta);
182
+ }
183
+ homes() {
184
+ const configured = this.deps.crewlyHomes?.() ?? [path.join(os.homedir(), '.crewly')];
185
+ return configured.map((h) => real(h) ?? path.resolve(h));
186
+ }
187
+ /**
188
+ * Check a candidate file against every guard.
189
+ *
190
+ * Refuses: missing, symlink, not a regular file, empty, over the cap, outside
191
+ * the project, inside Crewly's home, an obvious secret name, or text that
192
+ * the secret redactor flags.
193
+ *
194
+ * @param candidate - Path as written
195
+ * @param projectDir - The agent's project directory
196
+ * @returns The real path and size, or the reason
197
+ */
198
+ vetFile(candidate, projectDir) {
199
+ const abs = path.resolve(projectDir, candidate);
200
+ let st;
201
+ try {
202
+ st = fs.lstatSync(abs);
203
+ }
204
+ catch {
205
+ return { ok: false, reason: 'file not found' };
206
+ }
207
+ if (st.isSymbolicLink())
208
+ return { ok: false, reason: 'is a symbolic link; pass the real file' };
209
+ if (!st.isFile())
210
+ return { ok: false, reason: 'is not a regular file' };
211
+ if (st.size === 0)
212
+ return { ok: false, reason: 'is empty' };
213
+ if (st.size > C.MAX_FILE_BYTES)
214
+ return { ok: false, reason: `is larger than the ${formatSize(C.MAX_FILE_BYTES)} limit` };
215
+ const r = real(abs);
216
+ const project = real(projectDir);
217
+ if (!r || !project)
218
+ return { ok: false, reason: 'cannot resolve the project directory' };
219
+ if (!within(r, project))
220
+ return { ok: false, reason: 'is outside your project directory; copy it there first' };
221
+ if (this.homes().some((h) => within(r, h)))
222
+ return { ok: false, reason: "is inside Crewly's home directory" };
223
+ // Check the path below the project root, so a project that lives under e.g. node_modules still works.
224
+ if (isSecretPath(path.relative(project, r)))
225
+ return { ok: false, reason: 'looks like a secret (credentials or key file) and is never sent' };
226
+ if (st.size <= C.SECRET_SCAN_MAX_BYTES && looksLikeText(r)) {
227
+ try {
228
+ if (redactSecretsWithCount(fs.readFileSync(r, 'utf8')).count > 0) {
229
+ return { ok: false, reason: 'contains what looks like a secret and is never sent' };
230
+ }
231
+ }
232
+ catch {
233
+ return { ok: false, reason: 'could not be read' };
234
+ }
235
+ }
236
+ return { ok: true, real: r, size: st.size };
237
+ }
238
+ /**
239
+ * Upload one vetted file as a handoff.
240
+ *
241
+ * @param file - Real path
242
+ * @param sizeBytes - Size
243
+ * @param agent - Agent session to attribute the upload to
244
+ * @returns The handoff
245
+ * @throws When Cloud or the storage upload fails (the half-made handoff is withdrawn)
246
+ */
247
+ async uploadFile(file, sizeBytes, agent) {
248
+ const api = this.deps.api();
249
+ const attr = agent ? { agent } : { asOwner: true };
250
+ const fileName = safeName(path.basename(file));
251
+ const created = await api.request('POST', '/handoffs', {
252
+ body: { fileName, sizeBytes, contentType: C.DEFAULT_CONTENT_TYPE },
253
+ ...attr,
254
+ });
255
+ const id = created.handoffId;
256
+ const url = created.upload?.url;
257
+ if (!id || !url)
258
+ throw new Error('Cloud answered without an upload URL');
259
+ try {
260
+ await this.putFile({ url, headers: created.upload?.headers ?? {} }, file, sizeBytes, C.UPLOAD_TIMEOUT_MS);
261
+ await api.request('POST', `/handoffs/${id}/complete`, attr);
262
+ }
263
+ catch (err) {
264
+ await api.request('DELETE', `/handoffs/${id}`, attr).catch(() => undefined);
265
+ throw err;
266
+ }
267
+ return { fileName, sizeBytes, handoffId: id };
268
+ }
269
+ /**
270
+ * Upload the files of one outgoing message and append their marker lines.
271
+ * Never throws: a file that cannot go is reported in `failed` and the text
272
+ * is still returned so the message can be posted anyway.
273
+ *
274
+ * @param input - Text, explicit files, auto-detect switch, project dir
275
+ * @returns New text plus what went and what did not
276
+ */
277
+ async attachToMessage(input) {
278
+ const attached = [];
279
+ const failed = [];
280
+ const tried = new Set();
281
+ const attempt = async (candidate, explicit) => {
282
+ const key = path.resolve(input.projectDir, candidate);
283
+ if (tried.has(key))
284
+ return;
285
+ tried.add(key);
286
+ if (attached.length >= C.MAX_FILES_PER_MESSAGE) {
287
+ if (explicit)
288
+ failed.push({ file: candidate, reason: `more than ${C.MAX_FILES_PER_MESSAGE} files in one message` });
289
+ return;
290
+ }
291
+ const vetted = this.vetFile(candidate, input.projectDir);
292
+ if (!vetted.ok) {
293
+ // A path merely mentioned in prose that is not a sendable file is not an error.
294
+ if (explicit)
295
+ failed.push({ file: candidate, reason: vetted.reason });
296
+ return;
297
+ }
298
+ try {
299
+ attached.push(await this.uploadFile(vetted.real, vetted.size, input.agent));
300
+ }
301
+ catch (err) {
302
+ const reason = err instanceof Error ? err.message : String(err);
303
+ this.log('warn', 'File handoff upload failed', { file: path.basename(candidate), reason });
304
+ failed.push({ file: candidate, reason });
305
+ }
306
+ };
307
+ for (const f of input.explicit ?? [])
308
+ await attempt(f, true);
309
+ if (input.auto) {
310
+ for (const f of extractPathCandidates(input.content)) {
311
+ if (attached.length >= C.MAX_FILES_PER_MESSAGE)
312
+ break;
313
+ await attempt(f, false);
314
+ }
315
+ }
316
+ const lines = attached.map((a) => formatMarker(a.fileName, a.sizeBytes, a.handoffId));
317
+ const content = lines.length > 0 ? `${input.content.replace(/\s+$/, '')}\n${lines.join('\n')}` : input.content;
318
+ return { content, attached, failed };
319
+ }
320
+ /**
321
+ * Download one handoff into `<inboxRoot>/<handoffId>/<fileName>`.
322
+ *
323
+ * Not acknowledged: a room message reaches every machine (and every agent)
324
+ * in the room, and an ack deletes the Cloud copy, so the first receiver
325
+ * would take it from all the others. Cloud removes it after 24 h. A file
326
+ * already in this inbox is reused, and two local agents fetching the same
327
+ * one share a single download.
328
+ *
329
+ * @param handoffId - Id from a marker
330
+ * @param inboxRoot - The inbox folder (`<project data dir>/inbox`)
331
+ * @param agent - Receiving agent session
332
+ * @param timeoutMs - Ceiling for the transfer
333
+ * @returns Where the file is now
334
+ * @throws When the handoff is not ready, the transfer fails, or the size differs
335
+ */
336
+ async downloadToInbox(handoffId, inboxRoot, agent, timeoutMs = C.DOWNLOAD_TIMEOUT_MS) {
337
+ const key = path.join(inboxRoot, handoffId);
338
+ const existing = this.inboxFileIn(key);
339
+ if (existing)
340
+ return existing;
341
+ const running = this.inFlight.get(key);
342
+ if (running)
343
+ return running;
344
+ const job = this.fetchToInbox(handoffId, inboxRoot, agent, timeoutMs).finally(() => this.inFlight.delete(key));
345
+ this.inFlight.set(key, job);
346
+ return job;
347
+ }
348
+ /** Downloads in progress, by `<inboxRoot>/<handoffId>`. */
349
+ inFlight = new Map();
350
+ /**
351
+ * The finished file in a handoff's inbox folder, if there is one.
352
+ *
353
+ * @param dir - `<inboxRoot>/<handoffId>`
354
+ * @returns The file, or null
355
+ */
356
+ inboxFileIn(dir) {
357
+ try {
358
+ const name = fs.readdirSync(dir).find((n) => !n.startsWith('.'));
359
+ if (!name)
360
+ return null;
361
+ const dest = path.join(dir, name);
362
+ return { path: dest, fileName: name, sizeBytes: fs.statSync(dest).size };
363
+ }
364
+ catch {
365
+ return null;
366
+ }
367
+ }
368
+ /**
369
+ * The transfer itself, for {@link downloadToInbox}.
370
+ *
371
+ * @param handoffId - Id from a marker
372
+ * @param inboxRoot - The inbox folder
373
+ * @param agent - Receiving agent session
374
+ * @param timeoutMs - Ceiling for the transfer
375
+ * @returns Where the file is now
376
+ */
377
+ async fetchToInbox(handoffId, inboxRoot, agent, timeoutMs) {
378
+ const api = this.deps.api();
379
+ const attr = agent ? { agent } : { asOwner: true };
380
+ const meta = await api.request('GET', `/handoffs/${handoffId}`, { ...attr, timeoutMs });
381
+ if (meta.status !== 'ready' || !meta.download?.url)
382
+ throw new Error('the sender has not finished uploading');
383
+ const size = Number(meta.sizeBytes);
384
+ const name = safeName(meta.fileName ?? 'file');
385
+ const dir = path.join(inboxRoot, handoffId);
386
+ fs.mkdirSync(dir, { recursive: true });
387
+ const dest = path.join(dir, name);
388
+ const part = path.join(dir, `.${handoffId}.part`);
389
+ try {
390
+ await this.getFile(meta.download.url, part, timeoutMs);
391
+ const got = fs.statSync(part).size;
392
+ if (Number.isFinite(size) && got !== size)
393
+ throw new Error(`size mismatch (got ${got} bytes, expected ${size})`);
394
+ fs.renameSync(part, dest);
395
+ }
396
+ catch (err) {
397
+ fs.rmSync(part, { force: true });
398
+ throw err;
399
+ }
400
+ return { path: dest, fileName: name, sizeBytes: fs.statSync(dest).size };
401
+ }
402
+ /**
403
+ * Replace every marker in a delivered message with the local path of the
404
+ * downloaded file (`File from <sender>: <path>`). A file that cannot be
405
+ * fetched in time stays in the text with a note and the manual command.
406
+ * Downloads run in parallel under one overall deadline.
407
+ *
408
+ * @param text - Message text as delivered
409
+ * @param sender - Who sent it (display name)
410
+ * @param inboxRoot - The receiving agent's inbox folder
411
+ * @param agent - Receiving agent session
412
+ * @returns Text with the markers rewritten (unchanged when it has none)
413
+ */
414
+ async expandInbound(text, sender, inboxRoot, agent) {
415
+ const ids = findMarkerIds(text);
416
+ if (ids.length === 0)
417
+ return text;
418
+ const deadline = Date.now() + C.DOWNLOAD_TOTAL_TIMEOUT_MS;
419
+ const results = new Map();
420
+ await Promise.all(ids.slice(0, C.MAX_FILES_PER_MESSAGE).map(async (id) => {
421
+ try {
422
+ const budget = Math.max(1, Math.min(C.DOWNLOAD_TIMEOUT_MS, deadline - Date.now()));
423
+ const file = await this.downloadToInbox(id, inboxRoot, agent, budget);
424
+ results.set(id, `File from ${sender}: ${file.path}`);
425
+ this.log('info', 'File handoff received', { handoffId: id, bytes: file.sizeBytes });
426
+ }
427
+ catch (err) {
428
+ const reason = err instanceof Error ? err.message : String(err);
429
+ this.log('warn', 'File handoff download failed', { handoffId: id, reason });
430
+ results.set(id, `File from ${sender} could not be fetched (${reason}). Fetch it yourself: receive-file --id ${id}`);
431
+ }
432
+ }));
433
+ return text.replace(MARKER_TOKEN_RE, (whole, id) => results.get(id) ?? whole);
434
+ }
435
+ }
436
+ /** Whether the first bytes look like text (no NUL byte). */
437
+ function looksLikeText(file) {
438
+ const probeBytes = 4096;
439
+ let fd = null;
440
+ try {
441
+ fd = fs.openSync(file, 'r');
442
+ const buf = Buffer.alloc(probeBytes);
443
+ const n = fs.readSync(fd, buf, 0, probeBytes, 0);
444
+ return !buf.subarray(0, n).includes(0);
445
+ }
446
+ catch {
447
+ return false;
448
+ }
449
+ finally {
450
+ if (fd !== null)
451
+ fs.closeSync(fd);
452
+ }
453
+ }
454
+ //# sourceMappingURL=file-handoff.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-handoff.service.js","sourceRoot":"","sources":["../../../../../../backend/src/services/file-handoff/file-handoff.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Wiring of the automatic file hand-off to the running backend: the Cloud
3
+ * client, the agent -> project lookup, and the "does this room reach another
4
+ * machine" test.
5
+ *
6
+ * @module services/file-handoff/file-handoff.wiring
7
+ */
8
+ import { FileHandoffService } from './file-handoff.service.js';
9
+ /**
10
+ * The process-wide service, built on first use.
11
+ *
12
+ * @returns The service
13
+ */
14
+ export declare function getFileHandoffService(): FileHandoffService;
15
+ /** Replace the service (tests). */
16
+ export declare function setFileHandoffService(next: FileHandoffService | null): void;
17
+ /**
18
+ * The project directory an agent works in: its team's first project, else
19
+ * the path the agent's own shell reports (`hint`, from `CREWLY_PROJECT_PATH`).
20
+ * The server's knowledge wins, so a client cannot widen the project root.
21
+ *
22
+ * @param agentSession - Agent session name
23
+ * @param hint - Project path the caller reported
24
+ * @returns Absolute directory, or null when neither is usable
25
+ */
26
+ export declare function projectDirForAgent(agentSession: string | undefined, hint?: string): Promise<string | null>;
27
+ /**
28
+ * The inbox folder for files delivered to a project's agents.
29
+ *
30
+ * @param projectDir - Project root
31
+ * @returns `<project data dir>/inbox`
32
+ */
33
+ export declare function inboxRootFor(projectDir: string): string;
34
+ /**
35
+ * Whether a chat room is a Slack room with at least one Crewly agent that runs
36
+ * on ANOTHER machine of this account.
37
+ *
38
+ * @param chatChannelId - chat-v2 channel id
39
+ * @returns True when a file mentioned there should be attached automatically
40
+ */
41
+ export declare function roomHasRemoteColleague(chatChannelId: string): Promise<boolean>;
42
+ //# sourceMappingURL=file-handoff.wiring.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-handoff.wiring.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/file-handoff/file-handoff.wiring.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAUH,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAI/D;;;;GAIG;AACH,wBAAgB,qBAAqB,IAAI,kBAAkB,CAO1D;AAED,mCAAmC;AACnC,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,kBAAkB,GAAG,IAAI,GAAG,IAAI,CAE3E;AAED;;;;;;;;GAQG;AACH,wBAAsB,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAehH;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAWpF"}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Wiring of the automatic file hand-off to the running backend: the Cloud
3
+ * client, the agent -> project lookup, and the "does this room reach another
4
+ * machine" test.
5
+ *
6
+ * @module services/file-handoff/file-handoff.wiring
7
+ */
8
+ import * as os from 'os';
9
+ import * as path from 'path';
10
+ import * as fs from 'fs';
11
+ import { getAppsParts } from '../apps/apps.wiring.js';
12
+ import { StorageService } from '../core/storage.service.js';
13
+ import { getCrewlyHomePath, resolveProjectDataDir } from '../core/crewly-home.utils.js';
14
+ import { LoggerService } from '../core/logger.service.js';
15
+ import { FILE_HANDOFF_CONSTANTS } from '../../constants.js';
16
+ import { FileHandoffService } from './file-handoff.service.js';
17
+ let instance = null;
18
+ /**
19
+ * The process-wide service, built on first use.
20
+ *
21
+ * @returns The service
22
+ */
23
+ export function getFileHandoffService() {
24
+ instance ??= new FileHandoffService({
25
+ api: () => getAppsParts().client,
26
+ crewlyHomes: () => [path.join(os.homedir(), '.crewly'), getCrewlyHomePath(), process.env.CREWLY_HOME ?? ''].filter(Boolean),
27
+ log: (level, message, meta) => LoggerService.getInstance().createComponentLogger('FileHandoff')[level](message, meta),
28
+ });
29
+ return instance;
30
+ }
31
+ /** Replace the service (tests). */
32
+ export function setFileHandoffService(next) {
33
+ instance = next;
34
+ }
35
+ /**
36
+ * The project directory an agent works in: its team's first project, else
37
+ * the path the agent's own shell reports (`hint`, from `CREWLY_PROJECT_PATH`).
38
+ * The server's knowledge wins, so a client cannot widen the project root.
39
+ *
40
+ * @param agentSession - Agent session name
41
+ * @param hint - Project path the caller reported
42
+ * @returns Absolute directory, or null when neither is usable
43
+ */
44
+ export async function projectDirForAgent(agentSession, hint) {
45
+ if (agentSession) {
46
+ try {
47
+ const storage = StorageService.getInstance();
48
+ const team = (await storage.getTeams()).find((t) => (t.members ?? []).some((m) => m.sessionName === agentSession));
49
+ const projectId = team?.projectIds?.[0];
50
+ if (projectId) {
51
+ const project = (await storage.getProjects()).find((p) => p.id === projectId);
52
+ if (project?.path && fs.existsSync(project.path))
53
+ return project.path;
54
+ }
55
+ }
56
+ catch {
57
+ /* fall through to the hint */
58
+ }
59
+ }
60
+ return hint && path.isAbsolute(hint) && fs.existsSync(hint) ? hint : null;
61
+ }
62
+ /**
63
+ * The inbox folder for files delivered to a project's agents.
64
+ *
65
+ * @param projectDir - Project root
66
+ * @returns `<project data dir>/inbox`
67
+ */
68
+ export function inboxRootFor(projectDir) {
69
+ return path.join(resolveProjectDataDir(projectDir), FILE_HANDOFF_CONSTANTS.INBOX_DIR);
70
+ }
71
+ /**
72
+ * Whether a chat room is a Slack room with at least one Crewly agent that runs
73
+ * on ANOTHER machine of this account.
74
+ *
75
+ * @param chatChannelId - chat-v2 channel id
76
+ * @returns True when a file mentioned there should be attached automatically
77
+ */
78
+ export async function roomHasRemoteColleague(chatChannelId) {
79
+ try {
80
+ const { getSlackTeamChannelService } = await import('../slack/slack-team-channel.service.js');
81
+ const mapping = getSlackTeamChannelService()?.findByChatChannelId(chatChannelId);
82
+ if (!mapping)
83
+ return false;
84
+ const { getSlackDirectoryService } = await import('../slack/slack-directory.service.js');
85
+ const entries = (await getSlackDirectoryService()?.list(mapping.slackChannelId)) ?? [];
86
+ return entries.some((e) => e.kind === 'agent' && e.inChannel && e.source === 'this-account');
87
+ }
88
+ catch {
89
+ return false;
90
+ }
91
+ }
92
+ //# sourceMappingURL=file-handoff.wiring.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-handoff.wiring.js","sourceRoot":"","sources":["../../../../../../backend/src/services/file-handoff/file-handoff.wiring.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AACxF,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAE/D,IAAI,QAAQ,GAA8B,IAAI,CAAC;AAE/C;;;;GAIG;AACH,MAAM,UAAU,qBAAqB;IACnC,QAAQ,KAAK,IAAI,kBAAkB,CAAC;QAClC,GAAG,EAAE,GAAG,EAAE,CAAC,YAAY,EAAE,CAAC,MAAM;QAChC,WAAW,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,EAAE,iBAAiB,EAAE,EAAE,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;QAC3H,GAAG,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC,aAAa,CAAC,WAAW,EAAE,CAAC,qBAAqB,CAAC,aAAa,CAAC,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,IAAI,CAAC;KACtH,CAAC,CAAC;IACH,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,mCAAmC;AACnC,MAAM,UAAU,qBAAqB,CAAC,IAA+B;IACnE,QAAQ,GAAG,IAAI,CAAC;AAClB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,YAAgC,EAAE,IAAa;IACtF,IAAI,YAAY,EAAE,CAAC;QACjB,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,cAAc,CAAC,WAAW,EAAE,CAAC;YAC7C,MAAM,IAAI,GAAG,CAAC,MAAM,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,KAAK,YAAY,CAAC,CAAC,CAAC;YACnH,MAAM,SAAS,GAAG,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC;YACxC,IAAI,SAAS,EAAE,CAAC;gBACd,MAAM,OAAO,GAAG,CAAC,MAAM,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,SAAS,CAAC,CAAC;gBAC9E,IAAI,OAAO,EAAE,IAAI,IAAI,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC;oBAAE,OAAO,OAAO,CAAC,IAAI,CAAC;YACxE,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,8BAA8B;QAChC,CAAC;IACH,CAAC;IACD,OAAO,IAAI,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;AAC5E,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,UAAkB;IAC7C,OAAO,IAAI,CAAC,IAAI,CAAC,qBAAqB,CAAC,UAAU,CAAC,EAAE,sBAAsB,CAAC,SAAS,CAAC,CAAC;AACxF,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,aAAqB;IAChE,IAAI,CAAC;QACH,MAAM,EAAE,0BAA0B,EAAE,GAAG,MAAM,MAAM,CAAC,wCAAwC,CAAC,CAAC;QAC9F,MAAM,OAAO,GAAG,0BAA0B,EAAE,EAAE,mBAAmB,CAAC,aAAa,CAAC,CAAC;QACjF,IAAI,CAAC,OAAO;YAAE,OAAO,KAAK,CAAC;QAC3B,MAAM,EAAE,wBAAwB,EAAE,GAAG,MAAM,MAAM,CAAC,qCAAqC,CAAC,CAAC;QACzF,MAAM,OAAO,GAAG,CAAC,MAAM,wBAAwB,EAAE,EAAE,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,IAAI,EAAE,CAAC;QACvF,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,IAAI,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,MAAM,KAAK,cAAc,CAAC,CAAC;IAC/F,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
@@ -7,7 +7,10 @@
7
7
  * nothing inside it can be re-included. When (and only when) git reports the
8
8
  * tickets folder as ignored, this appends a small block to the project's
9
9
  * `.gitignore` that re-includes `.crewly/`, re-ignores everything in it, then
10
- * re-includes `tickets/`. Existing lines are never touched.
10
+ * re-includes `tickets/`. Existing lines are never touched, and any existing
11
+ * `!.crewly/...` negations (e.g. `!.crewly/wiki/`) are re-emitted after the
12
+ * block's `.crewly/*` so the block cannot re-ignore what the project already
13
+ * tracks.
11
14
  *
12
15
  * @module services/project-tickets/ticket-tracking
13
16
  */
@@ -26,6 +29,16 @@ export type TicketTrackingOutcome =
26
29
  | 'still-ignored';
27
30
  /** Git runner (injectable for tests). */
28
31
  export type GitRunner = (cwd: string, args: string[]) => Promise<GitResult>;
32
+ /**
33
+ * Build the block to append: the header, `!.crewly/` and `.crewly/*`, then the
34
+ * project's existing `.crewly` negations, then the tickets re-includes. The
35
+ * block's own `.crewly/*` comes after the project's rules, so without the
36
+ * re-emitted negations it would hide everything they had re-included.
37
+ *
38
+ * @param current - Current `.gitignore` text
39
+ * @returns Block lines, in order
40
+ */
41
+ export declare function buildTicketsBlock(current: string): string[];
29
42
  /**
30
43
  * Make sure git tracks the project's tickets folder, appending the
31
44
  * re-include block to `<project>/.gitignore` when it is ignored.