@aexol/spectral 0.9.245 → 0.9.247

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 (43) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/cli.js +6 -0
  3. package/dist/commands/secret.d.ts +16 -0
  4. package/dist/commands/secret.d.ts.map +1 -0
  5. package/dist/commands/secret.js +153 -0
  6. package/dist/commands/serve.d.ts.map +1 -1
  7. package/dist/commands/serve.js +21 -0
  8. package/dist/extensions/list-secrets.d.ts +14 -0
  9. package/dist/extensions/list-secrets.d.ts.map +1 -0
  10. package/dist/extensions/list-secrets.js +63 -0
  11. package/dist/generated/zeus/const.d.ts.map +1 -1
  12. package/dist/generated/zeus/const.js +16 -0
  13. package/dist/generated/zeus/index.d.ts +73 -0
  14. package/dist/generated/zeus/index.d.ts.map +1 -1
  15. package/dist/relay/routing.d.ts +1 -1
  16. package/dist/relay/routing.d.ts.map +1 -1
  17. package/dist/relay/routing.js +93 -17
  18. package/dist/sdk/coding-agent/utils/shell.d.ts.map +1 -1
  19. package/dist/sdk/coding-agent/utils/shell.js +15 -1
  20. package/dist/secrets/index.d.ts +11 -0
  21. package/dist/secrets/index.d.ts.map +1 -0
  22. package/dist/secrets/index.js +10 -0
  23. package/dist/secrets/store.d.ts +109 -0
  24. package/dist/secrets/store.d.ts.map +1 -0
  25. package/dist/secrets/store.js +415 -0
  26. package/dist/server/agent-bridge.d.ts.map +1 -1
  27. package/dist/server/agent-bridge.js +20 -2
  28. package/dist/server/attachments/cleanup.d.ts +42 -0
  29. package/dist/server/attachments/cleanup.d.ts.map +1 -0
  30. package/dist/server/attachments/cleanup.js +100 -0
  31. package/dist/server/attachments/persist.d.ts +131 -0
  32. package/dist/server/attachments/persist.d.ts.map +1 -0
  33. package/dist/server/attachments/persist.js +406 -0
  34. package/dist/server/handlers/secrets.d.ts +40 -0
  35. package/dist/server/handlers/secrets.d.ts.map +1 -0
  36. package/dist/server/handlers/secrets.js +78 -0
  37. package/dist/server/session-stream.d.ts +5 -0
  38. package/dist/server/session-stream.d.ts.map +1 -1
  39. package/dist/server/session-stream.js +51 -1
  40. package/dist/server/storage.d.ts +6 -0
  41. package/dist/server/storage.d.ts.map +1 -1
  42. package/dist/server/storage.js +9 -0
  43. package/package.json +1 -1
@@ -0,0 +1,415 @@
1
+ /**
2
+ * Local encrypted secret store.
3
+ *
4
+ * Storage layout (both files under `getConfigDir()`, honoring
5
+ * `SPECTRAL_CONFIG_DIR` for tests):
6
+ * - `<configDir>/secrets.key` 32 random bytes, mode 0600 — AES-256 key
7
+ * - `<configDir>/secrets.json.enc` AES-256-GCM encrypted JSON, mode 0600
8
+ *
9
+ * Encrypted file layout:
10
+ * - bytes 0-7: magic `"SPSECRT1"` (format tag)
11
+ * - bytes 8-19: 12-byte random IV
12
+ * - bytes 20-35: 16-byte GCM auth tag
13
+ * - bytes 36+: ciphertext (UTF-8 JSON)
14
+ *
15
+ * Security rules enforced here and by this module's consumers:
16
+ * - `listSecrets()` returns ONLY `{ name, createdAt, updatedAt }` — values are
17
+ * never part of any listing.
18
+ * - `getSecretValue()` is INTERNAL: it is consumed only by
19
+ * `getSecretEnvVars()` (env-var injection into the agent's bash tool). It
20
+ * is deliberately NOT re-exported from `./index.js`, so neither the relay
21
+ * handlers nor the LLM-facing tool surface can reach it.
22
+ * - No caller ever logs a value; error messages and listings carry names only.
23
+ *
24
+ * All I/O is synchronous by design: `getSecretEnvVars()` must be callable from
25
+ * the sync `getShellEnv()` in `src/sdk/coding-agent/utils/shell.ts`. The store
26
+ * is tiny (values are capped at 64 KiB each), so sync reads are negligible.
27
+ *
28
+ * Keep this module zero-side-effect on import: nothing touches the filesystem
29
+ * until one of the exported functions is called.
30
+ */
31
+ import { chmodSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
32
+ import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
33
+ import { basename, dirname, join } from "node:path";
34
+ import { getConfigDir } from "../config.js";
35
+ /** Secret names: start with a letter, then letters/digits/underscores, max 64 chars. */
36
+ export const SECRET_NAME_PATTERN = /^[A-Z][A-Z0-9_]{0,63}$/;
37
+ /** Maximum UTF-8 byte size of a secret value (64 KiB). */
38
+ export const MAX_SECRET_VALUE_BYTES = 64 * 1024;
39
+ /** 8-byte format tag at the head of the encrypted store. */
40
+ const MAGIC = Buffer.from("SPSECRT1", "utf8");
41
+ const IV_LENGTH = 12;
42
+ const TAG_LENGTH = 16;
43
+ const KEY_LENGTH = 32;
44
+ /** Thrown when a secret name is missing/invalid after normalization. */
45
+ export class SecretNameError extends Error {
46
+ constructor(message) {
47
+ super(message);
48
+ this.name = "SecretNameError";
49
+ }
50
+ }
51
+ /** Thrown when a secret value exceeds `MAX_SECRET_VALUE_BYTES`. */
52
+ export class SecretValueError extends Error {
53
+ constructor(message) {
54
+ super(message);
55
+ this.name = "SecretValueError";
56
+ }
57
+ }
58
+ /** Thrown when the encrypted store is unreadable, corrupt, or key-mismatched. */
59
+ export class SecretStoreCorruptError extends Error {
60
+ constructor(message) {
61
+ super(message);
62
+ this.name = "SecretStoreCorruptError";
63
+ }
64
+ }
65
+ // ---------------------------------------------------------------------------
66
+ // Paths
67
+ // ---------------------------------------------------------------------------
68
+ /** Path to the AES-256 key file (32 random bytes). Lazily computed — see `getConfigDir`. */
69
+ export function getSecretKeyPath() {
70
+ return join(getConfigDir(), "secrets.key");
71
+ }
72
+ /** Path to the AES-256-GCM encrypted store. Lazily computed — see `getConfigDir`. */
73
+ export function getSecretDataPath() {
74
+ return join(getConfigDir(), "secrets.json.enc");
75
+ }
76
+ // ---------------------------------------------------------------------------
77
+ // Validation / normalization
78
+ // ---------------------------------------------------------------------------
79
+ /**
80
+ * Normalize a secret name per the shared contract: trim surrounding
81
+ * whitespace, uppercase, then validate against `SECRET_NAME_PATTERN`.
82
+ * Throws `SecretNameError` with a generic message — the offending raw input
83
+ * is never echoed back, so it can't leak into logs.
84
+ */
85
+ export function normalizeSecretName(raw) {
86
+ if (typeof raw !== "string" || raw.trim().length === 0) {
87
+ throw new SecretNameError("Secret name is required");
88
+ }
89
+ const name = raw.trim().toUpperCase();
90
+ if (!SECRET_NAME_PATTERN.test(name)) {
91
+ throw new SecretNameError("Invalid secret name: must start with a letter, contain only letters, digits and underscores, max 64 characters (pattern ^[A-Z][A-Z0-9_]{0,63}$)");
92
+ }
93
+ return name;
94
+ }
95
+ function assertValueSize(value) {
96
+ if (typeof value !== "string") {
97
+ throw new SecretValueError("Secret value must be a string");
98
+ }
99
+ if (Buffer.byteLength(value, "utf8") > MAX_SECRET_VALUE_BYTES) {
100
+ throw new SecretValueError(`Secret value too large: max ${MAX_SECRET_VALUE_BYTES} bytes (64 KiB)`);
101
+ }
102
+ }
103
+ // ---------------------------------------------------------------------------
104
+ // Crypto + file I/O
105
+ // ---------------------------------------------------------------------------
106
+ function encrypt(data, key) {
107
+ const iv = randomBytes(IV_LENGTH);
108
+ const cipher = createCipheriv("aes-256-gcm", key, iv);
109
+ const ciphertext = Buffer.concat([cipher.update(data, "utf8"), cipher.final()]);
110
+ const tag = cipher.getAuthTag();
111
+ return Buffer.concat([MAGIC, iv, tag, ciphertext]);
112
+ }
113
+ function decrypt(buf, key) {
114
+ if (buf.length < MAGIC.length + IV_LENGTH + TAG_LENGTH ||
115
+ !buf.subarray(0, MAGIC.length).equals(MAGIC)) {
116
+ throw new SecretStoreCorruptError("Unrecognized secret store format");
117
+ }
118
+ const iv = buf.subarray(MAGIC.length, MAGIC.length + IV_LENGTH);
119
+ const tag = buf.subarray(MAGIC.length + IV_LENGTH, MAGIC.length + IV_LENGTH + TAG_LENGTH);
120
+ const ciphertext = buf.subarray(MAGIC.length + IV_LENGTH + TAG_LENGTH);
121
+ try {
122
+ const decipher = createDecipheriv("aes-256-gcm", key, iv);
123
+ decipher.setAuthTag(tag);
124
+ return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
125
+ }
126
+ catch {
127
+ throw new SecretStoreCorruptError("Secret store is corrupt or was encrypted with a different key");
128
+ }
129
+ }
130
+ /**
131
+ * Read the EXISTING key file. Throws `SecretStoreCorruptError` when the key
132
+ * is missing or has the wrong size. Deliberately NEVER generates a key: key
133
+ * generation happens on write paths only (`ensureSecretKey`), so a read
134
+ * against encrypted data without a key surfaces the lost-key state instead of
135
+ * silently minting a fresh key that can never decrypt the store.
136
+ */
137
+ function readSecretKey() {
138
+ const keyPath = getSecretKeyPath();
139
+ let key;
140
+ try {
141
+ key = readFileSync(keyPath);
142
+ }
143
+ catch (err) {
144
+ if (err.code === "ENOENT") {
145
+ throw new SecretStoreCorruptError("Secrets key file is missing but encrypted data exists: restore secrets.key or delete the store and re-enter your secrets");
146
+ }
147
+ throw new SecretStoreCorruptError(`Cannot read secrets key: ${err instanceof Error ? err.message : String(err)}`);
148
+ }
149
+ if (key.length !== KEY_LENGTH) {
150
+ throw new SecretStoreCorruptError(`Secrets key file is corrupt (expected ${KEY_LENGTH} bytes): delete it and re-enter your secrets`);
151
+ }
152
+ return key;
153
+ }
154
+ /**
155
+ * Return the AES-256 key, generating a new one ONLY when the key file does
156
+ * not exist yet (write paths). An existing key with loose permissions is
157
+ * tightened back to 0600 (defense-in-depth).
158
+ */
159
+ function ensureSecretKey() {
160
+ const keyPath = getSecretKeyPath();
161
+ mkdirSync(dirname(keyPath), { recursive: true, mode: 0o700 });
162
+ if (existsSync(keyPath)) {
163
+ try {
164
+ chmodSync(keyPath, 0o600);
165
+ }
166
+ catch {
167
+ // Best-effort: some filesystems do not support chmod.
168
+ }
169
+ return readSecretKey();
170
+ }
171
+ const key = randomBytes(KEY_LENGTH);
172
+ writeFileSync(keyPath, key, { mode: 0o600 });
173
+ return key;
174
+ }
175
+ function emptyStoreData() {
176
+ return { version: 1, secrets: {} };
177
+ }
178
+ /** Read + decrypt the store. Returns an empty store when the file is missing. Throws on corruption. */
179
+ function readStoreData() {
180
+ const dataPath = getSecretDataPath();
181
+ if (!existsSync(dataPath))
182
+ return emptyStoreData();
183
+ let raw;
184
+ try {
185
+ raw = readFileSync(dataPath);
186
+ }
187
+ catch (err) {
188
+ throw new SecretStoreCorruptError(`Cannot read secret store: ${err instanceof Error ? err.message : String(err)}`);
189
+ }
190
+ // The read path NEVER generates a key: encrypted data without a key is a
191
+ // lost-key state, and minting a fresh key here would mask it (and touch
192
+ // the filesystem as a side effect of a read). Keys are generated on write
193
+ // paths only (ensureSecretKey via writeStoreData).
194
+ const key = readSecretKey();
195
+ const json = decrypt(raw, key);
196
+ try {
197
+ const parsed = JSON.parse(json);
198
+ if (parsed.version !== 1 || typeof parsed.secrets !== "object" || parsed.secrets === null) {
199
+ throw new SecretStoreCorruptError("Unrecognized secret store payload");
200
+ }
201
+ return { version: 1, secrets: parsed.secrets };
202
+ }
203
+ catch (err) {
204
+ if (err instanceof SecretStoreCorruptError)
205
+ throw err;
206
+ throw new SecretStoreCorruptError("Unrecognized secret store payload");
207
+ }
208
+ }
209
+ /** Orphaned `.tmp-*` files older than this are removed on the next write. */
210
+ const STALE_TMP_MS = 60_000;
211
+ /**
212
+ * Remove orphaned `secrets.json.enc.tmp-*` files left behind by crashed
213
+ * writers. Only files older than `STALE_TMP_MS` are removed so a concurrent
214
+ * process's in-flight write (tmp file created moments ago) is never deleted.
215
+ */
216
+ function cleanupStaleTmpFiles(dataPath) {
217
+ let entries;
218
+ try {
219
+ entries = readdirSync(dirname(dataPath));
220
+ }
221
+ catch {
222
+ return;
223
+ }
224
+ const prefix = `${basename(dataPath)}.tmp-`;
225
+ const cutoff = Date.now() - STALE_TMP_MS;
226
+ for (const entry of entries) {
227
+ if (!entry.startsWith(prefix))
228
+ continue;
229
+ const full = join(dirname(dataPath), entry);
230
+ try {
231
+ if (statSync(full).mtimeMs < cutoff)
232
+ unlinkSync(full);
233
+ }
234
+ catch {
235
+ // Ignore: the file vanished (another writer renamed it away).
236
+ }
237
+ }
238
+ }
239
+ /** Encrypt + atomically write the store (tmp file + rename), mode 0600. */
240
+ function writeStoreData(data) {
241
+ const dataPath = getSecretDataPath();
242
+ const key = ensureSecretKey();
243
+ const payload = encrypt(JSON.stringify(data), key);
244
+ // Unique per-request tmp name: pid alone is shared by every write this
245
+ // process makes, so a crashed write would make the next write silently
246
+ // reuse (and clobber) the orphan. A random hex suffix makes collisions
247
+ // between concurrent writers practically impossible.
248
+ const tmpPath = `${dataPath}.tmp-${process.pid}-${randomBytes(6).toString("hex")}`;
249
+ writeFileSync(tmpPath, payload, { mode: 0o600 });
250
+ try {
251
+ renameSync(tmpPath, dataPath);
252
+ }
253
+ catch (err) {
254
+ // Best-effort: remove this request's tmp file so failed writes cannot
255
+ // accumulate orphans.
256
+ try {
257
+ unlinkSync(tmpPath);
258
+ }
259
+ catch {
260
+ // Already gone.
261
+ }
262
+ throw err;
263
+ }
264
+ // Defense-in-depth: a pre-existing file with loose permissions gets tightened.
265
+ chmodSync(dataPath, 0o600);
266
+ cleanupStaleTmpFiles(dataPath);
267
+ }
268
+ // ---------------------------------------------------------------------------
269
+ // mtime-cached env injection
270
+ // ---------------------------------------------------------------------------
271
+ /**
272
+ * Cache keyed on the data file's `mtimeMs:size` so external writes (other CLI
273
+ * processes, the relay API) invalidate it without a restart. Our own write
274
+ * path (`setSecret`/`deleteSecret`) refreshes the cache explicitly, covering
275
+ * same-millisecond writes where the mtime would not change.
276
+ */
277
+ let envCache = null;
278
+ function dataFileCacheKey() {
279
+ try {
280
+ const data = statSync(getSecretDataPath());
281
+ const key = statSync(getSecretKeyPath());
282
+ return `${data.mtimeMs}:${data.size}:${key.mtimeMs}:${key.size}`;
283
+ }
284
+ catch {
285
+ return null;
286
+ }
287
+ }
288
+ function refreshEnvCache() {
289
+ const cacheKey = dataFileCacheKey();
290
+ const data = readStoreData();
291
+ // PATH is reserved: a secret named PATH would override the shell PATH that
292
+ // getShellEnv() deliberately rebuilds (bin dir injection) and break the
293
+ // bash tool. Stored under that name, it is ignored for env injection only.
294
+ const env = {};
295
+ for (const [name, record] of Object.entries(data.secrets)) {
296
+ if (name.toLowerCase() === "path")
297
+ continue;
298
+ env[name] = record.value;
299
+ }
300
+ envCache = { key: cacheKey ?? "empty", env };
301
+ }
302
+ /**
303
+ * Return the secret env vars (name → value) for injection into the agent
304
+ * shell environment. Synchronous, mtime-cached: new/updated/deleted secrets
305
+ * are picked up on the next call without a CLI restart.
306
+ *
307
+ * Failure modes degrade gracefully — a corrupt store, missing key, or unreadable
308
+ * file returns `{}` instead of throwing, because `getShellEnv()` is sync and
309
+ * must never break bash spawning. No values are ever logged.
310
+ */
311
+ export function getSecretEnvVars() {
312
+ try {
313
+ const cacheKey = dataFileCacheKey();
314
+ if (cacheKey === null) {
315
+ return {};
316
+ }
317
+ if (envCache && envCache.key === cacheKey) {
318
+ return envCache.env;
319
+ }
320
+ refreshEnvCache();
321
+ return envCache?.env ?? {};
322
+ }
323
+ catch {
324
+ return {};
325
+ }
326
+ }
327
+ // ---------------------------------------------------------------------------
328
+ // Public API
329
+ // ---------------------------------------------------------------------------
330
+ /**
331
+ * Create or update a secret. Returns the metadata-only view (never the value).
332
+ * Throws `SecretNameError` for invalid names, `SecretValueError` for oversized
333
+ * values, and `SecretStoreCorruptError` when the store cannot be read/written.
334
+ *
335
+ * Fully synchronous (no awaits): the read-modify-write below is atomic per
336
+ * event-loop turn, so concurrent relay POSTs in one process can never
337
+ * interleave — no mutex is needed. Cross-process writers are isolated by the
338
+ * unique per-request tmp name + rename in `writeStoreData`; the last rename
339
+ * wins and every writer re-reads the store on its next call.
340
+ */
341
+ export function setSecret(rawName, value) {
342
+ const name = normalizeSecretName(rawName);
343
+ assertValueSize(value);
344
+ const data = readStoreData();
345
+ const now = new Date().toISOString();
346
+ const existing = data.secrets[name];
347
+ data.secrets[name] = {
348
+ value,
349
+ createdAt: existing?.createdAt ?? now,
350
+ updatedAt: now,
351
+ };
352
+ writeStoreData(data);
353
+ refreshEnvCache();
354
+ return { name, createdAt: data.secrets[name].createdAt, updatedAt: now };
355
+ }
356
+ /**
357
+ * Delete a secret. Returns true when a record was removed, false when the
358
+ * name was not stored. Throws `SecretNameError` for invalid names.
359
+ */
360
+ export function deleteSecret(rawName) {
361
+ const name = normalizeSecretName(rawName);
362
+ const data = readStoreData();
363
+ if (!Object.prototype.hasOwnProperty.call(data.secrets, name)) {
364
+ return false;
365
+ }
366
+ delete data.secrets[name];
367
+ writeStoreData(data);
368
+ refreshEnvCache();
369
+ return true;
370
+ }
371
+ /**
372
+ * List secrets as metadata only — `{ name, createdAt, updatedAt }`. Values are
373
+ * never part of the returned shape (this is the source for the relay GET
374
+ * response and the `list_secrets` agent tool).
375
+ */
376
+ export function listSecrets() {
377
+ const data = readStoreData();
378
+ return Object.entries(data.secrets)
379
+ .map(([name, record]) => ({
380
+ name,
381
+ createdAt: record.createdAt,
382
+ updatedAt: record.updatedAt,
383
+ }))
384
+ .sort((a, b) => a.name.localeCompare(b.name));
385
+ }
386
+ /**
387
+ * INTERNAL — read a single secret value. Used ONLY for env-var injection via
388
+ * `getSecretEnvVars()`. Deliberately not re-exported from `./index.js` so it
389
+ * cannot be reached from the relay handlers or the LLM-facing tool surface.
390
+ * Never log the return value.
391
+ */
392
+ export function getSecretValue(rawName) {
393
+ const name = normalizeSecretName(rawName);
394
+ const data = readStoreData();
395
+ return data.secrets[name]?.value;
396
+ }
397
+ /**
398
+ * Remove the store + key (test helper and future "reset secrets" support).
399
+ * Returns true when either file was deleted.
400
+ */
401
+ export function resetSecretStore() {
402
+ let removed = false;
403
+ for (const path of [getSecretDataPath(), getSecretKeyPath()]) {
404
+ try {
405
+ unlinkSync(path);
406
+ removed = true;
407
+ }
408
+ catch (err) {
409
+ if (err.code !== "ENOENT")
410
+ throw err;
411
+ }
412
+ }
413
+ envCache = null;
414
+ return removed;
415
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"agent-bridge.d.ts","sourceRoot":"","sources":["../../src/server/agent-bridge.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"agent-bridge.d.ts","sourceRoot":"","sources":["../../src/server/agent-bridge.ts"],"names":[],"mappings":"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"}
@@ -63,9 +63,11 @@ import { BrowserService } from "../extensions/browser/browser-service.js";
63
63
  import subagentExt from "../agent/index.js";
64
64
  import designerExtension from "../designer/index.js";
65
65
  import logicTraceExtension from "../extensions/logic-trace.js";
66
+ import listSecretsExtension from "../extensions/list-secrets.js";
66
67
  import observationalMemory from "../memory/index.js";
67
68
  import { OBSERVATIONAL_MEMORY_CONTEXT_CUSTOM_TYPE, OBSERVATIONAL_MEMORY_SNAPSHOT_CUSTOM_TYPE, } from "../memory/types.js";
68
69
  import { fetchAllowedModels as defaultFetchAllowedModels, } from "../relay/models-fetch.js";
70
+ import { annotatedUserMessageText } from "./attachments/persist.js";
69
71
  import { humanizeProviderError } from "./error-humanizer.js";
70
72
  import { readStudioBinding, getBindingFilePath } from "../studio-binding.js";
71
73
  // Facade imports: cohesive helpers extracted verbatim to `./agent-bridge/` modules.
@@ -368,6 +370,12 @@ export class AgentBridge {
368
370
  async (ext) => {
369
371
  observationalMemory(ext);
370
372
  },
373
+ // list_secrets: metadata-only view of the local encrypted secret store
374
+ // (names + timestamps). Values are never returned to the LLM — they are
375
+ // injected as env vars into the bash tool via getShellEnv().
376
+ async (ext) => {
377
+ listSecretsExtension(ext);
378
+ },
371
379
  ];
372
380
  // Always register subagent tool — execute() checks settings from disk
373
381
  // on every invocation, so toggling takes effect immediately (no restart).
@@ -432,9 +440,19 @@ export class AgentBridge {
432
440
  const imageBlocks = (msg.images ?? [])
433
441
  .map(toImageContent)
434
442
  .filter((block) => block !== null);
443
+ // Attachment persistence (Option A): annotate rehydrated user
444
+ // messages whose attachments are still on disk, so the LLM sees
445
+ // the same saved-at hint the original prompt carried. Attachments
446
+ // that were swept (or never persisted) leave the text untouched.
447
+ const text = annotatedUserMessageText({
448
+ cwd: this.opts.cwd,
449
+ sessionId: this.opts.sessionId,
450
+ content: msg.content,
451
+ images: msg.images,
452
+ });
435
453
  const content = imageBlocks.length > 0
436
- ? [...imageBlocks, { type: "text", text: msg.content }]
437
- : msg.content;
454
+ ? [...imageBlocks, { type: "text", text }]
455
+ : text;
438
456
  sessionManager.appendMessage({
439
457
  role: "user",
440
458
  content,
@@ -0,0 +1,42 @@
1
+ /**
2
+ * TTL GC for persisted chat attachments (`.spectral/attachments/<id>/`).
3
+ *
4
+ * Mirrors `src/server/offload-gc.ts`: the sweep removes per-session
5
+ * directories whose session no longer exists AND whose directory mtime is
6
+ * older than `SPECTRAL_ATTACHMENT_TTL_DAYS` (default 7). Sessions that still
7
+ * exist in the store are never swept, however old their attachments are.
8
+ *
9
+ * Best-effort and synchronous: callers fire-and-forget it (server startup,
10
+ * first prompt). Never throws.
11
+ *
12
+ * @module @spectral/server/attachments/cleanup
13
+ */
14
+ /** Session dirs older than this (in days) are swept when their session is gone. */
15
+ export declare const DEFAULT_ATTACHMENT_TTL_DAYS = 7;
16
+ /** Effective TTL in ms (override via `SPECTRAL_ATTACHMENT_TTL_DAYS`). */
17
+ export declare function getAttachmentTtlMs(): number;
18
+ export interface AttachmentSweepResult {
19
+ removedSessions: number;
20
+ removedBytes: number;
21
+ }
22
+ export interface SweepAttachmentsOptions {
23
+ /** `.spectral/attachments` root to sweep (per project cwd). */
24
+ root: string;
25
+ /**
26
+ * Session ids that still exist — their directories are NEVER swept,
27
+ * however old. Omitted = treat every session as gone (TTL-only sweep).
28
+ */
29
+ activeSessionIds?: Iterable<string>;
30
+ /** Epoch ms "now" (tests). Defaults to `Date.now()`. */
31
+ now?: number;
32
+ /** TTL override in ms (tests). Defaults to `getAttachmentTtlMs()`. */
33
+ ttlMs?: number;
34
+ }
35
+ /**
36
+ * Remove stale attachment session directories. A directory is removed only
37
+ * when BOTH: its name matches no active session id AND its mtime is older
38
+ * than the TTL. Dotfiles (`.gitignore`) and stray non-directories are left
39
+ * alone. Never throws.
40
+ */
41
+ export declare function sweepAttachments(options: SweepAttachmentsOptions): AttachmentSweepResult;
42
+ //# sourceMappingURL=cleanup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cleanup.d.ts","sourceRoot":"","sources":["../../../src/server/attachments/cleanup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAOH,mFAAmF;AACnF,eAAO,MAAM,2BAA2B,IAAI,CAAC;AAE7C,yEAAyE;AACzE,wBAAgB,kBAAkB,IAAI,MAAM,CAG3C;AAsBD,MAAM,WAAW,qBAAqB;IACpC,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,uBAAuB;IACtC,+DAA+D;IAC/D,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,gBAAgB,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACpC,wDAAwD;IACxD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,sEAAsE;IACtE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,qBAAqB,CAuCxF"}
@@ -0,0 +1,100 @@
1
+ /**
2
+ * TTL GC for persisted chat attachments (`.spectral/attachments/<id>/`).
3
+ *
4
+ * Mirrors `src/server/offload-gc.ts`: the sweep removes per-session
5
+ * directories whose session no longer exists AND whose directory mtime is
6
+ * older than `SPECTRAL_ATTACHMENT_TTL_DAYS` (default 7). Sessions that still
7
+ * exist in the store are never swept, however old their attachments are.
8
+ *
9
+ * Best-effort and synchronous: callers fire-and-forget it (server startup,
10
+ * first prompt). Never throws.
11
+ *
12
+ * @module @spectral/server/attachments/cleanup
13
+ */
14
+ import { existsSync, readdirSync, rmSync, statSync } from "node:fs";
15
+ import { join } from "node:path";
16
+ import { envPositiveInt } from "../../sdk/coding-agent/core/tools/offload.js";
17
+ /** Session dirs older than this (in days) are swept when their session is gone. */
18
+ export const DEFAULT_ATTACHMENT_TTL_DAYS = 7;
19
+ /** Effective TTL in ms (override via `SPECTRAL_ATTACHMENT_TTL_DAYS`). */
20
+ export function getAttachmentTtlMs() {
21
+ return envPositiveInt("SPECTRAL_ATTACHMENT_TTL_DAYS", DEFAULT_ATTACHMENT_TTL_DAYS) *
22
+ 24 * 60 * 60 * 1000;
23
+ }
24
+ function dirSizeBytes(dir) {
25
+ let total = 0;
26
+ let entries;
27
+ try {
28
+ entries = readdirSync(dir, { withFileTypes: true });
29
+ }
30
+ catch {
31
+ return 0;
32
+ }
33
+ for (const entry of entries) {
34
+ const full = join(dir, entry.name);
35
+ try {
36
+ if (entry.isDirectory())
37
+ total += dirSizeBytes(full);
38
+ else if (entry.isFile())
39
+ total += statSync(full).size;
40
+ }
41
+ catch {
42
+ // Skip unreadable entries — GC is best-effort.
43
+ }
44
+ }
45
+ return total;
46
+ }
47
+ /**
48
+ * Remove stale attachment session directories. A directory is removed only
49
+ * when BOTH: its name matches no active session id AND its mtime is older
50
+ * than the TTL. Dotfiles (`.gitignore`) and stray non-directories are left
51
+ * alone. Never throws.
52
+ */
53
+ export function sweepAttachments(options) {
54
+ const result = { removedSessions: 0, removedBytes: 0 };
55
+ const { root } = options;
56
+ if (!root || !existsSync(root))
57
+ return result;
58
+ const active = new Set(options.activeSessionIds ?? []);
59
+ const ttlMs = options.ttlMs ?? getAttachmentTtlMs();
60
+ const now = options.now ?? Date.now();
61
+ let entries;
62
+ try {
63
+ entries = readdirSync(root, { withFileTypes: true });
64
+ }
65
+ catch {
66
+ return result;
67
+ }
68
+ for (const entry of entries) {
69
+ // Only sweep session directories; leave `.gitignore` and strays alone.
70
+ if (!entry.isDirectory() || entry.name.startsWith("."))
71
+ continue;
72
+ if (active.has(entry.name))
73
+ continue;
74
+ const dir = join(root, entry.name);
75
+ let mtimeMs;
76
+ try {
77
+ const stat = statSync(dir);
78
+ if (!stat.isDirectory())
79
+ continue;
80
+ mtimeMs = stat.mtimeMs;
81
+ }
82
+ catch {
83
+ continue;
84
+ }
85
+ // `stat.mtimeMs` carries sub-ms precision while `Date.now()` does not —
86
+ // floor it so a directory created in the same millisecond as `now`
87
+ // (ttlMs = 0) is deterministically "older".
88
+ if (now - Math.floor(mtimeMs) < ttlMs)
89
+ continue;
90
+ try {
91
+ result.removedBytes += dirSizeBytes(dir);
92
+ rmSync(dir, { recursive: true, force: true });
93
+ result.removedSessions += 1;
94
+ }
95
+ catch {
96
+ // Best-effort.
97
+ }
98
+ }
99
+ return result;
100
+ }