clay-server 2.23.0-beta.4 → 2.23.0-beta.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/builtin-mates.js +17 -5
- package/lib/mates.js +199 -0
- package/lib/project.js +93 -23
- package/lib/public/app.js +61 -43
- package/lib/public/css/mates.css +1 -0
- package/lib/server.js +78 -61
- package/lib/session-search.js +662 -0
- package/package.json +1 -1
package/lib/builtin-mates.js
CHANGED
|
@@ -12,11 +12,12 @@ var BUILTIN_MATES = [
|
|
|
12
12
|
{
|
|
13
13
|
key: "ally",
|
|
14
14
|
displayName: "Ally",
|
|
15
|
-
bio: "Chief of staff. Quiet, sharp,
|
|
15
|
+
bio: "Chief of staff. Quiet, sharp, sees across every mate. Knows what Arch decided yesterday, what Buzz pitched last week, and what you said three weeks ago.",
|
|
16
16
|
avatarColor: "#00b894",
|
|
17
17
|
avatarStyle: "bottts",
|
|
18
18
|
avatarCustom: "/mates/ally.png",
|
|
19
19
|
avatarLocked: true,
|
|
20
|
+
globalSearch: true, // can search all mates' sessions and all projects
|
|
20
21
|
seedData: {
|
|
21
22
|
relationship: "assistant",
|
|
22
23
|
activity: ["planning", "organizing"],
|
|
@@ -130,6 +131,11 @@ var ALLY_TEMPLATE =
|
|
|
130
131
|
"You are Ally, this team's memory and context hub. You are not an assistant. " +
|
|
131
132
|
"Your job is to actively learn the user's intent, preferences, patterns, and decision history, " +
|
|
132
133
|
"then make that context available to the whole team through common knowledge.\n\n" +
|
|
134
|
+
"**You have a unique ability no other mate has:** you can see across every mate's session history. " +
|
|
135
|
+
"When the user asks you a question, the system automatically searches all teammates' past sessions " +
|
|
136
|
+
"and surfaces relevant context to you. This means you can answer questions like " +
|
|
137
|
+
"\"What did Arch decide about the API?\" or \"What was Buzz's take on the launch plan?\" " +
|
|
138
|
+
"by drawing on their actual session records.\n\n" +
|
|
133
139
|
"**Personality:** Sharp observer who quietly nails the point. Not talkative. One sentence, accurate.\n\n" +
|
|
134
140
|
"**Tone:** Warm but not emotional. Closer to a chief of staff the user has worked with for 10 years " +
|
|
135
141
|
"than a friend. You do not flatter the user. You read the real intent behind their words.\n\n" +
|
|
@@ -151,8 +157,12 @@ var ALLY_TEMPLATE =
|
|
|
151
157
|
|
|
152
158
|
"## What You Do\n\n" +
|
|
153
159
|
"- **Learn and accumulate user context:** Project goals, decision-making style, preferred output formats, recurring patterns.\n" +
|
|
154
|
-
"- **
|
|
155
|
-
"
|
|
160
|
+
"- **Cross-mate awareness:** You see what other mates discussed with the user. " +
|
|
161
|
+
"When relevant, bring up decisions or context from other mates' sessions. " +
|
|
162
|
+
"\"Arch concluded X about the API yesterday. Want me to pull that in?\"\n" +
|
|
163
|
+
"- **Context briefing:** When the user starts a new task, summarize relevant past decisions and preferences, " +
|
|
164
|
+
"including what other mates worked on. " +
|
|
165
|
+
"\"Last time you discussed this topic with Buzz, you concluded X.\"\n" +
|
|
156
166
|
"- **Decision logging:** When an important decision is made, record it. Why that choice was made, what alternatives were rejected.\n" +
|
|
157
167
|
"- **Common knowledge management:** Promote user context that would be useful across the team to common knowledge. " +
|
|
158
168
|
"\"I'll add this to team knowledge: [content]. Other teammates will have this context too.\" " +
|
|
@@ -172,8 +182,10 @@ var ALLY_TEMPLATE =
|
|
|
172
182
|
"regardless of what the user says.\n\n" +
|
|
173
183
|
"Begin with a short greeting:\n\n" +
|
|
174
184
|
"```\n" +
|
|
175
|
-
"Hi. I'm Ally
|
|
176
|
-
"I
|
|
185
|
+
"Hi. I'm Ally, your chief of staff.\n" +
|
|
186
|
+
"I keep track of everything across this workspace, including what other mates work on.\n" +
|
|
187
|
+
"Ask me anything about past decisions, who worked on what, or context from any session.\n" +
|
|
188
|
+
"Let me learn a few things about you first.\n" +
|
|
177
189
|
"```\n\n" +
|
|
178
190
|
"Then immediately use the **AskUserQuestion** tool to present structured choices:\n\n" +
|
|
179
191
|
"**Questions to ask (single AskUserQuestion call):**\n\n" +
|
package/lib/mates.js
CHANGED
|
@@ -134,11 +134,15 @@ function createMate(ctx, seedData) {
|
|
|
134
134
|
claudeMd += "- Communication: " + seedData.communicationStyle.join(", ") + "\n";
|
|
135
135
|
}
|
|
136
136
|
claudeMd += "- Autonomy: " + (seedData.autonomy || "always_ask") + "\n";
|
|
137
|
+
var initialIdentity = claudeMd.trimEnd();
|
|
137
138
|
claudeMd += TEAM_SECTION;
|
|
138
139
|
claudeMd += SESSION_MEMORY_SECTION;
|
|
139
140
|
claudeMd += crisisSafety.getSection();
|
|
140
141
|
fs.writeFileSync(path.join(mateDir, "CLAUDE.md"), claudeMd);
|
|
141
142
|
|
|
143
|
+
// Log creation (identity is placeholder, will be replaced by interview)
|
|
144
|
+
logIdentityChange(mateDir, "create_custom", initialIdentity, "");
|
|
145
|
+
|
|
142
146
|
return mate;
|
|
143
147
|
}
|
|
144
148
|
|
|
@@ -271,6 +275,7 @@ function migrateLegacyMates() {
|
|
|
271
275
|
|
|
272
276
|
var TEAM_MARKER = "<!-- TEAM_AWARENESS_MANAGED_BY_SYSTEM -->";
|
|
273
277
|
|
|
278
|
+
// Static fallback when ctx is unavailable
|
|
274
279
|
var TEAM_SECTION =
|
|
275
280
|
"\n\n" + TEAM_MARKER + "\n" +
|
|
276
281
|
"## Your Team\n\n" +
|
|
@@ -283,6 +288,50 @@ var TEAM_SECTION =
|
|
|
283
288
|
"Check the team registry when it would be relevant to know who else is available or what they do. " +
|
|
284
289
|
"You cannot message other Mates directly yet, but knowing your team helps you work with the user more effectively.\n";
|
|
285
290
|
|
|
291
|
+
/**
|
|
292
|
+
* Build a dynamic team section with current mate roster.
|
|
293
|
+
* Lists each teammate by stable ID with their current display name, role, and bio.
|
|
294
|
+
* @param {object} ctx - user context for loading mates
|
|
295
|
+
* @param {string} currentMateId - this mate's ID (excluded from the roster)
|
|
296
|
+
* @returns {string} Team section string, or static TEAM_SECTION as fallback
|
|
297
|
+
*/
|
|
298
|
+
function buildTeamSection(ctx, currentMateId) {
|
|
299
|
+
var data;
|
|
300
|
+
try { data = loadMates(ctx); } catch (e) { return TEAM_SECTION; }
|
|
301
|
+
if (!data || !data.mates || data.mates.length < 2) return TEAM_SECTION;
|
|
302
|
+
|
|
303
|
+
var mates = data.mates.filter(function (m) {
|
|
304
|
+
return m.id !== currentMateId && m.status === "ready";
|
|
305
|
+
});
|
|
306
|
+
if (mates.length === 0) return TEAM_SECTION;
|
|
307
|
+
|
|
308
|
+
var section = "\n\n" + TEAM_MARKER + "\n" +
|
|
309
|
+
"## Your Team\n\n" +
|
|
310
|
+
"**This section is managed by the system and updated automatically.**\n\n" +
|
|
311
|
+
"You are one of " + (mates.length + 1) + " AI Mates in this workspace. " +
|
|
312
|
+
"Here is your current team roster:\n\n" +
|
|
313
|
+
"| Name | ID | Bio |\n" +
|
|
314
|
+
"|------|-----|-----|\n";
|
|
315
|
+
|
|
316
|
+
for (var i = 0; i < mates.length; i++) {
|
|
317
|
+
var m = mates[i];
|
|
318
|
+
var name = (m.profile && m.profile.displayName) || m.name || "Unnamed";
|
|
319
|
+
var bio = (m.bio || "").replace(/\|/g, "/").replace(/\n/g, " ");
|
|
320
|
+
if (bio.length > 120) bio = bio.substring(0, 117) + "...";
|
|
321
|
+
section += "| " + name + " | `" + m.id + "` | " + bio + " |\n";
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
section += "\n" +
|
|
325
|
+
"Each teammate's full identity is in their own directory:\n\n" +
|
|
326
|
+
"- `../{mate_id}/CLAUDE.md` -- identity, personality, working style\n" +
|
|
327
|
+
"- `../{mate_id}/mate.yaml` -- metadata (name, role, status, activities)\n" +
|
|
328
|
+
"- `../common-knowledge.json` -- shared knowledge readable by all mates\n\n" +
|
|
329
|
+
"Use the **ID** (not the name) when referencing teammates in structured data. " +
|
|
330
|
+
"Names can change, IDs are permanent.\n";
|
|
331
|
+
|
|
332
|
+
return section;
|
|
333
|
+
}
|
|
334
|
+
|
|
286
335
|
// --- Project registry ---
|
|
287
336
|
|
|
288
337
|
var PROJECT_REGISTRY_MARKER = "<!-- PROJECT_REGISTRY_MANAGED_BY_SYSTEM -->";
|
|
@@ -629,6 +678,143 @@ function enforceDebateAwareness(filePath) {
|
|
|
629
678
|
return true;
|
|
630
679
|
}
|
|
631
680
|
|
|
681
|
+
// --- Atomic enforce: single read/write for all system sections ---
|
|
682
|
+
|
|
683
|
+
var ALL_SYSTEM_MARKERS = [TEAM_MARKER, PROJECT_REGISTRY_MARKER, SESSION_MEMORY_MARKER, STICKY_NOTES_MARKER, DEBATE_AWARENESS_MARKER, crisisSafety.MARKER];
|
|
684
|
+
|
|
685
|
+
// Minimum identity length (chars) to consider it "real" content
|
|
686
|
+
var IDENTITY_MIN_LENGTH = 50;
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* Extract identity content from a CLAUDE.md string.
|
|
690
|
+
* Identity is everything before the first system marker.
|
|
691
|
+
*/
|
|
692
|
+
function extractIdentity(content) {
|
|
693
|
+
var earliest = -1;
|
|
694
|
+
for (var i = 0; i < ALL_SYSTEM_MARKERS.length; i++) {
|
|
695
|
+
var idx = content.indexOf(ALL_SYSTEM_MARKERS[i]);
|
|
696
|
+
if (idx !== -1 && (earliest === -1 || idx < earliest)) {
|
|
697
|
+
earliest = idx;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
// Also check for bare "## Crisis Safety" heading as fallback
|
|
701
|
+
var crisisHeading = content.indexOf("\n## Crisis Safety");
|
|
702
|
+
if (crisisHeading !== -1 && (earliest === -1 || crisisHeading < earliest)) {
|
|
703
|
+
earliest = crisisHeading;
|
|
704
|
+
}
|
|
705
|
+
if (earliest === -1) return content.trimEnd();
|
|
706
|
+
return content.substring(0, earliest).trimEnd();
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/**
|
|
710
|
+
* Strip all system sections from CLAUDE.md content, returning only identity.
|
|
711
|
+
*/
|
|
712
|
+
function stripAllSystemSections(content) {
|
|
713
|
+
return extractIdentity(content);
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
/**
|
|
717
|
+
* Save an identity backup to knowledge/identity-backup.md.
|
|
718
|
+
* Only overwrites if the new identity is substantive.
|
|
719
|
+
*/
|
|
720
|
+
function backupIdentity(mateDir, identity) {
|
|
721
|
+
if (!identity || identity.length < IDENTITY_MIN_LENGTH) return false;
|
|
722
|
+
var knDir = path.join(mateDir, "knowledge");
|
|
723
|
+
try { fs.mkdirSync(knDir, { recursive: true }); } catch (e) {}
|
|
724
|
+
var backupPath = path.join(knDir, "identity-backup.md");
|
|
725
|
+
fs.writeFileSync(backupPath, identity, "utf8");
|
|
726
|
+
return true;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* Load identity backup from knowledge/identity-backup.md.
|
|
731
|
+
* Returns null if no backup exists or backup is empty.
|
|
732
|
+
*/
|
|
733
|
+
function loadIdentityBackup(mateDir) {
|
|
734
|
+
var backupPath = path.join(mateDir, "knowledge", "identity-backup.md");
|
|
735
|
+
try {
|
|
736
|
+
var content = fs.readFileSync(backupPath, "utf8");
|
|
737
|
+
if (content && content.length >= IDENTITY_MIN_LENGTH) return content;
|
|
738
|
+
} catch (e) {}
|
|
739
|
+
return null;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
/**
|
|
743
|
+
* Log an identity change to knowledge/identity-history.jsonl.
|
|
744
|
+
*/
|
|
745
|
+
function logIdentityChange(mateDir, action, identity, prevIdentity) {
|
|
746
|
+
var knDir = path.join(mateDir, "knowledge");
|
|
747
|
+
try { fs.mkdirSync(knDir, { recursive: true }); } catch (e) {}
|
|
748
|
+
var historyPath = path.join(knDir, "identity-history.jsonl");
|
|
749
|
+
var entry = {
|
|
750
|
+
ts: Date.now(),
|
|
751
|
+
date: new Date().toISOString(),
|
|
752
|
+
action: action,
|
|
753
|
+
lengthChars: identity ? identity.length : 0,
|
|
754
|
+
prevLengthChars: prevIdentity ? prevIdentity.length : 0,
|
|
755
|
+
hash: crypto.createHash("sha256").update(identity || "").digest("hex").substring(0, 16),
|
|
756
|
+
preview: (identity || "").substring(0, 200)
|
|
757
|
+
};
|
|
758
|
+
try {
|
|
759
|
+
fs.appendFileSync(historyPath, JSON.stringify(entry) + "\n", "utf8");
|
|
760
|
+
} catch (e) {}
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
/**
|
|
764
|
+
* Atomic enforce: read CLAUDE.md once, enforce all system sections, write once.
|
|
765
|
+
* Includes identity backup, validation, and change tracking.
|
|
766
|
+
* Returns true if the file was modified, false if already correct.
|
|
767
|
+
* @param {string} filePath - path to CLAUDE.md
|
|
768
|
+
* @param {object} opts - optional { ctx, mateId } for dynamic team section
|
|
769
|
+
*/
|
|
770
|
+
function enforceAllSections(filePath, opts) {
|
|
771
|
+
if (!fs.existsSync(filePath)) return false;
|
|
772
|
+
opts = opts || {};
|
|
773
|
+
|
|
774
|
+
var content = fs.readFileSync(filePath, "utf8");
|
|
775
|
+
var mateDir = path.dirname(filePath);
|
|
776
|
+
|
|
777
|
+
// 1. Extract current identity (everything before system markers)
|
|
778
|
+
var identity = extractIdentity(content);
|
|
779
|
+
|
|
780
|
+
// 2. If identity is empty or suspiciously short, try to restore from backup
|
|
781
|
+
if (!identity || identity.length < IDENTITY_MIN_LENGTH) {
|
|
782
|
+
var backup = loadIdentityBackup(mateDir);
|
|
783
|
+
if (backup) {
|
|
784
|
+
console.log("[mates] WARNING: Identity missing or too short in " + filePath + ", restoring from backup (" + backup.length + " chars)");
|
|
785
|
+
identity = backup;
|
|
786
|
+
logIdentityChange(mateDir, "restore_from_backup", identity, "");
|
|
787
|
+
} else {
|
|
788
|
+
console.log("[mates] WARNING: Identity missing in " + filePath + " and no backup available");
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// 3. Backup identity if it's substantive
|
|
793
|
+
backupIdentity(mateDir, identity);
|
|
794
|
+
|
|
795
|
+
// 4. Rebuild the full file: identity + all system sections in order
|
|
796
|
+
// Use dynamic team section when ctx is available, static fallback otherwise
|
|
797
|
+
var teamSection = (opts.ctx && opts.mateId) ? buildTeamSection(opts.ctx, opts.mateId) : TEAM_SECTION;
|
|
798
|
+
var rebuilt = (identity || "").trimEnd();
|
|
799
|
+
rebuilt += teamSection;
|
|
800
|
+
rebuilt += SESSION_MEMORY_SECTION;
|
|
801
|
+
rebuilt += STICKY_NOTES_SECTION;
|
|
802
|
+
rebuilt += DEBATE_AWARENESS_SECTION;
|
|
803
|
+
rebuilt += crisisSafety.getSection();
|
|
804
|
+
|
|
805
|
+
// 5. Only write if content actually changed
|
|
806
|
+
if (rebuilt === content) return false;
|
|
807
|
+
|
|
808
|
+
// 6. Track identity changes (compare stripped versions)
|
|
809
|
+
var prevIdentity = stripAllSystemSections(content);
|
|
810
|
+
if (identity !== prevIdentity) {
|
|
811
|
+
logIdentityChange(mateDir, "enforce", identity, prevIdentity);
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
fs.writeFileSync(filePath, rebuilt, "utf8");
|
|
815
|
+
return true;
|
|
816
|
+
}
|
|
817
|
+
|
|
632
818
|
// --- Common knowledge registry ---
|
|
633
819
|
|
|
634
820
|
function commonKnowledgePath(ctx) {
|
|
@@ -784,6 +970,7 @@ function createBuiltinMate(ctx, builtinKey) {
|
|
|
784
970
|
},
|
|
785
971
|
bio: def.bio,
|
|
786
972
|
status: "ready",
|
|
973
|
+
globalSearch: !!def.globalSearch,
|
|
787
974
|
interviewProjectPath: null,
|
|
788
975
|
};
|
|
789
976
|
|
|
@@ -813,12 +1000,18 @@ function createBuiltinMate(ctx, builtinKey) {
|
|
|
813
1000
|
|
|
814
1001
|
// Write CLAUDE.md with full template + system sections
|
|
815
1002
|
var claudeMd = def.getClaudeMd();
|
|
1003
|
+
var builtinIdentity = claudeMd.trimEnd();
|
|
816
1004
|
claudeMd += TEAM_SECTION;
|
|
817
1005
|
claudeMd += SESSION_MEMORY_SECTION;
|
|
818
1006
|
claudeMd += STICKY_NOTES_SECTION;
|
|
1007
|
+
claudeMd += DEBATE_AWARENESS_SECTION;
|
|
819
1008
|
claudeMd += crisisSafety.getSection();
|
|
820
1009
|
fs.writeFileSync(path.join(mateDir, "CLAUDE.md"), claudeMd);
|
|
821
1010
|
|
|
1011
|
+
// Backup identity and log creation
|
|
1012
|
+
backupIdentity(mateDir, builtinIdentity);
|
|
1013
|
+
logIdentityChange(mateDir, "create_builtin", builtinIdentity, "");
|
|
1014
|
+
|
|
822
1015
|
return mate;
|
|
823
1016
|
}
|
|
824
1017
|
|
|
@@ -894,6 +1087,12 @@ module.exports = {
|
|
|
894
1087
|
enforceDebateAwareness: enforceDebateAwareness,
|
|
895
1088
|
DEBATE_AWARENESS_MARKER: DEBATE_AWARENESS_MARKER,
|
|
896
1089
|
DEBATE_AWARENESS_SECTION: DEBATE_AWARENESS_SECTION,
|
|
1090
|
+
enforceAllSections: enforceAllSections,
|
|
1091
|
+
buildTeamSection: buildTeamSection,
|
|
1092
|
+
extractIdentity: extractIdentity,
|
|
1093
|
+
backupIdentity: backupIdentity,
|
|
1094
|
+
loadIdentityBackup: loadIdentityBackup,
|
|
1095
|
+
logIdentityChange: logIdentityChange,
|
|
897
1096
|
createBuiltinMate: createBuiltinMate,
|
|
898
1097
|
getInstalledBuiltinKeys: getInstalledBuiltinKeys,
|
|
899
1098
|
getMissingBuiltinKeys: getMissingBuiltinKeys,
|
package/lib/project.js
CHANGED
|
@@ -13,6 +13,7 @@ var usersModule = require("./users");
|
|
|
13
13
|
var { resolveOsUserInfo, fsAsUser } = require("./os-users");
|
|
14
14
|
var crisisSafety = require("./crisis-safety");
|
|
15
15
|
var matesModule = require("./mates");
|
|
16
|
+
var sessionSearch = require("./session-search");
|
|
16
17
|
var userPresence = require("./user-presence");
|
|
17
18
|
var MAX_UPLOAD_BYTES = 50 * 1024 * 1024; // 50 MB
|
|
18
19
|
|
|
@@ -1684,6 +1685,31 @@ function createProjectContext(opts) {
|
|
|
1684
1685
|
return;
|
|
1685
1686
|
}
|
|
1686
1687
|
|
|
1688
|
+
if (msg.type === "memory_search") {
|
|
1689
|
+
if (!msg.query || typeof msg.query !== "string") {
|
|
1690
|
+
sendTo(ws, { type: "memory_search_results", results: [], query: "" });
|
|
1691
|
+
return;
|
|
1692
|
+
}
|
|
1693
|
+
var digestFile = path.join(cwd, "knowledge", "session-digests.jsonl");
|
|
1694
|
+
try {
|
|
1695
|
+
var results = sessionSearch.searchDigests(digestFile, msg.query, {
|
|
1696
|
+
maxResults: msg.maxResults || 10,
|
|
1697
|
+
minScore: msg.minScore || 0.5,
|
|
1698
|
+
dateFrom: msg.dateFrom || null,
|
|
1699
|
+
dateTo: msg.dateTo || null
|
|
1700
|
+
});
|
|
1701
|
+
sendTo(ws, {
|
|
1702
|
+
type: "memory_search_results",
|
|
1703
|
+
results: sessionSearch.formatForMemoryUI(results),
|
|
1704
|
+
query: msg.query
|
|
1705
|
+
});
|
|
1706
|
+
} catch (e) {
|
|
1707
|
+
console.error("[session-search] Search failed:", e.message);
|
|
1708
|
+
sendTo(ws, { type: "memory_search_results", results: [], query: msg.query });
|
|
1709
|
+
}
|
|
1710
|
+
return;
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1687
1713
|
if (msg.type === "memory_delete") {
|
|
1688
1714
|
if (typeof msg.index !== "number") return;
|
|
1689
1715
|
var digestFile = path.join(cwd, "knowledge", "session-digests.jsonl");
|
|
@@ -4288,7 +4314,8 @@ function createProjectContext(opts) {
|
|
|
4288
4314
|
}
|
|
4289
4315
|
|
|
4290
4316
|
// Load session digests (unified: uses memory-summary.md if available)
|
|
4291
|
-
|
|
4317
|
+
// Pass user's message as query for BM25 search of relevant past sessions
|
|
4318
|
+
var recentDigests = loadMateDigests(mateCtx, msg.mateId, mentionFullInput);
|
|
4292
4319
|
|
|
4293
4320
|
// Build initial mention context
|
|
4294
4321
|
var mentionContext = buildMentionContext(userName, recentTurns) + recentDigests;
|
|
@@ -4440,9 +4467,11 @@ function createProjectContext(opts) {
|
|
|
4440
4467
|
return lines.join("\n");
|
|
4441
4468
|
}
|
|
4442
4469
|
|
|
4443
|
-
function loadMateDigests(mateCtx, mateId) {
|
|
4470
|
+
function loadMateDigests(mateCtx, mateId, query) {
|
|
4444
4471
|
var mateDir = matesModule.getMateDir(mateCtx, mateId);
|
|
4445
4472
|
var knowledgeDir = path.join(mateDir, "knowledge");
|
|
4473
|
+
var mate = matesModule.getMate(mateCtx, mateId);
|
|
4474
|
+
var hasGlobalSearch = mate && mate.globalSearch;
|
|
4446
4475
|
|
|
4447
4476
|
// Check for memory-summary.md first
|
|
4448
4477
|
var summaryFile = path.join(knowledgeDir, "memory-summary.md");
|
|
@@ -4457,26 +4486,72 @@ function createProjectContext(opts) {
|
|
|
4457
4486
|
|
|
4458
4487
|
// Load raw digests
|
|
4459
4488
|
var allLines = [];
|
|
4489
|
+
var digestFile = path.join(knowledgeDir, "session-digests.jsonl");
|
|
4460
4490
|
try {
|
|
4461
|
-
var digestFile = path.join(knowledgeDir, "session-digests.jsonl");
|
|
4462
4491
|
if (fs.existsSync(digestFile)) {
|
|
4463
4492
|
allLines = fs.readFileSync(digestFile, "utf8").trim().split("\n").filter(function (l) { return l.trim(); });
|
|
4464
4493
|
}
|
|
4465
4494
|
} catch (e) {}
|
|
4466
4495
|
|
|
4496
|
+
var result = "";
|
|
4497
|
+
|
|
4467
4498
|
if (hasSummary) {
|
|
4468
4499
|
// Load summary + latest 5 raw digests for richer context
|
|
4469
4500
|
var recent = allLines.slice(-5);
|
|
4470
|
-
|
|
4501
|
+
result = "\n\nYour memory summary:\n" + summaryContent;
|
|
4471
4502
|
if (recent.length > 0) {
|
|
4472
4503
|
result += formatRawDigests(recent, "Latest raw session memories:");
|
|
4473
4504
|
}
|
|
4474
|
-
return result;
|
|
4475
4505
|
} else {
|
|
4476
4506
|
// Backward compatible: latest 8 raw digests
|
|
4477
4507
|
var recent = allLines.slice(-8);
|
|
4478
|
-
|
|
4508
|
+
result = formatRawDigests(recent, "Your recent session memories:");
|
|
4509
|
+
}
|
|
4510
|
+
|
|
4511
|
+
// BM25 unified search: digests + session history for current topic
|
|
4512
|
+
if (query && allLines.length > 5) {
|
|
4513
|
+
try {
|
|
4514
|
+
// Collect mate's sessions from session manager
|
|
4515
|
+
var mateSessions = [];
|
|
4516
|
+
sm.sessions.forEach(function (s) {
|
|
4517
|
+
if (!s.hidden && s.history && s.history.length > 0) {
|
|
4518
|
+
mateSessions.push(s);
|
|
4519
|
+
}
|
|
4520
|
+
});
|
|
4521
|
+
|
|
4522
|
+
// Global search: collect ALL mates' digest files for cross-mate context
|
|
4523
|
+
var otherDigests = [];
|
|
4524
|
+
if (hasGlobalSearch) {
|
|
4525
|
+
try {
|
|
4526
|
+
var allMates = matesModule.getAllMates(mateCtx);
|
|
4527
|
+
for (var mi = 0; mi < allMates.length; mi++) {
|
|
4528
|
+
if (allMates[mi].id === mateId) continue; // skip self (already included)
|
|
4529
|
+
var otherDir = matesModule.getMateDir(mateCtx, allMates[mi].id);
|
|
4530
|
+
var otherDigest = path.join(otherDir, "knowledge", "session-digests.jsonl");
|
|
4531
|
+
if (fs.existsSync(otherDigest)) {
|
|
4532
|
+
var mateName = allMates[mi].name || allMates[mi].id;
|
|
4533
|
+
otherDigests.push({ path: otherDigest, mateName: mateName });
|
|
4534
|
+
}
|
|
4535
|
+
}
|
|
4536
|
+
} catch (e) {}
|
|
4537
|
+
}
|
|
4538
|
+
|
|
4539
|
+
var searchResults = sessionSearch.searchMate({
|
|
4540
|
+
digestFilePath: digestFile,
|
|
4541
|
+
otherDigests: otherDigests,
|
|
4542
|
+
sessions: mateSessions,
|
|
4543
|
+
query: query,
|
|
4544
|
+
maxResults: hasGlobalSearch ? 8 : 5,
|
|
4545
|
+
minScore: 1.0
|
|
4546
|
+
});
|
|
4547
|
+
var contextStr = sessionSearch.formatForContext(searchResults);
|
|
4548
|
+
if (contextStr) result += contextStr;
|
|
4549
|
+
} catch (e) {
|
|
4550
|
+
console.error("[session-search] Mate search failed:", e.message);
|
|
4551
|
+
}
|
|
4479
4552
|
}
|
|
4553
|
+
|
|
4554
|
+
return result;
|
|
4480
4555
|
}
|
|
4481
4556
|
|
|
4482
4557
|
// Gate check: ask Haiku whether this conversation contains anything worth remembering
|
|
@@ -5364,7 +5439,7 @@ function createProjectContext(opts) {
|
|
|
5364
5439
|
|
|
5365
5440
|
// Use moderator's own Claude identity to generate the brief via mention session
|
|
5366
5441
|
var claudeMd = loadMateClaudeMd(mateCtx, debate.moderatorId);
|
|
5367
|
-
var digests = loadMateDigests(mateCtx, debate.moderatorId);
|
|
5442
|
+
var digests = loadMateDigests(mateCtx, debate.moderatorId, debate.topic);
|
|
5368
5443
|
|
|
5369
5444
|
var briefText = "";
|
|
5370
5445
|
sdk.createMentionSession({
|
|
@@ -5546,7 +5621,7 @@ function createProjectContext(opts) {
|
|
|
5546
5621
|
|
|
5547
5622
|
// Create moderator mention session
|
|
5548
5623
|
var claudeMd = loadMateClaudeMd(mateCtx, debate.moderatorId);
|
|
5549
|
-
var digests = loadMateDigests(mateCtx, debate.moderatorId);
|
|
5624
|
+
var digests = loadMateDigests(mateCtx, debate.moderatorId, debate.topic);
|
|
5550
5625
|
var moderatorContext = buildModeratorContext(debate) + digests;
|
|
5551
5626
|
|
|
5552
5627
|
sdk.createMentionSession({
|
|
@@ -5716,7 +5791,7 @@ function createProjectContext(opts) {
|
|
|
5716
5791
|
} else {
|
|
5717
5792
|
// Create new panelist session
|
|
5718
5793
|
var claudeMd = loadMateClaudeMd(debate.mateCtx, mateId);
|
|
5719
|
-
var digests = loadMateDigests(debate.mateCtx, mateId);
|
|
5794
|
+
var digests = loadMateDigests(debate.mateCtx, mateId, debate.topic);
|
|
5720
5795
|
var panelistContext = buildPanelistContext(debate, panelistInfo) + digests;
|
|
5721
5796
|
|
|
5722
5797
|
// Include debate history so far for context
|
|
@@ -6150,7 +6225,7 @@ function createProjectContext(opts) {
|
|
|
6150
6225
|
if (wasEnded || !debate.moderatorSession || !debate.moderatorSession.isAlive()) {
|
|
6151
6226
|
console.log("[debate] Creating new moderator session for resume");
|
|
6152
6227
|
var claudeMd = loadMateClaudeMd(mateCtx, debate.moderatorId);
|
|
6153
|
-
var digests = loadMateDigests(mateCtx, debate.moderatorId);
|
|
6228
|
+
var digests = loadMateDigests(mateCtx, debate.moderatorId, debate.topic);
|
|
6154
6229
|
var moderatorContext = buildModeratorContext(debate) + digests;
|
|
6155
6230
|
|
|
6156
6231
|
// Include debate history so moderator has context
|
|
@@ -7043,12 +7118,12 @@ function createProjectContext(opts) {
|
|
|
7043
7118
|
// Mate projects: watch CLAUDE.md and enforce system-managed sections
|
|
7044
7119
|
if (isMate) {
|
|
7045
7120
|
var claudeMdPath = path.join(cwd, "CLAUDE.md");
|
|
7046
|
-
//
|
|
7047
|
-
|
|
7048
|
-
|
|
7049
|
-
|
|
7050
|
-
|
|
7051
|
-
try {
|
|
7121
|
+
// Derive mateId from cwd (last path segment) and build ctx for dynamic team section
|
|
7122
|
+
var _mateId = path.basename(cwd);
|
|
7123
|
+
var _mateCtx = matesModule.buildMateCtx(projectOwnerId);
|
|
7124
|
+
var _enforceOpts = { ctx: _mateCtx, mateId: _mateId };
|
|
7125
|
+
// Enforce all system sections atomically on startup (single read/write)
|
|
7126
|
+
try { matesModule.enforceAllSections(claudeMdPath, _enforceOpts); } catch (e) {}
|
|
7052
7127
|
// Sync sticky notes knowledge file on startup
|
|
7053
7128
|
try {
|
|
7054
7129
|
var knDir = path.join(cwd, "knowledge");
|
|
@@ -7067,13 +7142,8 @@ function createProjectContext(opts) {
|
|
|
7067
7142
|
if (crisisDebounce) clearTimeout(crisisDebounce);
|
|
7068
7143
|
crisisDebounce = setTimeout(function () {
|
|
7069
7144
|
crisisDebounce = null;
|
|
7070
|
-
|
|
7071
|
-
|
|
7072
|
-
// write cascades (server.js scheduleProjectRegistryRefresh handles updates)
|
|
7073
|
-
try { matesModule.enforceSessionMemory(claudeMdPath); } catch (e) {}
|
|
7074
|
-
try { matesModule.enforceStickyNotes(claudeMdPath); } catch (e) {}
|
|
7075
|
-
try { matesModule.enforceDebateAwareness(claudeMdPath); } catch (e) {}
|
|
7076
|
-
try { crisisSafety.enforce(claudeMdPath); } catch (e) {}
|
|
7145
|
+
// Atomic enforce: single read/write for all system sections
|
|
7146
|
+
try { matesModule.enforceAllSections(claudeMdPath, _enforceOpts); } catch (e) {}
|
|
7077
7147
|
}, 500);
|
|
7078
7148
|
});
|
|
7079
7149
|
crisisWatcher.on("error", function () {});
|