@aexol/spectral 0.9.15 → 0.9.17

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 (54) hide show
  1. package/dist/memory/compaction.d.ts +1 -1
  2. package/dist/memory/compaction.js +3 -3
  3. package/dist/memory/config.d.ts +12 -0
  4. package/dist/memory/config.d.ts.map +1 -1
  5. package/dist/memory/config.js +27 -0
  6. package/dist/memory/deterministic-pruner.d.ts +65 -0
  7. package/dist/memory/deterministic-pruner.d.ts.map +1 -0
  8. package/dist/memory/deterministic-pruner.js +333 -0
  9. package/dist/memory/hooks/compaction-hook.d.ts.map +1 -1
  10. package/dist/memory/hooks/compaction-hook.js +160 -132
  11. package/dist/memory/hooks/inter-agent-receiver.d.ts +3 -0
  12. package/dist/memory/hooks/inter-agent-receiver.d.ts.map +1 -0
  13. package/dist/memory/hooks/inter-agent-receiver.js +183 -0
  14. package/dist/memory/hooks/observer-trigger.d.ts.map +1 -1
  15. package/dist/memory/hooks/observer-trigger.js +14 -0
  16. package/dist/memory/index.d.ts.map +1 -1
  17. package/dist/memory/index.js +6 -0
  18. package/dist/memory/inter-agent-broker-singleton.d.ts +5 -0
  19. package/dist/memory/inter-agent-broker-singleton.d.ts.map +1 -0
  20. package/dist/memory/inter-agent-broker-singleton.js +7 -0
  21. package/dist/memory/inter-agent-relay.d.ts +7 -0
  22. package/dist/memory/inter-agent-relay.d.ts.map +1 -0
  23. package/dist/memory/inter-agent-relay.js +41 -0
  24. package/dist/memory/project-observations-store.d.ts +7 -0
  25. package/dist/memory/project-observations-store.d.ts.map +1 -1
  26. package/dist/memory/prompts.d.ts.map +1 -1
  27. package/dist/memory/prompts.js +9 -0
  28. package/dist/memory/tools/receive-agent-observations.d.ts +17 -0
  29. package/dist/memory/tools/receive-agent-observations.d.ts.map +1 -0
  30. package/dist/memory/tools/receive-agent-observations.js +68 -0
  31. package/dist/memory/tools/share-project-observation.d.ts +8 -0
  32. package/dist/memory/tools/share-project-observation.d.ts.map +1 -0
  33. package/dist/memory/tools/share-project-observation.js +106 -0
  34. package/dist/memory/unified-compaction.d.ts +59 -0
  35. package/dist/memory/unified-compaction.d.ts.map +1 -0
  36. package/dist/memory/unified-compaction.js +332 -0
  37. package/dist/relay/dispatcher.d.ts +1 -1
  38. package/dist/relay/dispatcher.d.ts.map +1 -1
  39. package/dist/relay/dispatcher.js +13 -0
  40. package/dist/server/handlers/files-search.d.ts +26 -0
  41. package/dist/server/handlers/files-search.d.ts.map +1 -0
  42. package/dist/server/handlers/files-search.js +89 -0
  43. package/dist/server/inter-agent-broker.d.ts +111 -0
  44. package/dist/server/inter-agent-broker.d.ts.map +1 -0
  45. package/dist/server/inter-agent-broker.js +136 -0
  46. package/dist/server/session-stream.d.ts +1 -0
  47. package/dist/server/session-stream.d.ts.map +1 -1
  48. package/dist/server/session-stream.js +43 -0
  49. package/dist/server/storage.d.ts +28 -0
  50. package/dist/server/storage.d.ts.map +1 -1
  51. package/dist/server/storage.js +101 -0
  52. package/dist/server/wire.d.ts +15 -0
  53. package/dist/server/wire.d.ts.map +1 -1
  54. package/package.json +1 -1
@@ -0,0 +1,332 @@
1
+ /**
2
+ * Unified Compaction — single LLM call replacing both:
3
+ * 1. Core spectral generateSummary() (narrative conversation summary)
4
+ * 2. Sync catch-up observer (observation extraction from gap entries)
5
+ *
6
+ * Uses a text-based delimited output format (compatible with any model
7
+ * without requiring native structured-output support):
8
+ *
9
+ * ===NARRATIVE===
10
+ * ## Goal
11
+ * ...
12
+ *
13
+ * ===OBSERVATIONS===
14
+ * [YYYY-MM-DD HH:MM] [relevance] Content line
15
+ * ...
16
+ */
17
+ import { completeSimple } from "../sdk/ai/index.js";
18
+ import { extractFileOpsFromMessage, computeFileLists, createFileOps, formatFileOperations, } from "../sdk/coding-agent/core/compaction/utils.js";
19
+ import { hashId } from "./ids.js";
20
+ import { nowTimestamp, truncateRecordContent } from "./serialize.js";
21
+ import { debugLog } from "./debug-log.js";
22
+ // ============================================================================
23
+ // Section Delimiters
24
+ // ============================================================================
25
+ const NARRATIVE_SECTION = "===NARRATIVE===";
26
+ const OBSERVATIONS_SECTION = "===OBSERVATIONS===";
27
+ // ============================================================================
28
+ // Prompt Templates
29
+ // ============================================================================
30
+ const UNIFIED_SYSTEM_PROMPT = `You are a context compression agent. Your task has TWO complementary outputs that MUST be produced together in a single response.
31
+
32
+ TASK A — NARRATIVE SUMMARY: Create a structured checkpoint of the conversation progress. This is what another LLM reads to understand where the work stands.
33
+
34
+ TASK B — FACTUAL MEMORY: Extract timestamped observations (single-line facts with relevance tags). These records are the assistant's ONLY memory of this conversation after the raw messages fall out of context.
35
+
36
+ Do NOT continue the conversation. Do NOT respond to any questions in the conversation. ONLY output the structured result using the EXACT format specified.`;
37
+ function buildUnifiedPrompt(conversationText, previousSummary, customInstructions, currentTime) {
38
+ let prompt = `<conversation>\n${conversationText}\n</conversation>\n\n`;
39
+ if (previousSummary) {
40
+ prompt += `<previous-summary>\n${previousSummary}\n</previous-summary>\n\n`;
41
+ }
42
+ prompt += `Current local time: ${currentTime}\n\n`;
43
+ prompt += `Output your response in TWO sections delimited by the exact markers shown below.
44
+
45
+ SECTION A — ${NARRATIVE_SECTION}
46
+
47
+ Produce a structured context checkpoint summary using this EXACT format:
48
+
49
+ ## Goal
50
+ [What is the user trying to accomplish? Can be multiple items. If updating a previous summary, preserve existing goals and add new ones.]
51
+
52
+ ## Constraints & Preferences
53
+ - [Any constraints, preferences, or requirements mentioned by user]
54
+ - [Or "(none)" if none were mentioned]
55
+ [When updating: preserve existing constraints, add new ones.]
56
+
57
+ ## Progress
58
+ ### Done
59
+ - [x] [Completed tasks/changes. Preserve previously completed items, add newly completed ones.]
60
+
61
+ ### In Progress
62
+ - [ ] [Current work. Update based on progress.]
63
+
64
+ ### Blocked
65
+ - [Issues preventing progress, if any. Remove blockers that have been resolved.]
66
+
67
+ ## Key Decisions
68
+ - **[Decision]**: [Brief rationale]
69
+ [Preserve all previous decisions, add new ones.]
70
+
71
+ ## Next Steps
72
+ 1. [Ordered list of what should happen next. Update based on current state.]
73
+
74
+ ## Critical Context
75
+ - [Any data, examples, or references needed to continue]
76
+ - [Or "(none)" if not applicable]
77
+
78
+ Keep each section concise. Preserve exact file paths, function names, and error messages.
79
+ ${customInstructions ? `\nAdditional focus for the narrative: ${customInstructions}` : ""}
80
+
81
+ SECTION B — ${OBSERVATIONS_SECTION}
82
+
83
+ Extract NEW timestamped facts from the conversation. Each observation must be on its own line in this exact format:
84
+
85
+ [YYYY-MM-DD HH:MM] [relevance] Single-line plain prose fact. No markdown, no bullets.
86
+
87
+ Do NOT restate facts already captured in the <previous-summary>. Skip routine, low-information events (tool-call acks, status updates).
88
+
89
+ OBSERVATION CONTENT RULES:
90
+ - Single line of plain prose. No markdown, no bullets, no code fences, no XML/HTML tags, no emojis.
91
+ - Preserve user assertions exactly. When the user STATES something about themselves, their project, or their environment, capture it as an assertion. When the user ASKS something, capture it as a question.
92
+ - Use precise action verbs (completed, resolved, implemented, chose, configured etc.).
93
+ - Mark concrete completions explicitly: "completed: implemented X at path/to/file.ts; user confirmed tests pass."
94
+ - Split compound statements into separate observations (one fact per line).
95
+ - Group repeated similar tool calls into a single observation.
96
+ - Preserve exact file paths, function names, line numbers, error messages (verbatim), package names, identifiers.
97
+ - Use the timestamp from the closest conversation message. Fall back to the current local time if no message timestamp applies.
98
+
99
+ RELEVANCE LEVELS (pick one per observation):
100
+ - critical: user identity, role, persistent preferences, explicit corrections, concrete completions that future runs MUST NOT redo.
101
+ - high: non-trivial technical decisions, architectural direction, unresolved blockers, key constraints.
102
+ - medium: task-level context that helps within the current work. Default when unsure between medium and high.
103
+ - low: routine tool-call acks, repetitive status updates, trivially re-derivable content. Skip these unless they carry unique detail.
104
+
105
+ Do NOT default everything to "high" or "critical". Most observations should be "medium". Use "low" only for truly routine items.
106
+
107
+ Focus areas for observations:
108
+ - User identity, preferences, constraints, corrections.
109
+ - Project goals, architectural decisions, rationale.
110
+ - File paths and line numbers of changes.
111
+ - Error messages (verbatim).
112
+ - Completed work that should not be redone.
113
+ - Named identifiers (package names, function/variable names, ticket IDs, commit SHAs).`;
114
+ return prompt;
115
+ }
116
+ // ============================================================================
117
+ // Conversation Serialization
118
+ // ============================================================================
119
+ const TOOL_RESULT_MAX_CHARS = 2000;
120
+ const VISION_DESCRIPTION_PREFIX = "[Image description from ";
121
+ function stripVisionBlocks(text) {
122
+ if (!text.includes(VISION_DESCRIPTION_PREFIX))
123
+ return text;
124
+ const lines = text.split("\n");
125
+ const kept = [];
126
+ let skipping = false;
127
+ for (const line of lines) {
128
+ if (!skipping && line.startsWith(VISION_DESCRIPTION_PREFIX)) {
129
+ skipping = true;
130
+ continue;
131
+ }
132
+ if (skipping) {
133
+ if (line === "]") {
134
+ skipping = false;
135
+ }
136
+ continue;
137
+ }
138
+ kept.push(line);
139
+ }
140
+ return kept.join("\n").replace(/\n{3,}/g, "\n\n").trim();
141
+ }
142
+ function serializeMessagesForUnified(messages) {
143
+ const parts = [];
144
+ for (const msg of messages) {
145
+ if (msg.role === "user") {
146
+ const content = typeof msg.content === "string"
147
+ ? msg.content
148
+ : Array.isArray(msg.content)
149
+ ? msg.content.filter((c) => c.type === "text").map(c => c.text).join("")
150
+ : "";
151
+ const cleaned = stripVisionBlocks(content);
152
+ if (cleaned)
153
+ parts.push(`[User]: ${cleaned}`);
154
+ }
155
+ else if (msg.role === "assistant" && "content" in msg && Array.isArray(msg.content)) {
156
+ const textParts = [];
157
+ const toolParts = [];
158
+ for (const block of msg.content) {
159
+ if (block.type === "text" && "text" in block) {
160
+ textParts.push(block.text);
161
+ }
162
+ else if (block.type === "toolCall" && "name" in block) {
163
+ const args = block.arguments;
164
+ const argsStr = Object.keys(args).sort().map(k => `${k}=${JSON.stringify(args[k])}`).join(", ");
165
+ toolParts.push(`${block.name}(${argsStr})`);
166
+ }
167
+ }
168
+ const combined = [...textParts, ...(toolParts.length ? [`[Tools: ${toolParts.join("; ")}]`] : [])];
169
+ if (combined.length)
170
+ parts.push(`[Assistant]: ${combined.join("\n")}`);
171
+ }
172
+ else if (msg.role === "toolResult") {
173
+ const text = Array.isArray(msg.content)
174
+ ? msg.content.filter((c) => c.type === "text").map(c => c.text).join("")
175
+ : typeof msg.content === "string" ? msg.content : "";
176
+ if (text) {
177
+ const truncated = text.length > TOOL_RESULT_MAX_CHARS
178
+ ? `${text.slice(0, TOOL_RESULT_MAX_CHARS)}\n[...truncated]`
179
+ : text;
180
+ parts.push(`[Tool result]: ${truncated}`);
181
+ }
182
+ }
183
+ else if (msg.role === "compactionSummary" && "summary" in msg) {
184
+ parts.push(`[Previous compaction summary]: ${msg.summary}`);
185
+ }
186
+ }
187
+ return parts.join("\n\n");
188
+ }
189
+ // ============================================================================
190
+ // Output Parsing
191
+ // ============================================================================
192
+ const OBSERVATION_LINE_RE = /^\[(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2})\]\s*\[(low|medium|high|critical)\]\s*(.+)$/i;
193
+ function parseObservations(text) {
194
+ const results = [];
195
+ const lines = text.split("\n");
196
+ for (const line of lines) {
197
+ const trimmed = line.trim();
198
+ if (!trimmed || trimmed.startsWith("#") || trimmed === OBSERVATIONS_SECTION)
199
+ continue;
200
+ const match = trimmed.match(OBSERVATION_LINE_RE);
201
+ if (match) {
202
+ results.push({
203
+ timestamp: match[1],
204
+ relevance: match[2].toLowerCase(),
205
+ content: match[3].trim(),
206
+ });
207
+ }
208
+ }
209
+ return results;
210
+ }
211
+ function normalizeObservation(raw) {
212
+ const content = truncateRecordContent(raw.content);
213
+ if (!content)
214
+ return undefined;
215
+ const validRelevance = ["low", "medium", "high", "critical"];
216
+ const relevance = validRelevance.includes(raw.relevance)
217
+ ? raw.relevance
218
+ : "medium";
219
+ return {
220
+ id: hashId(content),
221
+ content,
222
+ timestamp: raw.timestamp,
223
+ relevance,
224
+ };
225
+ }
226
+ /**
227
+ * Generate a unified compaction result: narrative summary + observations
228
+ * in a single LLM call. Uses delimited text output for broad model compatibility.
229
+ */
230
+ export async function generateUnifiedCompaction(input) {
231
+ const { messagesToSummarize, gapChunk, model, apiKey, headers, signal, previousSummary, customInstructions, thinkingLevel, reserveTokens, } = input;
232
+ const maxTokens = Math.min(Math.floor(0.8 * reserveTokens), model.maxTokens > 0 ? model.maxTokens : Number.POSITIVE_INFINITY);
233
+ // Build conversation text
234
+ const parts = [];
235
+ const coreText = serializeMessagesForUnified(messagesToSummarize);
236
+ if (coreText)
237
+ parts.push(coreText);
238
+ if (gapChunk?.trim()) {
239
+ parts.push(`\n--- Gap entries (unobserved raw conversation) ---\n${gapChunk}`);
240
+ }
241
+ const conversationText = parts.join("\n\n");
242
+ const currentTime = nowTimestamp();
243
+ const userPrompt = buildUnifiedPrompt(conversationText, previousSummary, customInstructions, currentTime);
244
+ const promptMessages = [
245
+ {
246
+ role: "user",
247
+ content: [{ type: "text", text: userPrompt }],
248
+ timestamp: Date.now(),
249
+ },
250
+ ];
251
+ const options = { maxTokens, signal, apiKey, headers };
252
+ if (model.reasoning && thinkingLevel && thinkingLevel !== "off") {
253
+ options.reasoning = thinkingLevel;
254
+ }
255
+ debugLog("unified_compaction.start", {
256
+ messageCount: messagesToSummarize.length,
257
+ hasGap: !!gapChunk?.trim(),
258
+ conversationLength: conversationText.length,
259
+ });
260
+ const response = await completeSimple(model, { systemPrompt: UNIFIED_SYSTEM_PROMPT, messages: promptMessages }, options);
261
+ if (response.stopReason === "error") {
262
+ throw new Error(`Unified compaction failed: ${response.errorMessage || "Unknown error"}`);
263
+ }
264
+ if (response.stopReason === "aborted") {
265
+ throw new Error("Unified compaction aborted");
266
+ }
267
+ const fullText = response.content
268
+ .filter((c) => c.type === "text")
269
+ .map((c) => c.text)
270
+ .join("\n");
271
+ // Parse the two sections
272
+ const narrativeIdx = fullText.indexOf(NARRATIVE_SECTION);
273
+ const obsIdx = fullText.indexOf(OBSERVATIONS_SECTION);
274
+ let narrativeSummary;
275
+ let rawObservationsText;
276
+ if (narrativeIdx >= 0 && obsIdx >= 0) {
277
+ // Both sections present
278
+ if (narrativeIdx < obsIdx) {
279
+ narrativeSummary = fullText.slice(narrativeIdx + NARRATIVE_SECTION.length, obsIdx).trim();
280
+ }
281
+ else {
282
+ narrativeSummary = fullText.slice(narrativeIdx + NARRATIVE_SECTION.length).trim();
283
+ }
284
+ rawObservationsText = fullText.slice(obsIdx + OBSERVATIONS_SECTION.length).trim();
285
+ }
286
+ else if (narrativeIdx >= 0) {
287
+ // Only narrative
288
+ narrativeSummary = fullText.slice(narrativeIdx + NARRATIVE_SECTION.length).trim();
289
+ rawObservationsText = "";
290
+ }
291
+ else if (obsIdx >= 0) {
292
+ // Only observations (unusual but handle gracefully)
293
+ narrativeSummary = fullText.slice(0, obsIdx).trim();
294
+ rawObservationsText = fullText.slice(obsIdx + OBSERVATIONS_SECTION.length).trim();
295
+ }
296
+ else {
297
+ // No delimiters found — treat entire output as narrative
298
+ debugLog("unified_compaction.no_delimiters", { textLength: fullText.length });
299
+ narrativeSummary = fullText;
300
+ rawObservationsText = "";
301
+ }
302
+ // Parse observations
303
+ const rawObservations = parseObservations(rawObservationsText);
304
+ const observations = [];
305
+ const seen = new Set();
306
+ for (const raw of rawObservations) {
307
+ const normalized = normalizeObservation(raw);
308
+ if (normalized && !seen.has(normalized.id)) {
309
+ seen.add(normalized.id);
310
+ observations.push(normalized);
311
+ }
312
+ }
313
+ // Compute file operations
314
+ const fileOps = createFileOps();
315
+ for (const msg of messagesToSummarize) {
316
+ extractFileOpsFromMessage(msg, fileOps);
317
+ }
318
+ const { readFiles, modifiedFiles } = computeFileLists(fileOps);
319
+ // Append file operations to narrative
320
+ const fullNarrativeSummary = narrativeSummary + formatFileOperations(readFiles, modifiedFiles);
321
+ debugLog("unified_compaction.result", {
322
+ narrativeLength: fullNarrativeSummary.length,
323
+ observationCount: observations.length,
324
+ rawObservationLines: rawObservations.length,
325
+ });
326
+ return {
327
+ narrativeSummary: fullNarrativeSummary,
328
+ observations,
329
+ readFiles,
330
+ modifiedFiles,
331
+ };
332
+ }
@@ -48,7 +48,7 @@ import type { RelayClient } from "./client.js";
48
48
  * `id` is populated when the route had an `:id` placeholder.
49
49
  */
50
50
  interface RouteMatch {
51
- route: "list_projects" | "create_project" | "update_project" | "delete_project" | "bind_studio" | "list_project_sessions" | "create_session" | "get_session" | "update_session" | "delete_session" | "get_session_memory" | "get_session_memory_details" | "compact_session" | "remember_and_delete_session" | "fork_session" | "list_path_autocomplete" | "pick_directory" | "enqueue_prompt" | "get_prompt_queue" | "remove_prompt" | "clear_prompt_queue" | "list_mcp_status" | "get_settings" | "put_settings";
51
+ route: "list_projects" | "create_project" | "update_project" | "delete_project" | "bind_studio" | "list_project_sessions" | "create_session" | "get_session" | "update_session" | "delete_session" | "get_session_memory" | "get_session_memory_details" | "compact_session" | "remember_and_delete_session" | "fork_session" | "list_path_autocomplete" | "pick_directory" | "search_project_files" | "enqueue_prompt" | "get_prompt_queue" | "remove_prompt" | "clear_prompt_queue" | "list_mcp_status" | "get_settings" | "put_settings";
52
52
  id?: string;
53
53
  /** Parsed query params, if the path carried a `?...` suffix. */
54
54
  query?: URLSearchParams;
@@ -1 +1 @@
1
- {"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../../src/relay/dispatcher.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../../src/relay/dispatcher.ts"],"names":[],"mappings":"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"}
@@ -43,6 +43,7 @@ import { BadRequestError, NotFoundError } from "../server/handlers/errors.js";
43
43
  import { handleListMcpStatus } from "../server/handlers/mcp-status.js";
44
44
  import { handlePathAutocomplete } from "../server/handlers/paths-autocomplete.js";
45
45
  import { handlePickDirectory } from "../server/handlers/paths-pick-directory.js";
46
+ import { handleSearchProjectFiles } from "../server/handlers/files-search.js";
46
47
  import { handleBindStudioProject, handleCreateProject, handleDeleteProject, handleListProjects, handleListSessionsByProject, handleUpdateProject, } from "../server/handlers/projects.js";
47
48
  import { handleCompactSession, handleCreateSession, handleDeleteSession, handleForkSession, handleRememberAndDeleteSession, handleGetSessionDetail, handleGetSessionMemoryDetails, handleGetSessionMemoryStatus, handleUpdateSession, } from "../server/handlers/sessions.js";
48
49
  import { handleClearPromptQueue, handleEnqueuePrompt, handleGetPromptQueue, handleRemovePrompt, } from "../server/handlers/queue.js";
@@ -101,6 +102,14 @@ export function matchRoute(method, path) {
101
102
  return { route: "create_session" };
102
103
  return null;
103
104
  }
105
+ // /api/projects/:id/files/search
106
+ const projectFilesMatch = /^\/api\/projects\/([^/]+)\/files\/search$/.exec(cleanPath);
107
+ if (projectFilesMatch) {
108
+ const id = decodeURIComponent(projectFilesMatch[1]);
109
+ if (method === "GET")
110
+ return { route: "search_project_files", id, query };
111
+ return null;
112
+ }
104
113
  // /api/projects/:id/bind-studio
105
114
  const bindStudioMatch = /^\/api\/projects\/([^/]+)\/bind-studio$/.exec(cleanPath);
106
115
  if (bindStudioMatch) {
@@ -406,6 +415,10 @@ async function dispatchRoute(match, body, deps) {
406
415
  }
407
416
  case "pick_directory":
408
417
  return handlePickDirectory();
418
+ case "search_project_files": {
419
+ const searchQuery = match.query?.get("query") ?? "";
420
+ return handleSearchProjectFiles(store, id, searchQuery);
421
+ }
409
422
  case "enqueue_prompt":
410
423
  return handleEnqueuePrompt(store, manager, id, asObject(body));
411
424
  case "get_prompt_queue":
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Project file search for the agent composer `@` mention dropdown.
3
+ *
4
+ * `GET /api/projects/:id/files/search?query=<query>` returns files whose
5
+ * relative path contains the query (case-insensitive). Results are limited
6
+ * to `MAX_RESULTS` and common generated directories are always excluded.
7
+ *
8
+ * The handler is synchronous-style but returns a Promise because `glob`
9
+ * exposes an async API.
10
+ */
11
+ import type { SessionStore } from "../storage.js";
12
+ export interface ProjectFileSearchResult {
13
+ /** Absolute filesystem path. */
14
+ path: string;
15
+ /** Path relative to the project root (POSIX separators). */
16
+ relativePath: string;
17
+ /** Filename (basename). */
18
+ name: string;
19
+ }
20
+ /**
21
+ * Search files in a project whose relative path contains `query`
22
+ * (case-insensitive). Returns an empty array when the project has no
23
+ * files, when the query is empty, or when the project path is unreadable.
24
+ */
25
+ export declare function handleSearchProjectFiles(store: SessionStore, projectId: string, query: string): Promise<ProjectFileSearchResult[]>;
26
+ //# sourceMappingURL=files-search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"files-search.d.ts","sourceRoot":"","sources":["../../../src/server/handlers/files-search.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAOH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAElD,MAAM,WAAW,uBAAuB;IACtC,gCAAgC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,4DAA4D;IAC5D,YAAY,EAAE,MAAM,CAAC;IACrB,2BAA2B;IAC3B,IAAI,EAAE,MAAM,CAAC;CACd;AAyBD;;;;GAIG;AACH,wBAAsB,wBAAwB,CAC5C,KAAK,EAAE,YAAY,EACnB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,uBAAuB,EAAE,CAAC,CAqDpC"}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Project file search for the agent composer `@` mention dropdown.
3
+ *
4
+ * `GET /api/projects/:id/files/search?query=<query>` returns files whose
5
+ * relative path contains the query (case-insensitive). Results are limited
6
+ * to `MAX_RESULTS` and common generated directories are always excluded.
7
+ *
8
+ * The handler is synchronous-style but returns a Promise because `glob`
9
+ * exposes an async API.
10
+ */
11
+ import { readFileSync } from "node:fs";
12
+ import { join } from "node:path";
13
+ import { glob } from "glob";
14
+ import { NotFoundError } from "./errors.js";
15
+ /** Maximum number of suggestions shown in the composer dropdown. */
16
+ const MAX_RESULTS = 20;
17
+ /**
18
+ * Directories/patterns that are never useful as `@` mention targets.
19
+ * These are applied in addition to the project's own `.gitignore`.
20
+ */
21
+ const ALWAYS_IGNORE = [
22
+ "**/node_modules/**",
23
+ "**/.git/**",
24
+ "**/.next/**",
25
+ "**/.nuxt/**",
26
+ "**/.svelte-kit/**",
27
+ "**/.astro/**",
28
+ "**/dist/**",
29
+ "**/build/**",
30
+ "**/.spectral/**",
31
+ "**/.vscode/**",
32
+ "**/.idea/**",
33
+ "**/coverage/**",
34
+ "**/*.log",
35
+ ];
36
+ /**
37
+ * Search files in a project whose relative path contains `query`
38
+ * (case-insensitive). Returns an empty array when the project has no
39
+ * files, when the query is empty, or when the project path is unreadable.
40
+ */
41
+ export async function handleSearchProjectFiles(store, projectId, query) {
42
+ const project = store.getProject(projectId);
43
+ if (!project) {
44
+ throw new NotFoundError("Project not found");
45
+ }
46
+ const projectPath = project.path;
47
+ const trimmedQuery = query.trim();
48
+ if (!trimmedQuery) {
49
+ return [];
50
+ }
51
+ const ignorePatterns = [...ALWAYS_IGNORE];
52
+ // Read root .gitignore when present. Nested .gitignores are ignored for
53
+ // now — this keeps the search fast and the implementation small; the
54
+ // hard-coded ALWAYS_IGNORE already catches the largest generated dirs.
55
+ try {
56
+ const gitignore = readFileSync(join(projectPath, ".gitignore"), "utf8");
57
+ const patterns = gitignore
58
+ .split("\n")
59
+ .map((line) => line.trim())
60
+ .filter((line) => line && !line.startsWith("#"));
61
+ ignorePatterns.push(...patterns);
62
+ }
63
+ catch {
64
+ // .gitignore missing or unreadable — proceed without it.
65
+ }
66
+ const lowerQuery = trimmedQuery.toLowerCase();
67
+ let matches;
68
+ try {
69
+ matches = await glob("**/*", {
70
+ cwd: projectPath,
71
+ nodir: true,
72
+ dot: false,
73
+ ignore: ignorePatterns,
74
+ });
75
+ }
76
+ catch {
77
+ // Project path unreadable or not a directory — return empty rather
78
+ // than crashing the composer dropdown.
79
+ return [];
80
+ }
81
+ const filtered = matches
82
+ .filter((relativePath) => relativePath.toLowerCase().includes(lowerQuery))
83
+ .slice(0, MAX_RESULTS);
84
+ return filtered.map((relativePath) => ({
85
+ path: join(projectPath, relativePath),
86
+ relativePath,
87
+ name: relativePath.split("/").pop() ?? relativePath,
88
+ }));
89
+ }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Inter-agent communication broker for `spectral serve`.
3
+ *
4
+ * Provides project-scoped, channel-based messaging between active agent
5
+ * sessions running in the same spectral process on the same machine. Messages
6
+ * are durably backed by SQLite so they survive reconnects and can be polled by
7
+ * sessions that were not attached when the message was sent.
8
+ *
9
+ * The broker is intentionally process-local: subscriptions are in-memory
10
+ * inside the `spectral serve` daemon. Cross-process delivery on the same
11
+ * machine happens through the shared SQLite backing when each process creates
12
+ * its own broker instance pointing at the same `sessions.db`.
13
+ */
14
+ import type { SessionStore } from "./storage.js";
15
+ export interface InterAgentMessage {
16
+ /** Unique message id (UUID). */
17
+ id: string;
18
+ /** Owning project. */
19
+ projectId: string;
20
+ /** Logical channel within the project (e.g. "memory", "task-handoff"). */
21
+ channel: string;
22
+ /** Session that sent the message. */
23
+ senderSessionId: string;
24
+ /** Optional human-readable sender name. */
25
+ senderName?: string;
26
+ /** When set, the message is a private direct message; otherwise broadcast. */
27
+ recipientSessionId?: string;
28
+ /** Message kind used to route interpretation (e.g. "observation"). */
29
+ kind: string;
30
+ /** Opaque JSON payload. */
31
+ payload: string;
32
+ /** Timestamp (ms since epoch) when the message was created. */
33
+ createdAt: number;
34
+ /** Timestamp (ms) when the recipient first polled it, if ever. */
35
+ deliveredAt?: number;
36
+ /** Timestamp (ms) after which the message may be garbage collected. */
37
+ expiresAt?: number;
38
+ }
39
+ export interface InterAgentSendInput {
40
+ projectId: string;
41
+ channel: string;
42
+ senderSessionId: string;
43
+ senderName?: string;
44
+ /** Omit for broadcast. */
45
+ recipientSessionId?: string;
46
+ kind: string;
47
+ payload: string;
48
+ /** Default: 24 hours. Set to 0 to disable expiry. */
49
+ ttlSeconds?: number;
50
+ }
51
+ export interface InterAgentPollInput {
52
+ projectId: string;
53
+ /** Filter by channel. Omit to poll all channels in the project. */
54
+ channel?: string;
55
+ /** Poll only messages addressed to this session (or broadcast). Omit to poll all. */
56
+ recipientSessionId?: string;
57
+ /** Only return messages created after this timestamp (exclusive). */
58
+ since?: number;
59
+ /** Maximum messages to return. Default 100. */
60
+ limit?: number;
61
+ /** Mark returned messages as delivered. Default true. */
62
+ markDelivered?: boolean;
63
+ }
64
+ export interface InterAgentSubscribeInput {
65
+ sessionId: string;
66
+ projectId: string;
67
+ channel?: string;
68
+ handler: (msg: InterAgentMessage) => void;
69
+ }
70
+ export interface InterAgentBrokerOptions {
71
+ store: SessionStore;
72
+ /** Default TTL in seconds for messages that don't specify one. Default 86400. */
73
+ defaultTtlSeconds?: number;
74
+ }
75
+ /**
76
+ * In-process broker for inter-session messages.
77
+ *
78
+ * All public methods are synchronous because the underlying `SessionStore`
79
+ * uses better-sqlite3. Live subscribers are called synchronously from `send`.
80
+ */
81
+ export declare class InterAgentBroker {
82
+ private readonly store;
83
+ private readonly defaultTtlSeconds;
84
+ /**
85
+ * In-process subscriptions keyed by `projectId:channel`. Each active session
86
+ * in the current process registers a handler here to receive live pushes.
87
+ */
88
+ private readonly subscriptions;
89
+ constructor(opts: InterAgentBrokerOptions);
90
+ /**
91
+ * Send a message. Persists it to SQLite and synchronously pushes it to any
92
+ * in-process subscribers whose session/channel filters match.
93
+ */
94
+ send(input: InterAgentSendInput): InterAgentMessage;
95
+ /**
96
+ * Poll for messages matching the filter. By default marks returned messages
97
+ * as delivered so they won't be returned again on subsequent polls.
98
+ */
99
+ poll(input: InterAgentPollInput): InterAgentMessage[];
100
+ /**
101
+ * Subscribe a session to live pushes for a project/channel. Returns an
102
+ * unsubscribe function. Subscriptions are in-process only.
103
+ */
104
+ subscribe(input: InterAgentSubscribeInput): () => void;
105
+ /**
106
+ * Delete expired messages from the backing store. Call periodically.
107
+ */
108
+ cleanup(maxAgeSeconds?: number): void;
109
+ private pushToSubscribers;
110
+ }
111
+ //# sourceMappingURL=inter-agent-broker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inter-agent-broker.d.ts","sourceRoot":"","sources":["../../src/server/inter-agent-broker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAGjD,MAAM,WAAW,iBAAiB;IAChC,gCAAgC;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,sBAAsB;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,OAAO,EAAE,MAAM,CAAC;IAChB,qCAAqC;IACrC,eAAe,EAAE,MAAM,CAAC;IACxB,2CAA2C;IAC3C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,8EAA8E;IAC9E,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,sEAAsE;IACtE,IAAI,EAAE,MAAM,CAAC;IACb,2BAA2B;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,+DAA+D;IAC/D,SAAS,EAAE,MAAM,CAAC;IAClB,kEAAkE;IAClE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uEAAuE;IACvE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0BAA0B;IAC1B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,qDAAqD;IACrD,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,mEAAmE;IACnE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qFAAqF;IACrF,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,qEAAqE;IACrE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yDAAyD;IACzD,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,CAAC,GAAG,EAAE,iBAAiB,KAAK,IAAI,CAAC;CAC3C;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,YAAY,CAAC;IACpB,iFAAiF;IACjF,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;GAKG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;IACrC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAE3C;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA4D;gBAE9E,IAAI,EAAE,uBAAuB;IAKzC;;;OAGG;IACH,IAAI,CAAC,KAAK,EAAE,mBAAmB,GAAG,iBAAiB;IAyBnD;;;OAGG;IACH,IAAI,CAAC,KAAK,EAAE,mBAAmB,GAAG,iBAAiB,EAAE;IAWrD;;;OAGG;IACH,SAAS,CAAC,KAAK,EAAE,wBAAwB,GAAG,MAAM,IAAI;IA4BtD;;OAEG;IACH,OAAO,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI;IAQrC,OAAO,CAAC,iBAAiB;CAqB1B"}