clay-server 2.19.0 → 2.20.0-beta.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.
- package/README.md +51 -91
- package/bin/cli.js +49 -14
- package/lib/cli-sessions.js +3 -3
- package/lib/config.js +30 -4
- package/lib/daemon.js +28 -5
- package/lib/mates.js +61 -7
- package/lib/notes.js +20 -0
- package/lib/os-users.js +71 -2
- package/lib/project.js +850 -184
- package/lib/public/app.js +160 -16
- package/lib/public/css/mates.css +316 -2
- package/lib/public/css/mention.css +23 -0
- package/lib/public/css/mobile-nav.css +198 -0
- package/lib/public/css/overlays.css +23 -0
- package/lib/public/css/rewind.css +32 -0
- package/lib/public/css/title-bar.css +3 -0
- package/lib/public/css/user-settings.css +2 -2
- package/lib/public/index.html +64 -14
- package/lib/public/modules/command-palette.js +44 -4
- package/lib/public/modules/filebrowser.js +2 -0
- package/lib/public/modules/input.js +11 -3
- package/lib/public/modules/mate-knowledge.js +2 -0
- package/lib/public/modules/mate-memory.js +353 -0
- package/lib/public/modules/mention.js +77 -2
- package/lib/public/modules/notifications.js +0 -8
- package/lib/public/modules/server-settings.js +11 -4
- package/lib/public/modules/sidebar.js +284 -6
- package/lib/public/modules/theme.js +10 -12
- package/lib/public/modules/tools.js +84 -12
- package/lib/public/modules/user-settings.js +45 -12
- package/lib/sdk-bridge.js +114 -35
- package/lib/server.js +84 -6
- package/lib/sessions.js +4 -4
- package/lib/users.js +26 -0
- package/package.json +1 -1
package/lib/project.js
CHANGED
|
@@ -129,7 +129,8 @@ function createProjectContext(opts) {
|
|
|
129
129
|
|
|
130
130
|
// Convert imageRefs in history entries to images with URLs for the client
|
|
131
131
|
function hydrateImageRefs(entry) {
|
|
132
|
-
if (!entry ||
|
|
132
|
+
if (!entry || !entry.imageRefs) return entry;
|
|
133
|
+
if (entry.type !== "user_message" && entry.type !== "mention_user") return entry;
|
|
133
134
|
var images = [];
|
|
134
135
|
for (var ri = 0; ri < entry.imageRefs.length; ri++) {
|
|
135
136
|
var ref = entry.imageRefs[ri];
|
|
@@ -419,6 +420,19 @@ function createProjectContext(opts) {
|
|
|
419
420
|
isMate: isMate,
|
|
420
421
|
dangerouslySkipPermissions: dangerouslySkipPermissions,
|
|
421
422
|
onProcessingChanged: onProcessingChanged,
|
|
423
|
+
onTurnDone: isMate ? function (session, preview) { digestDmTurn(session, preview); } : null,
|
|
424
|
+
getAutoContinueSetting: function (session) {
|
|
425
|
+
// Per-user setting in multi-user mode
|
|
426
|
+
if (usersModule.isMultiUser() && session && session.ownerId) {
|
|
427
|
+
return usersModule.getAutoContinue(session.ownerId);
|
|
428
|
+
}
|
|
429
|
+
// Single-user: fall back to daemon config
|
|
430
|
+
if (typeof opts.onGetDaemonConfig === "function") {
|
|
431
|
+
var dc = opts.onGetDaemonConfig();
|
|
432
|
+
return !!dc.autoContinueOnRateLimit;
|
|
433
|
+
}
|
|
434
|
+
return false;
|
|
435
|
+
},
|
|
422
436
|
});
|
|
423
437
|
|
|
424
438
|
// --- Ralph Loop state ---
|
|
@@ -1381,6 +1395,22 @@ function createProjectContext(opts) {
|
|
|
1381
1395
|
return;
|
|
1382
1396
|
}
|
|
1383
1397
|
|
|
1398
|
+
if (msg.type === "mention_stop") {
|
|
1399
|
+
var session = getSessionForWs(ws);
|
|
1400
|
+
if (session && session._mentionInProgress) {
|
|
1401
|
+
// Abort the active mention session for this mate
|
|
1402
|
+
var mateId = msg.mateId;
|
|
1403
|
+
if (mateId && session._mentionSessions && session._mentionSessions[mateId]) {
|
|
1404
|
+
session._mentionSessions[mateId].abort();
|
|
1405
|
+
session._mentionSessions[mateId].close();
|
|
1406
|
+
delete session._mentionSessions[mateId];
|
|
1407
|
+
}
|
|
1408
|
+
session._mentionInProgress = false;
|
|
1409
|
+
sendToSession(session.localId, { type: "mention_done", mateId: mateId, stopped: true });
|
|
1410
|
+
}
|
|
1411
|
+
return;
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1384
1414
|
// --- Debate ---
|
|
1385
1415
|
if (msg.type === "debate_start") {
|
|
1386
1416
|
handleDebateStart(ws, msg);
|
|
@@ -1406,6 +1436,9 @@ function createProjectContext(opts) {
|
|
|
1406
1436
|
try {
|
|
1407
1437
|
var entries = fs.readdirSync(knowledgeDir);
|
|
1408
1438
|
for (var ki = 0; ki < entries.length; ki++) {
|
|
1439
|
+
if (entries[ki] === "session-digests.jsonl") continue;
|
|
1440
|
+
if (entries[ki] === "sticky-notes.md") continue;
|
|
1441
|
+
if (entries[ki] === "memory-summary.md") continue;
|
|
1409
1442
|
if (entries[ki].endsWith(".md") || entries[ki].endsWith(".jsonl")) {
|
|
1410
1443
|
var stat = fs.statSync(path.join(knowledgeDir, entries[ki]));
|
|
1411
1444
|
files.push({ name: entries[ki], size: stat.size, mtime: stat.mtimeMs, common: false });
|
|
@@ -1559,6 +1592,56 @@ function createProjectContext(opts) {
|
|
|
1559
1592
|
return;
|
|
1560
1593
|
}
|
|
1561
1594
|
|
|
1595
|
+
// --- Memory (session digests) management ---
|
|
1596
|
+
if (msg.type === "memory_list") {
|
|
1597
|
+
var digestFile = path.join(cwd, "knowledge", "session-digests.jsonl");
|
|
1598
|
+
var summaryFile = path.join(cwd, "knowledge", "memory-summary.md");
|
|
1599
|
+
var entries = [];
|
|
1600
|
+
var summary = "";
|
|
1601
|
+
try {
|
|
1602
|
+
var raw = fs.readFileSync(digestFile, "utf8").trim();
|
|
1603
|
+
if (raw) {
|
|
1604
|
+
var lines = raw.split("\n");
|
|
1605
|
+
for (var mi = 0; mi < lines.length; mi++) {
|
|
1606
|
+
try {
|
|
1607
|
+
var obj = JSON.parse(lines[mi]);
|
|
1608
|
+
obj.index = mi;
|
|
1609
|
+
entries.push(obj);
|
|
1610
|
+
} catch (e) {}
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
} catch (e) { /* file may not exist */ }
|
|
1614
|
+
try {
|
|
1615
|
+
if (fs.existsSync(summaryFile)) {
|
|
1616
|
+
summary = fs.readFileSync(summaryFile, "utf8").trim();
|
|
1617
|
+
}
|
|
1618
|
+
} catch (e) {}
|
|
1619
|
+
// Return newest first
|
|
1620
|
+
entries.reverse();
|
|
1621
|
+
sendTo(ws, { type: "memory_list", entries: entries, summary: summary });
|
|
1622
|
+
return;
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
if (msg.type === "memory_delete") {
|
|
1626
|
+
if (typeof msg.index !== "number") return;
|
|
1627
|
+
var digestFile = path.join(cwd, "knowledge", "session-digests.jsonl");
|
|
1628
|
+
try {
|
|
1629
|
+
var raw = fs.readFileSync(digestFile, "utf8").trim();
|
|
1630
|
+
var lines = raw ? raw.split("\n") : [];
|
|
1631
|
+
if (msg.index >= 0 && msg.index < lines.length) {
|
|
1632
|
+
lines.splice(msg.index, 1);
|
|
1633
|
+
if (lines.length === 0) {
|
|
1634
|
+
fs.unlinkSync(digestFile);
|
|
1635
|
+
} else {
|
|
1636
|
+
fs.writeFileSync(digestFile, lines.join("\n") + "\n");
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
} catch (e) {}
|
|
1640
|
+
sendTo(ws, { type: "memory_deleted", index: msg.index });
|
|
1641
|
+
handleMessage(ws, { type: "memory_list" });
|
|
1642
|
+
return;
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1562
1645
|
if (msg.type === "push_subscribe") {
|
|
1563
1646
|
if (pushModule && msg.subscription) pushModule.addSubscription(msg.subscription, msg.replaceEndpoint);
|
|
1564
1647
|
return;
|
|
@@ -1802,6 +1885,8 @@ function createProjectContext(opts) {
|
|
|
1802
1885
|
if (v && isNewer(v, currentVersion)) {
|
|
1803
1886
|
latestVersion = v;
|
|
1804
1887
|
sendTo(ws, { type: "update_available", version: v });
|
|
1888
|
+
} else {
|
|
1889
|
+
sendTo(ws, { type: "up_to_date", version: currentVersion });
|
|
1805
1890
|
}
|
|
1806
1891
|
}).catch(function () {});
|
|
1807
1892
|
return;
|
|
@@ -1908,11 +1993,6 @@ function createProjectContext(opts) {
|
|
|
1908
1993
|
}
|
|
1909
1994
|
|
|
1910
1995
|
if (msg.type === "set_permission_mode" && msg.mode) {
|
|
1911
|
-
// When dangerouslySkipPermissions is active, don't allow UI to change mode
|
|
1912
|
-
if (dangerouslySkipPermissions) {
|
|
1913
|
-
send({ type: "config_state", model: sm.currentModel || "", mode: "bypassPermissions", effort: sm.currentEffort || "medium", betas: sm.currentBetas || [], thinking: sm.currentThinking || "adaptive", thinkingBudget: sm.currentThinkingBudget || 10000 });
|
|
1914
|
-
return;
|
|
1915
|
-
}
|
|
1916
1996
|
sm.currentPermissionMode = msg.mode;
|
|
1917
1997
|
var session = getSessionForWs(ws);
|
|
1918
1998
|
if (session) {
|
|
@@ -1926,14 +2006,12 @@ function createProjectContext(opts) {
|
|
|
1926
2006
|
if (typeof opts.onSetServerDefaultMode === "function") {
|
|
1927
2007
|
opts.onSetServerDefaultMode(msg.mode);
|
|
1928
2008
|
}
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
sdk.setPermissionMode(session, msg.mode);
|
|
1934
|
-
}
|
|
1935
|
-
send({ type: "config_state", model: sm.currentModel || "", mode: sm.currentPermissionMode, effort: sm.currentEffort || "medium", betas: sm.currentBetas || [], thinking: sm.currentThinking || "adaptive", thinkingBudget: sm.currentThinkingBudget || 10000 });
|
|
2009
|
+
sm.currentPermissionMode = msg.mode;
|
|
2010
|
+
var session = getSessionForWs(ws);
|
|
2011
|
+
if (session) {
|
|
2012
|
+
sdk.setPermissionMode(session, msg.mode);
|
|
1936
2013
|
}
|
|
2014
|
+
send({ type: "config_state", model: sm.currentModel || "", mode: sm.currentPermissionMode, effort: sm.currentEffort || "medium", betas: sm.currentBetas || [], thinking: sm.currentThinking || "adaptive", thinkingBudget: sm.currentThinkingBudget || 10000 });
|
|
1937
2015
|
return;
|
|
1938
2016
|
}
|
|
1939
2017
|
|
|
@@ -1941,14 +2019,12 @@ function createProjectContext(opts) {
|
|
|
1941
2019
|
if (typeof opts.onSetProjectDefaultMode === "function") {
|
|
1942
2020
|
opts.onSetProjectDefaultMode(slug, msg.mode);
|
|
1943
2021
|
}
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
sdk.setPermissionMode(session, msg.mode);
|
|
1949
|
-
}
|
|
1950
|
-
send({ type: "config_state", model: sm.currentModel || "", mode: sm.currentPermissionMode, effort: sm.currentEffort || "medium", betas: sm.currentBetas || [], thinking: sm.currentThinking || "adaptive", thinkingBudget: sm.currentThinkingBudget || 10000 });
|
|
2022
|
+
sm.currentPermissionMode = msg.mode;
|
|
2023
|
+
var session = getSessionForWs(ws);
|
|
2024
|
+
if (session) {
|
|
2025
|
+
sdk.setPermissionMode(session, msg.mode);
|
|
1951
2026
|
}
|
|
2027
|
+
send({ type: "config_state", model: sm.currentModel || "", mode: sm.currentPermissionMode, effort: sm.currentEffort || "medium", betas: sm.currentBetas || [], thinking: sm.currentThinking || "adaptive", thinkingBudget: sm.currentThinkingBudget || 10000 });
|
|
1952
2028
|
return;
|
|
1953
2029
|
}
|
|
1954
2030
|
|
|
@@ -2127,7 +2203,7 @@ function createProjectContext(opts) {
|
|
|
2127
2203
|
var pending = session.pendingAskUser[toolId];
|
|
2128
2204
|
if (!pending) return;
|
|
2129
2205
|
delete session.pendingAskUser[toolId];
|
|
2130
|
-
sm.sendAndRecord(session, { type: "ask_user_answered", toolId: toolId });
|
|
2206
|
+
sm.sendAndRecord(session, { type: "ask_user_answered", toolId: toolId, answers: answers });
|
|
2131
2207
|
pending.resolve({
|
|
2132
2208
|
behavior: "allow",
|
|
2133
2209
|
updatedInput: Object.assign({}, pending.input, { answers: answers }),
|
|
@@ -2324,7 +2400,7 @@ function createProjectContext(opts) {
|
|
|
2324
2400
|
|
|
2325
2401
|
// --- Browse directories (for add-project autocomplete) ---
|
|
2326
2402
|
if (msg.type === "browse_dir") {
|
|
2327
|
-
var rawPath = (msg.path || "").replace(/^~/,
|
|
2403
|
+
var rawPath = (msg.path || "").replace(/^~/, require("./config").REAL_HOME);
|
|
2328
2404
|
var absTarget = path.resolve(rawPath);
|
|
2329
2405
|
var parentDir, prefix;
|
|
2330
2406
|
try {
|
|
@@ -2363,7 +2439,7 @@ function createProjectContext(opts) {
|
|
|
2363
2439
|
|
|
2364
2440
|
// --- Add project from web UI ---
|
|
2365
2441
|
if (msg.type === "add_project") {
|
|
2366
|
-
var addPath = (msg.path || "").replace(/^~/,
|
|
2442
|
+
var addPath = (msg.path || "").replace(/^~/, require("./config").REAL_HOME);
|
|
2367
2443
|
var addAbs = path.resolve(addPath);
|
|
2368
2444
|
try {
|
|
2369
2445
|
var addStat = fs.statSync(addAbs);
|
|
@@ -2545,7 +2621,7 @@ function createProjectContext(opts) {
|
|
|
2545
2621
|
|
|
2546
2622
|
// --- Daemon config / server management (admin-only in multi-user mode) ---
|
|
2547
2623
|
if (msg.type === "get_daemon_config" || msg.type === "set_pin" || msg.type === "set_keep_awake" ||
|
|
2548
|
-
msg.type === "set_image_retention" || msg.type === "shutdown_server" || msg.type === "restart_server") {
|
|
2624
|
+
msg.type === "set_auto_continue" || msg.type === "set_image_retention" || msg.type === "shutdown_server" || msg.type === "restart_server") {
|
|
2549
2625
|
if (usersModule.isMultiUser()) {
|
|
2550
2626
|
var _wsUser = ws._clayUser;
|
|
2551
2627
|
if (!_wsUser || _wsUser.role !== "admin") {
|
|
@@ -2580,6 +2656,15 @@ function createProjectContext(opts) {
|
|
|
2580
2656
|
return;
|
|
2581
2657
|
}
|
|
2582
2658
|
|
|
2659
|
+
if (msg.type === "set_auto_continue") {
|
|
2660
|
+
if (typeof opts.onSetAutoContinue === "function") {
|
|
2661
|
+
var acResult = opts.onSetAutoContinue(msg.value);
|
|
2662
|
+
sendTo(ws, { type: "set_auto_continue_result", ok: acResult.ok, autoContinueOnRateLimit: acResult.autoContinueOnRateLimit });
|
|
2663
|
+
send({ type: "auto_continue_changed", autoContinueOnRateLimit: acResult.autoContinueOnRateLimit });
|
|
2664
|
+
}
|
|
2665
|
+
return;
|
|
2666
|
+
}
|
|
2667
|
+
|
|
2583
2668
|
if (msg.type === "set_image_retention") {
|
|
2584
2669
|
if (typeof opts.onSetImageRetention === "function") {
|
|
2585
2670
|
var irResult = opts.onSetImageRetention(msg.days);
|
|
@@ -2781,8 +2866,7 @@ function createProjectContext(opts) {
|
|
|
2781
2866
|
|
|
2782
2867
|
// --- Global CLAUDE.md ---
|
|
2783
2868
|
if (msg.type === "read_global_claude_md") {
|
|
2784
|
-
var
|
|
2785
|
-
var globalMdPath = path.join(os.homedir(), ".claude", "CLAUDE.md");
|
|
2869
|
+
var globalMdPath = path.join(require("./config").REAL_HOME, ".claude", "CLAUDE.md");
|
|
2786
2870
|
try {
|
|
2787
2871
|
var globalMdContent = fs.readFileSync(globalMdPath, "utf8");
|
|
2788
2872
|
sendTo(ws, { type: "global_claude_md_result", content: globalMdContent });
|
|
@@ -2793,8 +2877,7 @@ function createProjectContext(opts) {
|
|
|
2793
2877
|
}
|
|
2794
2878
|
|
|
2795
2879
|
if (msg.type === "write_global_claude_md") {
|
|
2796
|
-
var
|
|
2797
|
-
var globalMdDir = path.join(os2.homedir(), ".claude");
|
|
2880
|
+
var globalMdDir = path.join(require("./config").REAL_HOME, ".claude");
|
|
2798
2881
|
var globalMdWritePath = path.join(globalMdDir, "CLAUDE.md");
|
|
2799
2882
|
try {
|
|
2800
2883
|
if (!fs.existsSync(globalMdDir)) {
|
|
@@ -3037,22 +3120,53 @@ function createProjectContext(opts) {
|
|
|
3037
3120
|
}
|
|
3038
3121
|
|
|
3039
3122
|
// --- Sticky notes ---
|
|
3123
|
+
function syncNotesKnowledge() {
|
|
3124
|
+
if (!isMate) return;
|
|
3125
|
+
try {
|
|
3126
|
+
var knDir = path.join(cwd, "knowledge");
|
|
3127
|
+
var knFile = path.join(knDir, "sticky-notes.md");
|
|
3128
|
+
var text = nm.getActiveNotesText();
|
|
3129
|
+
if (text) {
|
|
3130
|
+
fs.mkdirSync(knDir, { recursive: true });
|
|
3131
|
+
fs.writeFileSync(knFile, text);
|
|
3132
|
+
} else {
|
|
3133
|
+
try { fs.unlinkSync(knFile); } catch (e) {}
|
|
3134
|
+
}
|
|
3135
|
+
} catch (e) {
|
|
3136
|
+
console.error("[project] Failed to sync sticky-notes.md:", e.message);
|
|
3137
|
+
}
|
|
3138
|
+
}
|
|
3139
|
+
|
|
3040
3140
|
if (msg.type === "note_create") {
|
|
3041
3141
|
var note = nm.create(msg);
|
|
3042
|
-
if (note)
|
|
3142
|
+
if (note) {
|
|
3143
|
+
send({ type: "note_created", note: note });
|
|
3144
|
+
syncNotesKnowledge();
|
|
3145
|
+
}
|
|
3043
3146
|
return;
|
|
3044
3147
|
}
|
|
3045
3148
|
|
|
3046
3149
|
if (msg.type === "note_update") {
|
|
3047
3150
|
if (!msg.id) return;
|
|
3048
3151
|
var updated = nm.update(msg.id, msg);
|
|
3049
|
-
if (updated)
|
|
3152
|
+
if (updated) {
|
|
3153
|
+
send({ type: "note_updated", note: updated });
|
|
3154
|
+
if (msg.text !== undefined || msg.hidden !== undefined) syncNotesKnowledge();
|
|
3155
|
+
}
|
|
3050
3156
|
return;
|
|
3051
3157
|
}
|
|
3052
3158
|
|
|
3053
3159
|
if (msg.type === "note_delete") {
|
|
3054
3160
|
if (!msg.id) return;
|
|
3055
|
-
if (nm.remove(msg.id))
|
|
3161
|
+
if (nm.remove(msg.id)) {
|
|
3162
|
+
send({ type: "note_deleted", id: msg.id });
|
|
3163
|
+
syncNotesKnowledge();
|
|
3164
|
+
}
|
|
3165
|
+
return;
|
|
3166
|
+
}
|
|
3167
|
+
|
|
3168
|
+
if (msg.type === "note_list_request") {
|
|
3169
|
+
sendTo(ws, { type: "notes_list", notes: nm.list() });
|
|
3056
3170
|
return;
|
|
3057
3171
|
}
|
|
3058
3172
|
|
|
@@ -3556,6 +3670,7 @@ function createProjectContext(opts) {
|
|
|
3556
3670
|
}
|
|
3557
3671
|
|
|
3558
3672
|
var userMsg = { type: "user_message", text: msg.text || "" };
|
|
3673
|
+
var savedImagePaths = [];
|
|
3559
3674
|
if (msg.images && msg.images.length > 0) {
|
|
3560
3675
|
userMsg.imageCount = msg.images.length;
|
|
3561
3676
|
// Save images as files, store URL references in history
|
|
@@ -3565,6 +3680,7 @@ function createProjectContext(opts) {
|
|
|
3565
3680
|
var savedName = saveImageFile(img.mediaType, img.data);
|
|
3566
3681
|
if (savedName) {
|
|
3567
3682
|
imageRefs.push({ mediaType: img.mediaType, file: savedName });
|
|
3683
|
+
savedImagePaths.push(path.join(imagesDir, savedName));
|
|
3568
3684
|
}
|
|
3569
3685
|
}
|
|
3570
3686
|
if (imageRefs.length > 0) {
|
|
@@ -3593,6 +3709,11 @@ function createProjectContext(opts) {
|
|
|
3593
3709
|
}
|
|
3594
3710
|
|
|
3595
3711
|
var fullText = msg.text || "";
|
|
3712
|
+
// Prepend saved image paths so Claude can copy/save them
|
|
3713
|
+
if (savedImagePaths.length > 0) {
|
|
3714
|
+
var imgPathLines = savedImagePaths.map(function (p) { return "[Uploaded image: " + p + "]"; }).join("\n");
|
|
3715
|
+
fullText = imgPathLines + (fullText ? "\n" + fullText : "");
|
|
3716
|
+
}
|
|
3596
3717
|
if (msg.pastes && msg.pastes.length > 0) {
|
|
3597
3718
|
for (var pi = 0; pi < msg.pastes.length; pi++) {
|
|
3598
3719
|
if (fullText) fullText += "\n\n";
|
|
@@ -3719,63 +3840,226 @@ function createProjectContext(opts) {
|
|
|
3719
3840
|
var mateDir = matesModule.getMateDir(mateCtx, mateId);
|
|
3720
3841
|
var knowledgeDir = path.join(mateDir, "knowledge");
|
|
3721
3842
|
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
' "date": "YYYY-MM-DD",',
|
|
3729
|
-
' "topic": "short topic description",',
|
|
3730
|
-
' "my_position": "what I said/recommended",',
|
|
3731
|
-
' "other_perspectives": "other mates or user perspectives if relevant",',
|
|
3732
|
-
' "decisions": "what was decided, or null if pending",',
|
|
3733
|
-
' "open_items": "what remains unresolved",',
|
|
3734
|
-
' "user_sentiment": "how the user seemed to feel about this topic"',
|
|
3735
|
-
"}",
|
|
3736
|
-
"",
|
|
3737
|
-
"IMPORTANT: Output ONLY the JSON object. Nothing else.",
|
|
3738
|
-
].join("\n");
|
|
3843
|
+
// Migration: generate initial summary if missing
|
|
3844
|
+
var summaryFile = path.join(knowledgeDir, "memory-summary.md");
|
|
3845
|
+
var digestFile = path.join(knowledgeDir, "session-digests.jsonl");
|
|
3846
|
+
if (!fs.existsSync(summaryFile) && fs.existsSync(digestFile)) {
|
|
3847
|
+
initMemorySummary(mateCtx, mateId, function () {});
|
|
3848
|
+
}
|
|
3739
3849
|
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3850
|
+
// Build conversation content for gate check (500 char cap each side)
|
|
3851
|
+
var userQ = userQuestion || "(unknown)";
|
|
3852
|
+
var mateR = mateResponse || "(unknown)";
|
|
3853
|
+
var conversationContent = "User: " + (userQ.length > 500 ? userQ.substring(0, 500) + "..." : userQ) +
|
|
3854
|
+
"\nMate: " + (mateR.length > 500 ? mateR.substring(0, 500) + "..." : mateR);
|
|
3855
|
+
|
|
3856
|
+
// Gate check: ask Haiku if this is worth remembering
|
|
3857
|
+
gateMemory(mateCtx, mateId, conversationContent, function (shouldRemember) {
|
|
3858
|
+
if (!shouldRemember) {
|
|
3859
|
+
console.log("[digest] Gate declined memory for mention, mate " + mateId);
|
|
3860
|
+
return;
|
|
3861
|
+
}
|
|
3862
|
+
|
|
3863
|
+
var digestPrompt = [
|
|
3864
|
+
"[SYSTEM: Session Digest]",
|
|
3865
|
+
"Summarize this conversation from YOUR perspective for your long-term memory.",
|
|
3866
|
+
"Output ONLY a single valid JSON object (no markdown, no code fences, no extra text).",
|
|
3867
|
+
"",
|
|
3868
|
+
"Schema:",
|
|
3869
|
+
"{",
|
|
3870
|
+
' "date": "YYYY-MM-DD",',
|
|
3871
|
+
' "type": "mention",',
|
|
3872
|
+
' "topic": "short topic description",',
|
|
3873
|
+
' "my_position": "what I said/recommended",',
|
|
3874
|
+
' "decisions": "what was decided, or null if pending",',
|
|
3875
|
+
' "open_items": "what remains unresolved",',
|
|
3876
|
+
' "user_sentiment": "how the user seemed to feel",',
|
|
3877
|
+
' "other_perspectives": "key points from others",',
|
|
3878
|
+
' "confidence": "high | medium | low",',
|
|
3879
|
+
' "revisit_later": true/false,',
|
|
3880
|
+
' "tags": ["relevant", "topic", "tags"]',
|
|
3881
|
+
"}",
|
|
3882
|
+
"",
|
|
3883
|
+
"IMPORTANT: Output ONLY the JSON object. Nothing else.",
|
|
3884
|
+
].join("\n");
|
|
3885
|
+
|
|
3886
|
+
var digestText = "";
|
|
3887
|
+
mentionSession.pushMessage(digestPrompt, {
|
|
3888
|
+
onActivity: function () {},
|
|
3889
|
+
onDelta: function (delta) {
|
|
3890
|
+
digestText += delta;
|
|
3891
|
+
},
|
|
3892
|
+
onDone: function () {
|
|
3893
|
+
var digestObj = null;
|
|
3894
|
+
try {
|
|
3895
|
+
var cleaned = digestText.trim();
|
|
3896
|
+
if (cleaned.indexOf("```") === 0) {
|
|
3897
|
+
cleaned = cleaned.replace(/^```[a-z]*\n?/, "").replace(/\n?```$/, "").trim();
|
|
3898
|
+
}
|
|
3899
|
+
digestObj = JSON.parse(cleaned);
|
|
3900
|
+
} catch (e) {
|
|
3901
|
+
console.error("[digest] Failed to parse digest JSON for mate " + mateId + ":", e.message);
|
|
3902
|
+
digestObj = {
|
|
3903
|
+
date: new Date().toISOString().slice(0, 10),
|
|
3904
|
+
topic: "parse_failed",
|
|
3905
|
+
raw: digestText.substring(0, 500),
|
|
3906
|
+
};
|
|
3752
3907
|
}
|
|
3753
|
-
digestObj = JSON.parse(cleaned);
|
|
3754
|
-
} catch (e) {
|
|
3755
|
-
console.error("[digest] Failed to parse digest JSON for mate " + mateId + ":", e.message);
|
|
3756
|
-
digestObj = {
|
|
3757
|
-
date: new Date().toISOString().slice(0, 10),
|
|
3758
|
-
topic: "parse_failed",
|
|
3759
|
-
raw: digestText.substring(0, 500),
|
|
3760
|
-
};
|
|
3761
|
-
}
|
|
3762
3908
|
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3909
|
+
try {
|
|
3910
|
+
fs.mkdirSync(knowledgeDir, { recursive: true });
|
|
3911
|
+
var digestFile = path.join(knowledgeDir, "session-digests.jsonl");
|
|
3912
|
+
fs.appendFileSync(digestFile, JSON.stringify(digestObj) + "\n");
|
|
3913
|
+
} catch (e) {
|
|
3914
|
+
console.error("[digest] Failed to write digest for mate " + mateId + ":", e.message);
|
|
3915
|
+
}
|
|
3916
|
+
|
|
3917
|
+
// Update memory summary
|
|
3918
|
+
updateMemorySummary(mateCtx, mateId, digestObj);
|
|
3919
|
+
},
|
|
3920
|
+
onError: function (err) {
|
|
3921
|
+
console.error("[digest] Digest generation failed for mate " + mateId + ":", err);
|
|
3922
|
+
},
|
|
3923
|
+
});
|
|
3924
|
+
});
|
|
3925
|
+
}
|
|
3926
|
+
|
|
3927
|
+
// Digest DM turn for mate projects - uses Haiku gate + conditional digest + summary update
|
|
3928
|
+
var _dmDigestPending = false;
|
|
3929
|
+
function digestDmTurn(session, responsePreview) {
|
|
3930
|
+
if (!isMate || _dmDigestPending) return;
|
|
3931
|
+
var mateId = path.basename(cwd);
|
|
3932
|
+
var mateCtx = matesModule.buildMateCtx(projectOwnerId);
|
|
3933
|
+
if (!matesModule.isMate(mateCtx, mateId)) return;
|
|
3934
|
+
|
|
3935
|
+
// Extract last user message from history
|
|
3936
|
+
var lastUserText = "";
|
|
3937
|
+
for (var hi = session.history.length - 1; hi >= 0; hi--) {
|
|
3938
|
+
var entry = session.history[hi];
|
|
3939
|
+
if (entry.type === "user_message" && entry.text) {
|
|
3940
|
+
lastUserText = entry.text;
|
|
3941
|
+
break;
|
|
3942
|
+
}
|
|
3943
|
+
}
|
|
3944
|
+
|
|
3945
|
+
// Use responsePreview (full accumulated response) instead of delta fragments
|
|
3946
|
+
var lastResponseText = responsePreview || "";
|
|
3947
|
+
if (!lastUserText && !lastResponseText) return;
|
|
3948
|
+
|
|
3949
|
+
var mateDir = matesModule.getMateDir(mateCtx, mateId);
|
|
3950
|
+
var knowledgeDir = path.join(mateDir, "knowledge");
|
|
3951
|
+
|
|
3952
|
+
// Migration: if memory-summary.md missing but digests exist, generate initial summary
|
|
3953
|
+
var summaryFile = path.join(knowledgeDir, "memory-summary.md");
|
|
3954
|
+
var digestFile = path.join(knowledgeDir, "session-digests.jsonl");
|
|
3955
|
+
if (!fs.existsSync(summaryFile) && fs.existsSync(digestFile)) {
|
|
3956
|
+
initMemorySummary(mateCtx, mateId, function () {
|
|
3957
|
+
console.log("[memory-migrate] Initial summary generated for mate " + mateId);
|
|
3958
|
+
});
|
|
3959
|
+
}
|
|
3960
|
+
|
|
3961
|
+
var conversationContent = "User: " + (lastUserText.length > 500 ? lastUserText.substring(0, 500) + "..." : lastUserText) +
|
|
3962
|
+
"\nMate: " + (lastResponseText.length > 500 ? lastResponseText.substring(0, 500) + "..." : lastResponseText);
|
|
3963
|
+
|
|
3964
|
+
_dmDigestPending = true;
|
|
3965
|
+
|
|
3966
|
+
// Gate check: ask Haiku if this is worth remembering
|
|
3967
|
+
gateMemory(mateCtx, mateId, conversationContent, function (shouldRemember) {
|
|
3968
|
+
if (!shouldRemember) {
|
|
3969
|
+
_dmDigestPending = false;
|
|
3970
|
+
console.log("[dm-digest] Gate declined memory for DM, mate " + mateId);
|
|
3971
|
+
return;
|
|
3972
|
+
}
|
|
3973
|
+
|
|
3974
|
+
var digestContext = [
|
|
3975
|
+
"[SYSTEM: Session Digest]",
|
|
3976
|
+
"Summarize this conversation from YOUR perspective for your long-term memory.",
|
|
3977
|
+
"",
|
|
3978
|
+
conversationContent,
|
|
3979
|
+
].join("\n");
|
|
3980
|
+
|
|
3981
|
+
var digestPrompt = [
|
|
3982
|
+
"Output ONLY a single valid JSON object (no markdown, no code fences, no extra text).",
|
|
3983
|
+
"",
|
|
3984
|
+
"Schema:",
|
|
3985
|
+
"{",
|
|
3986
|
+
' "date": "YYYY-MM-DD",',
|
|
3987
|
+
' "type": "dm",',
|
|
3988
|
+
' "topic": "short topic description",',
|
|
3989
|
+
' "my_position": "what I said/recommended",',
|
|
3990
|
+
' "decisions": "what was decided, or null if pending",',
|
|
3991
|
+
' "open_items": "what remains unresolved",',
|
|
3992
|
+
' "user_sentiment": "how the user seemed to feel",',
|
|
3993
|
+
' "user_intent": "what the user wanted",',
|
|
3994
|
+
' "confidence": "high | medium | low",',
|
|
3995
|
+
' "revisit_later": true/false,',
|
|
3996
|
+
' "tags": ["relevant", "topic", "tags"]',
|
|
3997
|
+
"}",
|
|
3998
|
+
"",
|
|
3999
|
+
"IMPORTANT: Output ONLY the JSON object. Nothing else.",
|
|
4000
|
+
].join("\n");
|
|
4001
|
+
|
|
4002
|
+
var digestText = "";
|
|
4003
|
+
var _digestSession = null;
|
|
4004
|
+
sdk.createMentionSession({
|
|
4005
|
+
claudeMd: "",
|
|
4006
|
+
model: "haiku",
|
|
4007
|
+
initialContext: digestContext,
|
|
4008
|
+
initialMessage: digestPrompt,
|
|
4009
|
+
onActivity: function () {},
|
|
4010
|
+
onDelta: function (delta) {
|
|
4011
|
+
digestText += delta;
|
|
4012
|
+
},
|
|
4013
|
+
onDone: function () {
|
|
4014
|
+
_dmDigestPending = false;
|
|
4015
|
+
var digestObj = null;
|
|
4016
|
+
try {
|
|
4017
|
+
var cleaned = digestText.trim();
|
|
4018
|
+
if (cleaned.indexOf("```") === 0) {
|
|
4019
|
+
cleaned = cleaned.replace(/^```[a-z]*\n?/, "").replace(/\n?```$/, "").trim();
|
|
4020
|
+
}
|
|
4021
|
+
digestObj = JSON.parse(cleaned);
|
|
4022
|
+
} catch (e) {
|
|
4023
|
+
console.error("[dm-digest] Failed to parse digest JSON for mate " + mateId + ":", e.message);
|
|
4024
|
+
digestObj = {
|
|
4025
|
+
date: new Date().toISOString().slice(0, 10),
|
|
4026
|
+
type: "dm",
|
|
4027
|
+
topic: "parse_failed",
|
|
4028
|
+
raw: digestText.substring(0, 500),
|
|
4029
|
+
};
|
|
4030
|
+
}
|
|
4031
|
+
|
|
4032
|
+
try {
|
|
4033
|
+
fs.mkdirSync(knowledgeDir, { recursive: true });
|
|
4034
|
+
var digestFile = path.join(knowledgeDir, "session-digests.jsonl");
|
|
4035
|
+
fs.appendFileSync(digestFile, JSON.stringify(digestObj) + "\n");
|
|
4036
|
+
} catch (e) {
|
|
4037
|
+
console.error("[dm-digest] Failed to write digest for mate " + mateId + ":", e.message);
|
|
4038
|
+
}
|
|
4039
|
+
|
|
4040
|
+
// Update memory summary
|
|
4041
|
+
updateMemorySummary(mateCtx, mateId, digestObj);
|
|
4042
|
+
|
|
4043
|
+
if (_digestSession) try { _digestSession.close(); } catch (e) {}
|
|
4044
|
+
},
|
|
4045
|
+
onError: function (err) {
|
|
4046
|
+
_dmDigestPending = false;
|
|
4047
|
+
console.error("[dm-digest] Digest generation failed for mate " + mateId + ":", err);
|
|
4048
|
+
if (_digestSession) try { _digestSession.close(); } catch (e) {}
|
|
4049
|
+
},
|
|
4050
|
+
}).then(function (ds) {
|
|
4051
|
+
_digestSession = ds;
|
|
4052
|
+
if (!ds) _dmDigestPending = false;
|
|
4053
|
+
}).catch(function (err) {
|
|
4054
|
+
_dmDigestPending = false;
|
|
4055
|
+
console.error("[dm-digest] Failed to create digest session for mate " + mateId + ":", err);
|
|
4056
|
+
});
|
|
3774
4057
|
});
|
|
3775
4058
|
}
|
|
3776
4059
|
|
|
3777
4060
|
function handleMention(ws, msg) {
|
|
3778
|
-
if (!msg.mateId
|
|
4061
|
+
if (!msg.mateId) return;
|
|
4062
|
+
if (!msg.text && (!msg.images || msg.images.length === 0) && (!msg.pastes || msg.pastes.length === 0)) return;
|
|
3779
4063
|
|
|
3780
4064
|
var session = getSessionForWs(ws);
|
|
3781
4065
|
if (!session) return;
|
|
@@ -3806,7 +4090,7 @@ function createProjectContext(opts) {
|
|
|
3806
4090
|
var avatarSeed = (mate.profile && mate.profile.avatarSeed) || mate.id;
|
|
3807
4091
|
|
|
3808
4092
|
// Build full mention text (include pasted content)
|
|
3809
|
-
var mentionFullInput = msg.text;
|
|
4093
|
+
var mentionFullInput = msg.text || "";
|
|
3810
4094
|
if (msg.pastes && msg.pastes.length > 0) {
|
|
3811
4095
|
for (var pi = 0; pi < msg.pastes.length; pi++) {
|
|
3812
4096
|
if (mentionFullInput) mentionFullInput += "\n\n";
|
|
@@ -3814,12 +4098,25 @@ function createProjectContext(opts) {
|
|
|
3814
4098
|
}
|
|
3815
4099
|
}
|
|
3816
4100
|
|
|
4101
|
+
// Save images to disk (same pattern as regular messages)
|
|
4102
|
+
var imageRefs = [];
|
|
4103
|
+
if (msg.images && msg.images.length > 0) {
|
|
4104
|
+
for (var imgIdx = 0; imgIdx < msg.images.length; imgIdx++) {
|
|
4105
|
+
var img = msg.images[imgIdx];
|
|
4106
|
+
var savedName = saveImageFile(img.mediaType, img.data);
|
|
4107
|
+
if (savedName) {
|
|
4108
|
+
imageRefs.push({ mediaType: img.mediaType, file: savedName });
|
|
4109
|
+
}
|
|
4110
|
+
}
|
|
4111
|
+
}
|
|
4112
|
+
|
|
3817
4113
|
// Save mention user message to session history
|
|
3818
4114
|
var mentionUserEntry = { type: "mention_user", text: msg.text, mateId: msg.mateId, mateName: mateName };
|
|
3819
4115
|
if (msg.pastes && msg.pastes.length > 0) mentionUserEntry.pastes = msg.pastes;
|
|
4116
|
+
if (imageRefs.length > 0) mentionUserEntry.imageRefs = imageRefs;
|
|
3820
4117
|
session.history.push(mentionUserEntry);
|
|
3821
4118
|
sm.appendToSessionFile(session, mentionUserEntry);
|
|
3822
|
-
sendToSessionOthers(ws, session.localId, mentionUserEntry);
|
|
4119
|
+
sendToSessionOthers(ws, session.localId, hydrateImageRefs(mentionUserEntry));
|
|
3823
4120
|
|
|
3824
4121
|
// Extract recent turns for continuity check
|
|
3825
4122
|
var recentTurns = getRecentTurns(session, MENTION_WINDOW);
|
|
@@ -3912,7 +4209,7 @@ function createProjectContext(opts) {
|
|
|
3912
4209
|
// Continue existing mention session with middle context
|
|
3913
4210
|
var middleContext = buildMiddleContext(recentTurns, msg.mateId);
|
|
3914
4211
|
var continuationText = middleContext ? middleContext + "\n\n" + mentionFullInput : mentionFullInput;
|
|
3915
|
-
existingSession.pushMessage(continuationText, mentionCallbacks);
|
|
4212
|
+
existingSession.pushMessage(continuationText, mentionCallbacks, msg.images);
|
|
3916
4213
|
} else {
|
|
3917
4214
|
// Clean up old session if it exists
|
|
3918
4215
|
if (existingSession) {
|
|
@@ -3929,26 +4226,8 @@ function createProjectContext(opts) {
|
|
|
3929
4226
|
// CLAUDE.md may not exist for new mates
|
|
3930
4227
|
}
|
|
3931
4228
|
|
|
3932
|
-
// Load
|
|
3933
|
-
var recentDigests =
|
|
3934
|
-
try {
|
|
3935
|
-
var digestFile = path.join(mateDir, "knowledge", "session-digests.jsonl");
|
|
3936
|
-
if (fs.existsSync(digestFile)) {
|
|
3937
|
-
var allLines = fs.readFileSync(digestFile, "utf8").trim().split("\n");
|
|
3938
|
-
var recent = allLines.slice(-5); // last 5 digests
|
|
3939
|
-
if (recent.length > 0) {
|
|
3940
|
-
recentDigests = "\n\nYour recent session memories (from past @mentions):\n";
|
|
3941
|
-
for (var di = 0; di < recent.length; di++) {
|
|
3942
|
-
try {
|
|
3943
|
-
var d = JSON.parse(recent[di]);
|
|
3944
|
-
recentDigests += "- [" + (d.date || "?") + "] " + (d.topic || "unknown") + ": " + (d.my_position || "") +
|
|
3945
|
-
(d.decisions ? " | Decisions: " + d.decisions : "") +
|
|
3946
|
-
(d.open_items ? " | Open: " + d.open_items : "") + "\n";
|
|
3947
|
-
} catch (e) {}
|
|
3948
|
-
}
|
|
3949
|
-
}
|
|
3950
|
-
}
|
|
3951
|
-
} catch (e) {}
|
|
4229
|
+
// Load session digests (unified: uses memory-summary.md if available)
|
|
4230
|
+
var recentDigests = loadMateDigests(mateCtx, msg.mateId);
|
|
3952
4231
|
|
|
3953
4232
|
// Build initial mention context
|
|
3954
4233
|
var mentionContext = buildMentionContext(userName, recentTurns) + recentDigests;
|
|
@@ -3958,6 +4237,7 @@ function createProjectContext(opts) {
|
|
|
3958
4237
|
claudeMd: claudeMd,
|
|
3959
4238
|
initialContext: mentionContext,
|
|
3960
4239
|
initialMessage: mentionFullInput,
|
|
4240
|
+
initialImages: msg.images || null,
|
|
3961
4241
|
onActivity: mentionCallbacks.onActivity,
|
|
3962
4242
|
onDelta: mentionCallbacks.onDelta,
|
|
3963
4243
|
onDone: mentionCallbacks.onDone,
|
|
@@ -4035,11 +4315,17 @@ function createProjectContext(opts) {
|
|
|
4035
4315
|
// Sort by length descending to match longest name first
|
|
4036
4316
|
names.sort(function (a, b) { return b.length - a.length; });
|
|
4037
4317
|
var mentioned = [];
|
|
4318
|
+
// Strip markdown inline formatting so **@Name**, ~~@Name~~, `@Name`, [@Name](url) etc. still match
|
|
4319
|
+
var cleaned = text
|
|
4320
|
+
.replace(/\[([^\]]*)\]\([^)]*\)/g, "$1") // [text](url) -> text
|
|
4321
|
+
.replace(/`([^`]*)`/g, "$1") // `code` -> code
|
|
4322
|
+
.replace(/(\*{1,3}|_{1,3}|~{2})/g, ""); // bold, italic, strikethrough markers
|
|
4038
4323
|
console.log("[debate-mention] nameMap keys:", JSON.stringify(names));
|
|
4039
|
-
console.log("[debate-mention] text snippet:",
|
|
4324
|
+
console.log("[debate-mention] text snippet:", cleaned.slice(0, 200));
|
|
4040
4325
|
for (var i = 0; i < names.length; i++) {
|
|
4041
|
-
|
|
4042
|
-
var
|
|
4326
|
+
// Match @Name followed by any non-name character (not alphanumeric, not Korean, not dash/underscore)
|
|
4327
|
+
var pattern = new RegExp("@" + escapeRegex(names[i]) + "(?![\\p{L}\\p{N}_-])", "iu");
|
|
4328
|
+
var matched = pattern.test(cleaned);
|
|
4043
4329
|
console.log("[debate-mention] testing @" + names[i] + " pattern=" + pattern.toString() + " matched=" + matched);
|
|
4044
4330
|
if (matched) {
|
|
4045
4331
|
var mateId = nameMap[names[i]];
|
|
@@ -4071,27 +4357,369 @@ function createProjectContext(opts) {
|
|
|
4071
4357
|
}
|
|
4072
4358
|
}
|
|
4073
4359
|
|
|
4360
|
+
function formatRawDigests(rawLines, headerLabel) {
|
|
4361
|
+
if (!rawLines || rawLines.length === 0) return "";
|
|
4362
|
+
var lines = ["\n\n" + (headerLabel || "Your recent session memories:")];
|
|
4363
|
+
for (var i = 0; i < rawLines.length; i++) {
|
|
4364
|
+
try {
|
|
4365
|
+
var d = JSON.parse(rawLines[i]);
|
|
4366
|
+
if (d.type === "debate" && d.my_role) {
|
|
4367
|
+
// Debate memories are role-played positions, not genuine opinions
|
|
4368
|
+
lines.push("- [" + (d.date || "?") + "] DEBATE (role: " + d.my_role + ") " + (d.topic || "unknown") +
|
|
4369
|
+
": argued " + (d.my_position || "N/A") + " (assigned role, not my actual opinion)" +
|
|
4370
|
+
(d.outcome ? " | Outcome: " + d.outcome : "") +
|
|
4371
|
+
(d.open_items ? " | Open: " + d.open_items : ""));
|
|
4372
|
+
} else {
|
|
4373
|
+
lines.push("- [" + (d.date || "?") + "] " + (d.topic || "unknown") + ": " + (d.my_position || "") +
|
|
4374
|
+
(d.decisions ? " | Decisions: " + d.decisions : "") +
|
|
4375
|
+
(d.open_items ? " | Open: " + d.open_items : ""));
|
|
4376
|
+
}
|
|
4377
|
+
} catch (e) {}
|
|
4378
|
+
}
|
|
4379
|
+
return lines.join("\n");
|
|
4380
|
+
}
|
|
4381
|
+
|
|
4074
4382
|
function loadMateDigests(mateCtx, mateId) {
|
|
4075
4383
|
var mateDir = matesModule.getMateDir(mateCtx, mateId);
|
|
4384
|
+
var knowledgeDir = path.join(mateDir, "knowledge");
|
|
4385
|
+
|
|
4386
|
+
// Check for memory-summary.md first
|
|
4387
|
+
var summaryFile = path.join(knowledgeDir, "memory-summary.md");
|
|
4388
|
+
var hasSummary = false;
|
|
4389
|
+
var summaryContent = "";
|
|
4076
4390
|
try {
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4391
|
+
if (fs.existsSync(summaryFile)) {
|
|
4392
|
+
summaryContent = fs.readFileSync(summaryFile, "utf8").trim();
|
|
4393
|
+
if (summaryContent) hasSummary = true;
|
|
4394
|
+
}
|
|
4395
|
+
} catch (e) {}
|
|
4396
|
+
|
|
4397
|
+
// Load raw digests
|
|
4398
|
+
var allLines = [];
|
|
4399
|
+
try {
|
|
4400
|
+
var digestFile = path.join(knowledgeDir, "session-digests.jsonl");
|
|
4401
|
+
if (fs.existsSync(digestFile)) {
|
|
4402
|
+
allLines = fs.readFileSync(digestFile, "utf8").trim().split("\n").filter(function (l) { return l.trim(); });
|
|
4403
|
+
}
|
|
4404
|
+
} catch (e) {}
|
|
4405
|
+
|
|
4406
|
+
if (hasSummary) {
|
|
4407
|
+
// Load summary + latest 3 raw digests
|
|
4408
|
+
var recent = allLines.slice(-3);
|
|
4409
|
+
var result = "\n\nYour memory summary:\n" + summaryContent;
|
|
4410
|
+
if (recent.length > 0) {
|
|
4411
|
+
result += formatRawDigests(recent, "Latest raw session memories:");
|
|
4412
|
+
}
|
|
4413
|
+
return result;
|
|
4414
|
+
} else {
|
|
4415
|
+
// Backward compatible: latest 5 raw digests
|
|
4080
4416
|
var recent = allLines.slice(-5);
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4417
|
+
return formatRawDigests(recent, "Your recent session memories:");
|
|
4418
|
+
}
|
|
4419
|
+
}
|
|
4420
|
+
|
|
4421
|
+
// Gate check: ask Haiku whether this conversation contains anything worth remembering
|
|
4422
|
+
function gateMemory(mateCtx, mateId, conversationContent, callback, opts) {
|
|
4423
|
+
opts = opts || {};
|
|
4424
|
+
var mateDir = matesModule.getMateDir(mateCtx, mateId);
|
|
4425
|
+
var knowledgeDir = path.join(mateDir, "knowledge");
|
|
4426
|
+
|
|
4427
|
+
// Load mate role/activities from mate.yaml (lightweight, no full CLAUDE.md)
|
|
4428
|
+
var mateRole = "";
|
|
4429
|
+
var mateActivities = "";
|
|
4430
|
+
try {
|
|
4431
|
+
var yamlRaw = fs.readFileSync(path.join(mateDir, "mate.yaml"), "utf8");
|
|
4432
|
+
var roleMatch = yamlRaw.match(/^relationship:\s*(.+)$/m);
|
|
4433
|
+
var actMatch = yamlRaw.match(/^activities:\s*(.+)$/m);
|
|
4434
|
+
if (roleMatch) mateRole = roleMatch[1].trim();
|
|
4435
|
+
if (actMatch) mateActivities = actMatch[1].trim();
|
|
4436
|
+
} catch (e) {}
|
|
4437
|
+
|
|
4438
|
+
// Load existing memory summary if available
|
|
4439
|
+
var summaryContent = "";
|
|
4440
|
+
try {
|
|
4441
|
+
var summaryFile = path.join(knowledgeDir, "memory-summary.md");
|
|
4442
|
+
if (fs.existsSync(summaryFile)) {
|
|
4443
|
+
summaryContent = fs.readFileSync(summaryFile, "utf8").trim();
|
|
4444
|
+
}
|
|
4445
|
+
} catch (e) {}
|
|
4446
|
+
|
|
4447
|
+
// Cap conversation content for gate (500 chars each side)
|
|
4448
|
+
var cappedContent = conversationContent;
|
|
4449
|
+
if (cappedContent.length > 1200) {
|
|
4450
|
+
cappedContent = cappedContent.substring(0, 1200) + "...";
|
|
4451
|
+
}
|
|
4452
|
+
|
|
4453
|
+
var gateContext = [
|
|
4454
|
+
"[SYSTEM: Memory Gate]",
|
|
4455
|
+
"You are a memory filter for an AI Mate.",
|
|
4456
|
+
"",
|
|
4457
|
+
"Mate role: " + (mateRole || "assistant"),
|
|
4458
|
+
"Mate activities: " + (mateActivities || "general"),
|
|
4459
|
+
"",
|
|
4460
|
+
"Current memory summary:",
|
|
4461
|
+
summaryContent || "No memory summary yet.",
|
|
4462
|
+
"",
|
|
4463
|
+
"Conversation just ended:",
|
|
4464
|
+
cappedContent,
|
|
4465
|
+
].join("\n");
|
|
4466
|
+
|
|
4467
|
+
var gatePrompt = opts.gatePrompt || [
|
|
4468
|
+
'Should this conversation be saved to long-term memory?',
|
|
4469
|
+
'Answer "yes" ONLY if there is:',
|
|
4470
|
+
"- A new decision or commitment",
|
|
4471
|
+
"- A change in position or strategy",
|
|
4472
|
+
"- New information relevant to this Mate's role",
|
|
4473
|
+
"- A user preference or pattern not already in the summary",
|
|
4474
|
+
"",
|
|
4475
|
+
'Answer "no" if:',
|
|
4476
|
+
"- It duplicates what is already in the memory summary",
|
|
4477
|
+
"- It is casual/trivial conversation",
|
|
4478
|
+
"- It is not relevant to this Mate's role",
|
|
4479
|
+
"",
|
|
4480
|
+
'Answer with ONLY "yes" or "no". Nothing else.',
|
|
4481
|
+
].join("\n");
|
|
4482
|
+
var defaultOnError = !!opts.defaultYes;
|
|
4483
|
+
|
|
4484
|
+
var gateText = "";
|
|
4485
|
+
var _gateSession = null;
|
|
4486
|
+
sdk.createMentionSession({
|
|
4487
|
+
claudeMd: "",
|
|
4488
|
+
model: "haiku",
|
|
4489
|
+
initialContext: gateContext,
|
|
4490
|
+
initialMessage: gatePrompt,
|
|
4491
|
+
onActivity: function () {},
|
|
4492
|
+
onDelta: function (delta) {
|
|
4493
|
+
gateText += delta;
|
|
4494
|
+
},
|
|
4495
|
+
onDone: function () {
|
|
4496
|
+
var answer = gateText.trim().toLowerCase();
|
|
4497
|
+
var shouldRemember = answer.indexOf("yes") !== -1;
|
|
4498
|
+
if (_gateSession) try { _gateSession.close(); } catch (e) {}
|
|
4499
|
+
callback(shouldRemember);
|
|
4500
|
+
},
|
|
4501
|
+
onError: function (err) {
|
|
4502
|
+
console.error("[memory-gate] Gate check failed for mate " + mateId + ":", err);
|
|
4503
|
+
if (_gateSession) try { _gateSession.close(); } catch (e) {}
|
|
4504
|
+
callback(defaultOnError);
|
|
4505
|
+
},
|
|
4506
|
+
}).then(function (gs) {
|
|
4507
|
+
_gateSession = gs;
|
|
4508
|
+
if (!gs) callback(defaultOnError);
|
|
4509
|
+
}).catch(function (err) {
|
|
4510
|
+
console.error("[memory-gate] Failed to create gate session for mate " + mateId + ":", err);
|
|
4511
|
+
callback(defaultOnError);
|
|
4512
|
+
});
|
|
4513
|
+
}
|
|
4514
|
+
|
|
4515
|
+
// Update (or create) memory-summary.md based on a new digest
|
|
4516
|
+
function updateMemorySummary(mateCtx, mateId, digestObj) {
|
|
4517
|
+
var mateDir = matesModule.getMateDir(mateCtx, mateId);
|
|
4518
|
+
var knowledgeDir = path.join(mateDir, "knowledge");
|
|
4519
|
+
var summaryFile = path.join(knowledgeDir, "memory-summary.md");
|
|
4520
|
+
|
|
4521
|
+
// Check if summary exists; if not, try initial generation first
|
|
4522
|
+
var summaryExists = false;
|
|
4523
|
+
var summaryContent = "";
|
|
4524
|
+
try {
|
|
4525
|
+
if (fs.existsSync(summaryFile)) {
|
|
4526
|
+
summaryContent = fs.readFileSync(summaryFile, "utf8").trim();
|
|
4527
|
+
if (summaryContent) summaryExists = true;
|
|
4528
|
+
}
|
|
4529
|
+
} catch (e) {}
|
|
4530
|
+
|
|
4531
|
+
if (!summaryExists) {
|
|
4532
|
+
// Try initial summary generation from existing digests (migration)
|
|
4533
|
+
initMemorySummary(mateCtx, mateId, function () {
|
|
4534
|
+
// After init, do incremental update with the new digest
|
|
4535
|
+
doIncrementalUpdate(mateCtx, mateId, knowledgeDir, summaryFile, digestObj);
|
|
4536
|
+
});
|
|
4537
|
+
} else {
|
|
4538
|
+
doIncrementalUpdate(mateCtx, mateId, knowledgeDir, summaryFile, digestObj);
|
|
4539
|
+
}
|
|
4540
|
+
}
|
|
4541
|
+
|
|
4542
|
+
// Incremental update of memory-summary.md with a single new digest
|
|
4543
|
+
function doIncrementalUpdate(mateCtx, mateId, knowledgeDir, summaryFile, digestObj) {
|
|
4544
|
+
var existingSummary = "";
|
|
4545
|
+
try {
|
|
4546
|
+
if (fs.existsSync(summaryFile)) {
|
|
4547
|
+
existingSummary = fs.readFileSync(summaryFile, "utf8").trim();
|
|
4548
|
+
}
|
|
4549
|
+
} catch (e) {}
|
|
4550
|
+
|
|
4551
|
+
var updateContext = [
|
|
4552
|
+
"[SYSTEM: Memory Summary Update]",
|
|
4553
|
+
"You are updating an AI Mate's long-term memory summary.",
|
|
4554
|
+
"",
|
|
4555
|
+
"Current summary:",
|
|
4556
|
+
existingSummary || "(empty, this is the first entry)",
|
|
4557
|
+
"",
|
|
4558
|
+
"New session digest to incorporate:",
|
|
4559
|
+
JSON.stringify(digestObj, null, 2),
|
|
4560
|
+
].join("\n");
|
|
4561
|
+
|
|
4562
|
+
var updatePrompt = [
|
|
4563
|
+
"Update the summary by:",
|
|
4564
|
+
"1. Adding new information from this session",
|
|
4565
|
+
"2. Updating existing entries if positions changed",
|
|
4566
|
+
"3. Moving resolved open threads out of \"Open Threads\"",
|
|
4567
|
+
"4. Adding to \"My Track Record\" if a past prediction/recommendation can now be evaluated",
|
|
4568
|
+
"5. Removing outdated or redundant information",
|
|
4569
|
+
"",
|
|
4570
|
+
"Maintain this structure:",
|
|
4571
|
+
"",
|
|
4572
|
+
"# Memory Summary",
|
|
4573
|
+
"Last updated: YYYY-MM-DD (session count: N+1)",
|
|
4574
|
+
"",
|
|
4575
|
+
"## User Patterns",
|
|
4576
|
+
"## Key Decisions",
|
|
4577
|
+
"## My Track Record",
|
|
4578
|
+
"## Open Threads",
|
|
4579
|
+
"## Recurring Topics",
|
|
4580
|
+
"",
|
|
4581
|
+
"Keep it concise. Each section should have at most 10 bullet points.",
|
|
4582
|
+
"Drop the oldest/least relevant if needed.",
|
|
4583
|
+
"Output ONLY the updated markdown. Nothing else.",
|
|
4584
|
+
].join("\n");
|
|
4585
|
+
|
|
4586
|
+
var updateText = "";
|
|
4587
|
+
var _updateSession = null;
|
|
4588
|
+
sdk.createMentionSession({
|
|
4589
|
+
claudeMd: "",
|
|
4590
|
+
model: "haiku",
|
|
4591
|
+
initialContext: updateContext,
|
|
4592
|
+
initialMessage: updatePrompt,
|
|
4593
|
+
onActivity: function () {},
|
|
4594
|
+
onDelta: function (delta) {
|
|
4595
|
+
updateText += delta;
|
|
4596
|
+
},
|
|
4597
|
+
onDone: function () {
|
|
4084
4598
|
try {
|
|
4085
|
-
var
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4599
|
+
var cleaned = updateText.trim();
|
|
4600
|
+
if (cleaned.indexOf("```") === 0) {
|
|
4601
|
+
cleaned = cleaned.replace(/^```[a-z]*\n?/, "").replace(/\n?```$/, "").trim();
|
|
4602
|
+
}
|
|
4603
|
+
fs.mkdirSync(knowledgeDir, { recursive: true });
|
|
4604
|
+
fs.writeFileSync(summaryFile, cleaned + "\n", "utf8");
|
|
4605
|
+
console.log("[memory-summary] Updated memory-summary.md for mate " + mateId);
|
|
4606
|
+
} catch (e) {
|
|
4607
|
+
console.error("[memory-summary] Failed to write memory-summary.md for mate " + mateId + ":", e.message);
|
|
4608
|
+
}
|
|
4609
|
+
if (_updateSession) try { _updateSession.close(); } catch (e) {}
|
|
4610
|
+
},
|
|
4611
|
+
onError: function (err) {
|
|
4612
|
+
console.error("[memory-summary] Summary update failed for mate " + mateId + ":", err);
|
|
4613
|
+
if (_updateSession) try { _updateSession.close(); } catch (e) {}
|
|
4614
|
+
},
|
|
4615
|
+
}).then(function (us) {
|
|
4616
|
+
_updateSession = us;
|
|
4617
|
+
}).catch(function (err) {
|
|
4618
|
+
console.error("[memory-summary] Failed to create summary update session for mate " + mateId + ":", err);
|
|
4619
|
+
});
|
|
4620
|
+
}
|
|
4621
|
+
|
|
4622
|
+
// Initial summary generation (migration): read latest 20 digests and generate first summary
|
|
4623
|
+
function initMemorySummary(mateCtx, mateId, callback) {
|
|
4624
|
+
var mateDir = matesModule.getMateDir(mateCtx, mateId);
|
|
4625
|
+
var knowledgeDir = path.join(mateDir, "knowledge");
|
|
4626
|
+
var summaryFile = path.join(knowledgeDir, "memory-summary.md");
|
|
4627
|
+
var digestFile = path.join(knowledgeDir, "session-digests.jsonl");
|
|
4628
|
+
|
|
4629
|
+
// Check if digests exist
|
|
4630
|
+
var allLines = [];
|
|
4631
|
+
try {
|
|
4632
|
+
if (fs.existsSync(digestFile)) {
|
|
4633
|
+
allLines = fs.readFileSync(digestFile, "utf8").trim().split("\n").filter(function (l) { return l.trim(); });
|
|
4090
4634
|
}
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4635
|
+
} catch (e) {}
|
|
4636
|
+
|
|
4637
|
+
if (allLines.length === 0) {
|
|
4638
|
+
// No digests to summarize, just callback
|
|
4639
|
+
callback();
|
|
4640
|
+
return;
|
|
4641
|
+
}
|
|
4642
|
+
|
|
4643
|
+
var recent = allLines.slice(-20);
|
|
4644
|
+
var digestsText = [];
|
|
4645
|
+
for (var i = 0; i < recent.length; i++) {
|
|
4646
|
+
try {
|
|
4647
|
+
var d = JSON.parse(recent[i]);
|
|
4648
|
+
digestsText.push(JSON.stringify(d));
|
|
4649
|
+
} catch (e) {}
|
|
4650
|
+
}
|
|
4651
|
+
|
|
4652
|
+
if (digestsText.length === 0) {
|
|
4653
|
+
callback();
|
|
4654
|
+
return;
|
|
4094
4655
|
}
|
|
4656
|
+
|
|
4657
|
+
var initContext = [
|
|
4658
|
+
"[SYSTEM: Initial Memory Summary]",
|
|
4659
|
+
"You are creating the first long-term memory summary for an AI Mate.",
|
|
4660
|
+
"",
|
|
4661
|
+
"Here are the most recent session digests (up to 20):",
|
|
4662
|
+
digestsText.join("\n"),
|
|
4663
|
+
].join("\n");
|
|
4664
|
+
|
|
4665
|
+
var initPrompt = [
|
|
4666
|
+
"Create a memory summary from these sessions.",
|
|
4667
|
+
"",
|
|
4668
|
+
"Structure:",
|
|
4669
|
+
"",
|
|
4670
|
+
"# Memory Summary",
|
|
4671
|
+
"Last updated: YYYY-MM-DD (session count: N)",
|
|
4672
|
+
"",
|
|
4673
|
+
"## User Patterns",
|
|
4674
|
+
"## Key Decisions",
|
|
4675
|
+
"## My Track Record",
|
|
4676
|
+
"## Open Threads",
|
|
4677
|
+
"## Recurring Topics",
|
|
4678
|
+
"",
|
|
4679
|
+
"Keep it concise. Focus on patterns and decisions, not individual session details.",
|
|
4680
|
+
"Each section should have at most 10 bullet points.",
|
|
4681
|
+
"Set session count to " + digestsText.length + ".",
|
|
4682
|
+
"Output ONLY the markdown. Nothing else.",
|
|
4683
|
+
].join("\n");
|
|
4684
|
+
|
|
4685
|
+
var initText = "";
|
|
4686
|
+
var _initSession = null;
|
|
4687
|
+
sdk.createMentionSession({
|
|
4688
|
+
claudeMd: "",
|
|
4689
|
+
model: "haiku",
|
|
4690
|
+
initialContext: initContext,
|
|
4691
|
+
initialMessage: initPrompt,
|
|
4692
|
+
onActivity: function () {},
|
|
4693
|
+
onDelta: function (delta) {
|
|
4694
|
+
initText += delta;
|
|
4695
|
+
},
|
|
4696
|
+
onDone: function () {
|
|
4697
|
+
try {
|
|
4698
|
+
var cleaned = initText.trim();
|
|
4699
|
+
if (cleaned.indexOf("```") === 0) {
|
|
4700
|
+
cleaned = cleaned.replace(/^```[a-z]*\n?/, "").replace(/\n?```$/, "").trim();
|
|
4701
|
+
}
|
|
4702
|
+
fs.mkdirSync(knowledgeDir, { recursive: true });
|
|
4703
|
+
fs.writeFileSync(summaryFile, cleaned + "\n", "utf8");
|
|
4704
|
+
console.log("[memory-summary] Generated initial memory-summary.md for mate " + mateId + " from " + digestsText.length + " digests");
|
|
4705
|
+
} catch (e) {
|
|
4706
|
+
console.error("[memory-summary] Failed to write initial memory-summary.md for mate " + mateId + ":", e.message);
|
|
4707
|
+
}
|
|
4708
|
+
if (_initSession) try { _initSession.close(); } catch (e) {}
|
|
4709
|
+
callback();
|
|
4710
|
+
},
|
|
4711
|
+
onError: function (err) {
|
|
4712
|
+
console.error("[memory-summary] Initial summary generation failed for mate " + mateId + ":", err);
|
|
4713
|
+
if (_initSession) try { _initSession.close(); } catch (e) {}
|
|
4714
|
+
callback();
|
|
4715
|
+
},
|
|
4716
|
+
}).then(function (is) {
|
|
4717
|
+
_initSession = is;
|
|
4718
|
+
if (!is) callback();
|
|
4719
|
+
}).catch(function (err) {
|
|
4720
|
+
console.error("[memory-summary] Failed to create init summary session for mate " + mateId + ":", err);
|
|
4721
|
+
callback();
|
|
4722
|
+
});
|
|
4095
4723
|
}
|
|
4096
4724
|
|
|
4097
4725
|
function buildModeratorContext(debate) {
|
|
@@ -4372,7 +5000,11 @@ function createProjectContext(opts) {
|
|
|
4372
5000
|
if (h.type === "debate_conclude_confirm") hasConclude = true;
|
|
4373
5001
|
if (h.type === "debate_turn_done" && h.role === "moderator") lastModText = h.text || "";
|
|
4374
5002
|
}
|
|
4375
|
-
|
|
5003
|
+
// conclude_confirm in history without a subsequent ended = still awaiting user decision
|
|
5004
|
+
if (hasConclude && !hasEnded) {
|
|
5005
|
+
debate.awaitingConcludeConfirm = true;
|
|
5006
|
+
} else if (!hasEnded && !hasConclude && lastModText !== null) {
|
|
5007
|
+
// No explicit entry yet; infer from last moderator text having no @mentions
|
|
4376
5008
|
var mentions = detectMentions(lastModText, debate.nameMap);
|
|
4377
5009
|
if (mentions.length === 0) {
|
|
4378
5010
|
debate.awaitingConcludeConfirm = true;
|
|
@@ -4646,6 +5278,7 @@ function createProjectContext(opts) {
|
|
|
4646
5278
|
console.log("[debate] No mentions detected, requesting user confirmation to end.");
|
|
4647
5279
|
debate.turnInProgress = false;
|
|
4648
5280
|
debate.awaitingConcludeConfirm = true;
|
|
5281
|
+
persistDebateState(session);
|
|
4649
5282
|
var concludeEntry = { type: "debate_conclude_confirm", topic: debate.topic, round: debate.round };
|
|
4650
5283
|
session.history.push(concludeEntry);
|
|
4651
5284
|
sm.appendToSessionFile(session, concludeEntry);
|
|
@@ -5254,68 +5887,86 @@ function createProjectContext(opts) {
|
|
|
5254
5887
|
var mateDir = matesModule.getMateDir(debate.mateCtx, mateId);
|
|
5255
5888
|
var knowledgeDir = path.join(mateDir, "knowledge");
|
|
5256
5889
|
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
|
|
5275
|
-
|
|
5276
|
-
|
|
5890
|
+
// Migration: generate initial summary if missing
|
|
5891
|
+
var summaryFile = path.join(knowledgeDir, "memory-summary.md");
|
|
5892
|
+
var digestFileCheck = path.join(knowledgeDir, "session-digests.jsonl");
|
|
5893
|
+
if (!fs.existsSync(summaryFile) && fs.existsSync(digestFileCheck)) {
|
|
5894
|
+
initMemorySummary(debate.mateCtx, mateId, function () {});
|
|
5895
|
+
}
|
|
5896
|
+
|
|
5897
|
+
// Debates are user-initiated structured events. The moderator already
|
|
5898
|
+
// synthesizes a summary, so skip the memory gate and always create a digest.
|
|
5899
|
+
(function () {
|
|
5900
|
+
var digestPrompt = [
|
|
5901
|
+
"[SYSTEM: Session Digest]",
|
|
5902
|
+
"Summarize this conversation from YOUR perspective for your long-term memory.",
|
|
5903
|
+
"Output ONLY a single valid JSON object (no markdown, no code fences, no extra text).",
|
|
5904
|
+
"",
|
|
5905
|
+
"Schema:",
|
|
5906
|
+
"{",
|
|
5907
|
+
' "date": "YYYY-MM-DD",',
|
|
5908
|
+
' "type": "debate",',
|
|
5909
|
+
' "topic": "short topic description",',
|
|
5910
|
+
' "my_position": "what I said/recommended",',
|
|
5911
|
+
' "decisions": "what was decided, or null if pending",',
|
|
5912
|
+
' "open_items": "what remains unresolved",',
|
|
5913
|
+
' "user_sentiment": "how the user seemed to feel",',
|
|
5914
|
+
' "my_role": "' + role + '",',
|
|
5915
|
+
' "other_perspectives": "key points from others",',
|
|
5916
|
+
' "outcome": "how the debate concluded",',
|
|
5917
|
+
' "confidence": "high | medium | low",',
|
|
5918
|
+
' "revisit_later": true/false,',
|
|
5919
|
+
' "tags": ["relevant", "topic", "tags"]',
|
|
5920
|
+
"}",
|
|
5921
|
+
"",
|
|
5922
|
+
"IMPORTANT: Output ONLY the JSON object. Nothing else.",
|
|
5923
|
+
].join("\n");
|
|
5924
|
+
|
|
5925
|
+
var digestText = "";
|
|
5926
|
+
mentionSession.pushMessage(digestPrompt, {
|
|
5927
|
+
onActivity: function () {},
|
|
5928
|
+
onDelta: function (delta) {
|
|
5929
|
+
digestText += delta;
|
|
5930
|
+
},
|
|
5931
|
+
onDone: function () {
|
|
5932
|
+
var digestObj = null;
|
|
5933
|
+
try {
|
|
5934
|
+
var cleaned = digestText.trim();
|
|
5935
|
+
if (cleaned.indexOf("```") === 0) {
|
|
5936
|
+
cleaned = cleaned.replace(/^```[a-z]*\n?/, "").replace(/\n?```$/, "").trim();
|
|
5937
|
+
}
|
|
5938
|
+
digestObj = JSON.parse(cleaned);
|
|
5939
|
+
} catch (e) {
|
|
5940
|
+
console.error("[debate-digest] Failed to parse digest JSON for mate " + mateId + ":", e.message);
|
|
5941
|
+
digestObj = {
|
|
5942
|
+
date: new Date().toISOString().slice(0, 10),
|
|
5943
|
+
type: "debate",
|
|
5944
|
+
topic: debate.topic,
|
|
5945
|
+
my_role: role,
|
|
5946
|
+
raw: digestText.substring(0, 500),
|
|
5947
|
+
};
|
|
5948
|
+
}
|
|
5277
5949
|
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
onDone: function () {
|
|
5285
|
-
var digestObj = null;
|
|
5286
|
-
try {
|
|
5287
|
-
var cleaned = digestText.trim();
|
|
5288
|
-
if (cleaned.indexOf("```") === 0) {
|
|
5289
|
-
cleaned = cleaned.replace(/^```[a-z]*\n?/, "").replace(/\n?```$/, "").trim();
|
|
5950
|
+
try {
|
|
5951
|
+
fs.mkdirSync(knowledgeDir, { recursive: true });
|
|
5952
|
+
var digestFile = path.join(knowledgeDir, "session-digests.jsonl");
|
|
5953
|
+
fs.appendFileSync(digestFile, JSON.stringify(digestObj) + "\n");
|
|
5954
|
+
} catch (e) {
|
|
5955
|
+
console.error("[debate-digest] Failed to write digest for mate " + mateId + ":", e.message);
|
|
5290
5956
|
}
|
|
5291
|
-
digestObj = JSON.parse(cleaned);
|
|
5292
|
-
} catch (e) {
|
|
5293
|
-
console.error("[debate-digest] Failed to parse digest JSON for mate " + mateId + ":", e.message);
|
|
5294
|
-
digestObj = {
|
|
5295
|
-
date: new Date().toISOString().slice(0, 10),
|
|
5296
|
-
type: "debate",
|
|
5297
|
-
topic: debate.topic,
|
|
5298
|
-
my_role: role,
|
|
5299
|
-
raw: digestText.substring(0, 500),
|
|
5300
|
-
};
|
|
5301
|
-
}
|
|
5302
5957
|
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
var digestFile = path.join(knowledgeDir, "session-digests.jsonl");
|
|
5306
|
-
fs.appendFileSync(digestFile, JSON.stringify(digestObj) + "\n");
|
|
5307
|
-
} catch (e) {
|
|
5308
|
-
console.error("[debate-digest] Failed to write digest for mate " + mateId + ":", e.message);
|
|
5309
|
-
}
|
|
5958
|
+
// Update memory summary
|
|
5959
|
+
updateMemorySummary(debate.mateCtx, mateId, digestObj);
|
|
5310
5960
|
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
|
|
5318
|
-
|
|
5961
|
+
// Close the session after digest
|
|
5962
|
+
mentionSession.close();
|
|
5963
|
+
},
|
|
5964
|
+
onError: function (err) {
|
|
5965
|
+
console.error("[debate-digest] Digest generation failed for mate " + mateId + ":", err);
|
|
5966
|
+
mentionSession.close();
|
|
5967
|
+
},
|
|
5968
|
+
});
|
|
5969
|
+
})();
|
|
5319
5970
|
}
|
|
5320
5971
|
|
|
5321
5972
|
// --- Session presence (who is viewing which session) ---
|
|
@@ -5568,7 +6219,7 @@ function createProjectContext(opts) {
|
|
|
5568
6219
|
return;
|
|
5569
6220
|
}
|
|
5570
6221
|
var skillUserInfo = getOsUserInfoForReq(req);
|
|
5571
|
-
var spawnCwd = scope === "global" ? (skillUserInfo ? skillUserInfo.home :
|
|
6222
|
+
var spawnCwd = scope === "global" ? (skillUserInfo ? skillUserInfo.home : require("./config").REAL_HOME) : cwd;
|
|
5572
6223
|
var scopeFlag = scope === "global" ? "--global" : "--project";
|
|
5573
6224
|
var skillSpawnOpts = {
|
|
5574
6225
|
cwd: spawnCwd,
|
|
@@ -5658,7 +6309,7 @@ function createProjectContext(opts) {
|
|
|
5658
6309
|
return;
|
|
5659
6310
|
}
|
|
5660
6311
|
var uninstallUserInfo = getOsUserInfoForReq(req);
|
|
5661
|
-
var baseDir = scope === "global" ? (uninstallUserInfo ? uninstallUserInfo.home :
|
|
6312
|
+
var baseDir = scope === "global" ? (uninstallUserInfo ? uninstallUserInfo.home : require("./config").REAL_HOME) : cwd;
|
|
5662
6313
|
var skillDir = path.join(baseDir, ".claude", "skills", skill);
|
|
5663
6314
|
// Safety: ensure skillDir is inside the expected .claude/skills directory
|
|
5664
6315
|
var expectedParent = path.join(baseDir, ".claude", "skills");
|
|
@@ -5709,7 +6360,7 @@ function createProjectContext(opts) {
|
|
|
5709
6360
|
// Installed skills (global + project)
|
|
5710
6361
|
if (req.method === "GET" && urlPath === "/api/installed-skills") {
|
|
5711
6362
|
var installed = {};
|
|
5712
|
-
var globalDir = path.join(
|
|
6363
|
+
var globalDir = path.join(require("./config").REAL_HOME, ".claude", "skills");
|
|
5713
6364
|
var projectDir = path.join(cwd, ".claude", "skills");
|
|
5714
6365
|
var scanDirs = [
|
|
5715
6366
|
{ dir: globalDir, scope: "global" },
|
|
@@ -5763,7 +6414,7 @@ function createProjectContext(opts) {
|
|
|
5763
6414
|
return;
|
|
5764
6415
|
}
|
|
5765
6416
|
// Read installed versions
|
|
5766
|
-
var globalSkillsDir = path.join(
|
|
6417
|
+
var globalSkillsDir = path.join(require("./config").REAL_HOME, ".claude", "skills");
|
|
5767
6418
|
var projectSkillsDir = path.join(cwd, ".claude", "skills");
|
|
5768
6419
|
var results = [];
|
|
5769
6420
|
var pending = skills.length;
|
|
@@ -5992,6 +6643,7 @@ function createProjectContext(opts) {
|
|
|
5992
6643
|
};
|
|
5993
6644
|
if (isMate) {
|
|
5994
6645
|
status.isMate = true;
|
|
6646
|
+
status.mateId = path.basename(cwd);
|
|
5995
6647
|
}
|
|
5996
6648
|
if (worktreeMeta) {
|
|
5997
6649
|
status.isWorktree = true;
|
|
@@ -6037,7 +6689,20 @@ function createProjectContext(opts) {
|
|
|
6037
6689
|
// Enforce immediately on startup
|
|
6038
6690
|
try { matesModule.enforceTeamAwareness(claudeMdPath); } catch (e) {}
|
|
6039
6691
|
try { matesModule.enforceSessionMemory(claudeMdPath); } catch (e) {}
|
|
6692
|
+
try { matesModule.enforceStickyNotes(claudeMdPath); } catch (e) {}
|
|
6040
6693
|
try { crisisSafety.enforce(claudeMdPath); } catch (e) {}
|
|
6694
|
+
// Sync sticky notes knowledge file on startup
|
|
6695
|
+
try {
|
|
6696
|
+
var knDir = path.join(cwd, "knowledge");
|
|
6697
|
+
var knFile = path.join(knDir, "sticky-notes.md");
|
|
6698
|
+
var notesText = nm.getActiveNotesText();
|
|
6699
|
+
if (notesText) {
|
|
6700
|
+
fs.mkdirSync(knDir, { recursive: true });
|
|
6701
|
+
fs.writeFileSync(knFile, notesText);
|
|
6702
|
+
} else {
|
|
6703
|
+
try { fs.unlinkSync(knFile); } catch (e) {}
|
|
6704
|
+
}
|
|
6705
|
+
} catch (e) {}
|
|
6041
6706
|
// Watch for changes
|
|
6042
6707
|
try {
|
|
6043
6708
|
crisisWatcher = fs.watch(claudeMdPath, function () {
|
|
@@ -6046,6 +6711,7 @@ function createProjectContext(opts) {
|
|
|
6046
6711
|
crisisDebounce = null;
|
|
6047
6712
|
try { matesModule.enforceTeamAwareness(claudeMdPath); } catch (e) {}
|
|
6048
6713
|
try { matesModule.enforceSessionMemory(claudeMdPath); } catch (e) {}
|
|
6714
|
+
try { matesModule.enforceStickyNotes(claudeMdPath); } catch (e) {}
|
|
6049
6715
|
try { crisisSafety.enforce(claudeMdPath); } catch (e) {}
|
|
6050
6716
|
}, 500);
|
|
6051
6717
|
});
|