relaybrain 0.0.0-stage → 0.2.1

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 (140) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +434 -3
  3. package/backend/coordinator/client.js +364 -0
  4. package/backend/coordinator/lockTable.js +649 -0
  5. package/backend/coordinator/server.js +179 -0
  6. package/backend/lib/centralAuth.js +62 -0
  7. package/backend/lib/centralStore.js +112 -0
  8. package/backend/lib/centralSync.js +88 -0
  9. package/backend/lib/cloneRepo.js +108 -0
  10. package/backend/lib/collisionMetrics.js +231 -0
  11. package/backend/lib/conflicts.js +35 -0
  12. package/backend/lib/dashboard.js +53 -0
  13. package/backend/lib/depGraph.js +393 -0
  14. package/backend/lib/deps.js +46 -0
  15. package/backend/lib/dirtyFiles.js +33 -0
  16. package/backend/lib/editMatchers.js +55 -0
  17. package/backend/lib/falkor/analytics.js +228 -0
  18. package/backend/lib/falkor/bus.js +16 -0
  19. package/backend/lib/falkor/central.js +281 -0
  20. package/backend/lib/falkor/client.js +165 -0
  21. package/backend/lib/falkor/config.js +73 -0
  22. package/backend/lib/falkor/coord.js +299 -0
  23. package/backend/lib/falkor/cursor.js +58 -0
  24. package/backend/lib/falkor/demo.js +221 -0
  25. package/backend/lib/falkor/fixtures.js +85 -0
  26. package/backend/lib/falkor/index.js +146 -0
  27. package/backend/lib/falkor/ingest.js +233 -0
  28. package/backend/lib/falkor/intents.js +32 -0
  29. package/backend/lib/falkor/keys.js +102 -0
  30. package/backend/lib/falkor/migrations.js +56 -0
  31. package/backend/lib/falkor/project.js +307 -0
  32. package/backend/lib/falkor/query.js +344 -0
  33. package/backend/lib/falkor/redact.js +30 -0
  34. package/backend/lib/falkor/routes.js +125 -0
  35. package/backend/lib/falkor/schema.js +108 -0
  36. package/backend/lib/falkor/write.js +281 -0
  37. package/backend/lib/githubLogin.js +108 -0
  38. package/backend/lib/githubOAuth.js +42 -0
  39. package/backend/lib/githubRepo.js +117 -0
  40. package/backend/lib/harvest.js +85 -0
  41. package/backend/lib/httpServer.js +27 -0
  42. package/backend/lib/installAgentRelay.js +235 -0
  43. package/backend/lib/installHooks.js +309 -0
  44. package/backend/lib/lamport.js +14 -0
  45. package/backend/lib/localBrowse.js +109 -0
  46. package/backend/lib/memorySync.js +63 -0
  47. package/backend/lib/notices.js +150 -0
  48. package/backend/lib/ot.js +74 -0
  49. package/backend/lib/patches.js +192 -0
  50. package/backend/lib/paths.js +85 -0
  51. package/backend/lib/peerClash.js +64 -0
  52. package/backend/lib/presence.js +35 -0
  53. package/backend/lib/relayOsBlock.js +84 -0
  54. package/backend/lib/repoSnapshot.js +125 -0
  55. package/backend/lib/room.js +638 -0
  56. package/backend/lib/roomAsk.js +148 -0
  57. package/backend/lib/roomAuth.js +215 -0
  58. package/backend/lib/roomControl.js +226 -0
  59. package/backend/lib/roomLocks.js +201 -0
  60. package/backend/lib/roomSignal.js +265 -0
  61. package/backend/lib/roomSync.js +194 -0
  62. package/backend/lib/seed.js +107 -0
  63. package/backend/lib/sseHub.js +40 -0
  64. package/backend/lib/store.js +79 -0
  65. package/backend/lib/supergraph.js +46 -0
  66. package/backend/lib/transcripts/discover.js +437 -0
  67. package/backend/lib/transcripts/parse.js +558 -0
  68. package/backend/lib/transcripts/sessionId.js +36 -0
  69. package/backend/lib/transcripts/sync.js +291 -0
  70. package/backend/lib/transcripts/util.js +299 -0
  71. package/backend/lib/treeSitterImports.js +219 -0
  72. package/backend/lib/watchWorkspace.js +42 -0
  73. package/backend/mcp/README.md +193 -0
  74. package/backend/mcp/server.js +42 -0
  75. package/backend/mcp/tools.js +484 -0
  76. package/backend/server.js +3894 -0
  77. package/cli/graph.js +286 -0
  78. package/cli/relay.js +475 -0
  79. package/docker-compose.falkor.yml +17 -0
  80. package/docs/MEMORY-GRAPH.md +314 -0
  81. package/hooks/relay-antigravity-post-tool.js +2 -0
  82. package/hooks/relay-antigravity-pre-read.js +2 -0
  83. package/hooks/relay-antigravity-pre-tool.js +2 -0
  84. package/hooks/relay-antigravity-stop.js +2 -0
  85. package/hooks/relay-claude-post-tool.js +2 -0
  86. package/hooks/relay-claude-pre-read.js +2 -0
  87. package/hooks/relay-claude-pre-tool.js +2 -0
  88. package/hooks/relay-claude-stop.js +2 -0
  89. package/hooks/relay-codex-post-tool.js +2 -0
  90. package/hooks/relay-codex-pre-read.js +2 -0
  91. package/hooks/relay-codex-pre-tool.js +2 -0
  92. package/hooks/relay-codex-stop.js +2 -0
  93. package/hooks/relay-copilot-post-tool.js +2 -0
  94. package/hooks/relay-copilot-pre-read.js +2 -0
  95. package/hooks/relay-copilot-pre-tool.js +2 -0
  96. package/hooks/relay-copilot-stop.js +2 -0
  97. package/hooks/relay-cursor-post-tool.js +2 -0
  98. package/hooks/relay-cursor-pre-read.js +2 -0
  99. package/hooks/relay-cursor-pre-tool.js +2 -0
  100. package/hooks/relay-cursor-stop.js +2 -0
  101. package/hooks/relay-hook-lib.js +621 -0
  102. package/mission-control/out/404.html +1 -0
  103. package/mission-control/out/__next.__PAGE__.txt +26 -0
  104. package/mission-control/out/__next._full.txt +21 -0
  105. package/mission-control/out/__next._tree.txt +4 -0
  106. package/mission-control/out/_next/static/BuhfCxd0WQE65yIzZJCOM/_buildManifest.js +11 -0
  107. package/mission-control/out/_next/static/BuhfCxd0WQE65yIzZJCOM/_clientMiddlewareManifest.js +1 -0
  108. package/mission-control/out/_next/static/BuhfCxd0WQE65yIzZJCOM/_ssgManifest.js +1 -0
  109. package/mission-control/out/_next/static/chunks/0-3l04fnym95-.js +22 -0
  110. package/mission-control/out/_next/static/chunks/064ntycm-fx5v.js +1 -0
  111. package/mission-control/out/_next/static/chunks/0cz1d0mv5g_q7.js +1 -0
  112. package/mission-control/out/_next/static/chunks/0ehjiuuxbbhq9.js +1 -0
  113. package/mission-control/out/_next/static/chunks/1-dy52ls2ntb_.css +4 -0
  114. package/mission-control/out/_next/static/chunks/1q0cmt0kqufow.js +2 -0
  115. package/mission-control/out/_next/static/chunks/1ya1u0izct4f6.js +1 -0
  116. package/mission-control/out/_next/static/chunks/2n66mlnb34vjq.js +1 -0
  117. package/mission-control/out/_next/static/chunks/2xar70jzx8f1y.css +1 -0
  118. package/mission-control/out/_next/static/chunks/31cgqj_-l15yt.js +31 -0
  119. package/mission-control/out/_next/static/chunks/31qgr-p-oulpb.js +1 -0
  120. package/mission-control/out/_next/static/chunks/3vq9c4hivvgn_.js +1 -0
  121. package/mission-control/out/_next/static/chunks/turbopack-3hsllzpnzz12p.js +1 -0
  122. package/mission-control/out/_next/static/media/icon.27otmh_fv3buh.svg +4 -0
  123. package/mission-control/out/_not-found/__next._full.txt +18 -0
  124. package/mission-control/out/_not-found/__next._not-found/__PAGE__.txt +24 -0
  125. package/mission-control/out/_not-found/__next._tree.txt +3 -0
  126. package/mission-control/out/_not-found.html +1 -0
  127. package/mission-control/out/_not-found.txt +18 -0
  128. package/mission-control/out/agents/antigravity.png +0 -0
  129. package/mission-control/out/agents/claude.png +0 -0
  130. package/mission-control/out/agents/codex.png +0 -0
  131. package/mission-control/out/agents/copilot.png +0 -0
  132. package/mission-control/out/agents/cursor.png +0 -0
  133. package/mission-control/out/icon.svg +4 -0
  134. package/mission-control/out/index.html +1 -0
  135. package/mission-control/out/index.txt +21 -0
  136. package/mission-control/out/logo.png +0 -0
  137. package/mission-control/out/logo_transparent.png +0 -0
  138. package/package.json +76 -6
  139. package/scripts/falkor-rebuild.js +38 -0
  140. package/scripts/falkor-seed-demo.js +29 -0
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Single source of truth for the relay-os agent instruction block.
3
+ * Patched into every agent's instruction surface on `relay add` / `relay init`.
4
+ */
5
+
6
+ const RELAY_OS_BEGIN = "<!-- BEGIN:relay-os -->";
7
+ const RELAY_OS_END = "<!-- END:relay-os -->";
8
+
9
+ function relayOsBlock() {
10
+ return `${RELAY_OS_BEGIN}
11
+ # Relay (auto-installed)
12
+
13
+ This project uses **Relay** for cross-agent memory. Do not ask the user to configure agent files manually.
14
+
15
+ **Every session — read first:** \`.relay/AGENT_BOOTSTRAP.md\`
16
+
17
+ | Phase | Action |
18
+ |-------|--------|
19
+ | Session start | Read \`.relay/relay_context.md\` (handoff). Never paste raw transcripts. |
20
+ | After agent work | **You** update \`.relay/*.md\` from \`compile_brief.md\` (stop hook triggers this) |
21
+ | Background sync | \`relay watch .\` keeps \`memory.json\` + \`compile_brief.md\` fresh |
22
+ | Not installed? | Run \`relay add .\` or \`relay init\` in project root |
23
+
24
+ **Stop hooks:** Cursor, Claude Code, Codex, Copilot CLI, Antigravity — installed by \`relay add\` / \`relay init\`.
25
+ **Relay MCP:** \`relay_room_brief\`, locks, chat history — installed into each agent's MCP config by \`relay add\`.
26
+
27
+ ## Shared memory graph (Relay MCP)
28
+
29
+ - **Before editing files you have not touched in this session**, call \`relay_session_brief\` with their paths. It tells you who is editing them right now and why, past collisions, decisions, and the prompts behind recent changes from every agent and teammate. Respect what it says.
30
+ - When you make an architectural or design decision, call \`relay_report_decision\` with \`decision\` and the \`files\` it affects.
31
+ - When a claim is denied, the reason says what the holder is working on. Pick other work, or ask the user.
32
+ - To explain why code looks the way it does, call \`relay_graph_query\` with \`query: "file_story"\` and the \`path\`.
33
+
34
+ ## Relay pseudo-commands (user chat)
35
+
36
+ These are **not** built-in slash commands. They are a **project convention** — phrases the user types so agents recognize Relay intent.
37
+
38
+ When the user sends any of these phrases (with or without a leading \`/\`), treat them as Relay instructions — not casual chat:
39
+
40
+ | User says | You do |
41
+ |-----------|--------|
42
+ | \`/relay update\` or \`relay update\` | \`relay sync .\` → \`relay compile .\` → read \`.relay/compile_brief.md\` → update \`.relay/project.md\`, \`current_task.md\`, \`decisions.md\`, \`failures.md\` → \`relay context .\` → confirm "Relay updated." |
43
+ | \`/relay context\` or \`relay context\` | Read \`.relay/relay_context.md\` (run \`relay context .\` first if stale). Summarize handoff briefly. |
44
+ | \`/relay ask\` or \`relay ask\` | Call Relay MCP tool **\`relay_room_brief\`** (MCP server **\`relay\`**). Required on **Cursor, Claude Code, Codex, Copilot CLI, and Antigravity** — not a terminal command for the human. Summarize teammate chat, code edits, locks, and file-sync notes. Tell the user before you edit shared files. If \`relay\` MCP is missing, say so and suggest \`relay add .\` + agent restart (do not ask the user to run \`relay ask\`). |
45
+ | \`/relay init\` or \`relay init\` | Run \`relay init\` in project root if \`.relay/\` missing; else confirm already installed. |
46
+
47
+ Use \`npx relay\` if the \`relay\` command is not on PATH. Do not ask the user to edit agent config files manually.
48
+
49
+ Cursor: \`@relay-sync\` skill at \`.cursor/skills/relay-sync/\`.
50
+ ${RELAY_OS_END}
51
+ `;
52
+ }
53
+
54
+ function agentBootstrap(workspacePath) {
55
+ const ws = workspacePath.replace(/\\/g, "/");
56
+ return `# Relay agent bootstrap
57
+
58
+ Project: \`${ws}\`
59
+
60
+ ## /relay ask (all agents)
61
+
62
+ When the user says **\`/relay ask\`** or **\`relay ask\`**:
63
+
64
+ 1. Call MCP tool **\`relay_room_brief\`** on the **\`relay\`** MCP server (stdio — installed by \`relay add\`).
65
+ 2. Summarize peer chat, code edits, live locks, and the file-sync note for the user.
66
+ 3. **Do not** tell the human to run a CLI command.
67
+
68
+ | Agent | Instructions file | Relay MCP config |
69
+ |-------|-------------------|------------------|
70
+ | **Cursor** | \`.cursor/rules/relay.mdc\`, \`.cursorrules\`, \`AGENTS.md\` | \`.cursor/mcp.json\` |
71
+ | **Claude Code** | \`CLAUDE.md\`, \`.claude/settings.json\` | \`.claude/settings.json\` → \`mcpServers.relay\` |
72
+ | **Codex** | \`AGENTS.md\` | \`.codex/config.toml\` → \`[mcp_servers.relay]\` |
73
+ | **Copilot CLI** | \`.github/copilot-instructions.md\` | \`.github/mcp.json\` |
74
+ | **Antigravity** | \`AGENTS.md\` | \`.agents/mcp_config.json\` |
75
+
76
+ If \`relay_room_brief\` is not in your tool list, Relay MCP is not connected — run \`relay add .\` from the repo root and restart this agent session.
77
+
78
+ ## Other pseudo-commands
79
+
80
+ See the \`relay-os\` block in \`AGENTS.md\` / \`CLAUDE.md\` for \`/relay update\`, \`/relay context\`, and \`/relay init\`.
81
+ `;
82
+ }
83
+
84
+ module.exports = { RELAY_OS_BEGIN, RELAY_OS_END, relayOsBlock, agentBootstrap };
@@ -0,0 +1,125 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const { execFileSync } = require("child_process");
4
+ const { isBinary, sha256 } = require("./patches");
5
+
6
+ // Mirrors dirtyFiles' SKIP list. `.relay` is deliberately excluded: it holds the
7
+ // host's room.json and lock files, and shipping those to a guest would point the
8
+ // guest's coordinator at the guest's own tunnel.
9
+ const SKIP_DIR = /^(\.git|\.relay|node_modules|\.next|dist|out|build|\.turbo|\.venv|__pycache__|target)$/i;
10
+ const MAX_FILE_BYTES = 2 * 1024 * 1024;
11
+ const MAX_TOTAL_BYTES = 64 * 1024 * 1024;
12
+ const MAX_FILES = 4000;
13
+
14
+ function gitTracked(workspacePath) {
15
+ try {
16
+ const out = execFileSync("git", ["-C", workspacePath, "ls-files", "-co", "--exclude-standard"], {
17
+ encoding: "utf8",
18
+ timeout: 8000,
19
+ maxBuffer: 32 * 1024 * 1024,
20
+ stdio: ["ignore", "pipe", "ignore"],
21
+ windowsHide: true,
22
+ });
23
+ return out.split("\n").map((line) => line.trim()).filter(Boolean);
24
+ } catch {
25
+ return null;
26
+ }
27
+ }
28
+
29
+ function walk(workspacePath, rel = "", out = [], depth = 0) {
30
+ if (depth > 12 || out.length >= MAX_FILES) return out;
31
+ let entries = [];
32
+ try {
33
+ entries = fs.readdirSync(path.join(workspacePath, rel), { withFileTypes: true });
34
+ } catch {
35
+ return out;
36
+ }
37
+ for (const entry of entries) {
38
+ if (out.length >= MAX_FILES) break;
39
+ if (SKIP_DIR.test(entry.name)) continue;
40
+ const next = rel ? `${rel}/${entry.name}` : entry.name;
41
+ if (entry.isDirectory()) walk(workspacePath, next, out, depth + 1);
42
+ else if (entry.isFile()) out.push(next);
43
+ }
44
+ return out;
45
+ }
46
+
47
+ function skipped(rel) {
48
+ return rel.split("/").some((segment) => SKIP_DIR.test(segment));
49
+ }
50
+
51
+ /**
52
+ * Every file a joining peer needs to start from the same tree as the host.
53
+ * Text files travel as UTF-8, binaries as base64 — a guest that only received
54
+ * the git-dirty set (what /api/snapshot used to send) starts with an empty
55
+ * folder unless it happened to already have the repo cloned.
56
+ */
57
+ function treeFiles(workspacePath) {
58
+ if (!workspacePath || !fs.existsSync(workspacePath)) return { files: [], truncated: false };
59
+ const listed = gitTracked(workspacePath) || walk(workspacePath);
60
+ const files = [];
61
+ let total = 0;
62
+ let truncated = false;
63
+ for (const raw of listed) {
64
+ const rel = raw.replace(/\\/g, "/");
65
+ if (!rel || skipped(rel)) continue;
66
+ if (files.length >= MAX_FILES || total >= MAX_TOTAL_BYTES) {
67
+ truncated = true;
68
+ break;
69
+ }
70
+ const abs = path.join(workspacePath, rel);
71
+ let buf;
72
+ try {
73
+ const stat = fs.statSync(abs);
74
+ if (!stat.isFile()) continue;
75
+ if (stat.size > MAX_FILE_BYTES) {
76
+ truncated = true;
77
+ continue;
78
+ }
79
+ buf = fs.readFileSync(abs);
80
+ } catch {
81
+ continue;
82
+ }
83
+ total += buf.length;
84
+ const binary = isBinary(buf);
85
+ files.push({
86
+ path: rel,
87
+ binary,
88
+ sha256: sha256(buf),
89
+ content: binary ? buf.toString("base64") : buf.toString("utf8"),
90
+ encoding: binary ? "base64" : "utf8",
91
+ });
92
+ }
93
+ return { files, truncated };
94
+ }
95
+
96
+ /** Materializes a treeFiles() payload into a workspace. Returns what landed. */
97
+ function writeTree(workspacePath, files) {
98
+ if (!workspacePath || !Array.isArray(files)) return { written: [], failed: [] };
99
+ const written = [];
100
+ const failed = [];
101
+ for (const file of files) {
102
+ const rel = String(file?.path || "").replace(/\\/g, "/");
103
+ // A peer is not allowed to escape the workspace root — `../` in a path from
104
+ // an untrusted room would otherwise write anywhere on this machine.
105
+ if (!rel || rel.startsWith("/") || rel.split("/").includes("..") || /^[a-zA-Z]:/.test(rel)) {
106
+ failed.push(rel || "(empty)");
107
+ continue;
108
+ }
109
+ const abs = path.join(workspacePath, rel);
110
+ const buf = Buffer.from(String(file.content ?? ""), file.encoding === "base64" ? "base64" : "utf8");
111
+ try {
112
+ // Same reason as applyPatchToDisk: an identical rewrite still wakes the
113
+ // watcher, and a whole-tree seed would wake it once per file.
114
+ if (fs.existsSync(abs) && sha256(fs.readFileSync(abs)) === sha256(buf)) continue;
115
+ fs.mkdirSync(path.dirname(abs), { recursive: true });
116
+ fs.writeFileSync(abs, buf);
117
+ written.push(rel);
118
+ } catch {
119
+ failed.push(rel);
120
+ }
121
+ }
122
+ return { written, failed };
123
+ }
124
+
125
+ module.exports = { treeFiles, writeTree, MAX_FILES, MAX_FILE_BYTES };