memorix 1.0.7 → 1.0.9
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/CHANGELOG.md +73 -11
- package/README.md +469 -409
- package/README.zh-CN.md +477 -408
- package/TEAM.md +106 -0
- package/dist/cli/index.js +34462 -27684
- package/dist/cli/index.js.map +1 -1
- package/dist/dashboard/static/app.js +983 -59
- package/dist/dashboard/static/index.html +24 -4
- package/dist/dashboard/static/style.css +663 -0
- package/dist/index.js +1692 -247
- package/dist/index.js.map +1 -1
- package/dist/sdk.d.ts +677 -0
- package/dist/sdk.js +19580 -0
- package/dist/sdk.js.map +1 -0
- package/dist/types.d.ts +12 -11
- package/dist/types.js +11 -10
- package/dist/types.js.map +1 -1
- package/docs/AGENT_OPERATOR_PLAYBOOK.md +684 -0
- package/docs/AI_CONTEXT.md +18 -0
- package/docs/API_REFERENCE.md +687 -0
- package/docs/ARCHITECTURE.md +488 -0
- package/docs/CLOUD_SYNC_AND_MULTI_AGENT_RESEARCH.md +470 -0
- package/docs/CONFIGURATION.md +265 -0
- package/docs/DESIGN_DECISIONS.md +358 -0
- package/docs/DEVELOPMENT.md +317 -0
- package/docs/DOCKER.md +138 -0
- package/docs/GIT_MEMORY.md +221 -0
- package/docs/KNOWN_ISSUES_AND_ROADMAP.md +149 -0
- package/docs/MEMORY_FORMATION_PIPELINE.md +224 -0
- package/docs/MODULES.md +383 -0
- package/docs/PERFORMANCE.md +64 -0
- package/docs/README.md +79 -0
- package/docs/SETUP.md +617 -0
- package/docs/hooks-architecture.md +108 -0
- package/package.json +24 -23
package/dist/index.js
CHANGED
|
@@ -76,6 +76,7 @@ function getDatabase(dataDir) {
|
|
|
76
76
|
db2.exec(CREATE_TEAM_ROLES_TABLE);
|
|
77
77
|
db2.exec(CREATE_GRAPH_ENTITIES_TABLE);
|
|
78
78
|
db2.exec(CREATE_GRAPH_RELATIONS_TABLE);
|
|
79
|
+
db2.exec(CREATE_CHAT_TRANSCRIPT_TABLE);
|
|
79
80
|
try {
|
|
80
81
|
db2.exec(`ALTER TABLE mini_skills ADD COLUMN sourceSnapshot TEXT NOT NULL DEFAULT ''`);
|
|
81
82
|
} catch {
|
|
@@ -115,7 +116,7 @@ function getDatabase(dataDir) {
|
|
|
115
116
|
_dbCache.set(normalized, db2);
|
|
116
117
|
return db2;
|
|
117
118
|
}
|
|
118
|
-
var BetterSqlite3, CREATE_OBSERVATIONS_TABLE, CREATE_MINI_SKILLS_TABLE, CREATE_SESSIONS_TABLE, CREATE_META_TABLE, CREATE_TEAM_AGENTS_TABLE, CREATE_TEAM_MESSAGES_TABLE, CREATE_TEAM_TASKS_TABLE, CREATE_TEAM_TASK_DEPS_TABLE, CREATE_TEAM_LOCKS_TABLE, CREATE_TEAM_ROLES_TABLE, CREATE_GRAPH_ENTITIES_TABLE, CREATE_GRAPH_RELATIONS_TABLE, CREATE_INDEXES, _dbCache;
|
|
119
|
+
var BetterSqlite3, CREATE_OBSERVATIONS_TABLE, CREATE_MINI_SKILLS_TABLE, CREATE_SESSIONS_TABLE, CREATE_META_TABLE, CREATE_TEAM_AGENTS_TABLE, CREATE_TEAM_MESSAGES_TABLE, CREATE_TEAM_TASKS_TABLE, CREATE_TEAM_TASK_DEPS_TABLE, CREATE_TEAM_LOCKS_TABLE, CREATE_TEAM_ROLES_TABLE, CREATE_CHAT_TRANSCRIPT_TABLE, CREATE_GRAPH_ENTITIES_TABLE, CREATE_GRAPH_RELATIONS_TABLE, CREATE_INDEXES, _dbCache;
|
|
119
120
|
var init_sqlite_db = __esm({
|
|
120
121
|
"src/store/sqlite-db.ts"() {
|
|
121
122
|
"use strict";
|
|
@@ -263,6 +264,19 @@ CREATE TABLE IF NOT EXISTS team_roles (
|
|
|
263
264
|
max_concurrent INTEGER NOT NULL DEFAULT 1,
|
|
264
265
|
created_at INTEGER NOT NULL
|
|
265
266
|
);
|
|
267
|
+
`;
|
|
268
|
+
CREATE_CHAT_TRANSCRIPT_TABLE = `
|
|
269
|
+
CREATE TABLE IF NOT EXISTS chat_transcript (
|
|
270
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
271
|
+
project_id TEXT NOT NULL,
|
|
272
|
+
thread_id TEXT NOT NULL DEFAULT 'default',
|
|
273
|
+
role TEXT NOT NULL,
|
|
274
|
+
content TEXT NOT NULL DEFAULT '',
|
|
275
|
+
sources_json TEXT NOT NULL DEFAULT '[]',
|
|
276
|
+
meta_json TEXT NOT NULL DEFAULT '{}',
|
|
277
|
+
error INTEGER NOT NULL DEFAULT 0,
|
|
278
|
+
created_at TEXT NOT NULL
|
|
279
|
+
);
|
|
266
280
|
`;
|
|
267
281
|
CREATE_GRAPH_ENTITIES_TABLE = `
|
|
268
282
|
CREATE TABLE IF NOT EXISTS graph_entities (
|
|
@@ -298,6 +312,7 @@ CREATE INDEX IF NOT EXISTS idx_team_tasks_role ON team_tasks(required_role);
|
|
|
298
312
|
CREATE INDEX IF NOT EXISTS idx_team_messages_role ON team_messages(to_role);
|
|
299
313
|
CREATE INDEX IF NOT EXISTS idx_graph_relations_from ON graph_relations(from_entity);
|
|
300
314
|
CREATE INDEX IF NOT EXISTS idx_graph_relations_to ON graph_relations(to_entity);
|
|
315
|
+
CREATE INDEX IF NOT EXISTS idx_chat_transcript_project ON chat_transcript(project_id, thread_id);
|
|
301
316
|
`;
|
|
302
317
|
_dbCache = /* @__PURE__ */ new Map();
|
|
303
318
|
}
|
|
@@ -480,16 +495,19 @@ __export(persistence_exports, {
|
|
|
480
495
|
import { promises as fs4 } from "fs";
|
|
481
496
|
import path5 from "path";
|
|
482
497
|
import os from "os";
|
|
498
|
+
function resolveDefaultDataDir() {
|
|
499
|
+
return process.env.MEMORIX_DATA_DIR || path5.join(os.homedir(), ".memorix", "data");
|
|
500
|
+
}
|
|
483
501
|
async function getProjectDataDir(_projectId, baseDir) {
|
|
484
|
-
const base = baseDir ??
|
|
502
|
+
const base = baseDir ?? resolveDefaultDataDir();
|
|
485
503
|
await fs4.mkdir(base, { recursive: true });
|
|
486
504
|
return base;
|
|
487
505
|
}
|
|
488
506
|
function getBaseDataDir(baseDir) {
|
|
489
|
-
return baseDir ??
|
|
507
|
+
return baseDir ?? resolveDefaultDataDir();
|
|
490
508
|
}
|
|
491
509
|
async function listProjectDirs(baseDir) {
|
|
492
|
-
const base = baseDir ??
|
|
510
|
+
const base = baseDir ?? resolveDefaultDataDir();
|
|
493
511
|
try {
|
|
494
512
|
const entries = await fs4.readdir(base, { withFileTypes: true });
|
|
495
513
|
return entries.filter((e) => e.isDirectory()).map((e) => path5.join(base, e.name));
|
|
@@ -498,7 +516,7 @@ async function listProjectDirs(baseDir) {
|
|
|
498
516
|
}
|
|
499
517
|
}
|
|
500
518
|
async function migrateSubdirsToFlat(baseDir) {
|
|
501
|
-
const base = baseDir ??
|
|
519
|
+
const base = baseDir ?? resolveDefaultDataDir();
|
|
502
520
|
await fs4.mkdir(base, { recursive: true });
|
|
503
521
|
let entries;
|
|
504
522
|
try {
|
|
@@ -648,17 +666,22 @@ async function hasExistingData(projectDir2) {
|
|
|
648
666
|
return false;
|
|
649
667
|
}
|
|
650
668
|
}
|
|
651
|
-
var DEFAULT_DATA_DIR;
|
|
652
669
|
var init_persistence = __esm({
|
|
653
670
|
"src/store/persistence.ts"() {
|
|
654
671
|
"use strict";
|
|
655
672
|
init_esm_shims();
|
|
656
673
|
init_persistence_json();
|
|
657
|
-
DEFAULT_DATA_DIR = process.env.MEMORIX_DATA_DIR || path5.join(os.homedir(), ".memorix", "data");
|
|
658
674
|
}
|
|
659
675
|
});
|
|
660
676
|
|
|
661
677
|
// src/store/graph-store.ts
|
|
678
|
+
var graph_store_exports = {};
|
|
679
|
+
__export(graph_store_exports, {
|
|
680
|
+
GraphSqliteStore: () => GraphSqliteStore,
|
|
681
|
+
getGraphStore: () => getGraphStore,
|
|
682
|
+
initGraphStore: () => initGraphStore,
|
|
683
|
+
resetGraphStore: () => resetGraphStore
|
|
684
|
+
});
|
|
662
685
|
import path6 from "path";
|
|
663
686
|
import fs5 from "fs";
|
|
664
687
|
function safeJsonParse(val, fallback) {
|
|
@@ -694,6 +717,16 @@ function getGraphStore() {
|
|
|
694
717
|
if (!_graphStore) throw new Error("[memorix] GraphStore not initialized \u2014 call initGraphStore() first");
|
|
695
718
|
return _graphStore;
|
|
696
719
|
}
|
|
720
|
+
function resetGraphStore() {
|
|
721
|
+
if (_graphStore) {
|
|
722
|
+
try {
|
|
723
|
+
_graphStore.close();
|
|
724
|
+
} catch {
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
_graphStore = null;
|
|
728
|
+
_graphDataDir = null;
|
|
729
|
+
}
|
|
697
730
|
var GraphSqliteStore, _graphStore, _graphDataDir;
|
|
698
731
|
var init_graph_store = __esm({
|
|
699
732
|
"src/store/graph-store.ts"() {
|
|
@@ -1016,16 +1049,17 @@ var init_types = __esm({
|
|
|
1016
1049
|
"use strict";
|
|
1017
1050
|
init_esm_shims();
|
|
1018
1051
|
OBSERVATION_ICONS = {
|
|
1019
|
-
"session-request": "
|
|
1020
|
-
"gotcha": "
|
|
1021
|
-
"problem-solution": "
|
|
1022
|
-
"how-it-works": "
|
|
1023
|
-
"what-changed": "
|
|
1024
|
-
"discovery": "
|
|
1025
|
-
"why-it-exists": "
|
|
1026
|
-
"decision": "
|
|
1027
|
-
"trade-off": "
|
|
1028
|
-
"reasoning": "
|
|
1052
|
+
"session-request": "[SESSION]",
|
|
1053
|
+
"gotcha": "[GOTCHA]",
|
|
1054
|
+
"problem-solution": "[FIX]",
|
|
1055
|
+
"how-it-works": "[INFO]",
|
|
1056
|
+
"what-changed": "[CHANGE]",
|
|
1057
|
+
"discovery": "[DISCOVERY]",
|
|
1058
|
+
"why-it-exists": "[WHY]",
|
|
1059
|
+
"decision": "[DECISION]",
|
|
1060
|
+
"trade-off": "[TRADEOFF]",
|
|
1061
|
+
"reasoning": "[REASONING]",
|
|
1062
|
+
"probe": "[PROBE]"
|
|
1029
1063
|
};
|
|
1030
1064
|
TOPIC_KEY_FAMILIES = {
|
|
1031
1065
|
"architecture": ["architecture", "design", "adr", "structure", "pattern"],
|
|
@@ -1039,6 +1073,15 @@ var init_types = __esm({
|
|
|
1039
1073
|
});
|
|
1040
1074
|
|
|
1041
1075
|
// src/store/mini-skill-store.ts
|
|
1076
|
+
var mini_skill_store_exports = {};
|
|
1077
|
+
__export(mini_skill_store_exports, {
|
|
1078
|
+
MiniSkillGracefulDegrade: () => MiniSkillGracefulDegrade,
|
|
1079
|
+
MiniSkillSqliteStore: () => MiniSkillSqliteStore,
|
|
1080
|
+
getMiniSkillStore: () => getMiniSkillStore,
|
|
1081
|
+
initMiniSkillStore: () => initMiniSkillStore,
|
|
1082
|
+
isMiniSkillStoreInitialized: () => isMiniSkillStoreInitialized,
|
|
1083
|
+
resetMiniSkillStore: () => resetMiniSkillStore
|
|
1084
|
+
});
|
|
1042
1085
|
import path7 from "path";
|
|
1043
1086
|
import fs6 from "fs";
|
|
1044
1087
|
function skillToRow(skill) {
|
|
@@ -1092,6 +1135,10 @@ function getMiniSkillStore() {
|
|
|
1092
1135
|
}
|
|
1093
1136
|
return _store;
|
|
1094
1137
|
}
|
|
1138
|
+
function resetMiniSkillStore() {
|
|
1139
|
+
_store = null;
|
|
1140
|
+
_storeDataDir = null;
|
|
1141
|
+
}
|
|
1095
1142
|
async function initMiniSkillStore(dataDir) {
|
|
1096
1143
|
if (_store && _storeDataDir === dataDir) return _store;
|
|
1097
1144
|
_store = null;
|
|
@@ -1338,6 +1385,12 @@ async function promoteToMiniSkill(projectDir2, projectId, observations2, options
|
|
|
1338
1385
|
`Cannot promote: ${nonActive.length} observation(s) are not active. Blocked: ${nonActive.map((o) => `#${o.id} (${o.status ?? "unknown"})`).join(", ")}. Only active observations can be promoted to permanent knowledge.`
|
|
1339
1386
|
);
|
|
1340
1387
|
}
|
|
1388
|
+
const probes = observations2.filter((o) => o.type === "probe");
|
|
1389
|
+
if (probes.length > 0) {
|
|
1390
|
+
throw new Error(
|
|
1391
|
+
`Cannot promote probe observations \u2014 they are operational heartbeats, not durable knowledge. Blocked: ${probes.map((o) => `#${o.id} "${o.title.substring(0, 60)}"`).join(", ")}.`
|
|
1392
|
+
);
|
|
1393
|
+
}
|
|
1341
1394
|
if (!options?.force) {
|
|
1342
1395
|
const commandLogs = observations2.filter((o) => COMMAND_LOG_TITLE.test(o.title));
|
|
1343
1396
|
if (commandLogs.length > 0) {
|
|
@@ -1412,7 +1465,7 @@ async function recordMiniSkillUsage(projectDir2, skillIds) {
|
|
|
1412
1465
|
function formatMiniSkillsForInjection(skills) {
|
|
1413
1466
|
if (skills.length === 0) return "";
|
|
1414
1467
|
const lines = [
|
|
1415
|
-
`##
|
|
1468
|
+
`## [SESSION] Project Mini-Skills (${skills.length} active)`,
|
|
1416
1469
|
""
|
|
1417
1470
|
];
|
|
1418
1471
|
for (const skill of skills) {
|
|
@@ -2968,6 +3021,8 @@ var init_intent_detector = __esm({
|
|
|
2968
3021
|
var provider_exports2 = {};
|
|
2969
3022
|
__export(provider_exports2, {
|
|
2970
3023
|
callLLM: () => callLLM,
|
|
3024
|
+
callLLMWithTools: () => callLLMWithTools,
|
|
3025
|
+
callLLMWithToolsStream: () => callLLMWithToolsStream,
|
|
2971
3026
|
getLLMConfig: () => getLLMConfig,
|
|
2972
3027
|
initLLM: () => initLLM,
|
|
2973
3028
|
isLLMEnabled: () => isLLMEnabled,
|
|
@@ -2987,6 +3042,59 @@ function parseLLMTimeoutMs(raw) {
|
|
|
2987
3042
|
if (parsed > LLM_TIMEOUT_MAX_MS) return LLM_TIMEOUT_MAX_MS;
|
|
2988
3043
|
return parsed;
|
|
2989
3044
|
}
|
|
3045
|
+
async function readResponseText(response, signal, maxBytes = MAX_LLM_RESPONSE_BYTES) {
|
|
3046
|
+
const contentLength = response.headers.get("content-length");
|
|
3047
|
+
if (contentLength && Number(contentLength) > maxBytes) {
|
|
3048
|
+
throw new Error(`LLM response too large (${contentLength} bytes)`);
|
|
3049
|
+
}
|
|
3050
|
+
const reader = response.body?.getReader();
|
|
3051
|
+
if (!reader) {
|
|
3052
|
+
signal?.throwIfAborted();
|
|
3053
|
+
return response.text();
|
|
3054
|
+
}
|
|
3055
|
+
const decoder = new TextDecoder();
|
|
3056
|
+
const chunks = [];
|
|
3057
|
+
let bytesRead = 0;
|
|
3058
|
+
const abortReader = () => {
|
|
3059
|
+
void reader.cancel(signal?.reason).catch(() => void 0);
|
|
3060
|
+
};
|
|
3061
|
+
signal?.addEventListener("abort", abortReader, { once: true });
|
|
3062
|
+
try {
|
|
3063
|
+
while (true) {
|
|
3064
|
+
signal?.throwIfAborted();
|
|
3065
|
+
const { value, done } = await reader.read();
|
|
3066
|
+
if (done) break;
|
|
3067
|
+
signal?.throwIfAborted();
|
|
3068
|
+
if (!value) continue;
|
|
3069
|
+
bytesRead += value.byteLength;
|
|
3070
|
+
if (bytesRead > maxBytes) {
|
|
3071
|
+
await reader.cancel("response too large").catch(() => void 0);
|
|
3072
|
+
throw new Error(`LLM response exceeded ${maxBytes} bytes`);
|
|
3073
|
+
}
|
|
3074
|
+
chunks.push(decoder.decode(value, { stream: true }));
|
|
3075
|
+
}
|
|
3076
|
+
chunks.push(decoder.decode());
|
|
3077
|
+
signal?.throwIfAborted();
|
|
3078
|
+
return chunks.join("");
|
|
3079
|
+
} finally {
|
|
3080
|
+
signal?.removeEventListener("abort", abortReader);
|
|
3081
|
+
reader.releaseLock();
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
3084
|
+
async function readResponseJson(response, signal, maxBytes = MAX_LLM_RESPONSE_BYTES) {
|
|
3085
|
+
const text = await readResponseText(response, signal, maxBytes);
|
|
3086
|
+
return JSON.parse(text);
|
|
3087
|
+
}
|
|
3088
|
+
async function* callLLMWithToolsStream(messages, tools) {
|
|
3089
|
+
if (!currentConfig) {
|
|
3090
|
+
throw new Error("LLM not configured. Set MEMORIX_LLM_API_KEY or OPENAI_API_KEY.");
|
|
3091
|
+
}
|
|
3092
|
+
if (currentConfig.provider === "anthropic") {
|
|
3093
|
+
yield* callAnthropicWithToolsStream(messages, tools);
|
|
3094
|
+
return;
|
|
3095
|
+
}
|
|
3096
|
+
yield* callOpenAIWithToolsStream(messages, tools);
|
|
3097
|
+
}
|
|
2990
3098
|
function initLLM() {
|
|
2991
3099
|
const { getLLMApiKey: getLLMApiKey2, getLLMProvider: getLLMProvider2, getLLMModel: getLLMModel2, getLLMBaseUrl: getLLMBaseUrl2 } = (init_config(), __toCommonJS(config_exports));
|
|
2992
3100
|
const apiKey = getLLMApiKey2();
|
|
@@ -3045,10 +3153,10 @@ async function callOpenAICompatible(systemPrompt, userMessage) {
|
|
|
3045
3153
|
})
|
|
3046
3154
|
});
|
|
3047
3155
|
if (!response.ok) {
|
|
3048
|
-
const error = await response
|
|
3156
|
+
const error = await readResponseText(response, void 0, 64 * 1024).catch(() => "unknown error");
|
|
3049
3157
|
throw new Error(`LLM API error (${response.status}): ${error}`);
|
|
3050
3158
|
}
|
|
3051
|
-
const data = await response
|
|
3159
|
+
const data = await readResponseJson(response);
|
|
3052
3160
|
return {
|
|
3053
3161
|
content: data.choices[0]?.message?.content ?? "",
|
|
3054
3162
|
usage: data.usage ? {
|
|
@@ -3079,10 +3187,10 @@ async function callAnthropic(systemPrompt, userMessage) {
|
|
|
3079
3187
|
})
|
|
3080
3188
|
});
|
|
3081
3189
|
if (!response.ok) {
|
|
3082
|
-
const error = await response
|
|
3190
|
+
const error = await readResponseText(response, void 0, 64 * 1024).catch(() => "unknown error");
|
|
3083
3191
|
throw new Error(`Anthropic API error (${response.status}): ${error}`);
|
|
3084
3192
|
}
|
|
3085
|
-
const data = await response
|
|
3193
|
+
const data = await readResponseJson(response);
|
|
3086
3194
|
return {
|
|
3087
3195
|
content: data.content[0]?.text ?? "",
|
|
3088
3196
|
usage: data.usage ? {
|
|
@@ -3091,7 +3199,418 @@ async function callAnthropic(systemPrompt, userMessage) {
|
|
|
3091
3199
|
} : void 0
|
|
3092
3200
|
};
|
|
3093
3201
|
}
|
|
3094
|
-
|
|
3202
|
+
async function callLLMWithTools(messages, tools, signal) {
|
|
3203
|
+
if (!currentConfig) {
|
|
3204
|
+
throw new Error("LLM not configured. Set MEMORIX_LLM_API_KEY or OPENAI_API_KEY.");
|
|
3205
|
+
}
|
|
3206
|
+
if (currentConfig.provider === "anthropic") {
|
|
3207
|
+
return callAnthropicWithTools(messages, tools, signal);
|
|
3208
|
+
}
|
|
3209
|
+
return callOpenAIWithTools(messages, tools, signal);
|
|
3210
|
+
}
|
|
3211
|
+
async function callOpenAIWithTools(messages, tools, signal) {
|
|
3212
|
+
const config = currentConfig;
|
|
3213
|
+
let base = config.baseUrl.replace(/\/+$/, "");
|
|
3214
|
+
if (!base.endsWith("/v1")) base += "/v1";
|
|
3215
|
+
const url = `${base}/chat/completions`;
|
|
3216
|
+
const openaiMessages = messages.map((msg) => {
|
|
3217
|
+
if (msg.role === "tool") {
|
|
3218
|
+
return { role: "tool", content: msg.content, tool_call_id: msg.toolCallId };
|
|
3219
|
+
}
|
|
3220
|
+
if (msg.role === "assistant" && msg.toolCalls && msg.toolCalls.length > 0) {
|
|
3221
|
+
return {
|
|
3222
|
+
role: "assistant",
|
|
3223
|
+
content: msg.content || null,
|
|
3224
|
+
tool_calls: msg.toolCalls.map((tc) => ({
|
|
3225
|
+
id: tc.id,
|
|
3226
|
+
type: "function",
|
|
3227
|
+
function: { name: tc.name, arguments: tc.arguments }
|
|
3228
|
+
}))
|
|
3229
|
+
};
|
|
3230
|
+
}
|
|
3231
|
+
return { role: msg.role, content: msg.content };
|
|
3232
|
+
});
|
|
3233
|
+
const openaiTools = tools.map((t) => ({
|
|
3234
|
+
type: "function",
|
|
3235
|
+
function: { name: t.name, description: t.description, parameters: t.parameters }
|
|
3236
|
+
}));
|
|
3237
|
+
const fetchSignal = signal ? AbortSignal.any([signal, AbortSignal.timeout(LLM_CALL_TIMEOUT_MS)]) : AbortSignal.timeout(LLM_CALL_TIMEOUT_MS);
|
|
3238
|
+
const response = await fetch(url, {
|
|
3239
|
+
signal: fetchSignal,
|
|
3240
|
+
method: "POST",
|
|
3241
|
+
headers: {
|
|
3242
|
+
"Content-Type": "application/json",
|
|
3243
|
+
"Authorization": `Bearer ${config.apiKey}`
|
|
3244
|
+
},
|
|
3245
|
+
body: JSON.stringify({
|
|
3246
|
+
model: config.model,
|
|
3247
|
+
messages: openaiMessages,
|
|
3248
|
+
tools: openaiTools.length > 0 ? openaiTools : void 0,
|
|
3249
|
+
temperature: 0.3,
|
|
3250
|
+
max_tokens: 2048
|
|
3251
|
+
})
|
|
3252
|
+
});
|
|
3253
|
+
if (!response.ok) {
|
|
3254
|
+
const error = await readResponseText(response, signal, 64 * 1024).catch(() => "unknown error");
|
|
3255
|
+
throw new Error(`LLM tool-call API error (${response.status}): ${error}`);
|
|
3256
|
+
}
|
|
3257
|
+
const data = await readResponseJson(response, signal);
|
|
3258
|
+
const choice = data.choices[0];
|
|
3259
|
+
const content = choice?.message?.content ?? "";
|
|
3260
|
+
const toolCalls = (choice?.message?.tool_calls ?? []).map((tc) => ({
|
|
3261
|
+
id: tc.id,
|
|
3262
|
+
name: tc.function.name,
|
|
3263
|
+
arguments: tc.function.arguments
|
|
3264
|
+
}));
|
|
3265
|
+
const stopReason = choice?.finish_reason === "tool_calls" ? "tool_use" : choice?.finish_reason === "stop" ? "stop" : "unknown";
|
|
3266
|
+
return {
|
|
3267
|
+
content,
|
|
3268
|
+
toolCalls,
|
|
3269
|
+
stopReason,
|
|
3270
|
+
usage: data.usage ? {
|
|
3271
|
+
promptTokens: data.usage.prompt_tokens,
|
|
3272
|
+
completionTokens: data.usage.completion_tokens
|
|
3273
|
+
} : void 0
|
|
3274
|
+
};
|
|
3275
|
+
}
|
|
3276
|
+
async function callAnthropicWithTools(messages, tools, signal) {
|
|
3277
|
+
const config = currentConfig;
|
|
3278
|
+
const url = `${config.baseUrl}/messages`;
|
|
3279
|
+
const systemContent = messages.find((m) => m.role === "system")?.content ?? "";
|
|
3280
|
+
const nonSystemMessages = messages.filter((m) => m.role !== "system");
|
|
3281
|
+
const anthropicMessages = [];
|
|
3282
|
+
for (const msg of nonSystemMessages) {
|
|
3283
|
+
if (msg.role === "user") {
|
|
3284
|
+
anthropicMessages.push({ role: "user", content: msg.content });
|
|
3285
|
+
} else if (msg.role === "assistant") {
|
|
3286
|
+
const contentBlocks = [];
|
|
3287
|
+
if (msg.content) contentBlocks.push({ type: "text", text: msg.content });
|
|
3288
|
+
if (msg.toolCalls) {
|
|
3289
|
+
for (const tc of msg.toolCalls) {
|
|
3290
|
+
contentBlocks.push({
|
|
3291
|
+
type: "tool_use",
|
|
3292
|
+
id: tc.id,
|
|
3293
|
+
name: tc.name,
|
|
3294
|
+
input: JSON.parse(tc.arguments)
|
|
3295
|
+
});
|
|
3296
|
+
}
|
|
3297
|
+
}
|
|
3298
|
+
anthropicMessages.push({ role: "assistant", content: contentBlocks });
|
|
3299
|
+
} else if (msg.role === "tool") {
|
|
3300
|
+
anthropicMessages.push({
|
|
3301
|
+
role: "user",
|
|
3302
|
+
content: [{
|
|
3303
|
+
type: "tool_result",
|
|
3304
|
+
tool_use_id: msg.toolCallId,
|
|
3305
|
+
content: msg.content
|
|
3306
|
+
}]
|
|
3307
|
+
});
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
3310
|
+
const anthropicTools = tools.map((t) => ({
|
|
3311
|
+
name: t.name,
|
|
3312
|
+
description: t.description,
|
|
3313
|
+
input_schema: t.parameters
|
|
3314
|
+
}));
|
|
3315
|
+
const fetchSignal = signal ? AbortSignal.any([signal, AbortSignal.timeout(LLM_CALL_TIMEOUT_MS)]) : AbortSignal.timeout(LLM_CALL_TIMEOUT_MS);
|
|
3316
|
+
const response = await fetch(url, {
|
|
3317
|
+
signal: fetchSignal,
|
|
3318
|
+
method: "POST",
|
|
3319
|
+
headers: {
|
|
3320
|
+
"Content-Type": "application/json",
|
|
3321
|
+
"x-api-key": config.apiKey,
|
|
3322
|
+
"anthropic-version": "2023-06-01"
|
|
3323
|
+
},
|
|
3324
|
+
body: JSON.stringify({
|
|
3325
|
+
model: config.model,
|
|
3326
|
+
system: systemContent,
|
|
3327
|
+
messages: anthropicMessages,
|
|
3328
|
+
tools: anthropicTools.length > 0 ? anthropicTools : void 0,
|
|
3329
|
+
temperature: 0.3,
|
|
3330
|
+
max_tokens: 2048
|
|
3331
|
+
})
|
|
3332
|
+
});
|
|
3333
|
+
if (!response.ok) {
|
|
3334
|
+
const error = await readResponseText(response, signal, 64 * 1024).catch(() => "unknown error");
|
|
3335
|
+
throw new Error(`Anthropic tool-call API error (${response.status}): ${error}`);
|
|
3336
|
+
}
|
|
3337
|
+
const data = await readResponseJson(response, signal);
|
|
3338
|
+
let content = "";
|
|
3339
|
+
const toolCalls = [];
|
|
3340
|
+
for (const block of data.content) {
|
|
3341
|
+
if (block.type === "text" && block.text) {
|
|
3342
|
+
content += block.text;
|
|
3343
|
+
} else if (block.type === "tool_use" && block.id && block.name) {
|
|
3344
|
+
toolCalls.push({
|
|
3345
|
+
id: block.id,
|
|
3346
|
+
name: block.name,
|
|
3347
|
+
arguments: JSON.stringify(block.input ?? {})
|
|
3348
|
+
});
|
|
3349
|
+
}
|
|
3350
|
+
}
|
|
3351
|
+
const stopReason = data.stop_reason === "tool_use" ? "tool_use" : data.stop_reason === "end_turn" ? "end_turn" : "unknown";
|
|
3352
|
+
return {
|
|
3353
|
+
content,
|
|
3354
|
+
toolCalls,
|
|
3355
|
+
stopReason,
|
|
3356
|
+
usage: data.usage ? {
|
|
3357
|
+
promptTokens: data.usage.input_tokens,
|
|
3358
|
+
completionTokens: data.usage.output_tokens
|
|
3359
|
+
} : void 0
|
|
3360
|
+
};
|
|
3361
|
+
}
|
|
3362
|
+
async function* callOpenAIWithToolsStream(messages, tools) {
|
|
3363
|
+
const config = currentConfig;
|
|
3364
|
+
let base = config.baseUrl.replace(/\/+$/, "");
|
|
3365
|
+
if (!base.endsWith("/v1")) base += "/v1";
|
|
3366
|
+
const url = `${base}/chat/completions`;
|
|
3367
|
+
const openaiMessages = messages.map((msg) => {
|
|
3368
|
+
if (msg.role === "tool") {
|
|
3369
|
+
return { role: "tool", content: msg.content, tool_call_id: msg.toolCallId };
|
|
3370
|
+
}
|
|
3371
|
+
if (msg.role === "assistant" && msg.toolCalls && msg.toolCalls.length > 0) {
|
|
3372
|
+
return {
|
|
3373
|
+
role: "assistant",
|
|
3374
|
+
content: msg.content || null,
|
|
3375
|
+
tool_calls: msg.toolCalls.map((tc) => ({
|
|
3376
|
+
id: tc.id,
|
|
3377
|
+
type: "function",
|
|
3378
|
+
function: { name: tc.name, arguments: tc.arguments }
|
|
3379
|
+
}))
|
|
3380
|
+
};
|
|
3381
|
+
}
|
|
3382
|
+
return { role: msg.role, content: msg.content };
|
|
3383
|
+
});
|
|
3384
|
+
const openaiTools = tools.map((t) => ({
|
|
3385
|
+
type: "function",
|
|
3386
|
+
function: { name: t.name, description: t.description, parameters: t.parameters }
|
|
3387
|
+
}));
|
|
3388
|
+
const response = await fetch(url, {
|
|
3389
|
+
signal: AbortSignal.timeout(LLM_CALL_TIMEOUT_MS * 2),
|
|
3390
|
+
// longer timeout for streaming
|
|
3391
|
+
method: "POST",
|
|
3392
|
+
headers: {
|
|
3393
|
+
"Content-Type": "application/json",
|
|
3394
|
+
"Authorization": `Bearer ${config.apiKey}`
|
|
3395
|
+
},
|
|
3396
|
+
body: JSON.stringify({
|
|
3397
|
+
model: config.model,
|
|
3398
|
+
messages: openaiMessages,
|
|
3399
|
+
tools: openaiTools.length > 0 ? openaiTools : void 0,
|
|
3400
|
+
temperature: 0.3,
|
|
3401
|
+
max_tokens: 2048,
|
|
3402
|
+
stream: true
|
|
3403
|
+
})
|
|
3404
|
+
});
|
|
3405
|
+
if (!response.ok) {
|
|
3406
|
+
const error = await response.text().catch(() => "unknown error");
|
|
3407
|
+
throw new Error(`LLM streaming API error (${response.status}): ${error}`);
|
|
3408
|
+
}
|
|
3409
|
+
const reader = response.body?.getReader();
|
|
3410
|
+
if (!reader) throw new Error("No response body for streaming");
|
|
3411
|
+
const decoder = new TextDecoder();
|
|
3412
|
+
let fullContent = "";
|
|
3413
|
+
const toolCallMap = /* @__PURE__ */ new Map();
|
|
3414
|
+
let finishReason = "unknown";
|
|
3415
|
+
let buffer = "";
|
|
3416
|
+
try {
|
|
3417
|
+
while (true) {
|
|
3418
|
+
const { done, value } = await reader.read();
|
|
3419
|
+
if (done) break;
|
|
3420
|
+
buffer += decoder.decode(value, { stream: true });
|
|
3421
|
+
const lines = buffer.split("\n");
|
|
3422
|
+
buffer = lines.pop() ?? "";
|
|
3423
|
+
for (const line of lines) {
|
|
3424
|
+
const trimmed = line.trim();
|
|
3425
|
+
if (!trimmed || trimmed === "data: [DONE]") continue;
|
|
3426
|
+
if (!trimmed.startsWith("data: ")) continue;
|
|
3427
|
+
try {
|
|
3428
|
+
const chunk = JSON.parse(trimmed.slice(6));
|
|
3429
|
+
const delta = chunk.choices?.[0]?.delta;
|
|
3430
|
+
if (!delta) continue;
|
|
3431
|
+
if (delta.content) {
|
|
3432
|
+
fullContent += delta.content;
|
|
3433
|
+
yield { type: "text", content: delta.content };
|
|
3434
|
+
}
|
|
3435
|
+
if (delta.tool_calls) {
|
|
3436
|
+
for (const tc of delta.tool_calls) {
|
|
3437
|
+
const idx = tc.index ?? 0;
|
|
3438
|
+
if (!toolCallMap.has(idx)) {
|
|
3439
|
+
toolCallMap.set(idx, {
|
|
3440
|
+
id: tc.id ?? "",
|
|
3441
|
+
name: tc.function?.name ?? "",
|
|
3442
|
+
arguments: tc.function?.arguments ?? ""
|
|
3443
|
+
});
|
|
3444
|
+
} else {
|
|
3445
|
+
const existing = toolCallMap.get(idx);
|
|
3446
|
+
if (tc.id) existing.id = tc.id;
|
|
3447
|
+
if (tc.function?.name) existing.name = tc.function.name;
|
|
3448
|
+
if (tc.function?.arguments) existing.arguments += tc.function.arguments;
|
|
3449
|
+
}
|
|
3450
|
+
}
|
|
3451
|
+
}
|
|
3452
|
+
if (chunk.choices?.[0]?.finish_reason) {
|
|
3453
|
+
finishReason = chunk.choices[0].finish_reason;
|
|
3454
|
+
}
|
|
3455
|
+
} catch {
|
|
3456
|
+
}
|
|
3457
|
+
}
|
|
3458
|
+
}
|
|
3459
|
+
} finally {
|
|
3460
|
+
reader.releaseLock();
|
|
3461
|
+
}
|
|
3462
|
+
const toolCalls = [...toolCallMap.values()].map((tc) => ({
|
|
3463
|
+
id: tc.id,
|
|
3464
|
+
name: tc.name,
|
|
3465
|
+
arguments: tc.arguments
|
|
3466
|
+
}));
|
|
3467
|
+
for (const tc of toolCalls) {
|
|
3468
|
+
yield { type: "tool_call", toolCall: tc };
|
|
3469
|
+
}
|
|
3470
|
+
const stopReason = finishReason === "tool_calls" ? "tool_use" : finishReason === "stop" ? "stop" : "unknown";
|
|
3471
|
+
yield {
|
|
3472
|
+
type: "done",
|
|
3473
|
+
response: {
|
|
3474
|
+
content: fullContent,
|
|
3475
|
+
toolCalls,
|
|
3476
|
+
stopReason
|
|
3477
|
+
}
|
|
3478
|
+
};
|
|
3479
|
+
}
|
|
3480
|
+
async function* callAnthropicWithToolsStream(messages, tools) {
|
|
3481
|
+
const config = currentConfig;
|
|
3482
|
+
const url = `${config.baseUrl}/messages`;
|
|
3483
|
+
const systemContent = messages.find((m) => m.role === "system")?.content ?? "";
|
|
3484
|
+
const nonSystemMessages = messages.filter((m) => m.role !== "system");
|
|
3485
|
+
const anthropicMessages = [];
|
|
3486
|
+
for (const msg of nonSystemMessages) {
|
|
3487
|
+
if (msg.role === "user") {
|
|
3488
|
+
anthropicMessages.push({ role: "user", content: msg.content });
|
|
3489
|
+
} else if (msg.role === "assistant") {
|
|
3490
|
+
const contentBlocks = [];
|
|
3491
|
+
if (msg.content) contentBlocks.push({ type: "text", text: msg.content });
|
|
3492
|
+
if (msg.toolCalls) {
|
|
3493
|
+
for (const tc of msg.toolCalls) {
|
|
3494
|
+
contentBlocks.push({
|
|
3495
|
+
type: "tool_use",
|
|
3496
|
+
id: tc.id,
|
|
3497
|
+
name: tc.name,
|
|
3498
|
+
input: JSON.parse(tc.arguments)
|
|
3499
|
+
});
|
|
3500
|
+
}
|
|
3501
|
+
}
|
|
3502
|
+
anthropicMessages.push({ role: "assistant", content: contentBlocks });
|
|
3503
|
+
} else if (msg.role === "tool") {
|
|
3504
|
+
anthropicMessages.push({
|
|
3505
|
+
role: "user",
|
|
3506
|
+
content: [{
|
|
3507
|
+
type: "tool_result",
|
|
3508
|
+
tool_use_id: msg.toolCallId,
|
|
3509
|
+
content: msg.content
|
|
3510
|
+
}]
|
|
3511
|
+
});
|
|
3512
|
+
}
|
|
3513
|
+
}
|
|
3514
|
+
const anthropicTools = tools.map((t) => ({
|
|
3515
|
+
name: t.name,
|
|
3516
|
+
description: t.description,
|
|
3517
|
+
input_schema: t.parameters
|
|
3518
|
+
}));
|
|
3519
|
+
const response = await fetch(url, {
|
|
3520
|
+
signal: AbortSignal.timeout(LLM_CALL_TIMEOUT_MS * 2),
|
|
3521
|
+
method: "POST",
|
|
3522
|
+
headers: {
|
|
3523
|
+
"Content-Type": "application/json",
|
|
3524
|
+
"x-api-key": config.apiKey,
|
|
3525
|
+
"anthropic-version": "2023-06-01"
|
|
3526
|
+
},
|
|
3527
|
+
body: JSON.stringify({
|
|
3528
|
+
model: config.model,
|
|
3529
|
+
system: systemContent,
|
|
3530
|
+
messages: anthropicMessages,
|
|
3531
|
+
tools: anthropicTools.length > 0 ? anthropicTools : void 0,
|
|
3532
|
+
temperature: 0.3,
|
|
3533
|
+
max_tokens: 2048,
|
|
3534
|
+
stream: true
|
|
3535
|
+
})
|
|
3536
|
+
});
|
|
3537
|
+
if (!response.ok) {
|
|
3538
|
+
const error = await response.text().catch(() => "unknown error");
|
|
3539
|
+
throw new Error(`Anthropic streaming API error (${response.status}): ${error}`);
|
|
3540
|
+
}
|
|
3541
|
+
const reader = response.body?.getReader();
|
|
3542
|
+
if (!reader) throw new Error("No response body for streaming");
|
|
3543
|
+
const decoder = new TextDecoder();
|
|
3544
|
+
let fullContent = "";
|
|
3545
|
+
const toolCalls = [];
|
|
3546
|
+
let currentToolId = "";
|
|
3547
|
+
let currentToolName = "";
|
|
3548
|
+
let currentToolInput = "";
|
|
3549
|
+
let stopReason = "unknown";
|
|
3550
|
+
let buffer = "";
|
|
3551
|
+
try {
|
|
3552
|
+
while (true) {
|
|
3553
|
+
const { done, value } = await reader.read();
|
|
3554
|
+
if (done) break;
|
|
3555
|
+
buffer += decoder.decode(value, { stream: true });
|
|
3556
|
+
const lines = buffer.split("\n");
|
|
3557
|
+
buffer = lines.pop() ?? "";
|
|
3558
|
+
for (const line of lines) {
|
|
3559
|
+
const trimmed = line.trim();
|
|
3560
|
+
if (!trimmed.startsWith("data: ")) continue;
|
|
3561
|
+
try {
|
|
3562
|
+
const event = JSON.parse(trimmed.slice(6));
|
|
3563
|
+
if (event.type === "content_block_delta") {
|
|
3564
|
+
const delta = event.delta;
|
|
3565
|
+
if (delta?.type === "text_delta" && delta.text) {
|
|
3566
|
+
fullContent += delta.text;
|
|
3567
|
+
yield { type: "text", content: delta.text };
|
|
3568
|
+
} else if (delta?.type === "input_json_delta" && delta.partial_json) {
|
|
3569
|
+
currentToolInput += delta.partial_json;
|
|
3570
|
+
}
|
|
3571
|
+
} else if (event.type === "content_block_start") {
|
|
3572
|
+
const block = event.content_block;
|
|
3573
|
+
if (block?.type === "tool_use") {
|
|
3574
|
+
currentToolId = block.id ?? "";
|
|
3575
|
+
currentToolName = block.name ?? "";
|
|
3576
|
+
currentToolInput = "";
|
|
3577
|
+
}
|
|
3578
|
+
} else if (event.type === "content_block_stop") {
|
|
3579
|
+
if (currentToolId && currentToolName) {
|
|
3580
|
+
const tc = {
|
|
3581
|
+
id: currentToolId,
|
|
3582
|
+
name: currentToolName,
|
|
3583
|
+
arguments: currentToolInput || "{}"
|
|
3584
|
+
};
|
|
3585
|
+
toolCalls.push(tc);
|
|
3586
|
+
yield { type: "tool_call", toolCall: tc };
|
|
3587
|
+
currentToolId = "";
|
|
3588
|
+
currentToolName = "";
|
|
3589
|
+
currentToolInput = "";
|
|
3590
|
+
}
|
|
3591
|
+
} else if (event.type === "message_delta") {
|
|
3592
|
+
if (event.delta?.stop_reason) {
|
|
3593
|
+
stopReason = event.delta.stop_reason;
|
|
3594
|
+
}
|
|
3595
|
+
}
|
|
3596
|
+
} catch {
|
|
3597
|
+
}
|
|
3598
|
+
}
|
|
3599
|
+
}
|
|
3600
|
+
} finally {
|
|
3601
|
+
reader.releaseLock();
|
|
3602
|
+
}
|
|
3603
|
+
const mappedStopReason = stopReason === "tool_use" ? "tool_use" : stopReason === "end_turn" ? "end_turn" : "unknown";
|
|
3604
|
+
yield {
|
|
3605
|
+
type: "done",
|
|
3606
|
+
response: {
|
|
3607
|
+
content: fullContent,
|
|
3608
|
+
toolCalls,
|
|
3609
|
+
stopReason: mappedStopReason
|
|
3610
|
+
}
|
|
3611
|
+
};
|
|
3612
|
+
}
|
|
3613
|
+
var LLM_TIMEOUT_DEFAULT_MS, LLM_TIMEOUT_MIN_MS, LLM_TIMEOUT_MAX_MS, MAX_LLM_RESPONSE_BYTES, LLM_CALL_TIMEOUT_MS, PROVIDER_DEFAULTS, currentConfig;
|
|
3095
3614
|
var init_provider2 = __esm({
|
|
3096
3615
|
"src/llm/provider.ts"() {
|
|
3097
3616
|
"use strict";
|
|
@@ -3099,6 +3618,7 @@ var init_provider2 = __esm({
|
|
|
3099
3618
|
LLM_TIMEOUT_DEFAULT_MS = 3e4;
|
|
3100
3619
|
LLM_TIMEOUT_MIN_MS = 1e3;
|
|
3101
3620
|
LLM_TIMEOUT_MAX_MS = 3e5;
|
|
3621
|
+
MAX_LLM_RESPONSE_BYTES = 2 * 1024 * 1024;
|
|
3102
3622
|
LLM_CALL_TIMEOUT_MS = parseLLMTimeoutMs(process.env.MEMORIX_LLM_TIMEOUT_MS);
|
|
3103
3623
|
PROVIDER_DEFAULTS = {
|
|
3104
3624
|
openai: { baseUrl: "https://api.openai.com/v1", model: "gpt-4.1-nano" },
|
|
@@ -3185,7 +3705,7 @@ function idPriority(id) {
|
|
|
3185
3705
|
return 2;
|
|
3186
3706
|
}
|
|
3187
3707
|
function getRegistryPath(baseDir) {
|
|
3188
|
-
return path8.join(baseDir ?? registryDir ??
|
|
3708
|
+
return path8.join(baseDir ?? registryDir ?? DEFAULT_DATA_DIR, ALIAS_FILE);
|
|
3189
3709
|
}
|
|
3190
3710
|
async function loadRegistry(baseDir) {
|
|
3191
3711
|
if (registryCache) return registryCache;
|
|
@@ -3357,12 +3877,12 @@ function initAliasRegistry(dataDir) {
|
|
|
3357
3877
|
function resetAliasCache() {
|
|
3358
3878
|
registryCache = null;
|
|
3359
3879
|
}
|
|
3360
|
-
var
|
|
3880
|
+
var DEFAULT_DATA_DIR, ALIAS_FILE, registryCache, registryDir;
|
|
3361
3881
|
var init_aliases = __esm({
|
|
3362
3882
|
"src/project/aliases.ts"() {
|
|
3363
3883
|
"use strict";
|
|
3364
3884
|
init_esm_shims();
|
|
3365
|
-
|
|
3885
|
+
DEFAULT_DATA_DIR = process.env.MEMORIX_DATA_DIR || path8.join(os2.homedir(), ".memorix", "data");
|
|
3366
3886
|
ALIAS_FILE = ".project-aliases.json";
|
|
3367
3887
|
registryCache = null;
|
|
3368
3888
|
registryDir = null;
|
|
@@ -3924,6 +4444,10 @@ async function searchObservations(options) {
|
|
|
3924
4444
|
if (statusFilter === "all") return true;
|
|
3925
4445
|
const doc = hit.document;
|
|
3926
4446
|
return (doc.status || "active") === statusFilter;
|
|
4447
|
+
}).filter((hit) => {
|
|
4448
|
+
if (options.type === "probe") return true;
|
|
4449
|
+
const doc = hit.document;
|
|
4450
|
+
return doc.type !== "probe";
|
|
3927
4451
|
}).map((hit) => {
|
|
3928
4452
|
const doc = hit.document;
|
|
3929
4453
|
const obsType = doc.type;
|
|
@@ -3939,7 +4463,7 @@ async function searchObservations(options) {
|
|
|
3939
4463
|
time: formatTime(doc.createdAt),
|
|
3940
4464
|
rawTime: doc.createdAt,
|
|
3941
4465
|
type: obsType,
|
|
3942
|
-
icon: OBSERVATION_ICONS[obsType] ?? "
|
|
4466
|
+
icon: OBSERVATION_ICONS[obsType] ?? "[UNKNOWN]",
|
|
3943
4467
|
title: doc.title,
|
|
3944
4468
|
tokens: doc.tokens,
|
|
3945
4469
|
score: (hit.score ?? 1) * recencyBoost,
|
|
@@ -4164,7 +4688,7 @@ async function searchObservations(options) {
|
|
|
4164
4688
|
} else if (isSynthesized) {
|
|
4165
4689
|
entry.matchedFields = ["synthesized", ...reasons];
|
|
4166
4690
|
} else if (isCore) {
|
|
4167
|
-
entry.matchedFields = ["
|
|
4691
|
+
entry.matchedFields = ["core core", ...reasons];
|
|
4168
4692
|
} else {
|
|
4169
4693
|
entry.matchedFields = reasons;
|
|
4170
4694
|
}
|
|
@@ -4212,7 +4736,7 @@ async function getTimeline(anchorId, projectId, depthBefore = 3, depthAfter = 3)
|
|
|
4212
4736
|
id: obs.id,
|
|
4213
4737
|
time: formatTime(obs.createdAt),
|
|
4214
4738
|
type: obsType,
|
|
4215
|
-
icon: OBSERVATION_ICONS[obsType] ?? "
|
|
4739
|
+
icon: OBSERVATION_ICONS[obsType] ?? "[UNKNOWN]",
|
|
4216
4740
|
title: obs.title,
|
|
4217
4741
|
tokens: obs.tokens,
|
|
4218
4742
|
source: obs.source || void 0,
|
|
@@ -5626,10 +6150,10 @@ function resolveEvidenceBasis(fields) {
|
|
|
5626
6150
|
function evidenceBasisLine(basis, commitHash) {
|
|
5627
6151
|
if (basis === "repository") {
|
|
5628
6152
|
const commit = commitHash ? ` \u2014 commit ${commitHash.substring(0, 7)}` : "";
|
|
5629
|
-
return
|
|
6153
|
+
return `[OK] Repository-backed${commit}`;
|
|
5630
6154
|
}
|
|
5631
6155
|
if (basis === "synthesized") {
|
|
5632
|
-
return "
|
|
6156
|
+
return "[SYNTHESIZED] Synthesized \u2014 explicit analysis citing repository evidence";
|
|
5633
6157
|
}
|
|
5634
6158
|
return "";
|
|
5635
6159
|
}
|
|
@@ -6157,10 +6681,10 @@ var init_extract = __esm({
|
|
|
6157
6681
|
pattern: /\b([A-Z][a-zA-Z_-]{2,30})\s*[:=]\s*([^\n,;]{2,60})/g,
|
|
6158
6682
|
format: (m) => `${m[1]}: ${m[2].trim()}`
|
|
6159
6683
|
},
|
|
6160
|
-
// Arrow notation (e.g., "MySQL
|
|
6684
|
+
// Arrow notation (e.g., "MySQL -> PostgreSQL", "v1.0 -> v2.0")
|
|
6161
6685
|
{
|
|
6162
|
-
pattern: /\b(\S{2,30})\s*
|
|
6163
|
-
format: (m) => `${m[1]}
|
|
6686
|
+
pattern: /\b(\S{2,30})\s*(?:->|=>|>)\s*(\S{2,30})/g,
|
|
6687
|
+
format: (m) => `${m[1]} -> ${m[2]}`
|
|
6164
6688
|
},
|
|
6165
6689
|
// Version numbers (e.g., "v1.2.3", "version 2.0")
|
|
6166
6690
|
{
|
|
@@ -6305,6 +6829,11 @@ function hasContradiction(oldText, newText) {
|
|
|
6305
6829
|
];
|
|
6306
6830
|
return negationPatterns.some((p) => p.test(newText));
|
|
6307
6831
|
}
|
|
6832
|
+
function normalizedSearchSimilarity(score) {
|
|
6833
|
+
if (!Number.isFinite(score) || score <= 0) return 0;
|
|
6834
|
+
if (score <= 1) return score;
|
|
6835
|
+
return 0;
|
|
6836
|
+
}
|
|
6308
6837
|
function mergeNarratives(oldNarrative, newNarrative) {
|
|
6309
6838
|
if (newNarrative.length > oldNarrative.length * 1.5) return newNarrative;
|
|
6310
6839
|
if (oldNarrative.length > newNarrative.length * 1.5) return oldNarrative;
|
|
@@ -6395,12 +6924,13 @@ function scoreCandidate(extracted, candidate) {
|
|
|
6395
6924
|
`${extracted.title} ${extracted.narrative}`,
|
|
6396
6925
|
`${candidate.title} ${candidate.narrative}`
|
|
6397
6926
|
);
|
|
6398
|
-
const
|
|
6927
|
+
const searchSimilarity = normalizedSearchSimilarity(candidate.score);
|
|
6928
|
+
const score = searchSimilarity * 0.6 + (entityMatch ? 0.2 : 0) + contentOverlap * 0.2;
|
|
6399
6929
|
const newLength = extracted.narrative.length + extracted.facts.join(" ").length;
|
|
6400
6930
|
const oldLength = candidate.narrative.length + candidate.facts.length;
|
|
6401
6931
|
const richer = newLength > oldLength * 1.15;
|
|
6402
6932
|
const contradiction = hasContradiction(candidate.narrative, extracted.narrative);
|
|
6403
|
-
return { score, entityMatch, richer, contradiction };
|
|
6933
|
+
return { score, searchSimilarity, entityMatch, richer, contradiction };
|
|
6404
6934
|
}
|
|
6405
6935
|
async function runResolve(extracted, projectId, searchMemories, getObservation2, useLLM = false) {
|
|
6406
6936
|
const query = `${extracted.title} ${extracted.narrative.substring(0, 200)}`;
|
|
@@ -6423,7 +6953,7 @@ async function runResolve(extracted, projectId, searchMemories, getObservation2,
|
|
|
6423
6953
|
}));
|
|
6424
6954
|
scored.sort((a, b) => b.score - a.score);
|
|
6425
6955
|
const best = scored[0];
|
|
6426
|
-
if (best.
|
|
6956
|
+
if (best.searchSimilarity >= SIMILARITY_DUPLICATE) {
|
|
6427
6957
|
if (best.richer) {
|
|
6428
6958
|
const existing = getObservation2(best.hit.observationId);
|
|
6429
6959
|
const oldFacts = existing?.facts ?? best.hit.facts.split("\n").filter(Boolean);
|
|
@@ -6610,7 +7140,8 @@ var init_evaluate = __esm({
|
|
|
6610
7140
|
"how-it-works": 0.6,
|
|
6611
7141
|
"discovery": 0.55,
|
|
6612
7142
|
"what-changed": 0.45,
|
|
6613
|
-
"session-request": 0.4
|
|
7143
|
+
"session-request": 0.4,
|
|
7144
|
+
"probe": 0.1
|
|
6614
7145
|
};
|
|
6615
7146
|
SPECIFICITY_PATTERNS = [
|
|
6616
7147
|
/\b\d+\.\d+\.\d+\b/,
|
|
@@ -6820,16 +7351,36 @@ function getMetricsSummary() {
|
|
|
6820
7351
|
async function runFormation(input, config) {
|
|
6821
7352
|
const startTime = Date.now();
|
|
6822
7353
|
let stagesCompleted = 0;
|
|
7354
|
+
const stageDurationsMs = {};
|
|
7355
|
+
const emitStageEvent = (stage, status, stageDurationMs) => {
|
|
7356
|
+
try {
|
|
7357
|
+
config.onStageEvent?.({
|
|
7358
|
+
stage,
|
|
7359
|
+
status,
|
|
7360
|
+
stageDurationMs,
|
|
7361
|
+
totalElapsedMs: Date.now() - startTime
|
|
7362
|
+
});
|
|
7363
|
+
} catch {
|
|
7364
|
+
}
|
|
7365
|
+
};
|
|
6823
7366
|
const existingEntities = config.getEntityNames();
|
|
7367
|
+
const extractStartTime = Date.now();
|
|
7368
|
+
emitStageEvent("extract", "start");
|
|
6824
7369
|
const extraction = await runExtract(input, existingEntities, config.useLLM);
|
|
7370
|
+
stageDurationsMs.extract = Date.now() - extractStartTime;
|
|
7371
|
+
emitStageEvent("extract", "success", stageDurationsMs.extract);
|
|
6825
7372
|
stagesCompleted = 1;
|
|
6826
7373
|
let resolution;
|
|
6827
7374
|
if (input.topicKey) {
|
|
7375
|
+
stageDurationsMs.resolve = 0;
|
|
7376
|
+
emitStageEvent("resolve", "skipped", 0);
|
|
6828
7377
|
resolution = {
|
|
6829
7378
|
action: "new",
|
|
6830
7379
|
reason: "TopicKey upsert \u2014 bypasses resolve stage"
|
|
6831
7380
|
};
|
|
6832
7381
|
} else {
|
|
7382
|
+
const resolveStartTime = Date.now();
|
|
7383
|
+
emitStageEvent("resolve", "start");
|
|
6833
7384
|
resolution = await runResolve(
|
|
6834
7385
|
extraction,
|
|
6835
7386
|
input.projectId,
|
|
@@ -6837,9 +7388,15 @@ async function runFormation(input, config) {
|
|
|
6837
7388
|
config.getObservation,
|
|
6838
7389
|
config.useLLM
|
|
6839
7390
|
);
|
|
7391
|
+
stageDurationsMs.resolve = Date.now() - resolveStartTime;
|
|
7392
|
+
emitStageEvent("resolve", "success", stageDurationsMs.resolve);
|
|
6840
7393
|
}
|
|
6841
7394
|
stagesCompleted = 2;
|
|
7395
|
+
const evaluateStartTime = Date.now();
|
|
7396
|
+
emitStageEvent("evaluate", "start");
|
|
6842
7397
|
const evaluation = runEvaluate(extraction);
|
|
7398
|
+
stageDurationsMs.evaluate = Date.now() - evaluateStartTime;
|
|
7399
|
+
emitStageEvent("evaluate", "success", stageDurationsMs.evaluate);
|
|
6843
7400
|
stagesCompleted = 3;
|
|
6844
7401
|
const durationMs = Date.now() - startTime;
|
|
6845
7402
|
const formed = {
|
|
@@ -6858,7 +7415,8 @@ async function runFormation(input, config) {
|
|
|
6858
7415
|
mode: config.useLLM ? "llm" : "rules",
|
|
6859
7416
|
durationMs,
|
|
6860
7417
|
stagesCompleted,
|
|
6861
|
-
shadow: config.mode === "shadow"
|
|
7418
|
+
shadow: config.mode === "shadow",
|
|
7419
|
+
stageDurationsMs
|
|
6862
7420
|
},
|
|
6863
7421
|
// Governance fields
|
|
6864
7422
|
governance: {
|
|
@@ -7047,6 +7605,9 @@ function scoreObservationForSessionContext(obs, projectTokens, now = Date.now())
|
|
|
7047
7605
|
if (obs.valueCategory === "core") {
|
|
7048
7606
|
score += 2;
|
|
7049
7607
|
}
|
|
7608
|
+
if (obs.type === "probe") {
|
|
7609
|
+
score -= 100;
|
|
7610
|
+
}
|
|
7050
7611
|
return score;
|
|
7051
7612
|
}
|
|
7052
7613
|
async function startSession(projectDir2, projectId, opts) {
|
|
@@ -7139,7 +7700,7 @@ async function getSessionContext(projectDir2, projectId, limit = 3) {
|
|
|
7139
7700
|
lines.push("*Recent activity signals and search guidance for this session.*");
|
|
7140
7701
|
if (l1HookObs.length > 0) {
|
|
7141
7702
|
for (const obs of l1HookObs) {
|
|
7142
|
-
lines.push(
|
|
7703
|
+
lines.push(`[HOOK] ${redactCredentials(obs.title)}`);
|
|
7143
7704
|
}
|
|
7144
7705
|
lines.push("");
|
|
7145
7706
|
}
|
|
@@ -7157,7 +7718,7 @@ async function getSessionContext(projectDir2, projectId, limit = 3) {
|
|
|
7157
7718
|
hints.push(`${totalHookCount} hook trace(s) available \u2014 use \`memorix_timeline\` for activity expansion`);
|
|
7158
7719
|
}
|
|
7159
7720
|
for (const hint of hints) {
|
|
7160
|
-
lines.push(
|
|
7721
|
+
lines.push(`[TIP] ${hint}`);
|
|
7161
7722
|
}
|
|
7162
7723
|
lines.push("");
|
|
7163
7724
|
}
|
|
@@ -7184,7 +7745,7 @@ async function getSessionContext(projectDir2, projectId, limit = 3) {
|
|
|
7184
7745
|
lines.push("## Key Project Memories");
|
|
7185
7746
|
lines.push("*Durable working context \u2014 explicit decisions, gotchas, and discoveries.*");
|
|
7186
7747
|
for (const obs of l2Obs) {
|
|
7187
|
-
const emoji = TYPE_EMOJI[obs.type] ?? "
|
|
7748
|
+
const emoji = TYPE_EMOJI[obs.type] ?? "[PIN]";
|
|
7188
7749
|
const fact = obs.facts?.[0] ? ` \u2014 ${redactCredentials(obs.facts[0])}` : "";
|
|
7189
7750
|
lines.push(`${emoji} ${redactCredentials(obs.title)}${fact}`);
|
|
7190
7751
|
}
|
|
@@ -7204,10 +7765,10 @@ async function getSessionContext(projectDir2, projectId, limit = 3) {
|
|
|
7204
7765
|
}
|
|
7205
7766
|
const l3Lines = [];
|
|
7206
7767
|
if (l3GitCount > 0) {
|
|
7207
|
-
l3Lines.push(
|
|
7768
|
+
l3Lines.push(`[PIN] ${l3GitCount} git-memory item(s) \u2014 use \`memorix_search\` to retrieve repository evidence`);
|
|
7208
7769
|
}
|
|
7209
7770
|
if (totalHookCount > 0) {
|
|
7210
|
-
l3Lines.push(
|
|
7771
|
+
l3Lines.push(`[HOOK] ${totalHookCount} hook trace(s) \u2014 use \`memorix_timeline\` for full activity expansion`);
|
|
7211
7772
|
}
|
|
7212
7773
|
if (l3Lines.length > 0) {
|
|
7213
7774
|
lines.push("## L3 Evidence");
|
|
@@ -7247,15 +7808,16 @@ var init_session = __esm({
|
|
|
7247
7808
|
init_secret_filter();
|
|
7248
7809
|
PRIORITY_TYPES = /* @__PURE__ */ new Set(["gotcha", "decision", "problem-solution", "trade-off", "discovery"]);
|
|
7249
7810
|
TYPE_EMOJI = {
|
|
7250
|
-
"gotcha": "
|
|
7251
|
-
"decision": "
|
|
7252
|
-
"problem-solution": "
|
|
7253
|
-
"trade-off": "
|
|
7254
|
-
"discovery": "
|
|
7255
|
-
"how-it-works": "
|
|
7256
|
-
"what-changed": "
|
|
7257
|
-
"why-it-exists": "
|
|
7258
|
-
"session-request": "
|
|
7811
|
+
"gotcha": "[DISCOVERY]",
|
|
7812
|
+
"decision": "[WHY]",
|
|
7813
|
+
"problem-solution": "[FIX]",
|
|
7814
|
+
"trade-off": "[TRADEOFF]",
|
|
7815
|
+
"discovery": "[DISCOVERY]",
|
|
7816
|
+
"how-it-works": "[INFO]",
|
|
7817
|
+
"what-changed": "[CHANGE]",
|
|
7818
|
+
"why-it-exists": "[DECISION]",
|
|
7819
|
+
"session-request": "[SESSION]",
|
|
7820
|
+
"probe": "[PROBE]"
|
|
7259
7821
|
};
|
|
7260
7822
|
TYPE_WEIGHTS2 = {
|
|
7261
7823
|
"gotcha": 6,
|
|
@@ -7334,11 +7896,17 @@ function getValueCategoryMultiplier(doc) {
|
|
|
7334
7896
|
return 1;
|
|
7335
7897
|
}
|
|
7336
7898
|
function getEffectiveRetentionDays(doc) {
|
|
7899
|
+
const typeOverride = TYPE_RETENTION_OVERRIDE[doc.type];
|
|
7900
|
+
if (typeOverride !== void 0) {
|
|
7901
|
+
const raw2 = typeOverride * getSourceRetentionMultiplier(doc);
|
|
7902
|
+
return Math.max(MIN_RETENTION_DAYS, raw2);
|
|
7903
|
+
}
|
|
7337
7904
|
const importance = getImportanceLevel(doc);
|
|
7338
7905
|
const raw = RETENTION_DAYS[importance] * getSourceRetentionMultiplier(doc) * getValueCategoryMultiplier(doc);
|
|
7339
7906
|
return Math.max(MIN_RETENTION_DAYS, raw);
|
|
7340
7907
|
}
|
|
7341
7908
|
function isImmune(doc) {
|
|
7909
|
+
if (doc.type === "probe") return false;
|
|
7342
7910
|
if (doc.valueCategory === "core") return true;
|
|
7343
7911
|
const importance = getImportanceLevel(doc);
|
|
7344
7912
|
if (importance === "critical") return true;
|
|
@@ -7347,6 +7915,7 @@ function isImmune(doc) {
|
|
|
7347
7915
|
return concepts.some((c) => PROTECTED_TAGS.has(c));
|
|
7348
7916
|
}
|
|
7349
7917
|
function getImmunityReason(doc) {
|
|
7918
|
+
if (doc.type === "probe") return null;
|
|
7350
7919
|
if (doc.valueCategory === "core") return "core valueCategory (formation-classified)";
|
|
7351
7920
|
const importance = getImportanceLevel(doc);
|
|
7352
7921
|
if (importance === "critical") return "critical importance";
|
|
@@ -7503,7 +8072,7 @@ async function archiveExpired(projectDir2, referenceTime, accessMap) {
|
|
|
7503
8072
|
return { archived: archivedCount, remaining: activeObs.length - archivedCount };
|
|
7504
8073
|
});
|
|
7505
8074
|
}
|
|
7506
|
-
var RETENTION_DAYS, BASE_IMPORTANCE, TYPE_IMPORTANCE, PROTECTED_TAGS, MIN_ACCESS_FOR_IMMUNITY, MIN_RETENTION_DAYS;
|
|
8075
|
+
var RETENTION_DAYS, BASE_IMPORTANCE, TYPE_IMPORTANCE, TYPE_RETENTION_OVERRIDE, PROTECTED_TAGS, MIN_ACCESS_FOR_IMMUNITY, MIN_RETENTION_DAYS;
|
|
7507
8076
|
var init_retention = __esm({
|
|
7508
8077
|
"src/memory/retention.ts"() {
|
|
7509
8078
|
"use strict";
|
|
@@ -7531,7 +8100,12 @@ var init_retention = __esm({
|
|
|
7531
8100
|
"what-changed": "low",
|
|
7532
8101
|
"why-it-exists": "medium",
|
|
7533
8102
|
discovery: "low",
|
|
7534
|
-
"session-request": "low"
|
|
8103
|
+
"session-request": "low",
|
|
8104
|
+
probe: "low"
|
|
8105
|
+
};
|
|
8106
|
+
TYPE_RETENTION_OVERRIDE = {
|
|
8107
|
+
probe: 7
|
|
8108
|
+
// Operational heartbeats: expire after ~7 days regardless of source/valueCategory
|
|
7535
8109
|
};
|
|
7536
8110
|
PROTECTED_TAGS = /* @__PURE__ */ new Set(["keep", "important", "pinned", "critical"]);
|
|
7537
8111
|
MIN_ACCESS_FOR_IMMUNITY = 3;
|
|
@@ -7797,7 +8371,7 @@ ${narrative}`;
|
|
|
7797
8371
|
lines.push("");
|
|
7798
8372
|
}
|
|
7799
8373
|
if (gotchas.length > 0) {
|
|
7800
|
-
lines.push("##
|
|
8374
|
+
lines.push("## [WARN] Critical Gotchas");
|
|
7801
8375
|
lines.push("");
|
|
7802
8376
|
for (const g of gotchas) {
|
|
7803
8377
|
lines.push(`### ${g.title}`);
|
|
@@ -7809,7 +8383,7 @@ ${narrative}`;
|
|
|
7809
8383
|
}
|
|
7810
8384
|
}
|
|
7811
8385
|
if (decisions.length > 0) {
|
|
7812
|
-
lines.push("##
|
|
8386
|
+
lines.push("## [BUILD] Architecture Decisions");
|
|
7813
8387
|
lines.push("");
|
|
7814
8388
|
for (const d of decisions) {
|
|
7815
8389
|
lines.push(`### ${d.title}`);
|
|
@@ -7821,7 +8395,7 @@ ${narrative}`;
|
|
|
7821
8395
|
}
|
|
7822
8396
|
}
|
|
7823
8397
|
if (howItWorks.length > 0) {
|
|
7824
|
-
lines.push("##
|
|
8398
|
+
lines.push("## [DOCS] How It Works");
|
|
7825
8399
|
lines.push("");
|
|
7826
8400
|
for (const h of howItWorks) {
|
|
7827
8401
|
lines.push(`### ${h.title}`);
|
|
@@ -7830,7 +8404,7 @@ ${narrative}`;
|
|
|
7830
8404
|
}
|
|
7831
8405
|
}
|
|
7832
8406
|
if (problems.length > 0) {
|
|
7833
|
-
lines.push("##
|
|
8407
|
+
lines.push("## [TOOL] Common Problems & Solutions");
|
|
7834
8408
|
lines.push("");
|
|
7835
8409
|
for (const p of problems) {
|
|
7836
8410
|
lines.push(`### ${p.title}`);
|
|
@@ -7842,7 +8416,7 @@ ${narrative}`;
|
|
|
7842
8416
|
}
|
|
7843
8417
|
}
|
|
7844
8418
|
if (tradeoffs.length > 0) {
|
|
7845
|
-
lines.push("##
|
|
8419
|
+
lines.push("## [TRADEOFF] Trade-offs");
|
|
7846
8420
|
lines.push("");
|
|
7847
8421
|
for (const t of tradeoffs) {
|
|
7848
8422
|
lines.push(`### ${t.title}`);
|
|
@@ -7851,7 +8425,7 @@ ${narrative}`;
|
|
|
7851
8425
|
}
|
|
7852
8426
|
}
|
|
7853
8427
|
if (others.length > 0) {
|
|
7854
|
-
lines.push("##
|
|
8428
|
+
lines.push("## [PLAN] Notes");
|
|
7855
8429
|
lines.push("");
|
|
7856
8430
|
for (const o of others.slice(0, 5)) {
|
|
7857
8431
|
lines.push(`- **${o.title}**: ${o.narrative?.split("\n")[0] || ""}`);
|
|
@@ -7859,13 +8433,13 @@ ${narrative}`;
|
|
|
7859
8433
|
lines.push("");
|
|
7860
8434
|
}
|
|
7861
8435
|
if (allConcepts.length > 0) {
|
|
7862
|
-
lines.push("##
|
|
8436
|
+
lines.push("## [TAG] Related Concepts");
|
|
7863
8437
|
lines.push("");
|
|
7864
8438
|
lines.push(allConcepts.map((c) => `\`${c}\``).join(", "));
|
|
7865
8439
|
lines.push("");
|
|
7866
8440
|
}
|
|
7867
8441
|
if (allFacts.length > 0) {
|
|
7868
|
-
lines.push("##
|
|
8442
|
+
lines.push("## [PIN] Quick Facts");
|
|
7869
8443
|
lines.push("");
|
|
7870
8444
|
for (const f of allFacts.slice(0, 15)) {
|
|
7871
8445
|
lines.push(`- ${f}`);
|
|
@@ -9096,7 +9670,7 @@ async function exportAsMarkdown(projectDir2, projectId) {
|
|
|
9096
9670
|
if (Object.keys(data.stats.typeBreakdown).length > 0) {
|
|
9097
9671
|
lines.push("## Type Distribution");
|
|
9098
9672
|
for (const [type, count2] of Object.entries(data.stats.typeBreakdown).sort((a, b) => b[1] - a[1])) {
|
|
9099
|
-
const icon = OBSERVATION_ICONS2[type] ?? "
|
|
9673
|
+
const icon = OBSERVATION_ICONS2[type] ?? "[UNKNOWN]";
|
|
9100
9674
|
lines.push(`- ${icon} ${type}: ${count2}`);
|
|
9101
9675
|
}
|
|
9102
9676
|
lines.push("");
|
|
@@ -9104,7 +9678,7 @@ async function exportAsMarkdown(projectDir2, projectId) {
|
|
|
9104
9678
|
if (data.sessions.length > 0) {
|
|
9105
9679
|
lines.push("## Sessions");
|
|
9106
9680
|
for (const s of data.sessions) {
|
|
9107
|
-
const status = s.status === "active" ? "
|
|
9681
|
+
const status = s.status === "active" ? "[CHANGE]" : "[OK]";
|
|
9108
9682
|
const agent = s.agent ? ` [${s.agent}]` : "";
|
|
9109
9683
|
lines.push(`### ${status} ${s.id}${agent}`);
|
|
9110
9684
|
lines.push(`Started: ${s.startedAt}${s.endedAt ? ` | Ended: ${s.endedAt}` : ""}`);
|
|
@@ -9124,7 +9698,7 @@ async function exportAsMarkdown(projectDir2, projectId) {
|
|
|
9124
9698
|
for (const [entity, observations2] of byEntity) {
|
|
9125
9699
|
lines.push(`### ${entity}`);
|
|
9126
9700
|
for (const obs of observations2) {
|
|
9127
|
-
const icon = OBSERVATION_ICONS2[obs.type] ?? "
|
|
9701
|
+
const icon = OBSERVATION_ICONS2[obs.type] ?? "[UNKNOWN]";
|
|
9128
9702
|
lines.push(`#### ${icon} #${obs.id} ${obs.title}`);
|
|
9129
9703
|
lines.push(`Type: ${obs.type} | Created: ${obs.createdAt}${obs.topicKey ? ` | Topic: ${obs.topicKey}` : ""}${obs.revisionCount && obs.revisionCount > 1 ? ` | Rev: ${obs.revisionCount}` : ""}`);
|
|
9130
9704
|
lines.push("");
|
|
@@ -9190,15 +9764,15 @@ var init_export_import = __esm({
|
|
|
9190
9764
|
init_obs_store();
|
|
9191
9765
|
init_session_store();
|
|
9192
9766
|
OBSERVATION_ICONS2 = {
|
|
9193
|
-
"session-request": "
|
|
9194
|
-
"gotcha": "
|
|
9195
|
-
"problem-solution": "
|
|
9196
|
-
"how-it-works": "
|
|
9197
|
-
"what-changed": "
|
|
9198
|
-
"discovery": "
|
|
9199
|
-
"why-it-exists": "
|
|
9200
|
-
"decision": "
|
|
9201
|
-
"trade-off": "
|
|
9767
|
+
"session-request": "[SESSION]",
|
|
9768
|
+
"gotcha": "[GOTCHA]",
|
|
9769
|
+
"problem-solution": "[FIX]",
|
|
9770
|
+
"how-it-works": "[INFO]",
|
|
9771
|
+
"what-changed": "[CHANGE]",
|
|
9772
|
+
"discovery": "[DISCOVERY]",
|
|
9773
|
+
"why-it-exists": "[WHY]",
|
|
9774
|
+
"decision": "[DECISION]",
|
|
9775
|
+
"trade-off": "[TRADEOFF]"
|
|
9202
9776
|
};
|
|
9203
9777
|
}
|
|
9204
9778
|
});
|
|
@@ -9259,6 +9833,449 @@ var init_project_classification = __esm({
|
|
|
9259
9833
|
}
|
|
9260
9834
|
});
|
|
9261
9835
|
|
|
9836
|
+
// src/wiki/generator.ts
|
|
9837
|
+
var generator_exports = {};
|
|
9838
|
+
__export(generator_exports, {
|
|
9839
|
+
contextualHasSubstance: () => contextualHasSubstance,
|
|
9840
|
+
generateKnowledgeBase: () => generateKnowledgeBase,
|
|
9841
|
+
isCommandLog: () => isCommandLog,
|
|
9842
|
+
isEligible: () => isEligible,
|
|
9843
|
+
isExcludedType: () => isExcludedType
|
|
9844
|
+
});
|
|
9845
|
+
function isExcludedType(o) {
|
|
9846
|
+
return o.type === "probe";
|
|
9847
|
+
}
|
|
9848
|
+
function isCommandLog(o) {
|
|
9849
|
+
return COMMAND_LOG_TITLE3.test(o.title || "");
|
|
9850
|
+
}
|
|
9851
|
+
function isInactive(o) {
|
|
9852
|
+
const status = o.status ?? "active";
|
|
9853
|
+
return status !== "active";
|
|
9854
|
+
}
|
|
9855
|
+
function isOtherProject(o, projectId) {
|
|
9856
|
+
return o.projectId !== projectId;
|
|
9857
|
+
}
|
|
9858
|
+
function contextualHasSubstance(o) {
|
|
9859
|
+
if (o.valueCategory !== "contextual") return true;
|
|
9860
|
+
const hasFacts = (o.facts?.length ?? 0) > 0;
|
|
9861
|
+
const hasConcepts = (o.concepts?.length ?? 0) > 0;
|
|
9862
|
+
const hasFiles = (o.filesModified?.length ?? 0) > 0;
|
|
9863
|
+
const hasEntity = !!(o.entityName && o.entityName !== "quick-note" && o.entityName !== "unknown");
|
|
9864
|
+
return hasFacts || hasConcepts || hasFiles || hasEntity;
|
|
9865
|
+
}
|
|
9866
|
+
function isEligible(o, projectId) {
|
|
9867
|
+
if (isExcludedType(o)) return false;
|
|
9868
|
+
if (isCommandLog(o)) return false;
|
|
9869
|
+
if (isInactive(o)) return false;
|
|
9870
|
+
if (isOtherProject(o, projectId)) return false;
|
|
9871
|
+
if (o.valueCategory === "ephemeral") return false;
|
|
9872
|
+
if (!contextualHasSubstance(o)) return false;
|
|
9873
|
+
return true;
|
|
9874
|
+
}
|
|
9875
|
+
function obsToItem(o) {
|
|
9876
|
+
const ref = {
|
|
9877
|
+
kind: o.source === "git" ? "git" : "observation",
|
|
9878
|
+
id: `obs:${o.id}`,
|
|
9879
|
+
title: o.title
|
|
9880
|
+
};
|
|
9881
|
+
return {
|
|
9882
|
+
title: o.title,
|
|
9883
|
+
summary: o.narrative?.slice(0, 200) || "",
|
|
9884
|
+
type: o.type,
|
|
9885
|
+
entityName: o.entityName || void 0,
|
|
9886
|
+
refs: [ref]
|
|
9887
|
+
};
|
|
9888
|
+
}
|
|
9889
|
+
function skillToItem(s) {
|
|
9890
|
+
const ref = {
|
|
9891
|
+
kind: "mini-skill",
|
|
9892
|
+
id: `skill:${s.id}`,
|
|
9893
|
+
title: s.title
|
|
9894
|
+
};
|
|
9895
|
+
const obsRefs = s.sourceObservationIds.map(
|
|
9896
|
+
(oid) => ({ kind: "observation", id: `obs:${oid}` })
|
|
9897
|
+
);
|
|
9898
|
+
return {
|
|
9899
|
+
title: s.title,
|
|
9900
|
+
summary: s.instruction?.slice(0, 200) || "",
|
|
9901
|
+
type: "mini-skill",
|
|
9902
|
+
entityName: s.sourceEntity || void 0,
|
|
9903
|
+
refs: [ref, ...obsRefs]
|
|
9904
|
+
};
|
|
9905
|
+
}
|
|
9906
|
+
function buildGitSection(observations2) {
|
|
9907
|
+
const gitObs = observations2.filter(
|
|
9908
|
+
(o) => o.source === "git" && o.sourceDetail === "git-ingest"
|
|
9909
|
+
);
|
|
9910
|
+
const items = gitObs.map(obsToItem);
|
|
9911
|
+
return {
|
|
9912
|
+
id: "git-backed-facts",
|
|
9913
|
+
title: "Git-backed Facts",
|
|
9914
|
+
items,
|
|
9915
|
+
empty: items.length === 0
|
|
9916
|
+
};
|
|
9917
|
+
}
|
|
9918
|
+
function buildSkillsSection(skills) {
|
|
9919
|
+
const items = skills.map(skillToItem);
|
|
9920
|
+
return {
|
|
9921
|
+
id: "promoted-skills",
|
|
9922
|
+
title: "Promoted Skills",
|
|
9923
|
+
items,
|
|
9924
|
+
empty: items.length === 0
|
|
9925
|
+
};
|
|
9926
|
+
}
|
|
9927
|
+
function buildProjectOverview(projectId, eligibleObs, skills) {
|
|
9928
|
+
const refs = [
|
|
9929
|
+
...eligibleObs.slice(0, 5).map((o) => ({
|
|
9930
|
+
kind: o.source === "git" ? "git" : "observation",
|
|
9931
|
+
id: `obs:${o.id}`,
|
|
9932
|
+
title: o.title
|
|
9933
|
+
})),
|
|
9934
|
+
...skills.slice(0, 5).map((s) => ({
|
|
9935
|
+
kind: "mini-skill",
|
|
9936
|
+
id: `skill:${s.id}`,
|
|
9937
|
+
title: s.title
|
|
9938
|
+
}))
|
|
9939
|
+
];
|
|
9940
|
+
if (refs.length === 0) {
|
|
9941
|
+
return {
|
|
9942
|
+
id: "project-overview",
|
|
9943
|
+
title: "Project Overview",
|
|
9944
|
+
items: [],
|
|
9945
|
+
empty: true
|
|
9946
|
+
};
|
|
9947
|
+
}
|
|
9948
|
+
const lines = [];
|
|
9949
|
+
lines.push(`Project: ${projectId}`);
|
|
9950
|
+
lines.push(`Observations in KB: ${eligibleObs.length}`);
|
|
9951
|
+
lines.push(`Promoted skills: ${skills.length}`);
|
|
9952
|
+
const entityCounts = /* @__PURE__ */ new Map();
|
|
9953
|
+
for (const o of eligibleObs) {
|
|
9954
|
+
if (o.entityName) {
|
|
9955
|
+
entityCounts.set(o.entityName, (entityCounts.get(o.entityName) || 0) + 1);
|
|
9956
|
+
}
|
|
9957
|
+
}
|
|
9958
|
+
const topEntities = [...entityCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5);
|
|
9959
|
+
if (topEntities.length > 0) {
|
|
9960
|
+
lines.push(`Top entities: ${topEntities.map(([name, count2]) => `${name} (${count2})`).join(", ")}`);
|
|
9961
|
+
}
|
|
9962
|
+
return {
|
|
9963
|
+
id: "project-overview",
|
|
9964
|
+
title: "Project Overview",
|
|
9965
|
+
items: [{
|
|
9966
|
+
title: projectId,
|
|
9967
|
+
summary: lines.join("\n"),
|
|
9968
|
+
type: "overview",
|
|
9969
|
+
refs
|
|
9970
|
+
}]
|
|
9971
|
+
};
|
|
9972
|
+
}
|
|
9973
|
+
function generateKnowledgeBase(options) {
|
|
9974
|
+
const { projectId, observations: observations2, miniSkills } = options;
|
|
9975
|
+
const eligible = observations2.filter((o) => isEligible(o, projectId));
|
|
9976
|
+
const scopedMiniSkills = miniSkills.filter((s) => s.projectId === projectId);
|
|
9977
|
+
const typedSections = SECTION_DEFS.map((def) => {
|
|
9978
|
+
const matched = eligible.filter(def.typeMatch);
|
|
9979
|
+
const items = matched.map(obsToItem);
|
|
9980
|
+
return {
|
|
9981
|
+
id: def.id,
|
|
9982
|
+
title: def.title,
|
|
9983
|
+
items,
|
|
9984
|
+
empty: items.length === 0
|
|
9985
|
+
};
|
|
9986
|
+
});
|
|
9987
|
+
const projectOverview = buildProjectOverview(projectId, eligible, scopedMiniSkills);
|
|
9988
|
+
const gitSection = buildGitSection(eligible);
|
|
9989
|
+
const skillsSection = buildSkillsSection(scopedMiniSkills);
|
|
9990
|
+
const sections = [
|
|
9991
|
+
projectOverview,
|
|
9992
|
+
...typedSections,
|
|
9993
|
+
gitSection,
|
|
9994
|
+
skillsSection
|
|
9995
|
+
];
|
|
9996
|
+
const allRefs = sections.flatMap((s) => s.items.flatMap((i) => i.refs));
|
|
9997
|
+
const obsRefCount = allRefs.filter((r) => r.kind === "observation" || r.kind === "git").length;
|
|
9998
|
+
const skillRefCount = allRefs.filter((r) => r.kind === "mini-skill").length;
|
|
9999
|
+
return {
|
|
10000
|
+
title: "Knowledge Base",
|
|
10001
|
+
subtitle: "LLM Wiki",
|
|
10002
|
+
projectId,
|
|
10003
|
+
generatedAt: options.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
10004
|
+
sections,
|
|
10005
|
+
stats: {
|
|
10006
|
+
observationsUsed: eligible.length,
|
|
10007
|
+
miniSkillsUsed: scopedMiniSkills.length,
|
|
10008
|
+
refs: obsRefCount + skillRefCount
|
|
10009
|
+
}
|
|
10010
|
+
};
|
|
10011
|
+
}
|
|
10012
|
+
var COMMAND_LOG_TITLE3, SECTION_DEFS;
|
|
10013
|
+
var init_generator = __esm({
|
|
10014
|
+
"src/wiki/generator.ts"() {
|
|
10015
|
+
"use strict";
|
|
10016
|
+
init_esm_shims();
|
|
10017
|
+
COMMAND_LOG_TITLE3 = /^(Ran:|Command:|Executed:)\s/i;
|
|
10018
|
+
SECTION_DEFS = [
|
|
10019
|
+
{
|
|
10020
|
+
id: "core-decisions",
|
|
10021
|
+
title: "Core Decisions",
|
|
10022
|
+
typeMatch: (o) => o.type === "decision" || o.type === "trade-off" || o.type === "reasoning"
|
|
10023
|
+
},
|
|
10024
|
+
{
|
|
10025
|
+
id: "operational-knowledge",
|
|
10026
|
+
title: "Operational Knowledge",
|
|
10027
|
+
typeMatch: (o) => o.type === "how-it-works" || o.type === "what-changed" || o.type === "why-it-exists" || o.type === "discovery" || o.type === "session-request"
|
|
10028
|
+
},
|
|
10029
|
+
{
|
|
10030
|
+
id: "known-gotchas",
|
|
10031
|
+
title: "Known Gotchas",
|
|
10032
|
+
typeMatch: (o) => o.type === "gotcha" || o.type === "problem-solution"
|
|
10033
|
+
}
|
|
10034
|
+
];
|
|
10035
|
+
}
|
|
10036
|
+
});
|
|
10037
|
+
|
|
10038
|
+
// src/wiki/knowledge-graph.ts
|
|
10039
|
+
var knowledge_graph_exports = {};
|
|
10040
|
+
__export(knowledge_graph_exports, {
|
|
10041
|
+
generateKnowledgeGraph: () => generateKnowledgeGraph
|
|
10042
|
+
});
|
|
10043
|
+
function inferEdges(nodes, entityNameIndex) {
|
|
10044
|
+
const edges = [];
|
|
10045
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10046
|
+
const maxRelatesEdgesPerEntitySection = 8;
|
|
10047
|
+
function addEdge(source, target, edgeType) {
|
|
10048
|
+
const key = `${source}:${edgeType}:${target}`;
|
|
10049
|
+
if (seen.has(key)) return;
|
|
10050
|
+
if (source === target) return;
|
|
10051
|
+
seen.add(key);
|
|
10052
|
+
edges.push({
|
|
10053
|
+
id: `e_${edges.length}_${source}_${target}`,
|
|
10054
|
+
source,
|
|
10055
|
+
target,
|
|
10056
|
+
edgeType
|
|
10057
|
+
});
|
|
10058
|
+
}
|
|
10059
|
+
function rankNodesForEntity(a, b) {
|
|
10060
|
+
const evidenceDiff = (b.evidenceCount || 0) - (a.evidenceCount || 0);
|
|
10061
|
+
if (evidenceDiff !== 0) return evidenceDiff;
|
|
10062
|
+
return a.id.localeCompare(b.id);
|
|
10063
|
+
}
|
|
10064
|
+
for (const [, group] of entityNameIndex) {
|
|
10065
|
+
if (group.length < 2) continue;
|
|
10066
|
+
const bySection = /* @__PURE__ */ new Map();
|
|
10067
|
+
for (const node of group) {
|
|
10068
|
+
const sectionGroup = bySection.get(node.sectionId) || [];
|
|
10069
|
+
sectionGroup.push(node);
|
|
10070
|
+
bySection.set(node.sectionId, sectionGroup);
|
|
10071
|
+
}
|
|
10072
|
+
for (const sectionGroup of bySection.values()) {
|
|
10073
|
+
const ranked = [...sectionGroup].sort(rankNodesForEntity);
|
|
10074
|
+
if (ranked.length === 2) {
|
|
10075
|
+
addEdge(ranked[0].id, ranked[1].id, "relates_to");
|
|
10076
|
+
addEdge(ranked[1].id, ranked[0].id, "relates_to");
|
|
10077
|
+
continue;
|
|
10078
|
+
}
|
|
10079
|
+
const anchor = ranked[0];
|
|
10080
|
+
for (const node of ranked.slice(1, maxRelatesEdgesPerEntitySection + 1)) {
|
|
10081
|
+
addEdge(anchor.id, node.id, "relates_to");
|
|
10082
|
+
}
|
|
10083
|
+
}
|
|
10084
|
+
const sectionAnchors = [...bySection.entries()].map(([sectionId, sectionGroup]) => ({
|
|
10085
|
+
sectionId,
|
|
10086
|
+
anchor: [...sectionGroup].sort(rankNodesForEntity)[0]
|
|
10087
|
+
})).sort((a, b) => sectionPriority(a.sectionId) - sectionPriority(b.sectionId));
|
|
10088
|
+
for (let i = 0; i < sectionAnchors.length; i++) {
|
|
10089
|
+
for (let j = i + 1; j < sectionAnchors.length; j++) {
|
|
10090
|
+
const from = sectionAnchors[i].anchor;
|
|
10091
|
+
const to = sectionAnchors[j].anchor;
|
|
10092
|
+
const edgeType = sectionPriority(sectionAnchors[i].sectionId) === sectionPriority(sectionAnchors[j].sectionId) ? "relates_to" : "supports";
|
|
10093
|
+
addEdge(from.id, to.id, edgeType);
|
|
10094
|
+
}
|
|
10095
|
+
}
|
|
10096
|
+
}
|
|
10097
|
+
return edges;
|
|
10098
|
+
}
|
|
10099
|
+
function sectionPriority(sectionId) {
|
|
10100
|
+
const order = ["core-decisions", "known-gotchas", "operational-knowledge", "git-backed-facts", "promoted-skills"];
|
|
10101
|
+
const idx = order.indexOf(sectionId);
|
|
10102
|
+
return idx >= 0 ? idx : order.length;
|
|
10103
|
+
}
|
|
10104
|
+
function sectionIdForObs(o) {
|
|
10105
|
+
if (GIT_SECTION.typeMatch(o)) return GIT_SECTION.id;
|
|
10106
|
+
for (const def of SECTION_DEFS2) {
|
|
10107
|
+
if (def.typeMatch(o)) return def.id;
|
|
10108
|
+
}
|
|
10109
|
+
return "operational-knowledge";
|
|
10110
|
+
}
|
|
10111
|
+
function mapRelationType(relationType) {
|
|
10112
|
+
const lower = relationType.toLowerCase();
|
|
10113
|
+
if (lower.includes("support") || lower.includes("depend")) return "supports";
|
|
10114
|
+
if (lower.includes("relat") || lower.includes("connect") || lower.includes("associat")) return "relates_to";
|
|
10115
|
+
if (lower.includes("mention") || lower.includes("refer")) return "mentions";
|
|
10116
|
+
if (lower.includes("deriv") || lower.includes("origin") || lower.includes("source")) return "derived_from";
|
|
10117
|
+
return "relates_to";
|
|
10118
|
+
}
|
|
10119
|
+
function generateKnowledgeGraph(options) {
|
|
10120
|
+
const { projectId, observations: observations2, miniSkills, graphEntities, graphRelations } = options;
|
|
10121
|
+
const eligible = observations2.filter((o) => isEligible(o, projectId));
|
|
10122
|
+
const scopedMiniSkills = miniSkills.filter((s) => s.projectId === projectId);
|
|
10123
|
+
const nodes = [];
|
|
10124
|
+
const entityNameIndex = /* @__PURE__ */ new Map();
|
|
10125
|
+
for (const o of eligible) {
|
|
10126
|
+
const sectionId = sectionIdForObs(o);
|
|
10127
|
+
const ref = {
|
|
10128
|
+
kind: o.source === "git" ? "git" : "observation",
|
|
10129
|
+
id: `obs:${o.id}`,
|
|
10130
|
+
title: o.title
|
|
10131
|
+
};
|
|
10132
|
+
const node = {
|
|
10133
|
+
id: `obs:${o.id}`,
|
|
10134
|
+
label: o.title,
|
|
10135
|
+
nodeType: o.type,
|
|
10136
|
+
sectionId,
|
|
10137
|
+
entityName: o.entityName || void 0,
|
|
10138
|
+
evidenceCount: (o.facts?.length ?? 0) + (o.concepts?.length ?? 0) + (o.filesModified?.length ?? 0),
|
|
10139
|
+
summary: o.narrative?.slice(0, 200) || "",
|
|
10140
|
+
refs: [ref]
|
|
10141
|
+
};
|
|
10142
|
+
nodes.push(node);
|
|
10143
|
+
if (o.entityName) {
|
|
10144
|
+
const group = entityNameIndex.get(o.entityName) || [];
|
|
10145
|
+
group.push(node);
|
|
10146
|
+
entityNameIndex.set(o.entityName, group);
|
|
10147
|
+
}
|
|
10148
|
+
}
|
|
10149
|
+
for (const s of scopedMiniSkills) {
|
|
10150
|
+
const ref = {
|
|
10151
|
+
kind: "mini-skill",
|
|
10152
|
+
id: `skill:${s.id}`,
|
|
10153
|
+
title: s.title
|
|
10154
|
+
};
|
|
10155
|
+
const obsRefs = s.sourceObservationIds.map(
|
|
10156
|
+
(oid) => ({ kind: "observation", id: `obs:${oid}` })
|
|
10157
|
+
);
|
|
10158
|
+
const node = {
|
|
10159
|
+
id: `skill:${s.id}`,
|
|
10160
|
+
label: s.title,
|
|
10161
|
+
nodeType: "mini-skill",
|
|
10162
|
+
sectionId: "promoted-skills",
|
|
10163
|
+
entityName: s.sourceEntity || void 0,
|
|
10164
|
+
evidenceCount: obsRefs.length,
|
|
10165
|
+
summary: s.instruction?.slice(0, 200) || "",
|
|
10166
|
+
refs: [ref, ...obsRefs]
|
|
10167
|
+
};
|
|
10168
|
+
nodes.push(node);
|
|
10169
|
+
if (s.sourceEntity) {
|
|
10170
|
+
const group = entityNameIndex.get(s.sourceEntity) || [];
|
|
10171
|
+
group.push(node);
|
|
10172
|
+
entityNameIndex.set(s.sourceEntity, group);
|
|
10173
|
+
}
|
|
10174
|
+
}
|
|
10175
|
+
const edges = inferEdges(nodes, entityNameIndex);
|
|
10176
|
+
for (const s of scopedMiniSkills) {
|
|
10177
|
+
const skillNodeId = `skill:${s.id}`;
|
|
10178
|
+
for (const oid of s.sourceObservationIds) {
|
|
10179
|
+
const obsNodeId = `obs:${oid}`;
|
|
10180
|
+
if (nodes.some((n) => n.id === obsNodeId)) {
|
|
10181
|
+
edges.push({
|
|
10182
|
+
id: `e_${edges.length}_${skillNodeId}_${obsNodeId}`,
|
|
10183
|
+
source: skillNodeId,
|
|
10184
|
+
target: obsNodeId,
|
|
10185
|
+
edgeType: "derived_from"
|
|
10186
|
+
});
|
|
10187
|
+
}
|
|
10188
|
+
}
|
|
10189
|
+
}
|
|
10190
|
+
if (graphRelations && graphRelations.length > 0) {
|
|
10191
|
+
const entityNodeMap = /* @__PURE__ */ new Map();
|
|
10192
|
+
for (const n of nodes) {
|
|
10193
|
+
if (n.entityName && !entityNodeMap.has(n.entityName)) {
|
|
10194
|
+
entityNodeMap.set(n.entityName, n.id);
|
|
10195
|
+
}
|
|
10196
|
+
}
|
|
10197
|
+
const seen = new Set(edges.map((e) => `${e.source}:${e.edgeType}:${e.target}`));
|
|
10198
|
+
for (const rel of graphRelations) {
|
|
10199
|
+
const srcId = entityNodeMap.get(rel.from);
|
|
10200
|
+
const tgtId = entityNodeMap.get(rel.to);
|
|
10201
|
+
if (!srcId || !tgtId) continue;
|
|
10202
|
+
if (srcId === tgtId) continue;
|
|
10203
|
+
const edgeType = mapRelationType(rel.relationType);
|
|
10204
|
+
const key = `${srcId}:${edgeType}:${tgtId}`;
|
|
10205
|
+
if (seen.has(key)) continue;
|
|
10206
|
+
seen.add(key);
|
|
10207
|
+
edges.push({
|
|
10208
|
+
id: `e_gr_${edges.length}_${srcId}_${tgtId}`,
|
|
10209
|
+
source: srcId,
|
|
10210
|
+
target: tgtId,
|
|
10211
|
+
edgeType
|
|
10212
|
+
});
|
|
10213
|
+
}
|
|
10214
|
+
}
|
|
10215
|
+
const sectionCounts = {};
|
|
10216
|
+
for (const n of nodes) {
|
|
10217
|
+
sectionCounts[n.sectionId] = (sectionCounts[n.sectionId] || 0) + 1;
|
|
10218
|
+
}
|
|
10219
|
+
const clusters = ALL_SECTIONS.filter((def) => (sectionCounts[def.id] ?? 0) > 0).map((def) => ({
|
|
10220
|
+
id: `cluster:${def.id}`,
|
|
10221
|
+
label: def.label,
|
|
10222
|
+
sectionId: def.id,
|
|
10223
|
+
nodeCount: sectionCounts[def.id] ?? 0
|
|
10224
|
+
}));
|
|
10225
|
+
const stats = {
|
|
10226
|
+
totalNodes: nodes.length,
|
|
10227
|
+
totalEdges: edges.length,
|
|
10228
|
+
clusterCount: clusters.length,
|
|
10229
|
+
sectionCounts
|
|
10230
|
+
};
|
|
10231
|
+
return {
|
|
10232
|
+
title: "Knowledge Graph",
|
|
10233
|
+
projectId,
|
|
10234
|
+
generatedAt: options.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
10235
|
+
nodes,
|
|
10236
|
+
edges,
|
|
10237
|
+
clusters,
|
|
10238
|
+
stats
|
|
10239
|
+
};
|
|
10240
|
+
}
|
|
10241
|
+
var SECTION_DEFS2, GIT_SECTION, SKILLS_SECTION, ALL_SECTIONS;
|
|
10242
|
+
var init_knowledge_graph = __esm({
|
|
10243
|
+
"src/wiki/knowledge-graph.ts"() {
|
|
10244
|
+
"use strict";
|
|
10245
|
+
init_esm_shims();
|
|
10246
|
+
init_generator();
|
|
10247
|
+
SECTION_DEFS2 = [
|
|
10248
|
+
{
|
|
10249
|
+
id: "core-decisions",
|
|
10250
|
+
label: "Core Decisions",
|
|
10251
|
+
typeMatch: (o) => o.type === "decision" || o.type === "trade-off" || o.type === "reasoning"
|
|
10252
|
+
},
|
|
10253
|
+
{
|
|
10254
|
+
id: "operational-knowledge",
|
|
10255
|
+
label: "Operational Knowledge",
|
|
10256
|
+
typeMatch: (o) => o.type === "how-it-works" || o.type === "what-changed" || o.type === "why-it-exists" || o.type === "discovery" || o.type === "session-request"
|
|
10257
|
+
},
|
|
10258
|
+
{
|
|
10259
|
+
id: "known-gotchas",
|
|
10260
|
+
label: "Known Gotchas",
|
|
10261
|
+
typeMatch: (o) => o.type === "gotcha" || o.type === "problem-solution"
|
|
10262
|
+
}
|
|
10263
|
+
];
|
|
10264
|
+
GIT_SECTION = {
|
|
10265
|
+
id: "git-backed-facts",
|
|
10266
|
+
label: "Git-backed Facts",
|
|
10267
|
+
typeMatch: (o) => o.source === "git" && o.sourceDetail === "git-ingest"
|
|
10268
|
+
};
|
|
10269
|
+
SKILLS_SECTION = {
|
|
10270
|
+
id: "promoted-skills",
|
|
10271
|
+
label: "Promoted Skills",
|
|
10272
|
+
typeMatch: () => false
|
|
10273
|
+
// skills handled separately
|
|
10274
|
+
};
|
|
10275
|
+
ALL_SECTIONS = [...SECTION_DEFS2, GIT_SECTION, SKILLS_SECTION];
|
|
10276
|
+
}
|
|
10277
|
+
});
|
|
10278
|
+
|
|
9262
10279
|
// src/dashboard/server.ts
|
|
9263
10280
|
var server_exports = {};
|
|
9264
10281
|
__export(server_exports, {
|
|
@@ -9281,6 +10298,12 @@ function sendError(res, message, status = 500) {
|
|
|
9281
10298
|
function filterByProject(items, projectId) {
|
|
9282
10299
|
return items.filter((item) => item.projectId === projectId);
|
|
9283
10300
|
}
|
|
10301
|
+
function isActiveStatus(status) {
|
|
10302
|
+
return (status ?? "active") === "active";
|
|
10303
|
+
}
|
|
10304
|
+
function filterActiveByProject(items, projectId) {
|
|
10305
|
+
return items.filter((item) => item.projectId === projectId && isActiveStatus(item.status));
|
|
10306
|
+
}
|
|
9284
10307
|
function computeProjectGraphCounts(allEntities, allRelations, projectObs) {
|
|
9285
10308
|
const entityNames = new Set(
|
|
9286
10309
|
projectObs.filter((o) => (o.status ?? "active") === "active" && o.entityName).map((o) => o.entityName)
|
|
@@ -9313,6 +10336,7 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
|
|
|
9313
10336
|
const allObs = await getObservationStore().loadAll();
|
|
9314
10337
|
const projectSet = /* @__PURE__ */ new Map();
|
|
9315
10338
|
for (const obs of allObs) {
|
|
10339
|
+
if (!isActiveStatus(obs.status)) continue;
|
|
9316
10340
|
if (obs.projectId) {
|
|
9317
10341
|
projectSet.set(obs.projectId, (projectSet.get(obs.projectId) || 0) + 1);
|
|
9318
10342
|
}
|
|
@@ -9370,7 +10394,7 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
|
|
|
9370
10394
|
}
|
|
9371
10395
|
case "/observations": {
|
|
9372
10396
|
const allObs = await getObservationStore().loadAll();
|
|
9373
|
-
const observations2 =
|
|
10397
|
+
const observations2 = filterActiveByProject(allObs, effectiveProjectId);
|
|
9374
10398
|
sendJson(res, observations2);
|
|
9375
10399
|
break;
|
|
9376
10400
|
}
|
|
@@ -9384,12 +10408,16 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
|
|
|
9384
10408
|
await initGraphStore(effectiveDataDir);
|
|
9385
10409
|
const graph = { entities: getGraphStore().loadEntities(), relations: getGraphStore().loadRelations() };
|
|
9386
10410
|
const allObs = await getObservationStore().loadAll();
|
|
9387
|
-
const observations2 =
|
|
10411
|
+
const observations2 = filterActiveByProject(
|
|
10412
|
+
allObs,
|
|
10413
|
+
effectiveProjectId
|
|
10414
|
+
);
|
|
9388
10415
|
const nextId2 = await getObservationStore().loadIdCounter();
|
|
9389
10416
|
const projectGraphCounts = computeProjectGraphCounts(graph.entities, graph.relations, observations2);
|
|
9390
10417
|
const typeCounts = {};
|
|
9391
10418
|
for (const obs of observations2) {
|
|
9392
10419
|
const t = obs.type || "unknown";
|
|
10420
|
+
if (t === "probe") continue;
|
|
9393
10421
|
typeCounts[t] = (typeCounts[t] || 0) + 1;
|
|
9394
10422
|
}
|
|
9395
10423
|
const sourceCounts = { git: 0, agent: 0, manual: 0 };
|
|
@@ -9431,7 +10459,7 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
|
|
|
9431
10459
|
else if (score >= 1) retentionSummary.stale++;
|
|
9432
10460
|
else retentionSummary.archive++;
|
|
9433
10461
|
}
|
|
9434
|
-
const sorted = [...observations2].sort((a, b) => (b.id || 0) - (a.id || 0)).slice(0, 10);
|
|
10462
|
+
const sorted = [...observations2].filter((o) => o.type !== "probe").sort((a, b) => (b.id || 0) - (a.id || 0)).slice(0, 10);
|
|
9435
10463
|
let embeddingStatus = { enabled: false, provider: "", dimensions: 0 };
|
|
9436
10464
|
try {
|
|
9437
10465
|
const { getEmbeddingProvider: getEmbeddingProvider2 } = await Promise.resolve().then(() => (init_provider(), provider_exports));
|
|
@@ -9469,9 +10497,9 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
|
|
|
9469
10497
|
}
|
|
9470
10498
|
case "/retention": {
|
|
9471
10499
|
const allObs = await getObservationStore().loadAll();
|
|
9472
|
-
const observations2 =
|
|
10500
|
+
const observations2 = filterActiveByProject(allObs, effectiveProjectId);
|
|
9473
10501
|
const now = Date.now();
|
|
9474
|
-
const scored = observations2.map((obs) => {
|
|
10502
|
+
const scored = observations2.filter((obs) => obs.type !== "probe").map((obs) => {
|
|
9475
10503
|
const age = now - new Date(obs.createdAt || now).getTime();
|
|
9476
10504
|
const ageHours = age / (1e3 * 60 * 60);
|
|
9477
10505
|
const importance = obs.importance ?? 5;
|
|
@@ -9503,6 +10531,50 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
|
|
|
9503
10531
|
});
|
|
9504
10532
|
break;
|
|
9505
10533
|
}
|
|
10534
|
+
case "/knowledge": {
|
|
10535
|
+
const { generateKnowledgeBase: generateKnowledgeBase2 } = await Promise.resolve().then(() => (init_generator(), generator_exports));
|
|
10536
|
+
const { initObservations: initObservations2, getAllObservations: getAllObservations2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
|
|
10537
|
+
const { initMiniSkillStore: initMiniSkillStore2, getMiniSkillStore: getMiniSkillStore2 } = await Promise.resolve().then(() => (init_mini_skill_store(), mini_skill_store_exports));
|
|
10538
|
+
await initObservations2(effectiveDataDir);
|
|
10539
|
+
await initMiniSkillStore2(effectiveDataDir);
|
|
10540
|
+
const allObs = getAllObservations2();
|
|
10541
|
+
const skills = await getMiniSkillStore2().loadByProject(effectiveProjectId);
|
|
10542
|
+
const overview = generateKnowledgeBase2({
|
|
10543
|
+
projectId: effectiveProjectId,
|
|
10544
|
+
observations: allObs,
|
|
10545
|
+
miniSkills: skills
|
|
10546
|
+
});
|
|
10547
|
+
sendJson(res, overview);
|
|
10548
|
+
break;
|
|
10549
|
+
}
|
|
10550
|
+
case "/knowledge-graph": {
|
|
10551
|
+
const { generateKnowledgeGraph: generateKnowledgeGraph2 } = await Promise.resolve().then(() => (init_knowledge_graph(), knowledge_graph_exports));
|
|
10552
|
+
const { initObservations: initObservations2, getAllObservations: getAllObservations2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
|
|
10553
|
+
const { initMiniSkillStore: initMiniSkillStore2, getMiniSkillStore: getMiniSkillStore2 } = await Promise.resolve().then(() => (init_mini_skill_store(), mini_skill_store_exports));
|
|
10554
|
+
const { initGraphStore: initGraphStore2, getGraphStore: getGraphStore2 } = await Promise.resolve().then(() => (init_graph_store(), graph_store_exports));
|
|
10555
|
+
await initObservations2(effectiveDataDir);
|
|
10556
|
+
await initMiniSkillStore2(effectiveDataDir);
|
|
10557
|
+
await initGraphStore2(effectiveDataDir);
|
|
10558
|
+
const allObs = getAllObservations2();
|
|
10559
|
+
const skills = await getMiniSkillStore2().loadByProject(effectiveProjectId);
|
|
10560
|
+
const fullGraph = { entities: getGraphStore2().loadEntities(), relations: getGraphStore2().loadRelations() };
|
|
10561
|
+
const graphObs = await getObservationStore().loadAll();
|
|
10562
|
+
const projectEntityNames = new Set(
|
|
10563
|
+
graphObs.filter((o) => o.projectId === effectiveProjectId && (o.status ?? "active") === "active" && o.entityName).map((o) => o.entityName)
|
|
10564
|
+
);
|
|
10565
|
+
const scopedEntities = fullGraph.entities.filter((e) => projectEntityNames.has(e.name));
|
|
10566
|
+
const scopedEntityNameSet = new Set(scopedEntities.map((e) => e.name));
|
|
10567
|
+
const scopedRelations = fullGraph.relations.filter((r) => scopedEntityNameSet.has(r.from) && scopedEntityNameSet.has(r.to));
|
|
10568
|
+
const graph = generateKnowledgeGraph2({
|
|
10569
|
+
projectId: effectiveProjectId,
|
|
10570
|
+
observations: allObs,
|
|
10571
|
+
miniSkills: skills,
|
|
10572
|
+
graphEntities: scopedEntities,
|
|
10573
|
+
graphRelations: scopedRelations
|
|
10574
|
+
});
|
|
10575
|
+
sendJson(res, graph);
|
|
10576
|
+
break;
|
|
10577
|
+
}
|
|
9506
10578
|
case "/config": {
|
|
9507
10579
|
const os4 = await import("os");
|
|
9508
10580
|
const { existsSync: existsSync10 } = await import("fs");
|
|
@@ -9694,7 +10766,7 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
|
|
|
9694
10766
|
await initGraphStore(effectiveDataDir);
|
|
9695
10767
|
const fullGraph = { entities: getGraphStore().loadEntities(), relations: getGraphStore().loadRelations() };
|
|
9696
10768
|
const allObs = await getObservationStore().loadAll();
|
|
9697
|
-
const observations2 =
|
|
10769
|
+
const observations2 = filterActiveByProject(allObs, effectiveProjectId);
|
|
9698
10770
|
const nextId2 = await getObservationStore().loadIdCounter();
|
|
9699
10771
|
const exportEntityNames = new Set(
|
|
9700
10772
|
observations2.filter((o) => (o.status ?? "active") === "active" && o.entityName).map((o) => o.entityName)
|
|
@@ -9739,13 +10811,20 @@ async function serveStatic(req, res, staticDir) {
|
|
|
9739
10811
|
const ext = path14.extname(filePath);
|
|
9740
10812
|
res.writeHead(200, {
|
|
9741
10813
|
"Content-Type": MIME_TYPES[ext] || "application/octet-stream",
|
|
9742
|
-
"Cache-Control": "no-cache"
|
|
10814
|
+
"Cache-Control": "no-store, no-cache, must-revalidate, max-age=0",
|
|
10815
|
+
"Pragma": "no-cache",
|
|
10816
|
+
"Expires": "0"
|
|
9743
10817
|
});
|
|
9744
10818
|
res.end(data);
|
|
9745
10819
|
} catch {
|
|
9746
10820
|
try {
|
|
9747
10821
|
const indexData = await fs12.readFile(path14.join(staticDir, "index.html"));
|
|
9748
|
-
res.writeHead(200, {
|
|
10822
|
+
res.writeHead(200, {
|
|
10823
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
10824
|
+
"Cache-Control": "no-store, no-cache, must-revalidate, max-age=0",
|
|
10825
|
+
"Pragma": "no-cache",
|
|
10826
|
+
"Expires": "0"
|
|
10827
|
+
});
|
|
9749
10828
|
res.end(indexData);
|
|
9750
10829
|
} catch {
|
|
9751
10830
|
sendError(res, "Not found", 404);
|
|
@@ -9757,6 +10836,131 @@ function openBrowser(url) {
|
|
|
9757
10836
|
exec(cmd, () => {
|
|
9758
10837
|
});
|
|
9759
10838
|
}
|
|
10839
|
+
function parseJsonField(value, fallback) {
|
|
10840
|
+
if (typeof value !== "string") return value ?? fallback;
|
|
10841
|
+
try {
|
|
10842
|
+
return JSON.parse(value || JSON.stringify(fallback));
|
|
10843
|
+
} catch {
|
|
10844
|
+
return fallback;
|
|
10845
|
+
}
|
|
10846
|
+
}
|
|
10847
|
+
function normalizeDashboardAgent(teamStore, projectId, agent) {
|
|
10848
|
+
const id = agent.agent_id ?? agent.id ?? "";
|
|
10849
|
+
const agentProjectId = agent.project_id ?? agent.projectId ?? projectId;
|
|
10850
|
+
return {
|
|
10851
|
+
id,
|
|
10852
|
+
projectId: agentProjectId,
|
|
10853
|
+
instanceId: agent.instance_id ?? agent.instanceId,
|
|
10854
|
+
agentType: agent.agent_type ?? agent.agentType,
|
|
10855
|
+
name: agent.name,
|
|
10856
|
+
role: agent.role,
|
|
10857
|
+
capabilities: parseJsonField(agent.capabilities, []),
|
|
10858
|
+
status: agent.status,
|
|
10859
|
+
joinedAt: agent.joined_at ?? agent.joinedAt,
|
|
10860
|
+
lastSeenAt: agent.last_heartbeat ?? agent.last_seen_at ?? agent.lastSeenAt,
|
|
10861
|
+
leftAt: agent.left_at ?? agent.leftAt,
|
|
10862
|
+
unread: id ? teamStore.getUnreadCount(agentProjectId, id) : 0,
|
|
10863
|
+
source: agent.source || "sqlite"
|
|
10864
|
+
};
|
|
10865
|
+
}
|
|
10866
|
+
function normalizeDashboardLock(lock) {
|
|
10867
|
+
return {
|
|
10868
|
+
file: lock.file,
|
|
10869
|
+
projectId: lock.project_id ?? lock.projectId,
|
|
10870
|
+
lockedBy: lock.locked_by ?? lock.lockedBy,
|
|
10871
|
+
lockedAt: lock.locked_at ?? lock.lockedAt,
|
|
10872
|
+
expiresAt: lock.expires_at ?? lock.expiresAt
|
|
10873
|
+
};
|
|
10874
|
+
}
|
|
10875
|
+
function normalizeDashboardTask(task) {
|
|
10876
|
+
return {
|
|
10877
|
+
id: task.task_id ?? task.id,
|
|
10878
|
+
projectId: task.project_id ?? task.projectId,
|
|
10879
|
+
description: task.description,
|
|
10880
|
+
status: task.status,
|
|
10881
|
+
assignee: task.assignee_agent_id ?? task.assignee,
|
|
10882
|
+
result: task.result,
|
|
10883
|
+
metadata: parseJsonField(task.metadata, null),
|
|
10884
|
+
createdBy: task.created_by ?? task.createdBy,
|
|
10885
|
+
createdAt: task.created_at ?? task.createdAt,
|
|
10886
|
+
updatedAt: task.updated_at ?? task.updatedAt,
|
|
10887
|
+
deps: task.deps || [],
|
|
10888
|
+
requiredRole: task.required_role ?? task.requiredRole ?? null,
|
|
10889
|
+
preferredRole: task.preferred_role ?? task.preferredRole ?? null
|
|
10890
|
+
};
|
|
10891
|
+
}
|
|
10892
|
+
async function buildTeamSnapshot(dataDir, projectId, scope, mode) {
|
|
10893
|
+
try {
|
|
10894
|
+
const { initTeamStore: initTeamStore2 } = await Promise.resolve().then(() => (init_team_store(), team_store_exports));
|
|
10895
|
+
const teamStore = await initTeamStore2(dataDir);
|
|
10896
|
+
const effectiveProjectId = scope === "global" ? void 0 : projectId;
|
|
10897
|
+
const rawAgents = effectiveProjectId ? teamStore.listAgents(effectiveProjectId) : teamStore.listAllAgents();
|
|
10898
|
+
const rawLocks = effectiveProjectId ? teamStore.listLocks(effectiveProjectId) : teamStore.listAllLocks();
|
|
10899
|
+
const rawTasks = effectiveProjectId ? teamStore.listTasks(effectiveProjectId) : teamStore.listAllTasks();
|
|
10900
|
+
const available = effectiveProjectId ? teamStore.listTasks(effectiveProjectId, { available: true }) : teamStore.listAllTasks({ available: true });
|
|
10901
|
+
const agents = rawAgents.map((agent) => normalizeDashboardAgent(teamStore, projectId, agent));
|
|
10902
|
+
const locks = rawLocks.map(normalizeDashboardLock);
|
|
10903
|
+
const tasks = rawTasks.map(normalizeDashboardTask);
|
|
10904
|
+
const recentWindowMs = 7 * 24 * 60 * 60 * 1e3;
|
|
10905
|
+
const now = Date.now();
|
|
10906
|
+
const withTier = agents.map((agent) => {
|
|
10907
|
+
if (agent.status === "active") return { ...agent, activityTier: "active" };
|
|
10908
|
+
const seen = Date.parse(agent.lastSeenAt ?? "") || 0;
|
|
10909
|
+
return { ...agent, activityTier: now - seen <= recentWindowMs ? "recent" : "historical" };
|
|
10910
|
+
});
|
|
10911
|
+
const activeCount = withTier.filter((agent) => agent.activityTier === "active").length;
|
|
10912
|
+
const recentCount = withTier.filter((agent) => agent.activityTier === "recent").length;
|
|
10913
|
+
const historicalCount = withTier.filter((agent) => agent.activityTier === "historical").length;
|
|
10914
|
+
const roles = effectiveProjectId ? teamStore.listRoles(effectiveProjectId) : [];
|
|
10915
|
+
const roleOccupancy = effectiveProjectId ? teamStore.getRoleOccupancy(effectiveProjectId) : [];
|
|
10916
|
+
const handoffs = effectiveProjectId ? teamStore.listHandoffs(effectiveProjectId) : [];
|
|
10917
|
+
return {
|
|
10918
|
+
mode,
|
|
10919
|
+
readOnly: mode === "standalone",
|
|
10920
|
+
scope,
|
|
10921
|
+
agents: withTier,
|
|
10922
|
+
activeCount,
|
|
10923
|
+
recentCount,
|
|
10924
|
+
historicalCount,
|
|
10925
|
+
totalAgents: withTier.length,
|
|
10926
|
+
recentWindowDays: 7,
|
|
10927
|
+
locks,
|
|
10928
|
+
tasks,
|
|
10929
|
+
availableTasks: available.length,
|
|
10930
|
+
sessions: 0,
|
|
10931
|
+
roles,
|
|
10932
|
+
roleOccupancy,
|
|
10933
|
+
handoffs,
|
|
10934
|
+
openTasks: tasks.filter((task) => task.status === "pending" || task.status === "in_progress").length,
|
|
10935
|
+
openHandoffs: handoffs.filter((handoff) => handoff.handoff_status === "open" || handoff.handoffStatus === "open").length,
|
|
10936
|
+
totalUnread: withTier.reduce((sum, agent) => sum + (agent.unread || 0), 0),
|
|
10937
|
+
activeSessions: activeCount
|
|
10938
|
+
};
|
|
10939
|
+
} catch {
|
|
10940
|
+
return {
|
|
10941
|
+
mode,
|
|
10942
|
+
readOnly: mode === "standalone",
|
|
10943
|
+
scope,
|
|
10944
|
+
agents: [],
|
|
10945
|
+
activeCount: 0,
|
|
10946
|
+
recentCount: 0,
|
|
10947
|
+
historicalCount: 0,
|
|
10948
|
+
totalAgents: 0,
|
|
10949
|
+
recentWindowDays: 7,
|
|
10950
|
+
locks: [],
|
|
10951
|
+
tasks: [],
|
|
10952
|
+
availableTasks: 0,
|
|
10953
|
+
sessions: 0,
|
|
10954
|
+
roles: [],
|
|
10955
|
+
roleOccupancy: [],
|
|
10956
|
+
handoffs: [],
|
|
10957
|
+
openTasks: 0,
|
|
10958
|
+
openHandoffs: 0,
|
|
10959
|
+
totalUnread: 0,
|
|
10960
|
+
activeSessions: 0
|
|
10961
|
+
};
|
|
10962
|
+
}
|
|
10963
|
+
}
|
|
9760
10964
|
function readBody(req) {
|
|
9761
10965
|
return new Promise((resolve, reject) => {
|
|
9762
10966
|
const chunks = [];
|
|
@@ -9795,7 +10999,9 @@ async function startDashboard(dataDir, port, staticDir, projectId, projectName,
|
|
|
9795
10999
|
}
|
|
9796
11000
|
if (url.startsWith("/api/team")) {
|
|
9797
11001
|
if (!teamInstances) {
|
|
9798
|
-
|
|
11002
|
+
const parsedUrl = new URL(url, `http://127.0.0.1:${port}`);
|
|
11003
|
+
const scope = parsedUrl.searchParams.get("scope") || "project";
|
|
11004
|
+
sendJson(res, await buildTeamSnapshot(state.dataDir, state.projectId, scope, state.mode));
|
|
9799
11005
|
return;
|
|
9800
11006
|
}
|
|
9801
11007
|
try {
|
|
@@ -10583,7 +11789,7 @@ function buildReviewIterationHint(iteration, maxIterations, budgetRemaining) {
|
|
|
10583
11789
|
if (iteration >= maxIterations) {
|
|
10584
11790
|
return `
|
|
10585
11791
|
|
|
10586
|
-
|
|
11792
|
+
[WARN] This is the FINAL review iteration (${iteration}/${maxIterations}). Do NOT create more tasks. Summarize remaining issues and exit.`;
|
|
10587
11793
|
}
|
|
10588
11794
|
return `
|
|
10589
11795
|
|
|
@@ -11089,6 +12295,8 @@ import { spawnSync } from 'node:child_process';
|
|
|
11089
12295
|
export const MemorixPlugin = async ({ project, client, $, directory, worktree }) => {
|
|
11090
12296
|
// Generate a stable session ID for this plugin lifetime
|
|
11091
12297
|
const sessionId = \`opencode-\${Date.now().toString(36)}-\${Math.random().toString(36).slice(2, 8)}\`;
|
|
12298
|
+
let pendingAssistantResponse = null;
|
|
12299
|
+
let lastDeliveredAssistantKey = '';
|
|
11092
12300
|
|
|
11093
12301
|
/**
|
|
11094
12302
|
* Send event JSON to \`memorix hook\` via child_process.spawnSync.
|
|
@@ -11121,6 +12329,33 @@ export const MemorixPlugin = async ({ project, client, $, directory, worktree })
|
|
|
11121
12329
|
}
|
|
11122
12330
|
}
|
|
11123
12331
|
|
|
12332
|
+
function extractMessageInfo(input) {
|
|
12333
|
+
if (input && typeof input === 'object') {
|
|
12334
|
+
if (input.info && typeof input.info === 'object') return input.info;
|
|
12335
|
+
if (input.message && typeof input.message === 'object') return input.message;
|
|
12336
|
+
if (input.properties && typeof input.properties === 'object') {
|
|
12337
|
+
if (input.properties.info && typeof input.properties.info === 'object') return input.properties.info;
|
|
12338
|
+
if (input.properties.message && typeof input.properties.message === 'object') return input.properties.message;
|
|
12339
|
+
}
|
|
12340
|
+
}
|
|
12341
|
+
return input;
|
|
12342
|
+
}
|
|
12343
|
+
|
|
12344
|
+
function extractMessageText(message) {
|
|
12345
|
+
if (!message || typeof message !== 'object') return '';
|
|
12346
|
+
if (typeof message.content === 'string') return message.content.trim();
|
|
12347
|
+
const parts = Array.isArray(message.parts) ? message.parts : [];
|
|
12348
|
+
return parts
|
|
12349
|
+
.map((part) => {
|
|
12350
|
+
if (!part || typeof part !== 'object') return '';
|
|
12351
|
+
if (typeof part.text === 'string') return part.text;
|
|
12352
|
+
if (typeof part.content === 'string') return part.content;
|
|
12353
|
+
return '';
|
|
12354
|
+
})
|
|
12355
|
+
.join('')
|
|
12356
|
+
.trim();
|
|
12357
|
+
}
|
|
12358
|
+
|
|
11124
12359
|
return {
|
|
11125
12360
|
/** Session created \u2014 record session start */
|
|
11126
12361
|
'session.created': async ({ session }) => {
|
|
@@ -11133,6 +12368,21 @@ export const MemorixPlugin = async ({ project, client, $, directory, worktree })
|
|
|
11133
12368
|
|
|
11134
12369
|
/** Session idle \u2014 record session end */
|
|
11135
12370
|
'session.idle': async ({ session }) => {
|
|
12371
|
+
if (pendingAssistantResponse?.text) {
|
|
12372
|
+
const deliveryKey = pendingAssistantResponse.id
|
|
12373
|
+
? \`\${pendingAssistantResponse.id}:\${pendingAssistantResponse.text}\`
|
|
12374
|
+
: pendingAssistantResponse.text;
|
|
12375
|
+
if (deliveryKey !== lastDeliveredAssistantKey) {
|
|
12376
|
+
runHook({
|
|
12377
|
+
agent: 'opencode',
|
|
12378
|
+
hook_event_name: 'message.updated',
|
|
12379
|
+
ai_response: pendingAssistantResponse.text,
|
|
12380
|
+
message_id: pendingAssistantResponse.id,
|
|
12381
|
+
cwd: directory,
|
|
12382
|
+
});
|
|
12383
|
+
lastDeliveredAssistantKey = deliveryKey;
|
|
12384
|
+
}
|
|
12385
|
+
}
|
|
11136
12386
|
runHook({
|
|
11137
12387
|
agent: 'opencode',
|
|
11138
12388
|
hook_event_name: 'session.idle',
|
|
@@ -11161,6 +12411,19 @@ export const MemorixPlugin = async ({ project, client, $, directory, worktree })
|
|
|
11161
12411
|
});
|
|
11162
12412
|
},
|
|
11163
12413
|
|
|
12414
|
+
/** Message updated \u2014 cache the latest assistant response until session.idle */
|
|
12415
|
+
'message.updated': async (input, output) => {
|
|
12416
|
+
const message = extractMessageInfo(input);
|
|
12417
|
+
const role = message?.role ?? input?.role ?? input?.info?.role;
|
|
12418
|
+
if (role !== 'assistant') return;
|
|
12419
|
+
const text = extractMessageText(message);
|
|
12420
|
+
if (!text) return;
|
|
12421
|
+
pendingAssistantResponse = {
|
|
12422
|
+
id: message?.id ?? input?.id ?? input?.messageID,
|
|
12423
|
+
text,
|
|
12424
|
+
};
|
|
12425
|
+
},
|
|
12426
|
+
|
|
11164
12427
|
/** Session compacted \u2014 record post-compact event */
|
|
11165
12428
|
'session.compacted': async ({ session }) => {
|
|
11166
12429
|
runHook({
|
|
@@ -11410,7 +12673,7 @@ async function installHooks(agent, projectRoot, global = false) {
|
|
|
11410
12673
|
return {
|
|
11411
12674
|
agent,
|
|
11412
12675
|
configPath: pluginPath,
|
|
11413
|
-
events: ["session_start", "session_end", "post_tool", "post_edit", "post_compact", "post_command"],
|
|
12676
|
+
events: ["session_start", "session_end", "post_tool", "post_edit", "post_compact", "post_command", "post_response"],
|
|
11414
12677
|
generated: { note: "OpenCode plugin installed at " + pluginPath }
|
|
11415
12678
|
};
|
|
11416
12679
|
}
|
|
@@ -11617,6 +12880,7 @@ At the **beginning of every conversation**, BEFORE responding to the user:
|
|
|
11617
12880
|
|
|
11618
12881
|
**Important:** \`projectRoot\` is a detection anchor only; Git remains the source of truth for project identity.
|
|
11619
12882
|
In HTTP control-plane mode (\`memorix serve-http\` / \`memorix background start\`), explicit \`projectRoot\` binding is required for correct multi-project isolation.
|
|
12883
|
+
\`memorix_session_start\` is lightweight by default: it starts memory/session context only. Do not set \`joinTeam\` unless the user explicitly needs autonomous Agent Team tasks, messages, file locks, or orchestrated CLI-agent workflows.
|
|
11620
12884
|
|
|
11621
12885
|
## RULE 2: Store Important Context
|
|
11622
12886
|
|
|
@@ -11717,7 +12981,7 @@ This file contains the **minimum operating rules** for Memorix memory tools. It
|
|
|
11717
12981
|
For authoritative, up-to-date details on:
|
|
11718
12982
|
- **Support tiers** (core / extended / community) and what "installed" vs "runtime-ready" means
|
|
11719
12983
|
- **HTTP control-plane binding** and \`projectRoot\` isolation rules
|
|
11720
|
-
- **
|
|
12984
|
+
- **Opt-in team semantics** (\`joinTeam\`, \`team_manage join\`, roles, task claim, handoff validation)
|
|
11721
12985
|
- **Install vs runtime-ready distinction** \u2014 hook config written \u2260 agent will execute it
|
|
11722
12986
|
- **Agent-specific caveats** (Copilot project-level only, OpenCode plugin lifecycle, etc.)
|
|
11723
12987
|
|
|
@@ -11733,7 +12997,7 @@ async function uninstallHooks(agent, projectRoot, global = false) {
|
|
|
11733
12997
|
const configPath = global ? getGlobalConfigPath(agent) : getProjectConfigPath(agent, projectRoot);
|
|
11734
12998
|
let success = false;
|
|
11735
12999
|
try {
|
|
11736
|
-
if (agent === "kiro") {
|
|
13000
|
+
if (agent === "kiro" || agent === "opencode") {
|
|
11737
13001
|
await fs14.unlink(configPath);
|
|
11738
13002
|
success = true;
|
|
11739
13003
|
} else {
|
|
@@ -11867,7 +13131,7 @@ var init_installers = __esm({
|
|
|
11867
13131
|
"src/hooks/installers/index.ts"() {
|
|
11868
13132
|
"use strict";
|
|
11869
13133
|
init_esm_shims();
|
|
11870
|
-
OPENCODE_PLUGIN_VERSION =
|
|
13134
|
+
OPENCODE_PLUGIN_VERSION = 6;
|
|
11871
13135
|
}
|
|
11872
13136
|
});
|
|
11873
13137
|
|
|
@@ -12026,6 +13290,7 @@ init_obs_store();
|
|
|
12026
13290
|
init_orama_store();
|
|
12027
13291
|
init_mini_skill_store();
|
|
12028
13292
|
init_session_store();
|
|
13293
|
+
import { createHash as createHash4 } from "crypto";
|
|
12029
13294
|
import { watchFile } from "fs";
|
|
12030
13295
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
12031
13296
|
import { z as z2 } from "zod";
|
|
@@ -12295,7 +13560,7 @@ function formatTimeline(timeline) {
|
|
|
12295
13560
|
const anchorEffectiveSource = resolveSourceDetail(anchor.sourceDetail, anchor.source);
|
|
12296
13561
|
if (hasSrc && anchorEffectiveSource) {
|
|
12297
13562
|
const anchorBasis = resolveEvidenceBasis({ sourceDetail: anchor.sourceDetail, source: anchor.source });
|
|
12298
|
-
const basisSuffix = anchorBasis === "repository" ? " \u2014
|
|
13563
|
+
const basisSuffix = anchorBasis === "repository" ? " \u2014 [OK] repository-backed" : anchorBasis === "synthesized" ? " \u2014 [SYNTHESIZED] synthesized" : "";
|
|
12299
13564
|
lines.push(`*Expanding: ${sourceKindLabel(anchorEffectiveSource)}${basisSuffix}*`);
|
|
12300
13565
|
}
|
|
12301
13566
|
lines.push("");
|
|
@@ -12375,38 +13640,39 @@ function buildProvenanceHeader(sourceDetail, valueCategory, source, commitHash,
|
|
|
12375
13640
|
lines.push(verificationLine);
|
|
12376
13641
|
}
|
|
12377
13642
|
if (valueCategory === "core") {
|
|
12378
|
-
lines.push("
|
|
13643
|
+
lines.push("[CORE] Core \u2014 immune to decay");
|
|
12379
13644
|
} else if (valueCategory === "ephemeral") {
|
|
12380
|
-
lines.push("
|
|
13645
|
+
lines.push("[WARN] Ephemeral \u2014 short-lived signal");
|
|
12381
13646
|
}
|
|
12382
13647
|
return lines.join("\n");
|
|
12383
13648
|
}
|
|
12384
13649
|
function sourceKindLabel(sd) {
|
|
12385
|
-
if (sd === "git-ingest") return "
|
|
12386
|
-
if (sd === "hook") return "
|
|
12387
|
-
return "
|
|
13650
|
+
if (sd === "git-ingest") return "[PIN] Git Repository Evidence";
|
|
13651
|
+
if (sd === "hook") return "[HOOK] Hook Trace";
|
|
13652
|
+
return "[STORE] Explicit Working Memory";
|
|
12388
13653
|
}
|
|
12389
13654
|
function getTypeIcon(type) {
|
|
12390
13655
|
const icons = {
|
|
12391
|
-
"session-request": "
|
|
12392
|
-
"gotcha": "
|
|
12393
|
-
"problem-solution": "
|
|
12394
|
-
"how-it-works": "
|
|
12395
|
-
"what-changed": "
|
|
12396
|
-
"discovery": "
|
|
12397
|
-
"why-it-exists": "
|
|
12398
|
-
"decision": "
|
|
12399
|
-
"trade-off": "
|
|
12400
|
-
"reasoning": "
|
|
13656
|
+
"session-request": "[SESSION]",
|
|
13657
|
+
"gotcha": "[GOTCHA]",
|
|
13658
|
+
"problem-solution": "[FIX]",
|
|
13659
|
+
"how-it-works": "[INFO]",
|
|
13660
|
+
"what-changed": "[CHANGE]",
|
|
13661
|
+
"discovery": "[DISCOVERY]",
|
|
13662
|
+
"why-it-exists": "[WHY]",
|
|
13663
|
+
"decision": "[DECISION]",
|
|
13664
|
+
"trade-off": "[TRADEOFF]",
|
|
13665
|
+
"reasoning": "[REASONING]",
|
|
13666
|
+
"probe": "[PROBE]"
|
|
12401
13667
|
};
|
|
12402
|
-
return icons[type] ?? "
|
|
13668
|
+
return icons[type] ?? "[UNKNOWN]";
|
|
12403
13669
|
}
|
|
12404
13670
|
function getProgressiveDisclosureHint(hasProject) {
|
|
12405
13671
|
const lines = [
|
|
12406
|
-
"
|
|
13672
|
+
"[TIP] **Progressive Disclosure:** This index shows WHAT exists and retrieval COST.",
|
|
12407
13673
|
"- Use `memorix_detail` with typed refs (obs:42, skill:3) to fetch full details",
|
|
12408
13674
|
"- Use `memorix_timeline` to see chronological context around an observation",
|
|
12409
|
-
"- Critical types (
|
|
13675
|
+
"- Critical types ([GOTCHA] gotcha, [DECISION] decision, [TRADEOFF] trade-off) are often worth fetching immediately"
|
|
12410
13676
|
];
|
|
12411
13677
|
if (hasProject) {
|
|
12412
13678
|
lines.push("- For global results, prefer `memorix_detail refs=[{ id, projectId }]` to avoid cross-project ID ambiguity");
|
|
@@ -12417,7 +13683,7 @@ function formatEntryRef(entry) {
|
|
|
12417
13683
|
return entry.documentType === "mini-skill" ? `skill:${entry.id}` : `obs:${entry.id}`;
|
|
12418
13684
|
}
|
|
12419
13685
|
function formatLayerBadge(layer) {
|
|
12420
|
-
if (layer === "promoted") return "
|
|
13686
|
+
if (layer === "promoted") return "core";
|
|
12421
13687
|
if (layer === "evidence") return "ev";
|
|
12422
13688
|
return "-";
|
|
12423
13689
|
}
|
|
@@ -12590,7 +13856,7 @@ async function compactDetail(idsOrRefs) {
|
|
|
12590
13856
|
(o) => o.source === "git" && projectIds.has(o.projectId) && o.commitHash && obs.relatedCommits.includes(o.commitHash)
|
|
12591
13857
|
);
|
|
12592
13858
|
for (const gm of gitMems) {
|
|
12593
|
-
refs2.push(` \u2192 #${gm.id}
|
|
13859
|
+
refs2.push(` \u2192 #${gm.id} [CHANGE] ${gm.title}`);
|
|
12594
13860
|
}
|
|
12595
13861
|
}
|
|
12596
13862
|
if (obs.relatedEntities && obs.relatedEntities.length > 0) {
|
|
@@ -12603,7 +13869,7 @@ async function compactDetail(idsOrRefs) {
|
|
|
12603
13869
|
if (analysis.length > 0) {
|
|
12604
13870
|
refs2.push("Analysis:");
|
|
12605
13871
|
for (const r of analysis) {
|
|
12606
|
-
refs2.push(` \u2192 #${r.id} ${r.type === "reasoning" ? "
|
|
13872
|
+
refs2.push(` \u2192 #${r.id} ${r.type === "reasoning" ? "[REASONING]" : "[DECISION]"} ${r.title}`);
|
|
12607
13873
|
}
|
|
12608
13874
|
}
|
|
12609
13875
|
}
|
|
@@ -12614,7 +13880,7 @@ async function compactDetail(idsOrRefs) {
|
|
|
12614
13880
|
if (gitMems.length > 0) {
|
|
12615
13881
|
refs2.push("Repository evidence:");
|
|
12616
13882
|
for (const g of gitMems) {
|
|
12617
|
-
refs2.push(` \u2192 #${g.id}
|
|
13883
|
+
refs2.push(` \u2192 #${g.id} [CHANGE] ${g.title}`);
|
|
12618
13884
|
}
|
|
12619
13885
|
}
|
|
12620
13886
|
}
|
|
@@ -12653,7 +13919,7 @@ async function compactDetail(idsOrRefs) {
|
|
|
12653
13919
|
}
|
|
12654
13920
|
function formatMiniSkillDetail(skill, provenanceStatus) {
|
|
12655
13921
|
const lines = [];
|
|
12656
|
-
lines.push(`S${skill.id}
|
|
13922
|
+
lines.push(`S${skill.id} core ${skill.title}`);
|
|
12657
13923
|
lines.push("=".repeat(50));
|
|
12658
13924
|
lines.push(`Type: promoted knowledge (mini-skill)`);
|
|
12659
13925
|
lines.push(`Entity: ${skill.sourceEntity}`);
|
|
@@ -14697,47 +15963,47 @@ var WorkspaceSyncEngine = class _WorkspaceSyncEngine {
|
|
|
14697
15963
|
}
|
|
14698
15964
|
const lines = [];
|
|
14699
15965
|
if (applyResult.success) {
|
|
14700
|
-
lines.push(
|
|
15966
|
+
lines.push(`[OK] Applied ${applyResult.filesWritten.length} file(s) for ${target}`);
|
|
14701
15967
|
for (const f of applyResult.filesWritten) {
|
|
14702
15968
|
lines.push(` \u2192 ${f}`);
|
|
14703
15969
|
}
|
|
14704
15970
|
if (skillResult.copied.length > 0) {
|
|
14705
15971
|
lines.push(`
|
|
14706
|
-
|
|
15972
|
+
[SKILL] Copied ${skillResult.copied.length} skill(s):`);
|
|
14707
15973
|
for (const sk of skillResult.copied) {
|
|
14708
15974
|
lines.push(` \u2192 ${sk}`);
|
|
14709
15975
|
}
|
|
14710
15976
|
}
|
|
14711
15977
|
if (skillResult.skipped.length > 0) {
|
|
14712
15978
|
lines.push(`
|
|
14713
|
-
|
|
15979
|
+
[SKIP] Skipped ${skillResult.skipped.length} skill(s):`);
|
|
14714
15980
|
for (const sk of skillResult.skipped) {
|
|
14715
15981
|
lines.push(` \u2192 ${sk}`);
|
|
14716
15982
|
}
|
|
14717
15983
|
}
|
|
14718
15984
|
if (syncResult.skills.conflicts.length > 0) {
|
|
14719
15985
|
lines.push(`
|
|
14720
|
-
|
|
15986
|
+
[WARN] Name conflicts (${syncResult.skills.conflicts.length}):`);
|
|
14721
15987
|
for (const c of syncResult.skills.conflicts) {
|
|
14722
15988
|
lines.push(` \u2192 "${c.name}": kept ${c.kept.sourceAgent}, skipped ${c.skipped.sourceAgent}`);
|
|
14723
15989
|
}
|
|
14724
15990
|
}
|
|
14725
15991
|
if (applyResult.backups.length > 0) {
|
|
14726
15992
|
lines.push(`
|
|
14727
|
-
|
|
15993
|
+
[PACKAGE] Backups created (${applyResult.backups.length}):`);
|
|
14728
15994
|
for (const b of applyResult.backups) {
|
|
14729
15995
|
lines.push(` ${b.originalPath} \u2192 ${b.backupPath}`);
|
|
14730
15996
|
}
|
|
14731
15997
|
}
|
|
14732
15998
|
applier.cleanBackups(applyResult.backups);
|
|
14733
15999
|
} else {
|
|
14734
|
-
lines.push(
|
|
16000
|
+
lines.push(`[ERROR] Apply failed for ${target}`);
|
|
14735
16001
|
for (const e of applyResult.errors) {
|
|
14736
16002
|
lines.push(` Error: ${e}`);
|
|
14737
16003
|
}
|
|
14738
16004
|
if (applyResult.backups.length > 0) {
|
|
14739
16005
|
lines.push(`
|
|
14740
|
-
|
|
16006
|
+
[UPDATED] Rolled back ${applyResult.backups.length} file(s)`);
|
|
14741
16007
|
}
|
|
14742
16008
|
}
|
|
14743
16009
|
return {
|
|
@@ -14763,11 +16029,109 @@ var WorkspaceSyncEngine = class _WorkspaceSyncEngine {
|
|
|
14763
16029
|
}
|
|
14764
16030
|
};
|
|
14765
16031
|
|
|
16032
|
+
// src/server/tool-profile.ts
|
|
16033
|
+
init_esm_shims();
|
|
16034
|
+
var TOOL_PROFILES = Object.freeze({
|
|
16035
|
+
// ── lite: core cross-agent memory — always available ──────────────
|
|
16036
|
+
memorix_store: ["lite", "team", "full"],
|
|
16037
|
+
memorix_search: ["lite", "team", "full"],
|
|
16038
|
+
memorix_detail: ["lite", "team", "full"],
|
|
16039
|
+
memorix_resolve: ["lite", "team", "full"],
|
|
16040
|
+
memorix_timeline: ["lite", "team", "full"],
|
|
16041
|
+
memorix_suggest_topic_key: ["lite", "team", "full"],
|
|
16042
|
+
memorix_session_start: ["lite", "team", "full"],
|
|
16043
|
+
memorix_session_end: ["lite", "team", "full"],
|
|
16044
|
+
memorix_session_context: ["lite", "team", "full"],
|
|
16045
|
+
memorix_store_reasoning: ["lite", "team", "full"],
|
|
16046
|
+
memorix_search_reasoning: ["lite", "team", "full"],
|
|
16047
|
+
memorix_transfer: ["lite", "team", "full"],
|
|
16048
|
+
memorix_retention: ["lite", "team", "full"],
|
|
16049
|
+
// ── team: autonomous agent team surfaces — HTTP default ───────────
|
|
16050
|
+
memorix_dashboard: ["team", "full"],
|
|
16051
|
+
memorix_handoff: ["team", "full"],
|
|
16052
|
+
memorix_poll: ["team", "full"],
|
|
16053
|
+
team_manage: ["team", "full"],
|
|
16054
|
+
team_message: ["team", "full"],
|
|
16055
|
+
team_task: ["team", "full"],
|
|
16056
|
+
team_file_lock: ["team", "full"],
|
|
16057
|
+
// ── full: advanced / specialized — opt-in only ───────────────────
|
|
16058
|
+
memorix_audit_project: ["full"],
|
|
16059
|
+
memorix_deduplicate: ["full"],
|
|
16060
|
+
memorix_consolidate: ["full"],
|
|
16061
|
+
memorix_formation_metrics: ["full"],
|
|
16062
|
+
memorix_skills: ["full"],
|
|
16063
|
+
memorix_promote: ["full"],
|
|
16064
|
+
memorix_rules_sync: ["full"],
|
|
16065
|
+
memorix_workspace_sync: ["full"],
|
|
16066
|
+
memorix_ingest_image: ["full"],
|
|
16067
|
+
// ── MCP Official Memory Server compatibility (KG tools) ──────────
|
|
16068
|
+
// These are only useful to users specifically migrating from the
|
|
16069
|
+
// reference mcp-memory server. Hide them unless explicitly enabled.
|
|
16070
|
+
create_entities: ["full"],
|
|
16071
|
+
create_relations: ["full"],
|
|
16072
|
+
add_observations: ["full"],
|
|
16073
|
+
delete_entities: ["full"],
|
|
16074
|
+
delete_observations: ["full"],
|
|
16075
|
+
delete_relations: ["full"],
|
|
16076
|
+
read_graph: ["full"],
|
|
16077
|
+
search_nodes: ["full"],
|
|
16078
|
+
open_nodes: ["full"]
|
|
16079
|
+
});
|
|
16080
|
+
function isToolInProfile(toolName, profile) {
|
|
16081
|
+
const profiles = TOOL_PROFILES[toolName];
|
|
16082
|
+
if (!profiles) {
|
|
16083
|
+
return profile === "full";
|
|
16084
|
+
}
|
|
16085
|
+
return profiles.includes(profile);
|
|
16086
|
+
}
|
|
16087
|
+
function resolveToolProfile(opts) {
|
|
16088
|
+
const normalize = (v) => {
|
|
16089
|
+
if (!v) return null;
|
|
16090
|
+
const s = String(v).trim().toLowerCase();
|
|
16091
|
+
if (s === "lite" || s === "team" || s === "full") return s;
|
|
16092
|
+
return null;
|
|
16093
|
+
};
|
|
16094
|
+
return normalize(opts.explicit) ?? normalize(opts.envValue) ?? opts.fallback;
|
|
16095
|
+
}
|
|
16096
|
+
function describeProfile(profile) {
|
|
16097
|
+
switch (profile) {
|
|
16098
|
+
case "lite":
|
|
16099
|
+
return "lite (core memory + sessions, ~13 tools)";
|
|
16100
|
+
case "team":
|
|
16101
|
+
return "team (lite + agent team tools + dashboard, ~20 tools)";
|
|
16102
|
+
case "full":
|
|
16103
|
+
return "full (all tools including advanced / KG-compat)";
|
|
16104
|
+
}
|
|
16105
|
+
}
|
|
16106
|
+
|
|
14766
16107
|
// src/server.ts
|
|
14767
16108
|
init_provider2();
|
|
14768
16109
|
init_memory_manager();
|
|
14769
16110
|
init_formation();
|
|
14770
|
-
|
|
16111
|
+
|
|
16112
|
+
// src/server/formation-timeout.ts
|
|
16113
|
+
init_esm_shims();
|
|
16114
|
+
var DEFAULT_FORMATION_TIMEOUT_MS = 12e3;
|
|
16115
|
+
var FORMATION_TIMEOUT_MIN_MS = 1e3;
|
|
16116
|
+
var FORMATION_TIMEOUT_MAX_MS = 3e5;
|
|
16117
|
+
function parseFormationTimeoutMs(raw) {
|
|
16118
|
+
const value = raw?.trim();
|
|
16119
|
+
if (!value) return DEFAULT_FORMATION_TIMEOUT_MS;
|
|
16120
|
+
const parsed = Number(value);
|
|
16121
|
+
if (!Number.isInteger(parsed) || Number.isNaN(parsed)) {
|
|
16122
|
+
console.warn(
|
|
16123
|
+
`[memorix] MEMORIX_FORMATION_TIMEOUT_MS="${raw}" is invalid (must be a positive integer between ${FORMATION_TIMEOUT_MIN_MS}-${FORMATION_TIMEOUT_MAX_MS}ms). Using default ${DEFAULT_FORMATION_TIMEOUT_MS}ms.`
|
|
16124
|
+
);
|
|
16125
|
+
return DEFAULT_FORMATION_TIMEOUT_MS;
|
|
16126
|
+
}
|
|
16127
|
+
if (parsed < FORMATION_TIMEOUT_MIN_MS) return FORMATION_TIMEOUT_MIN_MS;
|
|
16128
|
+
if (parsed > FORMATION_TIMEOUT_MAX_MS) return FORMATION_TIMEOUT_MAX_MS;
|
|
16129
|
+
return parsed;
|
|
16130
|
+
}
|
|
16131
|
+
|
|
16132
|
+
// src/server.ts
|
|
16133
|
+
var FORMATION_TIMEOUT_MS = parseFormationTimeoutMs(process.env.MEMORIX_FORMATION_TIMEOUT_MS);
|
|
16134
|
+
var COMPACT_ON_WRITE_TIMEOUT_MS = 12e3;
|
|
14771
16135
|
var COMPRESSION_TIMEOUT_MS = 5e3;
|
|
14772
16136
|
var DEDUP_PER_PAIR_TIMEOUT_MS = 5e3;
|
|
14773
16137
|
function withTimeout(promise, ms, label) {
|
|
@@ -14779,6 +16143,11 @@ function withTimeout(promise, ms, label) {
|
|
|
14779
16143
|
})
|
|
14780
16144
|
]).finally(() => clearTimeout(timer));
|
|
14781
16145
|
}
|
|
16146
|
+
function formatFormationStageDurations(stageDurationsMs) {
|
|
16147
|
+
const orderedStages = ["extract", "resolve", "evaluate"];
|
|
16148
|
+
const parts = orderedStages.filter((stage) => stageDurationsMs[stage] !== void 0).map((stage) => `${stage}=${stageDurationsMs[stage]}ms`);
|
|
16149
|
+
return parts.join(", ");
|
|
16150
|
+
}
|
|
14782
16151
|
var lastInternalWriteMs = 0;
|
|
14783
16152
|
var markInternalWrite = () => {
|
|
14784
16153
|
lastInternalWriteMs = Date.now();
|
|
@@ -14793,7 +16162,8 @@ var OBSERVATION_TYPES = [
|
|
|
14793
16162
|
"discovery",
|
|
14794
16163
|
"why-it-exists",
|
|
14795
16164
|
"decision",
|
|
14796
|
-
"trade-off"
|
|
16165
|
+
"trade-off",
|
|
16166
|
+
"probe"
|
|
14797
16167
|
];
|
|
14798
16168
|
function coerceNumberArray(val) {
|
|
14799
16169
|
if (Array.isArray(val)) return val.map(Number);
|
|
@@ -14870,11 +16240,21 @@ function coerceObjectArray(val) {
|
|
|
14870
16240
|
}
|
|
14871
16241
|
return [];
|
|
14872
16242
|
}
|
|
16243
|
+
function createDeterministicInstanceId(projectId, agentType, agentName) {
|
|
16244
|
+
const digest = createHash4("sha256").update(projectId).update("\n").update(agentType).update("\n").update(agentName ?? "").digest("hex").slice(0, 24);
|
|
16245
|
+
return `auto-${digest}`;
|
|
16246
|
+
}
|
|
14873
16247
|
async function createMemorixServer(cwd, existingServer, sharedTeam, options = {}) {
|
|
14874
16248
|
const allowUntrackedFallback = options.allowUntrackedFallback ?? true;
|
|
14875
16249
|
const deferProjectInitUntilBound = options.deferProjectInitUntilBound ?? false;
|
|
14876
16250
|
const dashboardMode = options.dashboardMode ?? (sharedTeam ? "control-plane" : "standalone");
|
|
14877
16251
|
const configuredDashboardPort = options.dashboardPort ?? (dashboardMode === "control-plane" ? 3211 : 3210);
|
|
16252
|
+
const toolProfile = resolveToolProfile({
|
|
16253
|
+
explicit: options.toolProfile,
|
|
16254
|
+
envValue: process.env.MEMORIX_MODE,
|
|
16255
|
+
fallback: sharedTeam ? "team" : "lite"
|
|
16256
|
+
});
|
|
16257
|
+
const teamFeaturesEnabled = isToolInProfile("team_manage", toolProfile);
|
|
14878
16258
|
const detectedProject = detectProject(cwd);
|
|
14879
16259
|
let rawProject;
|
|
14880
16260
|
let projectResolved = true;
|
|
@@ -14949,6 +16329,9 @@ async function createMemorixServer(cwd, existingServer, sharedTeam, options = {}
|
|
|
14949
16329
|
} else {
|
|
14950
16330
|
console.error(`[memorix] LLM mode: off (set MEMORIX_LLM_API_KEY or OPENAI_API_KEY to enable)`);
|
|
14951
16331
|
}
|
|
16332
|
+
if (logPrefix === "startup") {
|
|
16333
|
+
console.error(`[memorix] Tool profile: ${describeProfile(toolProfile)}`);
|
|
16334
|
+
}
|
|
14952
16335
|
if (logPrefix === "startup") {
|
|
14953
16336
|
console.error(`[memorix] Project: ${project.id} (${project.name})`);
|
|
14954
16337
|
console.error(`[memorix] Data dir: ${projectDir2}`);
|
|
@@ -15009,13 +16392,20 @@ The path should point to a directory containing a .git folder.`
|
|
|
15009
16392
|
};
|
|
15010
16393
|
const server = existingServer ?? new McpServer({
|
|
15011
16394
|
name: "memorix",
|
|
15012
|
-
version: true ? "1.0.
|
|
16395
|
+
version: true ? "1.0.9" : "1.0.1"
|
|
16396
|
+
});
|
|
16397
|
+
const originalRegisterTool = server.registerTool.bind(server);
|
|
16398
|
+
server.registerTool = ((name, ...args) => {
|
|
16399
|
+
if (!isToolInProfile(name, toolProfile)) {
|
|
16400
|
+
return void 0;
|
|
16401
|
+
}
|
|
16402
|
+
return originalRegisterTool(name, ...args);
|
|
15013
16403
|
});
|
|
15014
16404
|
server.registerTool(
|
|
15015
16405
|
"memorix_store",
|
|
15016
16406
|
{
|
|
15017
16407
|
title: "Store Memory",
|
|
15018
|
-
description: "Store a new observation/memory. Automatically indexed for search. Use type to classify: gotcha (
|
|
16408
|
+
description: "Store a new observation/memory. Automatically indexed for search. Use type to classify: gotcha ([GOTCHA] critical pitfall), decision ([DECISION] architecture choice), problem-solution ([FIX] bug fix), how-it-works ([INFO] explanation), what-changed ([CHANGE] change), discovery ([DISCOVERY] insight), why-it-exists ([WHY] rationale), trade-off ([TRADEOFF] compromise), session-request ([SESSION] original goal). Stored memories persist across sessions and are shared with other IDEs (Cursor, Windsurf, Claude Code, Codex, Copilot, Kiro, Antigravity, Trae) via the same local data directory.",
|
|
15019
16409
|
inputSchema: {
|
|
15020
16410
|
entityName: z2.string().describe('The entity this observation belongs to (e.g., "auth-module", "port-config")'),
|
|
15021
16411
|
type: z2.enum(OBSERVATION_TYPES).describe("Observation type for classification"),
|
|
@@ -15060,6 +16450,19 @@ The path should point to a directory containing a .git folder.`
|
|
|
15060
16450
|
let formationResult = null;
|
|
15061
16451
|
let formationNote = "";
|
|
15062
16452
|
if (useFormation && !topicKey && !progress) {
|
|
16453
|
+
let currentFormationStage = "setup";
|
|
16454
|
+
const completedFormationStages = {};
|
|
16455
|
+
const formationStartTime = Date.now();
|
|
16456
|
+
const onFormationStageEvent = (event) => {
|
|
16457
|
+
if (event.status === "start") {
|
|
16458
|
+
currentFormationStage = event.stage;
|
|
16459
|
+
return;
|
|
16460
|
+
}
|
|
16461
|
+
currentFormationStage = event.stage;
|
|
16462
|
+
if (event.stageDurationMs !== void 0) {
|
|
16463
|
+
completedFormationStages[event.stage] = event.stageDurationMs;
|
|
16464
|
+
}
|
|
16465
|
+
};
|
|
15063
16466
|
try {
|
|
15064
16467
|
const formationConfig = {
|
|
15065
16468
|
mode: "active",
|
|
@@ -15093,7 +16496,8 @@ The path should point to a directory containing a .git folder.`
|
|
|
15093
16496
|
topicKey: o.topicKey
|
|
15094
16497
|
};
|
|
15095
16498
|
},
|
|
15096
|
-
getEntityNames: () => graphManager.getEntityNames()
|
|
16499
|
+
getEntityNames: () => graphManager.getEntityNames(),
|
|
16500
|
+
onStageEvent: onFormationStageEvent
|
|
15097
16501
|
};
|
|
15098
16502
|
formationResult = await withTimeout(
|
|
15099
16503
|
runFormation({
|
|
@@ -15108,7 +16512,7 @@ The path should point to a directory containing a .git folder.`
|
|
|
15108
16512
|
FORMATION_TIMEOUT_MS,
|
|
15109
16513
|
"Formation pipeline"
|
|
15110
16514
|
);
|
|
15111
|
-
const modeIcon = "
|
|
16515
|
+
const modeIcon = "[FAST]";
|
|
15112
16516
|
formationNote = `
|
|
15113
16517
|
${modeIcon} Formation[active]: ${formationResult.evaluation.category} (${formationResult.evaluation.score.toFixed(2)}) | ${formationResult.resolution.action} | ${formationResult.pipeline.durationMs}ms`;
|
|
15114
16518
|
if (formationResult.extraction.extractedFacts.length > 0) {
|
|
@@ -15119,8 +16523,16 @@ ${modeIcon} Formation[active]: ${formationResult.evaluation.category} (${formati
|
|
|
15119
16523
|
if (formationResult.extraction.typeCorrected) formationNote += ` | type\u2192${formationResult.type}`;
|
|
15120
16524
|
} catch (formationErr) {
|
|
15121
16525
|
const isTimeout = formationErr instanceof Error && formationErr.message.includes("timed out");
|
|
16526
|
+
const elapsedMs = Date.now() - formationStartTime;
|
|
16527
|
+
const stageSummary = formatFormationStageDurations(completedFormationStages);
|
|
16528
|
+
console.error(
|
|
16529
|
+
`[memorix] Formation ${isTimeout ? "timed out" : "failed"} in memorix_store after ${elapsedMs}ms/${FORMATION_TIMEOUT_MS}ms at stage ${currentFormationStage}${stageSummary ? ` | completed: ${stageSummary}` : ""}`
|
|
16530
|
+
);
|
|
16531
|
+
if (!isTimeout && formationErr instanceof Error) {
|
|
16532
|
+
console.error(`[memorix] Formation error: ${formationErr.message}`);
|
|
16533
|
+
}
|
|
15122
16534
|
formationNote = `
|
|
15123
|
-
|
|
16535
|
+
[WARN] Formation ${isTimeout ? "timed out" : "failed"} \u2014 storing base observation without enrichment`;
|
|
15124
16536
|
}
|
|
15125
16537
|
}
|
|
15126
16538
|
if (useFormation && formationResult && formationResult.resolution.action !== "new") {
|
|
@@ -15146,7 +16558,7 @@ ${modeIcon} Formation[active]: ${formationResult.evaluation.category} (${formati
|
|
|
15146
16558
|
return {
|
|
15147
16559
|
content: [{
|
|
15148
16560
|
type: "text",
|
|
15149
|
-
text:
|
|
16561
|
+
text: `[UPDATED] Formation MERGE: merged into #${targetId} (${reason})${formationNote}`
|
|
15150
16562
|
}]
|
|
15151
16563
|
};
|
|
15152
16564
|
}
|
|
@@ -15171,7 +16583,7 @@ ${modeIcon} Formation[active]: ${formationResult.evaluation.category} (${formati
|
|
|
15171
16583
|
return {
|
|
15172
16584
|
content: [{
|
|
15173
16585
|
type: "text",
|
|
15174
|
-
text:
|
|
16586
|
+
text: `[UPDATED] Formation EVOLVE: evolved #${targetId} (${reason})${formationNote}`
|
|
15175
16587
|
}]
|
|
15176
16588
|
};
|
|
15177
16589
|
}
|
|
@@ -15179,7 +16591,7 @@ ${modeIcon} Formation[active]: ${formationResult.evaluation.category} (${formati
|
|
|
15179
16591
|
return {
|
|
15180
16592
|
content: [{
|
|
15181
16593
|
type: "text",
|
|
15182
|
-
text:
|
|
16594
|
+
text: `[SKIP] Formation DISCARD: ${reason}${formationNote}`
|
|
15183
16595
|
}]
|
|
15184
16596
|
};
|
|
15185
16597
|
}
|
|
@@ -15210,7 +16622,7 @@ ${modeIcon} Formation[active]: ${formationResult.evaluation.category} (${formati
|
|
|
15210
16622
|
{ title, narrative, facts: safeFacts ?? [] },
|
|
15211
16623
|
existingMemories
|
|
15212
16624
|
),
|
|
15213
|
-
|
|
16625
|
+
COMPACT_ON_WRITE_TIMEOUT_MS,
|
|
15214
16626
|
"Compact-on-write"
|
|
15215
16627
|
);
|
|
15216
16628
|
if (decision.action === "UPDATE" && decision.targetId) {
|
|
@@ -15231,7 +16643,7 @@ ${modeIcon} Formation[active]: ${formationResult.evaluation.category} (${formati
|
|
|
15231
16643
|
sourceDetail: "explicit",
|
|
15232
16644
|
createdByAgentId: currentAgentId
|
|
15233
16645
|
});
|
|
15234
|
-
compactAction =
|
|
16646
|
+
compactAction = `[UPDATED] Compact UPDATE: merged into #${decision.targetId} (${decision.reason})`;
|
|
15235
16647
|
compactMerged = true;
|
|
15236
16648
|
return {
|
|
15237
16649
|
content: [{
|
|
@@ -15245,7 +16657,7 @@ Mode: ${decision.usedLLM ? "LLM" : "heuristic"}`
|
|
|
15245
16657
|
return {
|
|
15246
16658
|
content: [{
|
|
15247
16659
|
type: "text",
|
|
15248
|
-
text:
|
|
16660
|
+
text: `[SKIP] Compact SKIP: ${decision.reason}
|
|
15249
16661
|
Existing memory #${decision.targetId} already covers this.
|
|
15250
16662
|
Mode: ${decision.usedLLM ? "LLM" : "heuristic"}`
|
|
15251
16663
|
}]
|
|
@@ -15316,7 +16728,7 @@ Mode: ${decision.usedLLM ? "LLM" : "heuristic"}`
|
|
|
15316
16728
|
const attrCheck = await checkProjectAttribution(entityName, project.id, getAllObservations());
|
|
15317
16729
|
if (attrCheck.suspicious) {
|
|
15318
16730
|
attributionWarning = `
|
|
15319
|
-
|
|
16731
|
+
[WARN] Attribution notice: entity "${entityName}" has 0 observations in "${project.id}" but ${attrCheck.count} in "${attrCheck.knownIn}" (confidence: ${attrCheck.confidence}). Verify the correct project is bound before storing.`;
|
|
15320
16732
|
}
|
|
15321
16733
|
} catch {
|
|
15322
16734
|
}
|
|
@@ -15354,7 +16766,7 @@ Mode: ${decision.usedLLM ? "LLM" : "heuristic"}`
|
|
|
15354
16766
|
if (upserted) enrichmentParts.push(`topic upserted (rev ${obs.revisionCount ?? 1})`);
|
|
15355
16767
|
const enrichment = enrichmentParts.length > 0 ? `
|
|
15356
16768
|
Auto-enriched: ${enrichmentParts.join(", ")}` : "";
|
|
15357
|
-
const action = upserted ? "
|
|
16769
|
+
const action = upserted ? "[UPDATED] Updated" : "[OK] Stored";
|
|
15358
16770
|
if (!useFormation && !topicKey && !progress) {
|
|
15359
16771
|
const shadowFormation = async () => {
|
|
15360
16772
|
let oldCompactDecision = null;
|
|
@@ -15583,13 +16995,13 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
|
|
|
15583
16995
|
const result = await resolveObservations2(safeIds, status ?? "resolved");
|
|
15584
16996
|
const parts = [];
|
|
15585
16997
|
if (result.resolved.length > 0) {
|
|
15586
|
-
parts.push(
|
|
16998
|
+
parts.push(`[OK] Resolved ${result.resolved.length} observation(s): #${result.resolved.join(", #")}`);
|
|
15587
16999
|
}
|
|
15588
17000
|
if (result.notFound.length > 0) {
|
|
15589
|
-
parts.push(
|
|
17001
|
+
parts.push(`[WARN] Not found: #${result.notFound.join(", #")}`);
|
|
15590
17002
|
}
|
|
15591
17003
|
parts.push('\nResolved memories are hidden from default search. Use status="all" to include them.');
|
|
15592
|
-
parts.push('
|
|
17004
|
+
parts.push('[STATS] Run `memorix_retention` with `action: "report"` to check remaining cleanup status.');
|
|
15593
17005
|
return {
|
|
15594
17006
|
content: [{ type: "text", text: parts.join("\n") }]
|
|
15595
17007
|
};
|
|
@@ -15642,7 +17054,7 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
|
|
|
15642
17054
|
const attrCheck = await checkProjectAttribution(entityName, project.id, getAllObservations());
|
|
15643
17055
|
if (attrCheck.suspicious) {
|
|
15644
17056
|
reasoningAttributionWarning = `
|
|
15645
|
-
|
|
17057
|
+
[WARN] Attribution notice: entity "${entityName}" has 0 observations in "${project.id}" but ${attrCheck.count} in "${attrCheck.knownIn}" (confidence: ${attrCheck.confidence}). Verify the correct project is bound before storing.`;
|
|
15646
17058
|
}
|
|
15647
17059
|
} catch {
|
|
15648
17060
|
}
|
|
@@ -15663,12 +17075,12 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
|
|
|
15663
17075
|
createdByAgentId: currentAgentId
|
|
15664
17076
|
});
|
|
15665
17077
|
await graphManager.addObservations([
|
|
15666
|
-
{ entityName, contents: [`[#${obs.id}]
|
|
17078
|
+
{ entityName, contents: [`[#${obs.id}] [REASONING] ${decision}`] }
|
|
15667
17079
|
]);
|
|
15668
17080
|
return {
|
|
15669
17081
|
content: [{
|
|
15670
17082
|
type: "text",
|
|
15671
|
-
text:
|
|
17083
|
+
text: `[REASONING] Reasoning trace stored #${obs.id}: "${decision}"
|
|
15672
17084
|
Entity: ${entityName} | ${facts.length} facts | ${obs.tokens} tokens${reasoningAttributionWarning}`
|
|
15673
17085
|
}]
|
|
15674
17086
|
};
|
|
@@ -15705,7 +17117,7 @@ Entity: ${entityName} | ${facts.length} facts | ${obs.tokens} tokens${reasoningA
|
|
|
15705
17117
|
return {
|
|
15706
17118
|
content: [{
|
|
15707
17119
|
type: "text",
|
|
15708
|
-
text:
|
|
17120
|
+
text: `[OK] No suspicious observations found in project "${project.id}" (threshold: ${minCount}).`
|
|
15709
17121
|
}]
|
|
15710
17122
|
};
|
|
15711
17123
|
}
|
|
@@ -15765,7 +17177,7 @@ Entity: ${entityName} | ${facts.length} facts | ${obs.tokens} tokens${reasoningA
|
|
|
15765
17177
|
};
|
|
15766
17178
|
}
|
|
15767
17179
|
return {
|
|
15768
|
-
content: [{ type: "text", text:
|
|
17180
|
+
content: [{ type: "text", text: `[REASONING] Reasoning Traces:
|
|
15769
17181
|
${result.formatted}` }]
|
|
15770
17182
|
};
|
|
15771
17183
|
}
|
|
@@ -15790,7 +17202,7 @@ ${result.formatted}` }]
|
|
|
15790
17202
|
return {
|
|
15791
17203
|
content: [{
|
|
15792
17204
|
type: "text",
|
|
15793
|
-
text: "
|
|
17205
|
+
text: "[WARN] LLM not configured. Set MEMORIX_LLM_API_KEY or OPENAI_API_KEY to enable intelligent dedup.\n\nTip: Use memorix_consolidate for basic similarity-based merging without LLM."
|
|
15794
17206
|
}]
|
|
15795
17207
|
};
|
|
15796
17208
|
}
|
|
@@ -15825,29 +17237,29 @@ ${result.formatted}` }]
|
|
|
15825
17237
|
[{ id: older.id, title: older.title, narrative: older.narrative, facts: older.facts.join("\n") }]
|
|
15826
17238
|
);
|
|
15827
17239
|
if (decision && decision.action === "UPDATE" && decision.targetId) {
|
|
15828
|
-
actions.push(
|
|
17240
|
+
actions.push(`[UPDATED] #${older.id} "${older.title}" \u2192 superseded by #${newer.id} (${decision.reason})${decision.usedLLM ? " [LLM]" : " [heuristic]"}`);
|
|
15829
17241
|
toResolve.push(older.id);
|
|
15830
17242
|
} else if (decision && decision.action === "NONE") {
|
|
15831
|
-
actions.push(
|
|
17243
|
+
actions.push(`[DELETE] #${newer.id} "${newer.title}" \u2192 redundant (${decision.reason})${decision.usedLLM ? " [LLM]" : " [heuristic]"}`);
|
|
15832
17244
|
toResolve.push(newer.id);
|
|
15833
17245
|
} else if (decision && decision.action === "DELETE") {
|
|
15834
|
-
actions.push(
|
|
17246
|
+
actions.push(`[ERROR] #${decision.targetId ?? older.id} \u2192 outdated (${decision.reason})${decision.usedLLM ? " [LLM]" : " [heuristic]"}`);
|
|
15835
17247
|
toResolve.push(decision.targetId ?? older.id);
|
|
15836
17248
|
}
|
|
15837
17249
|
} catch (dedupErr) {
|
|
15838
|
-
actions.push(
|
|
17250
|
+
actions.push(`[WARN] comparison failed: ${dedupErr?.message ?? dedupErr}`);
|
|
15839
17251
|
}
|
|
15840
17252
|
}
|
|
15841
17253
|
}
|
|
15842
17254
|
}
|
|
15843
17255
|
if (actions.length === 0) {
|
|
15844
|
-
return { content: [{ type: "text", text:
|
|
17256
|
+
return { content: [{ type: "text", text: `[OK] Scanned ${candidates.length} memories across ${byEntity.size} entities \u2014 no duplicates found.` }] };
|
|
15845
17257
|
}
|
|
15846
17258
|
if (dryRun) {
|
|
15847
17259
|
return {
|
|
15848
17260
|
content: [{
|
|
15849
17261
|
type: "text",
|
|
15850
|
-
text:
|
|
17262
|
+
text: `[SEARCH] DRY RUN \u2014 ${actions.length} action(s) found:
|
|
15851
17263
|
|
|
15852
17264
|
${actions.join("\n")}
|
|
15853
17265
|
|
|
@@ -15860,7 +17272,7 @@ Run with dryRun=false to apply.`
|
|
|
15860
17272
|
return {
|
|
15861
17273
|
content: [{
|
|
15862
17274
|
type: "text",
|
|
15863
|
-
text:
|
|
17275
|
+
text: `[CLEANUP] Deduplicated: resolved ${unique.length} memory(ies)
|
|
15864
17276
|
|
|
15865
17277
|
${actions.join("\n")}`
|
|
15866
17278
|
}]
|
|
@@ -15976,11 +17388,11 @@ ${actions.join("\n")}`
|
|
|
15976
17388
|
const result = await archiveExpired2(projectDir2, void 0, accessMap);
|
|
15977
17389
|
if (result.archived === 0) {
|
|
15978
17390
|
return {
|
|
15979
|
-
content: [{ type: "text", text: "
|
|
17391
|
+
content: [{ type: "text", text: "[OK] No expired observations to archive. All memories are within their retention period." }]
|
|
15980
17392
|
};
|
|
15981
17393
|
}
|
|
15982
17394
|
return {
|
|
15983
|
-
content: [{ type: "text", text:
|
|
17395
|
+
content: [{ type: "text", text: `[ARCHIVED] Archived ${result.archived} expired observations (status set to 'archived' in-place)
|
|
15984
17396
|
${result.remaining} active observations remaining.
|
|
15985
17397
|
|
|
15986
17398
|
Archived memories are hidden from default search but can be found with status: "all".` }]
|
|
@@ -16015,7 +17427,7 @@ Archived memories are hidden from default search but can be found with status: "
|
|
|
16015
17427
|
const staleDocs = docs.filter((d) => getRetentionZone2(d) === "stale");
|
|
16016
17428
|
if (staleDocs.length === 0) {
|
|
16017
17429
|
return {
|
|
16018
|
-
content: [{ type: "text", text: "
|
|
17430
|
+
content: [{ type: "text", text: "[OK] No stale observations. All active memories are within 50% of their retention period." }]
|
|
16019
17431
|
};
|
|
16020
17432
|
}
|
|
16021
17433
|
const staleLines = [
|
|
@@ -16033,7 +17445,7 @@ Archived memories are hidden from default search but can be found with status: "
|
|
|
16033
17445
|
);
|
|
16034
17446
|
}
|
|
16035
17447
|
staleLines.push("");
|
|
16036
|
-
staleLines.push(">
|
|
17448
|
+
staleLines.push("> [TIP] Stale = past 50% of effective retention. Review or access to keep; otherwise will become archive candidates.");
|
|
16037
17449
|
const staleIds = staleDocs.map((d) => d.observationId);
|
|
16038
17450
|
staleLines.push("");
|
|
16039
17451
|
staleLines.push("### Suggested Actions");
|
|
@@ -16085,11 +17497,11 @@ Archived memories are hidden from default search but can be found with status: "
|
|
|
16085
17497
|
const candidateIds = candidates.map((c) => c.observationId);
|
|
16086
17498
|
lines.push("");
|
|
16087
17499
|
lines.push(`Candidate IDs: [${candidateIds.slice(0, 20).join(", ")}]${candidateIds.length > 20 ? ` \u2026 (${candidateIds.length} total)` : ""}`);
|
|
16088
|
-
lines.push(`>
|
|
17500
|
+
lines.push(`> [TIP] Use \`memorix_retention\` with \`action: "archive"\` to move all, or \`memorix_resolve\` with specific IDs.`);
|
|
16089
17501
|
lines.push("");
|
|
16090
17502
|
}
|
|
16091
17503
|
if (summary.stale > 0) {
|
|
16092
|
-
lines.push(`>
|
|
17504
|
+
lines.push(`> [TASK] ${summary.stale} stale observation(s) \u2014 use \`memorix_retention\` with \`action: "stale"\` for full details.`);
|
|
16093
17505
|
lines.push("");
|
|
16094
17506
|
}
|
|
16095
17507
|
lines.push(`### Top 5 Most Relevant`);
|
|
@@ -16120,12 +17532,12 @@ Archived memories are hidden from default search but can be found with status: "
|
|
|
16120
17532
|
return {
|
|
16121
17533
|
content: [{
|
|
16122
17534
|
type: "text",
|
|
16123
|
-
text: "
|
|
17535
|
+
text: "[STATS] Formation Pipeline: No metrics collected yet.\nStore some observations to start collecting runtime data."
|
|
16124
17536
|
}]
|
|
16125
17537
|
};
|
|
16126
17538
|
}
|
|
16127
17539
|
const lines = [
|
|
16128
|
-
"
|
|
17540
|
+
"[STATS] **Formation Pipeline Metrics**",
|
|
16129
17541
|
"",
|
|
16130
17542
|
`**Total observations processed:** ${summary.total}`,
|
|
16131
17543
|
`**Average value score:** ${summary.avgValueScore.toFixed(3)}`,
|
|
@@ -16141,7 +17553,7 @@ Archived memories are hidden from default search but can be found with status: "
|
|
|
16141
17553
|
];
|
|
16142
17554
|
for (const [cat, count2] of Object.entries(summary.categoryBreakdown)) {
|
|
16143
17555
|
const pct = (count2 / summary.total * 100).toFixed(1);
|
|
16144
|
-
const icon = cat === "core" ? "
|
|
17556
|
+
const icon = cat === "core" ? "[CHANGE]" : cat === "contextual" ? "[FIX]" : "[GOTCHA]";
|
|
16145
17557
|
lines.push(`- ${icon} **${cat}:** ${count2} (${pct}%)`);
|
|
16146
17558
|
}
|
|
16147
17559
|
lines.push("", "### Resolution Actions");
|
|
@@ -16183,7 +17595,7 @@ Archived memories are hidden from default search but can be found with status: "
|
|
|
16183
17595
|
};
|
|
16184
17596
|
}
|
|
16185
17597
|
);
|
|
16186
|
-
let enableKG =
|
|
17598
|
+
let enableKG = isToolInProfile("create_entities", toolProfile);
|
|
16187
17599
|
try {
|
|
16188
17600
|
const { homedir: homedir21 } = await import("os");
|
|
16189
17601
|
const { join: join23 } = await import("path");
|
|
@@ -16495,7 +17907,7 @@ Archived memories are hidden from default search but can be found with status: "
|
|
|
16495
17907
|
lines2.push("- No skills found");
|
|
16496
17908
|
}
|
|
16497
17909
|
if (scan.skillConflicts.length > 0) {
|
|
16498
|
-
lines2.push("", `###
|
|
17910
|
+
lines2.push("", `### [WARN] Skill Name Conflicts`);
|
|
16499
17911
|
for (const c of scan.skillConflicts) {
|
|
16500
17912
|
lines2.push(`- **${c.name}**: kept from ${c.kept.sourceAgent}, duplicate in ${c.skipped.sourceAgent}`);
|
|
16501
17913
|
}
|
|
@@ -16641,9 +18053,9 @@ ${skill.content}` }]
|
|
|
16641
18053
|
if (write && target) {
|
|
16642
18054
|
const path18 = engine.writeSkill(sk, target);
|
|
16643
18055
|
if (path18) {
|
|
16644
|
-
lines.push(`-
|
|
18056
|
+
lines.push(`- [OK] **Written**: \`${path18}\``);
|
|
16645
18057
|
} else {
|
|
16646
|
-
lines.push(`-
|
|
18058
|
+
lines.push(`- [ERROR] Failed to write`);
|
|
16647
18059
|
}
|
|
16648
18060
|
}
|
|
16649
18061
|
lines.push("");
|
|
@@ -16700,7 +18112,7 @@ ${skill.content}` }]
|
|
|
16700
18112
|
if (!deleted) {
|
|
16701
18113
|
return { content: [{ type: "text", text: `Mini-skill #${skillId} not found.` }], isError: true };
|
|
16702
18114
|
}
|
|
16703
|
-
return { content: [{ type: "text", text:
|
|
18115
|
+
return { content: [{ type: "text", text: `[OK] Deleted mini-skill #${skillId}.` }] };
|
|
16704
18116
|
}
|
|
16705
18117
|
if (!observationIds || observationIds.length === 0) {
|
|
16706
18118
|
return { content: [{ type: "text", text: "Error: `observationIds` is required for promote action. Use `memorix_search` to find observation IDs." }], isError: true };
|
|
@@ -16717,7 +18129,7 @@ ${skill.content}` }]
|
|
|
16717
18129
|
}
|
|
16718
18130
|
const skill = await promoteToMiniSkill2(projectDir2, project.id, matched, { trigger, instruction, tags });
|
|
16719
18131
|
const lines = [
|
|
16720
|
-
|
|
18132
|
+
`[OK] Created mini-skill #${skill.id}`,
|
|
16721
18133
|
"",
|
|
16722
18134
|
`**${skill.title}**`,
|
|
16723
18135
|
`**Do**: ${skill.instruction}`,
|
|
@@ -16748,7 +18160,7 @@ ${skill.content}` }]
|
|
|
16748
18160
|
if (action === "preview") {
|
|
16749
18161
|
const clusters = await findConsolidationCandidates2(projectDir2, project.id, { threshold: safeThreshold });
|
|
16750
18162
|
if (clusters.length === 0) {
|
|
16751
|
-
return { content: [{ type: "text", text: "
|
|
18163
|
+
return { content: [{ type: "text", text: "[OK] No consolidation candidates found. Your memories are already clean!" }] };
|
|
16752
18164
|
}
|
|
16753
18165
|
const lines2 = [`## Consolidation Preview`, `Found **${clusters.length}** clusters to merge:`, ""];
|
|
16754
18166
|
for (let i = 0; i < clusters.length; i++) {
|
|
@@ -16764,7 +18176,7 @@ ${skill.content}` }]
|
|
|
16764
18176
|
}
|
|
16765
18177
|
const result = await executeConsolidation2(projectDir2, project.id, { threshold: safeThreshold });
|
|
16766
18178
|
if (result.clustersFound === 0) {
|
|
16767
|
-
return { content: [{ type: "text", text: "
|
|
18179
|
+
return { content: [{ type: "text", text: "[OK] No consolidation needed. Memories are already clean!" }] };
|
|
16768
18180
|
}
|
|
16769
18181
|
const lines = [
|
|
16770
18182
|
`## Consolidation Complete`,
|
|
@@ -16783,18 +18195,20 @@ ${skill.content}` }]
|
|
|
16783
18195
|
"memorix_session_start",
|
|
16784
18196
|
{
|
|
16785
18197
|
title: "Start Session",
|
|
16786
|
-
description: "Start a new coding session. Returns context from previous sessions so you can resume work seamlessly. Call this at the beginning of a session to track activity and get injected context. Any previous active session for this project will be auto-closed.\n\nIMPORTANT for HTTP/control-plane mode: pass `projectRoot` with the absolute path to your workspace root (e.g., the directory open in your IDE). Memorix uses this to detect the git project and bind this session to the correct project context. Without it, project-scoped tools will be disabled.",
|
|
18198
|
+
description: "Start a new coding session. Returns context from previous sessions so you can resume work seamlessly. Call this at the beginning of a session to track activity and get injected context. Any previous active session for this project will be auto-closed. By default this is lightweight: it binds the project, opens a session, and injects context only. Team identity is opt-in via `joinTeam: true` or a separate `team_manage` join call.\n\nIMPORTANT for HTTP/control-plane mode: pass `projectRoot` with the absolute path to your workspace root (e.g., the directory open in your IDE). Memorix uses this to detect the git project and bind this session to the correct project context. Without it, project-scoped tools will be disabled.",
|
|
16787
18199
|
inputSchema: {
|
|
16788
18200
|
sessionId: z2.string().optional().describe("Custom session ID (auto-generated if omitted)"),
|
|
16789
18201
|
agent: z2.string().optional().describe('Agent/IDE name (e.g., "cursor", "windsurf", "claude-code")'),
|
|
16790
|
-
agentType: z2.string().optional().describe('Agent type for
|
|
16791
|
-
instanceId: z2.string().optional().describe("Stable instance ID for
|
|
18202
|
+
agentType: z2.string().optional().describe('Agent type used for optional Agent Team identity mapping (e.g., "windsurf", "cursor").'),
|
|
18203
|
+
instanceId: z2.string().optional().describe("Stable instance ID for optional Agent Team identity across restarts. If omitted with joinTeam=true, Memorix derives a deterministic fallback from the project and agent identity."),
|
|
18204
|
+
joinTeam: z2.boolean().optional().describe("If true, also join the autonomous agent team for this session. Defaults to false."),
|
|
18205
|
+
role: z2.string().optional().describe("Explicit role override used only when joinTeam=true."),
|
|
16792
18206
|
projectRoot: z2.string().optional().describe(
|
|
16793
|
-
"Absolute path to the workspace/project root directory (e.g., the folder open in your IDE). Memorix will detect the git project from this path and bind this session to it. Required for HTTP transport when multiple projects are open simultaneously."
|
|
18207
|
+
"Absolute path to the workspace/project root directory (e.g., the folder open in your IDE). Memorix will detect the git project from this path and bind this session to it. Required for HTTP transport when multiple projects are open simultaneously or when rebinding an existing control-plane session."
|
|
16794
18208
|
)
|
|
16795
18209
|
}
|
|
16796
18210
|
},
|
|
16797
|
-
async ({ sessionId, agent, agentType, instanceId, projectRoot: explicitRoot }) => {
|
|
18211
|
+
async ({ sessionId, agent, agentType, instanceId, joinTeam, role, projectRoot: explicitRoot }) => {
|
|
16798
18212
|
currentAgentId = void 0;
|
|
16799
18213
|
if (explicitRoot && typeof explicitRoot === "string") {
|
|
16800
18214
|
let bound = await switchProject(explicitRoot);
|
|
@@ -16841,18 +18255,27 @@ Ensure the path points to a directory containing a .git folder (or a subdirector
|
|
|
16841
18255
|
const { startSession: startSession2 } = await Promise.resolve().then(() => (init_session(), session_exports));
|
|
16842
18256
|
const result = await startSession2(projectDir2, project.id, { sessionId, agent });
|
|
16843
18257
|
const llmStatus = isLLMEnabled() ? `LLM enhanced mode: ${getLLMConfig()?.provider}/${getLLMConfig()?.model} (fact extraction + auto-dedup active)` : "LLM mode: off (set MEMORIX_LLM_API_KEY to enable enhanced memory quality)";
|
|
18258
|
+
const shouldJoinTeam = !!joinTeam;
|
|
16844
18259
|
let registeredAgent = null;
|
|
16845
18260
|
let watermarkInfo = "";
|
|
16846
18261
|
let rescueInfo = "";
|
|
18262
|
+
let teamJoinNotice = "";
|
|
16847
18263
|
try {
|
|
16848
|
-
if (
|
|
18264
|
+
if (!teamFeaturesEnabled && shouldJoinTeam) {
|
|
18265
|
+
teamJoinNotice = "Team join skipped: the current tool profile does not expose Agent Team tools.";
|
|
18266
|
+
} else if (shouldJoinTeam && typeof teamStore !== "undefined" && (agent || agentType)) {
|
|
16849
18267
|
const { AGENT_TYPE_ROLE_MAP: AGENT_TYPE_ROLE_MAP2 } = await Promise.resolve().then(() => (init_team_store(), team_store_exports));
|
|
16850
18268
|
const resolvedAgentType = agentType || agent || "unknown";
|
|
16851
|
-
const resolvedRole = (resolvedAgentType ? AGENT_TYPE_ROLE_MAP2[resolvedAgentType] : void 0) || "engineer";
|
|
18269
|
+
const resolvedRole = role || (resolvedAgentType ? AGENT_TYPE_ROLE_MAP2[resolvedAgentType] : void 0) || "engineer";
|
|
18270
|
+
const resolvedInstanceId = instanceId || createDeterministicInstanceId(
|
|
18271
|
+
project.id,
|
|
18272
|
+
resolvedAgentType,
|
|
18273
|
+
agent || agentType || void 0
|
|
18274
|
+
);
|
|
16852
18275
|
registeredAgent = teamStore.registerAgent({
|
|
16853
18276
|
projectId: project.id,
|
|
16854
18277
|
agentType: resolvedAgentType,
|
|
16855
|
-
instanceId:
|
|
18278
|
+
instanceId: resolvedInstanceId,
|
|
16856
18279
|
name: agent || agentType || void 0,
|
|
16857
18280
|
role: resolvedRole
|
|
16858
18281
|
});
|
|
@@ -16866,32 +18289,36 @@ Ensure the path points to a directory containing a .git folder (or a subdirector
|
|
|
16866
18289
|
));
|
|
16867
18290
|
const wm = computeWatermark2(lastSeen, currentGen, projectObs.length);
|
|
16868
18291
|
if (wm.newObservationCount > 0) {
|
|
16869
|
-
watermarkInfo =
|
|
18292
|
+
watermarkInfo = `[STATS] ${wm.newObservationCount} new observation(s) in this project since your last session.`;
|
|
16870
18293
|
}
|
|
16871
18294
|
teamStore.updateWatermark(registeredAgent.agent_id, currentGen);
|
|
16872
18295
|
const STALE_TTL_MS = 5 * 60 * 1e3;
|
|
16873
18296
|
const rescuedAgentIds = teamStore.detectAndMarkStale(project.id, STALE_TTL_MS);
|
|
16874
18297
|
if (rescuedAgentIds.length > 0) {
|
|
16875
|
-
rescueInfo =
|
|
18298
|
+
rescueInfo = `[RESCUE] ${rescuedAgentIds.length} stale agent(s) detected and rescued.`;
|
|
16876
18299
|
}
|
|
16877
18300
|
const availableTasks = teamStore.listTasks(project.id, { available: true });
|
|
16878
18301
|
if (availableTasks.length > 0) {
|
|
16879
18302
|
rescueInfo += rescueInfo ? "\n" : "";
|
|
16880
|
-
rescueInfo +=
|
|
18303
|
+
rescueInfo += `[TASK] ${availableTasks.length} task(s) available to claim. Use memorix_poll for details.`;
|
|
16881
18304
|
}
|
|
18305
|
+
} else if (shouldJoinTeam) {
|
|
18306
|
+
teamJoinNotice = "Team join skipped: pass `agent` or `agentType` to create an Agent Team identity.";
|
|
16882
18307
|
}
|
|
16883
18308
|
} catch {
|
|
16884
18309
|
}
|
|
16885
18310
|
const lines = [
|
|
16886
|
-
|
|
18311
|
+
`[OK] Session started: ${result.session.id}`,
|
|
16887
18312
|
`Project: ${project.name} (${project.id})`,
|
|
16888
18313
|
result.session.agent ? `Agent: ${result.session.agent}` : "",
|
|
16889
18314
|
registeredAgent ? `Agent ID: ${registeredAgent.agent_id} (instance: ${registeredAgent.instance_id})` : "",
|
|
18315
|
+
!registeredAgent ? "Team identity: not joined (memory/session context only)" : "",
|
|
16890
18316
|
llmStatus,
|
|
16891
|
-
|
|
16892
|
-
|
|
18317
|
+
teamJoinNotice,
|
|
18318
|
+
registeredAgent ? watermarkInfo : "",
|
|
18319
|
+
registeredAgent ? rescueInfo : "",
|
|
16893
18320
|
"",
|
|
16894
|
-
"
|
|
18321
|
+
"[TIP] Tips: Use `memorix_resolve` to mark completed tasks. Use `progress` param in `memorix_store` for task tracking. Use `topicKey` to prevent duplicate memories.",
|
|
16895
18322
|
""
|
|
16896
18323
|
];
|
|
16897
18324
|
try {
|
|
@@ -16931,27 +18358,27 @@ ${s.instruction}`.toLowerCase();
|
|
|
16931
18358
|
} catch {
|
|
16932
18359
|
}
|
|
16933
18360
|
if (result.previousContext) {
|
|
16934
|
-
lines.push("---", "
|
|
18361
|
+
lines.push("---", "[TASK] **Context from previous sessions:**", "", result.previousContext);
|
|
16935
18362
|
} else {
|
|
16936
18363
|
lines.push("No previous session context found. This appears to be a fresh project.");
|
|
16937
18364
|
}
|
|
16938
18365
|
try {
|
|
16939
|
-
if (typeof teamStore !== "undefined") {
|
|
18366
|
+
if (registeredAgent && teamFeaturesEnabled && typeof teamStore !== "undefined") {
|
|
16940
18367
|
const activeAgents = teamStore.listAgents(project.id, { status: "active" });
|
|
16941
18368
|
if (activeAgents.length > 0) {
|
|
16942
|
-
lines.push("", "---", "
|
|
18369
|
+
lines.push("", "---", "[TEAM] **Team Status:**");
|
|
16943
18370
|
for (const a of activeAgents) {
|
|
16944
|
-
lines.push(`-
|
|
18371
|
+
lines.push(`- [CHANGE] ${a.name}${a.role ? ` (${a.role})` : ""}`);
|
|
16945
18372
|
}
|
|
16946
18373
|
const locks = teamStore.listLocks(project.id);
|
|
16947
18374
|
if (locks.length > 0) {
|
|
16948
|
-
lines.push("", "
|
|
18375
|
+
lines.push("", "[LOCK] **Locked files:**");
|
|
16949
18376
|
for (const l of locks) {
|
|
16950
18377
|
const owner = teamStore.getAgent(l.locked_by);
|
|
16951
18378
|
lines.push(`- ${l.file} \u2014 ${owner?.name ?? l.locked_by.slice(0, 8)}`);
|
|
16952
18379
|
}
|
|
16953
18380
|
}
|
|
16954
|
-
lines.push("", "
|
|
18381
|
+
lines.push("", "[TIP] Use `team_manage` to register, `team_message` to check inbox, `team_task` to see tasks.");
|
|
16955
18382
|
}
|
|
16956
18383
|
}
|
|
16957
18384
|
} catch {
|
|
@@ -16965,7 +18392,7 @@ ${s.instruction}`.toLowerCase();
|
|
|
16965
18392
|
"memorix_session_end",
|
|
16966
18393
|
{
|
|
16967
18394
|
title: "End Session",
|
|
16968
|
-
description: "End a coding session with a structured summary. This summary will be injected into the next session so the next agent can resume work seamlessly.\n\nRecommended summary format:\n## Goal\n[What we were working on]\n\n## Discoveries\n- [Technical findings, gotchas, learnings]\n\n## Accomplished\n-
|
|
18395
|
+
description: "End a coding session with a structured summary. This summary will be injected into the next session so the next agent can resume work seamlessly.\n\nRecommended summary format:\n## Goal\n[What we were working on]\n\n## Discoveries\n- [Technical findings, gotchas, learnings]\n\n## Accomplished\n- [OK] [Completed tasks]\n- [PENDING] [Pending for next session]\n\n## Relevant Files\n- path/to/file \u2014 [what changed]",
|
|
16969
18396
|
inputSchema: {
|
|
16970
18397
|
sessionId: z2.string().describe("Session ID to close (from memorix_session_start)"),
|
|
16971
18398
|
summary: z2.string().optional().describe("Structured session summary (Goal/Discoveries/Accomplished/Files format)")
|
|
@@ -16983,7 +18410,7 @@ ${s.instruction}`.toLowerCase();
|
|
|
16983
18410
|
return {
|
|
16984
18411
|
content: [{
|
|
16985
18412
|
type: "text",
|
|
16986
|
-
text:
|
|
18413
|
+
text: `[OK] Session "${sessionId}" completed.
|
|
16987
18414
|
Duration: ${session.startedAt} \u2192 ${session.endedAt}
|
|
16988
18415
|
${summary ? "Summary saved for next session context injection." : "No summary provided \u2014 consider adding one for better cross-session context."}`
|
|
16989
18416
|
}]
|
|
@@ -17056,7 +18483,7 @@ ${json}
|
|
|
17056
18483
|
}]
|
|
17057
18484
|
};
|
|
17058
18485
|
}
|
|
17059
|
-
if (!jsonStr) return { content: [{ type: "text", text: "
|
|
18486
|
+
if (!jsonStr) return { content: [{ type: "text", text: "[ERROR] data is required for import" }], isError: true };
|
|
17060
18487
|
const { importFromJson: importFromJson2 } = await Promise.resolve().then(() => (init_export_import(), export_import_exports));
|
|
17061
18488
|
let parsed;
|
|
17062
18489
|
try {
|
|
@@ -17188,9 +18615,7 @@ ${json}
|
|
|
17188
18615
|
}
|
|
17189
18616
|
}
|
|
17190
18617
|
console.error(`[memorix] Dashboard staticDir: ${staticDir}`);
|
|
17191
|
-
startDashboard2(projectDir2, portNum, staticDir, project.id, project.name, false, {
|
|
17192
|
-
teamStore
|
|
17193
|
-
}, project.rootPath, projectResolved).then(() => {
|
|
18618
|
+
startDashboard2(projectDir2, portNum, staticDir, project.id, project.name, false, void 0, project.rootPath, projectResolved).then(() => {
|
|
17194
18619
|
dashboardRunning = true;
|
|
17195
18620
|
}).catch((err) => {
|
|
17196
18621
|
console.error("[memorix] Dashboard error:", err);
|
|
@@ -17240,16 +18665,20 @@ ${json}
|
|
|
17240
18665
|
}
|
|
17241
18666
|
}
|
|
17242
18667
|
);
|
|
17243
|
-
const { initTeamStore: initTeamStore2 } = await Promise.resolve().then(() => (init_team_store(), team_store_exports));
|
|
17244
18668
|
let teamStore;
|
|
17245
|
-
|
|
17246
|
-
|
|
17247
|
-
|
|
17248
|
-
|
|
17249
|
-
|
|
17250
|
-
|
|
17251
|
-
|
|
17252
|
-
|
|
18669
|
+
let initTeamStoreForProject;
|
|
18670
|
+
if (teamFeaturesEnabled) {
|
|
18671
|
+
const { initTeamStore: initTeamStore2 } = await Promise.resolve().then(() => (init_team_store(), team_store_exports));
|
|
18672
|
+
initTeamStoreForProject = initTeamStore2;
|
|
18673
|
+
if (sharedTeam?.teamStore) {
|
|
18674
|
+
teamStore = sharedTeam.teamStore;
|
|
18675
|
+
} else {
|
|
18676
|
+
teamStore = await initTeamStore2(projectDir2);
|
|
18677
|
+
}
|
|
18678
|
+
if (!teamStore.getEventBus()) {
|
|
18679
|
+
const { TeamEventBus: TeamEventBus2 } = await Promise.resolve().then(() => (init_event_bus(), event_bus_exports));
|
|
18680
|
+
teamStore.setEventBus(new TeamEventBus2());
|
|
18681
|
+
}
|
|
17253
18682
|
}
|
|
17254
18683
|
server.registerTool(
|
|
17255
18684
|
"team_manage",
|
|
@@ -17284,13 +18713,15 @@ ${json}
|
|
|
17284
18713
|
role: resolvedRole,
|
|
17285
18714
|
capabilities: capabilities ? coerceStringArray(capabilities) : void 0
|
|
17286
18715
|
});
|
|
18716
|
+
currentAgentId = agent.agent_id;
|
|
17287
18717
|
const caps = agent.capabilities ? JSON.parse(agent.capabilities) : [];
|
|
17288
18718
|
return {
|
|
17289
18719
|
content: [{
|
|
17290
18720
|
type: "text",
|
|
17291
|
-
text: `Joined
|
|
18721
|
+
text: `Joined Agent Team as "${agent.name}" (ID: ${agent.agent_id})
|
|
17292
18722
|
Instance ID: ${agent.instance_id}
|
|
17293
18723
|
Role: ${agent.role}
|
|
18724
|
+
This session now attributes Agent Team activity to that identity.
|
|
17294
18725
|
Active agents: ${teamStore.getActiveCount(project.id)}`
|
|
17295
18726
|
}]
|
|
17296
18727
|
};
|
|
@@ -17298,6 +18729,7 @@ Active agents: ${teamStore.getActiveCount(project.id)}`
|
|
|
17298
18729
|
if (action === "leave") {
|
|
17299
18730
|
if (!agentId) return { content: [{ type: "text", text: "agentId is required for leave" }], isError: true };
|
|
17300
18731
|
const left = teamStore.leaveAgent(agentId);
|
|
18732
|
+
if (currentAgentId === agentId) currentAgentId = void 0;
|
|
17301
18733
|
if (!left) return { content: [{ type: "text", text: "Agent not found" }] };
|
|
17302
18734
|
const releasedLocks = teamStore.releaseAllLocks(agentId);
|
|
17303
18735
|
const releasedTasks = teamStore.releaseTasksByAgent(agentId);
|
|
@@ -17321,7 +18753,7 @@ Active agents: ${teamStore.getActiveCount(project.id)}`
|
|
|
17321
18753
|
const lines = occupancy2.map(({ role: role2, activeAgents, vacant }) => {
|
|
17322
18754
|
const agentTypes = JSON.parse(role2.preferred_agent_types);
|
|
17323
18755
|
const agentNames = activeAgents.map((a) => a.name).join(", ") || "vacant";
|
|
17324
|
-
return `${role2.label} (${role2.role_id.split(":").pop()})
|
|
18756
|
+
return `${role2.label} (${role2.role_id.split(":").pop()}) - ${activeAgents.length}/${role2.max_concurrent} filled, ${vacant} vacant
|
|
17325
18757
|
Agents: ${agentNames}
|
|
17326
18758
|
Preferred types: ${agentTypes.join(", ") || "any"}${role2.description ? "\n " + role2.description : ""}`;
|
|
17327
18759
|
});
|
|
@@ -17353,11 +18785,11 @@ ${lines.join("\n\n")}` }] };
|
|
|
17353
18785
|
}
|
|
17354
18786
|
const roleLines = occupancy.map(({ role: role2, activeAgents, vacant }) => {
|
|
17355
18787
|
const agentNames = activeAgents.map((a) => a.name).join(", ") || "vacant";
|
|
17356
|
-
return `${role2.label}: ${activeAgents.length}/${role2.max_concurrent}
|
|
18788
|
+
return `${role2.label}: ${activeAgents.length}/${role2.max_concurrent} - ${agentNames}${vacant > 0 ? ` (${vacant} slot${vacant > 1 ? "s" : ""} open)` : ""}`;
|
|
17357
18789
|
});
|
|
17358
18790
|
const agentLines = agents.map((a) => {
|
|
17359
18791
|
const caps = a.capabilities ? JSON.parse(a.capabilities) : [];
|
|
17360
|
-
return `${a.status === "active" ? "
|
|
18792
|
+
return `${a.status === "active" ? "[active]" : "[inactive]"} ${a.name} (${a.agent_id.slice(0, 8)}) - ${a.role ?? "no role"} [${caps.join(", ") || "-"}]`;
|
|
17361
18793
|
});
|
|
17362
18794
|
return {
|
|
17363
18795
|
content: [{
|
|
@@ -17386,10 +18818,10 @@ ${agentLines.join("\n")}`
|
|
|
17386
18818
|
},
|
|
17387
18819
|
async ({ action, file, agentId }) => {
|
|
17388
18820
|
if (action === "lock") {
|
|
17389
|
-
if (!file || !agentId) return { content: [{ type: "text", text: "
|
|
18821
|
+
if (!file || !agentId) return { content: [{ type: "text", text: "[ERROR] file and agentId are required for lock" }], isError: true };
|
|
17390
18822
|
const agent = teamStore.getAgent(agentId);
|
|
17391
18823
|
if (!agent || agent.status !== "active") {
|
|
17392
|
-
return { content: [{ type: "text", text:
|
|
18824
|
+
return { content: [{ type: "text", text: `[ERROR] Unknown or inactive agent: ${agentId.slice(0, 8)}\u2026` }], isError: true };
|
|
17393
18825
|
}
|
|
17394
18826
|
const result = teamStore.acquireLock(project.id, file, agentId);
|
|
17395
18827
|
if (result.success) return { content: [{ type: "text", text: `Locked: ${file}` }] };
|
|
@@ -17397,7 +18829,7 @@ ${agentLines.join("\n")}`
|
|
|
17397
18829
|
return { content: [{ type: "text", text: `Denied \u2014 locked by ${owner?.name ?? result.lockedBy.slice(0, 8)}` }], isError: true };
|
|
17398
18830
|
}
|
|
17399
18831
|
if (action === "unlock") {
|
|
17400
|
-
if (!file || !agentId) return { content: [{ type: "text", text: "
|
|
18832
|
+
if (!file || !agentId) return { content: [{ type: "text", text: "[ERROR] file and agentId are required for unlock" }], isError: true };
|
|
17401
18833
|
const released = teamStore.releaseLock(project.id, file, agentId);
|
|
17402
18834
|
return { content: [{ type: "text", text: released ? `Unlocked: ${file}` : `Cannot unlock: not owner or not locked` }] };
|
|
17403
18835
|
}
|
|
@@ -17439,7 +18871,7 @@ ${lines.join("\n")}` }] };
|
|
|
17439
18871
|
async ({ action, description: desc, deps, taskId, agentId, result, status, available, metadata, requiredRole, preferredRole }) => {
|
|
17440
18872
|
try {
|
|
17441
18873
|
if (action === "create") {
|
|
17442
|
-
if (!desc) return { content: [{ type: "text", text: "
|
|
18874
|
+
if (!desc) return { content: [{ type: "text", text: "[ERROR] description is required for create" }], isError: true };
|
|
17443
18875
|
let parsedMeta;
|
|
17444
18876
|
if (metadata) {
|
|
17445
18877
|
try {
|
|
@@ -17451,7 +18883,7 @@ ${lines.join("\n")}` }] };
|
|
|
17451
18883
|
const existingTasks = teamStore.listTasks(project.id);
|
|
17452
18884
|
const guard = checkPipelineGuards2({ existingTasks, newTaskMeta: parsedMeta });
|
|
17453
18885
|
if (!guard.allowed) {
|
|
17454
|
-
return { content: [{ type: "text", text:
|
|
18886
|
+
return { content: [{ type: "text", text: `[ERROR] ${guard.reason}` }], isError: true };
|
|
17455
18887
|
}
|
|
17456
18888
|
const task = teamStore.createTask({
|
|
17457
18889
|
projectId: project.id,
|
|
@@ -17467,19 +18899,19 @@ ${lines.join("\n")}` }] };
|
|
|
17467
18899
|
return { content: [{ type: "text", text: `Task created: ${task.task_id.slice(0, 8)}\u2026 "${desc}"${taskDeps.length > 0 ? ` (depends on ${taskDeps.length})` : ""}${roleInfo}` }] };
|
|
17468
18900
|
}
|
|
17469
18901
|
if (action === "claim") {
|
|
17470
|
-
if (!taskId || !agentId) return { content: [{ type: "text", text: "
|
|
18902
|
+
if (!taskId || !agentId) return { content: [{ type: "text", text: "[ERROR] taskId and agentId required for claim" }], isError: true };
|
|
17471
18903
|
const agent = teamStore.getAgent(agentId);
|
|
17472
|
-
if (!agent || agent.status !== "active") return { content: [{ type: "text", text:
|
|
18904
|
+
if (!agent || agent.status !== "active") return { content: [{ type: "text", text: `[ERROR] Unknown or inactive agent` }], isError: true };
|
|
17473
18905
|
const claimResult = teamStore.claimTask(taskId, agentId);
|
|
17474
|
-
if (!claimResult.success) return { content: [{ type: "text", text:
|
|
18906
|
+
if (!claimResult.success) return { content: [{ type: "text", text: `[ERROR] ${claimResult.reason}` }], isError: true };
|
|
17475
18907
|
const hintSuffix = claimResult.hint ? `
|
|
17476
|
-
|
|
18908
|
+
[WARN] ${claimResult.hint}` : "";
|
|
17477
18909
|
return { content: [{ type: "text", text: `Task claimed by ${agent.name}: "${claimResult.task.description}"${hintSuffix}` }] };
|
|
17478
18910
|
}
|
|
17479
18911
|
if (action === "complete") {
|
|
17480
|
-
if (!taskId || !agentId || !result) return { content: [{ type: "text", text: "
|
|
18912
|
+
if (!taskId || !agentId || !result) return { content: [{ type: "text", text: "[ERROR] taskId, agentId, and result required for complete" }], isError: true };
|
|
17481
18913
|
const completeResult = teamStore.completeTask(taskId, agentId, result);
|
|
17482
|
-
if (!completeResult.success) return { content: [{ type: "text", text:
|
|
18914
|
+
if (!completeResult.success) return { content: [{ type: "text", text: `[ERROR] ${completeResult.reason}` }], isError: true };
|
|
17483
18915
|
const completedTask = teamStore.getTask(taskId);
|
|
17484
18916
|
return { content: [{ type: "text", text: `Task completed: "${completedTask?.description ?? taskId}"
|
|
17485
18917
|
Result: ${result}` }] };
|
|
@@ -17496,7 +18928,7 @@ Result: ${result}` }] };
|
|
|
17496
18928
|
return { content: [{ type: "text", text: `Tasks (${list.length}):
|
|
17497
18929
|
${lines.join("\n")}` }] };
|
|
17498
18930
|
} catch (err) {
|
|
17499
|
-
return { content: [{ type: "text", text:
|
|
18931
|
+
return { content: [{ type: "text", text: `[ERROR] ${err.message}` }], isError: true };
|
|
17500
18932
|
}
|
|
17501
18933
|
}
|
|
17502
18934
|
);
|
|
@@ -17519,9 +18951,9 @@ ${lines.join("\n")}` }] };
|
|
|
17519
18951
|
},
|
|
17520
18952
|
async ({ action, from, to, type: msgType, content, agentId, markRead, toRole, handoffStatus }) => {
|
|
17521
18953
|
if (action === "send") {
|
|
17522
|
-
if (!from || !msgType || !content) return { content: [{ type: "text", text: "
|
|
17523
|
-
if (!to && !toRole) return { content: [{ type: "text", text: "
|
|
17524
|
-
if (content.length > 1e4) return { content: [{ type: "text", text: "
|
|
18954
|
+
if (!from || !msgType || !content) return { content: [{ type: "text", text: "[ERROR] from, type, and content required for send" }], isError: true };
|
|
18955
|
+
if (!to && !toRole) return { content: [{ type: "text", text: "[ERROR] either to (agent ID) or toRole is required for send" }], isError: true };
|
|
18956
|
+
if (content.length > 1e4) return { content: [{ type: "text", text: "[ERROR] Message too large (max 10KB)" }], isError: true };
|
|
17525
18957
|
const msg = teamStore.sendMessage({
|
|
17526
18958
|
projectId: project.id,
|
|
17527
18959
|
senderAgentId: from,
|
|
@@ -17531,13 +18963,13 @@ ${lines.join("\n")}` }] };
|
|
|
17531
18963
|
toRole: toRole ?? null,
|
|
17532
18964
|
handoffStatus: handoffStatus ?? (msgType === "handoff" ? "open" : null)
|
|
17533
18965
|
});
|
|
17534
|
-
if ("error" in msg) return { content: [{ type: "text", text:
|
|
18966
|
+
if ("error" in msg) return { content: [{ type: "text", text: `[ERROR] ${msg.error}` }], isError: true };
|
|
17535
18967
|
const target = to ? `agent ${to.slice(0, 8)}\u2026` : `role ${toRole}`;
|
|
17536
18968
|
return { content: [{ type: "text", text: `Message sent (${msgType}) to ${target} | ID: ${msg.id.slice(0, 8)}\u2026${toRole ? ` [role: ${toRole}]` : ""}` }] };
|
|
17537
18969
|
}
|
|
17538
18970
|
if (action === "broadcast") {
|
|
17539
|
-
if (!from || !msgType || !content) return { content: [{ type: "text", text: "
|
|
17540
|
-
if (content.length > 1e4) return { content: [{ type: "text", text: "
|
|
18971
|
+
if (!from || !msgType || !content) return { content: [{ type: "text", text: "[ERROR] from, type, and content required for broadcast" }], isError: true };
|
|
18972
|
+
if (content.length > 1e4) return { content: [{ type: "text", text: "[ERROR] Message too large (max 10KB)" }], isError: true };
|
|
17541
18973
|
const msg = teamStore.sendMessage({
|
|
17542
18974
|
projectId: project.id,
|
|
17543
18975
|
senderAgentId: from,
|
|
@@ -17545,11 +18977,11 @@ ${lines.join("\n")}` }] };
|
|
|
17545
18977
|
type: msgType,
|
|
17546
18978
|
content
|
|
17547
18979
|
});
|
|
17548
|
-
if ("error" in msg) return { content: [{ type: "text", text:
|
|
18980
|
+
if ("error" in msg) return { content: [{ type: "text", text: `[ERROR] ${msg.error}` }], isError: true };
|
|
17549
18981
|
return { content: [{ type: "text", text: `Broadcast (${msgType}) | ID: ${msg.id.slice(0, 8)}\u2026` }] };
|
|
17550
18982
|
}
|
|
17551
18983
|
const inboxId = agentId || from || "";
|
|
17552
|
-
if (!inboxId) return { content: [{ type: "text", text: "
|
|
18984
|
+
if (!inboxId) return { content: [{ type: "text", text: "[ERROR] agentId required for inbox" }], isError: true };
|
|
17553
18985
|
const inbox = teamStore.getInbox(project.id, inboxId);
|
|
17554
18986
|
const unread = teamStore.getUnreadCount(project.id, inboxId);
|
|
17555
18987
|
if (inbox.length === 0) return { content: [{ type: "text", text: "Inbox empty" }] };
|
|
@@ -17571,7 +19003,7 @@ ${lines.join("\n")}` }] };
|
|
|
17571
19003
|
title: "Team Poll \u2014 Situational Awareness",
|
|
17572
19004
|
description: "Get a full snapshot of your team coordination state in one call. Returns: your agent info, watermark (new observations since last session), inbox (unread messages), tasks (your in-progress, available to claim, completed, failed), and team roster (active agents). Use this to decide what to work on next.",
|
|
17573
19005
|
inputSchema: {
|
|
17574
|
-
agentId: z2.string().optional().describe("Your agent ID (from team_manage join or session_start). If omitted, returns project-level overview only."),
|
|
19006
|
+
agentId: z2.string().optional().describe("Your agent ID (from team_manage join or session_start with joinTeam=true). If omitted, returns project-level overview only."),
|
|
17575
19007
|
markInboxRead: z2.boolean().optional().describe("If true, mark all inbox messages as read after returning them.")
|
|
17576
19008
|
}
|
|
17577
19009
|
},
|
|
@@ -17598,13 +19030,13 @@ ${lines.join("\n")}` }] };
|
|
|
17598
19030
|
}
|
|
17599
19031
|
const lines = [];
|
|
17600
19032
|
if (poll.agent) {
|
|
17601
|
-
lines.push(
|
|
19033
|
+
lines.push(`[AGENT] You: ${poll.agent.agentId.slice(0, 8)}\u2026 (${poll.agent.status})`);
|
|
17602
19034
|
}
|
|
17603
19035
|
if (poll.watermark.newObservationCount > 0) {
|
|
17604
|
-
lines.push(
|
|
19036
|
+
lines.push(`[STATS] ${poll.watermark.newObservationCount} new observation(s) since your last session`);
|
|
17605
19037
|
}
|
|
17606
19038
|
if (poll.inbox.unreadCount > 0) {
|
|
17607
|
-
lines.push(
|
|
19039
|
+
lines.push(`[INBOX] ${poll.inbox.unreadCount} unread message(s)`);
|
|
17608
19040
|
for (const m of poll.inbox.messages.slice(-5)) {
|
|
17609
19041
|
const sender = teamStore.getAgent(m.sender_agent_id);
|
|
17610
19042
|
lines.push(` ${m.read_at ? " " : "*"} [${m.type}] from ${sender?.name ?? m.sender_agent_id.slice(0, 8)}: ${m.content.slice(0, 80)}`);
|
|
@@ -17612,21 +19044,21 @@ ${lines.join("\n")}` }] };
|
|
|
17612
19044
|
}
|
|
17613
19045
|
if (poll.tasks.myInProgress.length > 0) {
|
|
17614
19046
|
lines.push(`
|
|
17615
|
-
|
|
19047
|
+
[TOOL] Your in-progress tasks (${poll.tasks.myInProgress.length}):`);
|
|
17616
19048
|
for (const t of poll.tasks.myInProgress) {
|
|
17617
19049
|
lines.push(` [~] ${t.task_id.slice(0, 8)}\u2026 "${t.description}"`);
|
|
17618
19050
|
}
|
|
17619
19051
|
}
|
|
17620
19052
|
if (poll.tasks.availableToClaim.length > 0) {
|
|
17621
19053
|
lines.push(`
|
|
17622
|
-
|
|
19054
|
+
[TASK] Available to claim (${poll.tasks.availableToClaim.length}):`);
|
|
17623
19055
|
for (const t of poll.tasks.availableToClaim) {
|
|
17624
19056
|
lines.push(` [ ] ${t.task_id.slice(0, 8)}\u2026 "${t.description}"`);
|
|
17625
19057
|
}
|
|
17626
19058
|
}
|
|
17627
19059
|
if (poll.tasks.recentlyCompleted.length > 0) {
|
|
17628
19060
|
lines.push(`
|
|
17629
|
-
|
|
19061
|
+
[OK] Completed (${poll.tasks.recentlyCompleted.length}):`);
|
|
17630
19062
|
for (const t of poll.tasks.recentlyCompleted.slice(-5)) {
|
|
17631
19063
|
const who = t.assignee_agent_id ? teamStore.getAgent(t.assignee_agent_id)?.name ?? t.assignee_agent_id.slice(0, 8) : "?";
|
|
17632
19064
|
lines.push(` [x] ${t.task_id.slice(0, 8)}\u2026 "${t.description}" \u2014 by ${who}`);
|
|
@@ -17634,15 +19066,15 @@ ${lines.join("\n")}` }] };
|
|
|
17634
19066
|
}
|
|
17635
19067
|
if (poll.tasks.recentlyFailed.length > 0) {
|
|
17636
19068
|
lines.push(`
|
|
17637
|
-
|
|
19069
|
+
[ERROR] Failed (${poll.tasks.recentlyFailed.length}):`);
|
|
17638
19070
|
for (const t of poll.tasks.recentlyFailed.slice(-3)) {
|
|
17639
19071
|
lines.push(` [!] ${t.task_id.slice(0, 8)}\u2026 "${t.description}" \u2014 ${t.result?.slice(0, 80) ?? "no reason"}`);
|
|
17640
19072
|
}
|
|
17641
19073
|
}
|
|
17642
19074
|
lines.push(`
|
|
17643
|
-
|
|
19075
|
+
[TEAM] Team: ${poll.team.activeAgents.length} active / ${poll.team.totalAgents} total`);
|
|
17644
19076
|
for (const a of poll.team.activeAgents) {
|
|
17645
|
-
lines.push(`
|
|
19077
|
+
lines.push(` - ${a.name} (${a.agent_type}) \u2014 ${a.role ?? "no role"}`);
|
|
17646
19078
|
}
|
|
17647
19079
|
if (lines.length === 0) {
|
|
17648
19080
|
lines.push('No team activity yet. Use team_manage action="join" to register, then team_task to create tasks.');
|
|
@@ -17656,7 +19088,7 @@ ${lines.join("\n")}` }] };
|
|
|
17656
19088
|
title: "Team Handoff \u2014 Agent Context Transfer",
|
|
17657
19089
|
description: "Create a structured handoff artifact when passing work to another agent. The handoff is stored as a durable observation (searchable, immune to archival) and a notification message is sent to the recipient. Use this when completing a task and another agent should continue, or when you want to leave context for whoever works on this next.",
|
|
17658
19090
|
inputSchema: {
|
|
17659
|
-
fromAgentId: z2.string().describe("Your agent ID (from team_manage join or session_start)"),
|
|
19091
|
+
fromAgentId: z2.string().describe("Your agent ID (from team_manage join or session_start with joinTeam=true)"),
|
|
17660
19092
|
summary: z2.string().describe("Human-readable summary of what you did and what needs to happen next"),
|
|
17661
19093
|
context: z2.string().describe("Detailed context for the next agent: what was done, current state, known issues, next steps"),
|
|
17662
19094
|
toAgentId: z2.string().optional().describe("Specific recipient agent ID. Omit to broadcast to all."),
|
|
@@ -17682,7 +19114,7 @@ ${lines.join("\n")}` }] };
|
|
|
17682
19114
|
teamStore
|
|
17683
19115
|
);
|
|
17684
19116
|
const lines = [
|
|
17685
|
-
|
|
19117
|
+
`[OK] Handoff created`,
|
|
17686
19118
|
`Observation: #${result.observationId}`,
|
|
17687
19119
|
`From: ${result.fromAgentId.slice(0, 8)}\u2026`,
|
|
17688
19120
|
result.toAgentId ? `To: ${result.toAgentId.slice(0, 8)}\u2026` : "To: broadcast (any agent)",
|
|
@@ -17735,7 +19167,7 @@ Preview: ${analysis.description.slice(0, 300)}${analysis.description.length > 30
|
|
|
17735
19167
|
return {
|
|
17736
19168
|
content: [{
|
|
17737
19169
|
type: "text",
|
|
17738
|
-
text:
|
|
19170
|
+
text: `[ERROR] Image ingestion failed: ${err instanceof Error ? err.message : String(err)}`
|
|
17739
19171
|
}],
|
|
17740
19172
|
isError: true
|
|
17741
19173
|
};
|
|
@@ -17813,7 +19245,7 @@ fi
|
|
|
17813
19245
|
const totalRules = scan.rulesCount;
|
|
17814
19246
|
const totalWorkflows = scan.workflows.length;
|
|
17815
19247
|
if (totalMCP > 0 || totalSkills > 0 || totalRules > 0 || totalWorkflows > 0) {
|
|
17816
|
-
lines.push("", "---", "
|
|
19248
|
+
lines.push("", "---", "[UPDATED] **Cross-Agent Sync Available**");
|
|
17817
19249
|
for (const [agent, servers] of Object.entries(scan.mcpConfigs)) {
|
|
17818
19250
|
const list = servers;
|
|
17819
19251
|
if (list.length > 0) {
|
|
@@ -17831,11 +19263,11 @@ fi
|
|
|
17831
19263
|
lines.push(`- **${agent}** skills: ${names.join(", ")}`);
|
|
17832
19264
|
}
|
|
17833
19265
|
}
|
|
17834
|
-
if (scan.skillConflicts.length > 0) lines.push(`
|
|
19266
|
+
if (scan.skillConflicts.length > 0) lines.push(` [WARN] ${scan.skillConflicts.length} name conflict(s)`);
|
|
17835
19267
|
if (totalRules > 0) lines.push(`- **${totalRules} rule(s)** found`);
|
|
17836
19268
|
if (totalWorkflows > 0) lines.push(`- **${totalWorkflows} workflow(s)** found`);
|
|
17837
19269
|
lines.push("");
|
|
17838
|
-
lines.push("
|
|
19270
|
+
lines.push("[WARN] IMPORTANT INSTRUCTIONS FOR AI AGENT:");
|
|
17839
19271
|
lines.push("1. Tell the user what configs/skills are available from other agents (list them).");
|
|
17840
19272
|
lines.push("2. Ask which ones they want to sync.");
|
|
17841
19273
|
lines.push("3. DO NOT manually copy files or run shell commands to sync.");
|
|
@@ -18004,27 +19436,40 @@ fi
|
|
|
18004
19436
|
} catch {
|
|
18005
19437
|
}
|
|
18006
19438
|
try {
|
|
19439
|
+
if (!initTeamStoreForProject) throw new Error("Team store init unavailable");
|
|
18007
19440
|
if (sharedTeam?.teamStore) {
|
|
18008
|
-
teamStore = await
|
|
19441
|
+
teamStore = await initTeamStoreForProject(canonicalProjectDir);
|
|
18009
19442
|
if (!teamStore.getEventBus()) {
|
|
18010
19443
|
const { TeamEventBus: TeamEventBus2 } = await Promise.resolve().then(() => (init_event_bus(), event_bus_exports));
|
|
18011
19444
|
teamStore.setEventBus(new TeamEventBus2());
|
|
18012
19445
|
}
|
|
18013
19446
|
} else {
|
|
18014
|
-
teamStore = await
|
|
19447
|
+
teamStore = await initTeamStoreForProject(canonicalProjectDir);
|
|
18015
19448
|
}
|
|
18016
19449
|
} catch {
|
|
18017
19450
|
}
|
|
18018
19451
|
await initializeProjectRuntime("switch");
|
|
18019
19452
|
return true;
|
|
18020
19453
|
};
|
|
19454
|
+
const handleTransportClose = () => {
|
|
19455
|
+
const agentId = currentAgentId;
|
|
19456
|
+
currentAgentId = void 0;
|
|
19457
|
+
if (!teamFeaturesEnabled || !agentId) return;
|
|
19458
|
+
try {
|
|
19459
|
+
teamStore.leaveAgent(agentId);
|
|
19460
|
+
teamStore.releaseAllLocks(agentId);
|
|
19461
|
+
teamStore.releaseTasksByAgent(agentId);
|
|
19462
|
+
} catch {
|
|
19463
|
+
}
|
|
19464
|
+
};
|
|
18021
19465
|
return {
|
|
18022
19466
|
server,
|
|
18023
19467
|
graphManager,
|
|
18024
19468
|
projectId: project.id,
|
|
18025
19469
|
deferredInit,
|
|
18026
19470
|
switchProject,
|
|
18027
|
-
isExplicitlyBound: () => explicitProjectBound
|
|
19471
|
+
isExplicitlyBound: () => explicitProjectBound,
|
|
19472
|
+
handleTransportClose
|
|
18028
19473
|
};
|
|
18029
19474
|
}
|
|
18030
19475
|
|