crewly 1.20.327 → 1.20.329
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.
- package/config/skills/agent/core/publish-app/SKILL.md +9 -0
- package/config/skills/agent/core/reply-channel/SKILL.md +19 -0
- package/config/skills/agent/core/reply-channel/execute.sh +38 -14
- package/config/skills/agent/core/reply-channel/execute.test.sh +18 -0
- package/config/skills/agent/core/send-file/SKILL.md +5 -0
- package/config/skills/agent/core/slack-post/SKILL.md +1 -0
- package/config/skills/agent/core/slack-post/execute.sh +20 -4
- package/dist/backend/backend/src/constants.d.ts +35 -0
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +42 -0
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/chat-v2/chat-v2.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/chat-v2/chat-v2.controller.js +31 -1
- package/dist/backend/backend/src/controllers/chat-v2/chat-v2.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/slack/slack.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/slack/slack.controller.js +16 -2
- package/dist/backend/backend/src/controllers/slack/slack.controller.js.map +1 -1
- package/dist/backend/backend/src/index.d.ts.map +1 -1
- package/dist/backend/backend/src/index.js +17 -0
- package/dist/backend/backend/src/index.js.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js +4 -0
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts +7 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.js +13 -2
- package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.js.map +1 -1
- package/dist/backend/backend/src/services/decisions/recurring-change-handoff.d.ts +10 -0
- package/dist/backend/backend/src/services/decisions/recurring-change-handoff.d.ts.map +1 -1
- package/dist/backend/backend/src/services/decisions/recurring-change-handoff.js +17 -2
- package/dist/backend/backend/src/services/decisions/recurring-change-handoff.js.map +1 -1
- package/dist/backend/backend/src/services/event-bus/event-to-workitem-bridge.service.d.ts +11 -0
- package/dist/backend/backend/src/services/event-bus/event-to-workitem-bridge.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/event-bus/event-to-workitem-bridge.service.js +41 -3
- package/dist/backend/backend/src/services/event-bus/event-to-workitem-bridge.service.js.map +1 -1
- package/dist/backend/backend/src/services/file-handoff/file-handoff.inbound.d.ts +35 -0
- package/dist/backend/backend/src/services/file-handoff/file-handoff.inbound.d.ts.map +1 -0
- package/dist/backend/backend/src/services/file-handoff/file-handoff.inbound.js +45 -0
- package/dist/backend/backend/src/services/file-handoff/file-handoff.inbound.js.map +1 -0
- package/dist/backend/backend/src/services/file-handoff/file-handoff.outbound.d.ts +44 -0
- package/dist/backend/backend/src/services/file-handoff/file-handoff.outbound.d.ts.map +1 -0
- package/dist/backend/backend/src/services/file-handoff/file-handoff.outbound.js +50 -0
- package/dist/backend/backend/src/services/file-handoff/file-handoff.outbound.js.map +1 -0
- package/dist/backend/backend/src/services/file-handoff/file-handoff.service.d.ts +224 -0
- package/dist/backend/backend/src/services/file-handoff/file-handoff.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/file-handoff/file-handoff.service.js +454 -0
- package/dist/backend/backend/src/services/file-handoff/file-handoff.service.js.map +1 -0
- package/dist/backend/backend/src/services/file-handoff/file-handoff.wiring.d.ts +42 -0
- package/dist/backend/backend/src/services/file-handoff/file-handoff.wiring.d.ts.map +1 -0
- package/dist/backend/backend/src/services/file-handoff/file-handoff.wiring.js +92 -0
- package/dist/backend/backend/src/services/file-handoff/file-handoff.wiring.js.map +1 -0
- package/dist/backend/backend/src/services/project-tickets/ticket-tracking.d.ts +14 -1
- package/dist/backend/backend/src/services/project-tickets/ticket-tracking.d.ts.map +1 -1
- package/dist/backend/backend/src/services/project-tickets/ticket-tracking.js +26 -2
- package/dist/backend/backend/src/services/project-tickets/ticket-tracking.js.map +1 -1
- package/dist/backend/build-info.json +2 -2
- package/dist/cli/backend/src/constants.d.ts +35 -0
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +42 -0
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js +4 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
- package/dist/cli/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts +7 -0
- package/dist/cli/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/chat-v2/chat-v2.dispatcher.service.js +13 -2
- package/dist/cli/backend/src/services/chat-v2/chat-v2.dispatcher.service.js.map +1 -1
- package/dist/cli/backend/src/services/project-tickets/ticket-tracking.d.ts +14 -1
- package/dist/cli/backend/src/services/project-tickets/ticket-tracking.d.ts.map +1 -1
- package/dist/cli/backend/src/services/project-tickets/ticket-tracking.js +26 -2
- package/dist/cli/backend/src/services/project-tickets/ticket-tracking.js.map +1 -1
- 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":"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"}
|
|
@@ -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.
|