@aexol/spectral 0.9.15 → 0.9.16

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 (44) hide show
  1. package/dist/memory/config.d.ts +12 -0
  2. package/dist/memory/config.d.ts.map +1 -1
  3. package/dist/memory/config.js +27 -0
  4. package/dist/memory/hooks/compaction-hook.d.ts.map +1 -1
  5. package/dist/memory/hooks/compaction-hook.js +2 -0
  6. package/dist/memory/hooks/inter-agent-receiver.d.ts +3 -0
  7. package/dist/memory/hooks/inter-agent-receiver.d.ts.map +1 -0
  8. package/dist/memory/hooks/inter-agent-receiver.js +183 -0
  9. package/dist/memory/hooks/observer-trigger.d.ts.map +1 -1
  10. package/dist/memory/hooks/observer-trigger.js +14 -0
  11. package/dist/memory/index.d.ts.map +1 -1
  12. package/dist/memory/index.js +6 -0
  13. package/dist/memory/inter-agent-broker-singleton.d.ts +5 -0
  14. package/dist/memory/inter-agent-broker-singleton.d.ts.map +1 -0
  15. package/dist/memory/inter-agent-broker-singleton.js +7 -0
  16. package/dist/memory/inter-agent-relay.d.ts +7 -0
  17. package/dist/memory/inter-agent-relay.d.ts.map +1 -0
  18. package/dist/memory/inter-agent-relay.js +41 -0
  19. package/dist/memory/project-observations-store.d.ts +7 -0
  20. package/dist/memory/project-observations-store.d.ts.map +1 -1
  21. package/dist/memory/tools/receive-agent-observations.d.ts +17 -0
  22. package/dist/memory/tools/receive-agent-observations.d.ts.map +1 -0
  23. package/dist/memory/tools/receive-agent-observations.js +68 -0
  24. package/dist/memory/tools/share-project-observation.d.ts +8 -0
  25. package/dist/memory/tools/share-project-observation.d.ts.map +1 -0
  26. package/dist/memory/tools/share-project-observation.js +106 -0
  27. package/dist/relay/dispatcher.d.ts +1 -1
  28. package/dist/relay/dispatcher.d.ts.map +1 -1
  29. package/dist/relay/dispatcher.js +13 -0
  30. package/dist/server/handlers/files-search.d.ts +26 -0
  31. package/dist/server/handlers/files-search.d.ts.map +1 -0
  32. package/dist/server/handlers/files-search.js +89 -0
  33. package/dist/server/inter-agent-broker.d.ts +111 -0
  34. package/dist/server/inter-agent-broker.d.ts.map +1 -0
  35. package/dist/server/inter-agent-broker.js +136 -0
  36. package/dist/server/session-stream.d.ts +1 -0
  37. package/dist/server/session-stream.d.ts.map +1 -1
  38. package/dist/server/session-stream.js +43 -0
  39. package/dist/server/storage.d.ts +28 -0
  40. package/dist/server/storage.d.ts.map +1 -1
  41. package/dist/server/storage.js +101 -0
  42. package/dist/server/wire.d.ts +15 -0
  43. package/dist/server/wire.d.ts.map +1 -1
  44. package/package.json +1 -1
@@ -0,0 +1,106 @@
1
+ import { Type } from "../../sdk/ai/index.js";
2
+ import { defineTool } from "../../sdk/coding-agent/index.js";
3
+ import { getProjectObsStore } from "../project-observations-store.js";
4
+ import { getInterAgentBroker } from "../inter-agent-broker-singleton.js";
5
+ import { getMemoryState } from "../branch.js";
6
+ import { RELEVANCE_VALUES } from "../types.js";
7
+ export const SHARE_PROJECT_OBSERVATION_TOOL_NAME = "share_project_observation";
8
+ function findObservationInBranch(branch, memoryId) {
9
+ if (!Array.isArray(branch))
10
+ return null;
11
+ const state = getMemoryState(branch);
12
+ for (const obs of state.committedObs) {
13
+ if (obs.id === memoryId) {
14
+ return { id: obs.id, content: obs.content, relevance: obs.relevance, originType: "observation" };
15
+ }
16
+ }
17
+ for (const obs of state.pendingObs) {
18
+ if (obs.id === memoryId) {
19
+ return { id: obs.id, content: obs.content, relevance: obs.relevance, originType: "observation" };
20
+ }
21
+ }
22
+ for (const reflection of state.reflections) {
23
+ const id = typeof reflection === "string" ? undefined : reflection.id;
24
+ const content = typeof reflection === "string" ? reflection : reflection.content;
25
+ if (id === memoryId) {
26
+ return { id, content, relevance: "high", originType: "reflection" };
27
+ }
28
+ }
29
+ return null;
30
+ }
31
+ function findObservationInStore(store, projectId, memoryId) {
32
+ const obs = store.getProjectObservationById(projectId, memoryId);
33
+ if (!obs)
34
+ return null;
35
+ const relevance = RELEVANCE_VALUES.includes(obs.relevance)
36
+ ? obs.relevance
37
+ : "medium";
38
+ return { id: obs.id, content: obs.content, relevance, originType: "observation" };
39
+ }
40
+ export const shareProjectObservationTool = defineTool({
41
+ name: SHARE_PROJECT_OBSERVATION_TOOL_NAME,
42
+ label: "Share observation with other agents",
43
+ description: "Broadcast a project observation or reflection to other active sessions working on this project. " +
44
+ "Use this when you discover a fact another agent should know immediately, " +
45
+ "rather than waiting for them to search project memory.",
46
+ promptSnippet: "Use share_project_observation(memory_id) to push a known observation to other active sessions.",
47
+ promptGuidelines: [
48
+ "Only share observations that are relevant to collaborators on the same project.",
49
+ "Prefer sharing high/critical relevance facts — avoid noise.",
50
+ "The memory_id must be a 12-character lowercase hex id from the current branch or project memory.",
51
+ ],
52
+ parameters: Type.Object({
53
+ memory_id: Type.String({
54
+ pattern: "^[a-f0-9]{12}$",
55
+ description: "12-character observation/reflection id.",
56
+ }),
57
+ }),
58
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
59
+ const store = getProjectObsStore();
60
+ const broker = getInterAgentBroker();
61
+ if (!store || !broker) {
62
+ return {
63
+ content: [{ type: "text", text: "Sharing is unavailable outside spectral serve." }],
64
+ details: { status: "unavailable" },
65
+ };
66
+ }
67
+ const projectId = store.getProjectByCwd(ctx.cwd);
68
+ const sessionId = ctx.sessionManager.getSessionId?.();
69
+ if (!projectId || !sessionId) {
70
+ return {
71
+ content: [{ type: "text", text: "No project/session context." }],
72
+ details: { status: "no_context" },
73
+ };
74
+ }
75
+ const branch = ctx.sessionManager.getBranch?.();
76
+ const obs = findObservationInBranch(branch, params.memory_id)
77
+ ?? findObservationInStore(store, projectId, params.memory_id);
78
+ if (!obs) {
79
+ return {
80
+ content: [{ type: "text", text: `Observation \`${params.memory_id}\` not found.` }],
81
+ details: { status: "not_found" },
82
+ };
83
+ }
84
+ broker.send({
85
+ projectId,
86
+ channel: "memory",
87
+ senderSessionId: sessionId,
88
+ kind: "observation",
89
+ payload: JSON.stringify({
90
+ memoryId: obs.id,
91
+ content: obs.content,
92
+ relevance: obs.relevance,
93
+ sourceSessionId: sessionId,
94
+ originType: obs.originType,
95
+ timestamp: new Date().toISOString(),
96
+ }),
97
+ });
98
+ return {
99
+ content: [{ type: "text", text: `Shared observation \`${obs.id}\` with other project sessions.` }],
100
+ details: { status: "ok", memoryId: obs.id },
101
+ };
102
+ },
103
+ });
104
+ export function registerShareProjectObservationTool(ext) {
105
+ ext.registerTool(shareProjectObservationTool);
106
+ }
@@ -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"}
@@ -0,0 +1,136 @@
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 { randomUUID } from "node:crypto";
15
+ /**
16
+ * In-process broker for inter-session messages.
17
+ *
18
+ * All public methods are synchronous because the underlying `SessionStore`
19
+ * uses better-sqlite3. Live subscribers are called synchronously from `send`.
20
+ */
21
+ export class InterAgentBroker {
22
+ store;
23
+ defaultTtlSeconds;
24
+ /**
25
+ * In-process subscriptions keyed by `projectId:channel`. Each active session
26
+ * in the current process registers a handler here to receive live pushes.
27
+ */
28
+ subscriptions = new Map();
29
+ constructor(opts) {
30
+ this.store = opts.store;
31
+ this.defaultTtlSeconds = opts.defaultTtlSeconds ?? 24 * 60 * 60;
32
+ }
33
+ /**
34
+ * Send a message. Persists it to SQLite and synchronously pushes it to any
35
+ * in-process subscribers whose session/channel filters match.
36
+ */
37
+ send(input) {
38
+ const now = Date.now();
39
+ const ttl = input.ttlSeconds === undefined
40
+ ? this.defaultTtlSeconds
41
+ : input.ttlSeconds;
42
+ const msg = {
43
+ id: randomUUID(),
44
+ projectId: input.projectId,
45
+ channel: input.channel,
46
+ senderSessionId: input.senderSessionId,
47
+ senderName: input.senderName,
48
+ recipientSessionId: input.recipientSessionId,
49
+ kind: input.kind,
50
+ payload: input.payload,
51
+ createdAt: now,
52
+ expiresAt: ttl > 0 ? now + ttl * 1000 : undefined,
53
+ };
54
+ this.store.insertInterAgentMessage(msg);
55
+ this.pushToSubscribers(msg);
56
+ return msg;
57
+ }
58
+ /**
59
+ * Poll for messages matching the filter. By default marks returned messages
60
+ * as delivered so they won't be returned again on subsequent polls.
61
+ */
62
+ poll(input) {
63
+ return this.store.pollInterAgentMessages({
64
+ projectId: input.projectId,
65
+ channel: input.channel,
66
+ recipientSessionId: input.recipientSessionId,
67
+ since: input.since,
68
+ limit: input.limit ?? 100,
69
+ markDelivered: input.markDelivered ?? true,
70
+ });
71
+ }
72
+ /**
73
+ * Subscribe a session to live pushes for a project/channel. Returns an
74
+ * unsubscribe function. Subscriptions are in-process only.
75
+ */
76
+ subscribe(input) {
77
+ const key = subscriptionKey(input.projectId, input.channel);
78
+ let set = this.subscriptions.get(key);
79
+ if (!set) {
80
+ set = new Set();
81
+ this.subscriptions.set(key, set);
82
+ }
83
+ const wrapped = (msg) => {
84
+ // Don't deliver the sender's own messages back to itself.
85
+ if (msg.senderSessionId === input.sessionId)
86
+ return;
87
+ // Respect direct-message targeting.
88
+ if (msg.recipientSessionId && msg.recipientSessionId !== input.sessionId)
89
+ return;
90
+ input.handler(msg);
91
+ };
92
+ set.add(wrapped);
93
+ return () => {
94
+ const current = this.subscriptions.get(key);
95
+ if (!current)
96
+ return;
97
+ current.delete(wrapped);
98
+ if (current.size === 0) {
99
+ this.subscriptions.delete(key);
100
+ }
101
+ };
102
+ }
103
+ /**
104
+ * Delete expired messages from the backing store. Call periodically.
105
+ */
106
+ cleanup(maxAgeSeconds) {
107
+ const now = Date.now();
108
+ if (maxAgeSeconds !== undefined) {
109
+ this.store.deleteInterAgentMessagesOlderThan(now - maxAgeSeconds * 1000);
110
+ }
111
+ this.store.deleteExpiredInterAgentMessages(now);
112
+ }
113
+ pushToSubscribers(msg) {
114
+ // Try both channel-specific and project-wide subscriptions.
115
+ const keys = [
116
+ subscriptionKey(msg.projectId, msg.channel),
117
+ subscriptionKey(msg.projectId, undefined),
118
+ ];
119
+ for (const key of keys) {
120
+ const set = this.subscriptions.get(key);
121
+ if (!set)
122
+ continue;
123
+ for (const handler of set) {
124
+ try {
125
+ handler(msg);
126
+ }
127
+ catch (err) {
128
+ console.error(`[inter-agent] subscriber error: ${err instanceof Error ? err.message : String(err)}`);
129
+ }
130
+ }
131
+ }
132
+ }
133
+ }
134
+ function subscriptionKey(projectId, channel) {
135
+ return channel ? `${projectId}:${channel}` : projectId;
136
+ }
@@ -196,6 +196,7 @@ export declare class SessionStreamManager {
196
196
  private readonly machineJwt;
197
197
  private readonly bridgeFactory;
198
198
  private readonly agentDir;
199
+ private readonly broker;
199
200
  private readonly streams;
200
201
  private disposed;
201
202
  constructor(opts: SessionStreamManagerOptions);
@@ -1 +1 @@
1
- {"version":3,"file":"session-stream.d.ts","sourceRoot":"","sources":["../../src/server/session-stream.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"session-stream.d.ts","sourceRoot":"","sources":["../../src/server/session-stream.ts"],"names":[],"mappings":"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"}
@@ -38,6 +38,8 @@
38
38
  */
39
39
  import { randomUUID } from "node:crypto";
40
40
  import { AgentBridge } from "./agent-bridge.js";
41
+ import { InterAgentBroker } from "./inter-agent-broker.js";
42
+ import { setInterAgentBroker } from "../memory/inter-agent-broker-singleton.js";
41
43
  import { getMemoryState, isSourceEntry, rawTokensSinceLastBound, rawTokensSinceLastCompaction, } from "../memory/branch.js";
42
44
  import { observationPoolTokens, renderSummary } from "../memory/compaction.js";
43
45
  import { loadConfig } from "../memory/config.js";
@@ -233,6 +235,7 @@ export class SessionStreamManager {
233
235
  machineJwt;
234
236
  bridgeFactory;
235
237
  agentDir;
238
+ broker;
236
239
  streams = new Map();
237
240
  disposed = false;
238
241
  constructor(opts) {
@@ -242,13 +245,19 @@ export class SessionStreamManager {
242
245
  this.machineJwt = opts.machineJwt;
243
246
  this.bridgeFactory = opts.bridgeFactory ?? DEFAULT_BRIDGE_FACTORY;
244
247
  this.agentDir = opts.agentDir;
248
+ // Broker for project-scoped inter-session communication.
249
+ this.broker = new InterAgentBroker({ store: this.store });
245
250
  // Wire the project observations store singleton so the memory extension's
246
251
  // read_project_observations tool can query cross-session observations.
247
252
  setProjectObsStore({
248
253
  insertProjectObservations: (pid, sid, obs, ts) => this.store.insertProjectObservations(pid, sid, obs, ts),
249
254
  searchProjectObservations: (pid, q, limit) => this.store.searchProjectObservations(pid, q, limit),
255
+ getProjectObservationById: (pid, oid) => this.store.getProjectObservationById(pid, oid),
250
256
  getProjectByCwd: (cwd) => this.store.getProjectByCwd(cwd),
251
257
  });
258
+ // Wire the inter-agent broker singleton so the memory extension can send
259
+ // and receive live observation messages between active sessions.
260
+ setInterAgentBroker(this.broker);
252
261
  }
253
262
  /**
254
263
  * Attach a subscriber to a session. Lazily creates the underlying spectral
@@ -1048,6 +1057,40 @@ export class SessionStreamManager {
1048
1057
  },
1049
1058
  };
1050
1059
  stream.bridge = this.bridgeFactory(bridgeOpts);
1060
+ // Subscribe this session to live inter-agent observation messages so the
1061
+ // browser UI can surface them even before the memory extension injects
1062
+ // them into the agent context.
1063
+ if (projectId) {
1064
+ stream.interAgentUnsubscribe = this.broker.subscribe({
1065
+ sessionId,
1066
+ projectId,
1067
+ channel: "memory",
1068
+ handler: (msg) => {
1069
+ if (msg.senderSessionId === sessionId)
1070
+ return;
1071
+ if (msg.kind !== "observation")
1072
+ return;
1073
+ try {
1074
+ const payload = JSON.parse(msg.payload);
1075
+ const relevance = payload.relevance;
1076
+ this.broadcast(stream, {
1077
+ type: "inter_agent_observation",
1078
+ projectId: msg.projectId,
1079
+ sourceSessionId: msg.senderSessionId,
1080
+ sourceSessionName: msg.senderName,
1081
+ memoryId: String(payload.memoryId ?? ""),
1082
+ content: String(payload.content ?? ""),
1083
+ relevance,
1084
+ originType: payload.originType === "reflection" ? "reflection" : "observation",
1085
+ timestamp: String(payload.timestamp ?? new Date().toISOString()),
1086
+ });
1087
+ }
1088
+ catch (err) {
1089
+ console.error(`[spectral] error: failed to broadcast inter-agent observation: ${err instanceof Error ? err.message : String(err)}`);
1090
+ }
1091
+ },
1092
+ });
1093
+ }
1051
1094
  stream.ready = stream.bridge
1052
1095
  .start()
1053
1096
  .then(() => {