memorix 1.0.7 → 1.0.8
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 +36 -9
- package/README.md +469 -409
- package/README.zh-CN.md +468 -415
- package/TEAM.md +106 -0
- package/dist/cli/index.js +31325 -26915
- package/dist/cli/index.js.map +1 -1
- package/dist/dashboard/static/app.js +49 -49
- package/dist/dashboard/static/index.html +2 -2
- package/dist/index.js +1061 -234
- package/dist/index.js.map +1 -1
- package/dist/sdk.d.ts +677 -0
- package/dist/sdk.js +18962 -0
- package/dist/sdk.js.map +1 -0
- package/dist/types.d.ts +10 -10
- package/dist/types.js +10 -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 +521 -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,13 +666,11 @@ 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
|
|
|
@@ -1016,16 +1032,16 @@ var init_types = __esm({
|
|
|
1016
1032
|
"use strict";
|
|
1017
1033
|
init_esm_shims();
|
|
1018
1034
|
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": "
|
|
1035
|
+
"session-request": "[SESSION]",
|
|
1036
|
+
"gotcha": "[GOTCHA]",
|
|
1037
|
+
"problem-solution": "[FIX]",
|
|
1038
|
+
"how-it-works": "[INFO]",
|
|
1039
|
+
"what-changed": "[CHANGE]",
|
|
1040
|
+
"discovery": "[DISCOVERY]",
|
|
1041
|
+
"why-it-exists": "[WHY]",
|
|
1042
|
+
"decision": "[DECISION]",
|
|
1043
|
+
"trade-off": "[TRADEOFF]",
|
|
1044
|
+
"reasoning": "[REASONING]"
|
|
1029
1045
|
};
|
|
1030
1046
|
TOPIC_KEY_FAMILIES = {
|
|
1031
1047
|
"architecture": ["architecture", "design", "adr", "structure", "pattern"],
|
|
@@ -1412,7 +1428,7 @@ async function recordMiniSkillUsage(projectDir2, skillIds) {
|
|
|
1412
1428
|
function formatMiniSkillsForInjection(skills) {
|
|
1413
1429
|
if (skills.length === 0) return "";
|
|
1414
1430
|
const lines = [
|
|
1415
|
-
`##
|
|
1431
|
+
`## [SESSION] Project Mini-Skills (${skills.length} active)`,
|
|
1416
1432
|
""
|
|
1417
1433
|
];
|
|
1418
1434
|
for (const skill of skills) {
|
|
@@ -2968,6 +2984,8 @@ var init_intent_detector = __esm({
|
|
|
2968
2984
|
var provider_exports2 = {};
|
|
2969
2985
|
__export(provider_exports2, {
|
|
2970
2986
|
callLLM: () => callLLM,
|
|
2987
|
+
callLLMWithTools: () => callLLMWithTools,
|
|
2988
|
+
callLLMWithToolsStream: () => callLLMWithToolsStream,
|
|
2971
2989
|
getLLMConfig: () => getLLMConfig,
|
|
2972
2990
|
initLLM: () => initLLM,
|
|
2973
2991
|
isLLMEnabled: () => isLLMEnabled,
|
|
@@ -2987,6 +3005,59 @@ function parseLLMTimeoutMs(raw) {
|
|
|
2987
3005
|
if (parsed > LLM_TIMEOUT_MAX_MS) return LLM_TIMEOUT_MAX_MS;
|
|
2988
3006
|
return parsed;
|
|
2989
3007
|
}
|
|
3008
|
+
async function readResponseText(response, signal, maxBytes = MAX_LLM_RESPONSE_BYTES) {
|
|
3009
|
+
const contentLength = response.headers.get("content-length");
|
|
3010
|
+
if (contentLength && Number(contentLength) > maxBytes) {
|
|
3011
|
+
throw new Error(`LLM response too large (${contentLength} bytes)`);
|
|
3012
|
+
}
|
|
3013
|
+
const reader = response.body?.getReader();
|
|
3014
|
+
if (!reader) {
|
|
3015
|
+
signal?.throwIfAborted();
|
|
3016
|
+
return response.text();
|
|
3017
|
+
}
|
|
3018
|
+
const decoder = new TextDecoder();
|
|
3019
|
+
const chunks = [];
|
|
3020
|
+
let bytesRead = 0;
|
|
3021
|
+
const abortReader = () => {
|
|
3022
|
+
void reader.cancel(signal?.reason).catch(() => void 0);
|
|
3023
|
+
};
|
|
3024
|
+
signal?.addEventListener("abort", abortReader, { once: true });
|
|
3025
|
+
try {
|
|
3026
|
+
while (true) {
|
|
3027
|
+
signal?.throwIfAborted();
|
|
3028
|
+
const { value, done } = await reader.read();
|
|
3029
|
+
if (done) break;
|
|
3030
|
+
signal?.throwIfAborted();
|
|
3031
|
+
if (!value) continue;
|
|
3032
|
+
bytesRead += value.byteLength;
|
|
3033
|
+
if (bytesRead > maxBytes) {
|
|
3034
|
+
await reader.cancel("response too large").catch(() => void 0);
|
|
3035
|
+
throw new Error(`LLM response exceeded ${maxBytes} bytes`);
|
|
3036
|
+
}
|
|
3037
|
+
chunks.push(decoder.decode(value, { stream: true }));
|
|
3038
|
+
}
|
|
3039
|
+
chunks.push(decoder.decode());
|
|
3040
|
+
signal?.throwIfAborted();
|
|
3041
|
+
return chunks.join("");
|
|
3042
|
+
} finally {
|
|
3043
|
+
signal?.removeEventListener("abort", abortReader);
|
|
3044
|
+
reader.releaseLock();
|
|
3045
|
+
}
|
|
3046
|
+
}
|
|
3047
|
+
async function readResponseJson(response, signal, maxBytes = MAX_LLM_RESPONSE_BYTES) {
|
|
3048
|
+
const text = await readResponseText(response, signal, maxBytes);
|
|
3049
|
+
return JSON.parse(text);
|
|
3050
|
+
}
|
|
3051
|
+
async function* callLLMWithToolsStream(messages, tools) {
|
|
3052
|
+
if (!currentConfig) {
|
|
3053
|
+
throw new Error("LLM not configured. Set MEMORIX_LLM_API_KEY or OPENAI_API_KEY.");
|
|
3054
|
+
}
|
|
3055
|
+
if (currentConfig.provider === "anthropic") {
|
|
3056
|
+
yield* callAnthropicWithToolsStream(messages, tools);
|
|
3057
|
+
return;
|
|
3058
|
+
}
|
|
3059
|
+
yield* callOpenAIWithToolsStream(messages, tools);
|
|
3060
|
+
}
|
|
2990
3061
|
function initLLM() {
|
|
2991
3062
|
const { getLLMApiKey: getLLMApiKey2, getLLMProvider: getLLMProvider2, getLLMModel: getLLMModel2, getLLMBaseUrl: getLLMBaseUrl2 } = (init_config(), __toCommonJS(config_exports));
|
|
2992
3063
|
const apiKey = getLLMApiKey2();
|
|
@@ -3045,10 +3116,10 @@ async function callOpenAICompatible(systemPrompt, userMessage) {
|
|
|
3045
3116
|
})
|
|
3046
3117
|
});
|
|
3047
3118
|
if (!response.ok) {
|
|
3048
|
-
const error = await response
|
|
3119
|
+
const error = await readResponseText(response, void 0, 64 * 1024).catch(() => "unknown error");
|
|
3049
3120
|
throw new Error(`LLM API error (${response.status}): ${error}`);
|
|
3050
3121
|
}
|
|
3051
|
-
const data = await response
|
|
3122
|
+
const data = await readResponseJson(response);
|
|
3052
3123
|
return {
|
|
3053
3124
|
content: data.choices[0]?.message?.content ?? "",
|
|
3054
3125
|
usage: data.usage ? {
|
|
@@ -3079,10 +3150,10 @@ async function callAnthropic(systemPrompt, userMessage) {
|
|
|
3079
3150
|
})
|
|
3080
3151
|
});
|
|
3081
3152
|
if (!response.ok) {
|
|
3082
|
-
const error = await response
|
|
3153
|
+
const error = await readResponseText(response, void 0, 64 * 1024).catch(() => "unknown error");
|
|
3083
3154
|
throw new Error(`Anthropic API error (${response.status}): ${error}`);
|
|
3084
3155
|
}
|
|
3085
|
-
const data = await response
|
|
3156
|
+
const data = await readResponseJson(response);
|
|
3086
3157
|
return {
|
|
3087
3158
|
content: data.content[0]?.text ?? "",
|
|
3088
3159
|
usage: data.usage ? {
|
|
@@ -3091,7 +3162,418 @@ async function callAnthropic(systemPrompt, userMessage) {
|
|
|
3091
3162
|
} : void 0
|
|
3092
3163
|
};
|
|
3093
3164
|
}
|
|
3094
|
-
|
|
3165
|
+
async function callLLMWithTools(messages, tools, signal) {
|
|
3166
|
+
if (!currentConfig) {
|
|
3167
|
+
throw new Error("LLM not configured. Set MEMORIX_LLM_API_KEY or OPENAI_API_KEY.");
|
|
3168
|
+
}
|
|
3169
|
+
if (currentConfig.provider === "anthropic") {
|
|
3170
|
+
return callAnthropicWithTools(messages, tools, signal);
|
|
3171
|
+
}
|
|
3172
|
+
return callOpenAIWithTools(messages, tools, signal);
|
|
3173
|
+
}
|
|
3174
|
+
async function callOpenAIWithTools(messages, tools, signal) {
|
|
3175
|
+
const config = currentConfig;
|
|
3176
|
+
let base = config.baseUrl.replace(/\/+$/, "");
|
|
3177
|
+
if (!base.endsWith("/v1")) base += "/v1";
|
|
3178
|
+
const url = `${base}/chat/completions`;
|
|
3179
|
+
const openaiMessages = messages.map((msg) => {
|
|
3180
|
+
if (msg.role === "tool") {
|
|
3181
|
+
return { role: "tool", content: msg.content, tool_call_id: msg.toolCallId };
|
|
3182
|
+
}
|
|
3183
|
+
if (msg.role === "assistant" && msg.toolCalls && msg.toolCalls.length > 0) {
|
|
3184
|
+
return {
|
|
3185
|
+
role: "assistant",
|
|
3186
|
+
content: msg.content || null,
|
|
3187
|
+
tool_calls: msg.toolCalls.map((tc) => ({
|
|
3188
|
+
id: tc.id,
|
|
3189
|
+
type: "function",
|
|
3190
|
+
function: { name: tc.name, arguments: tc.arguments }
|
|
3191
|
+
}))
|
|
3192
|
+
};
|
|
3193
|
+
}
|
|
3194
|
+
return { role: msg.role, content: msg.content };
|
|
3195
|
+
});
|
|
3196
|
+
const openaiTools = tools.map((t) => ({
|
|
3197
|
+
type: "function",
|
|
3198
|
+
function: { name: t.name, description: t.description, parameters: t.parameters }
|
|
3199
|
+
}));
|
|
3200
|
+
const fetchSignal = signal ? AbortSignal.any([signal, AbortSignal.timeout(LLM_CALL_TIMEOUT_MS)]) : AbortSignal.timeout(LLM_CALL_TIMEOUT_MS);
|
|
3201
|
+
const response = await fetch(url, {
|
|
3202
|
+
signal: fetchSignal,
|
|
3203
|
+
method: "POST",
|
|
3204
|
+
headers: {
|
|
3205
|
+
"Content-Type": "application/json",
|
|
3206
|
+
"Authorization": `Bearer ${config.apiKey}`
|
|
3207
|
+
},
|
|
3208
|
+
body: JSON.stringify({
|
|
3209
|
+
model: config.model,
|
|
3210
|
+
messages: openaiMessages,
|
|
3211
|
+
tools: openaiTools.length > 0 ? openaiTools : void 0,
|
|
3212
|
+
temperature: 0.3,
|
|
3213
|
+
max_tokens: 2048
|
|
3214
|
+
})
|
|
3215
|
+
});
|
|
3216
|
+
if (!response.ok) {
|
|
3217
|
+
const error = await readResponseText(response, signal, 64 * 1024).catch(() => "unknown error");
|
|
3218
|
+
throw new Error(`LLM tool-call API error (${response.status}): ${error}`);
|
|
3219
|
+
}
|
|
3220
|
+
const data = await readResponseJson(response, signal);
|
|
3221
|
+
const choice = data.choices[0];
|
|
3222
|
+
const content = choice?.message?.content ?? "";
|
|
3223
|
+
const toolCalls = (choice?.message?.tool_calls ?? []).map((tc) => ({
|
|
3224
|
+
id: tc.id,
|
|
3225
|
+
name: tc.function.name,
|
|
3226
|
+
arguments: tc.function.arguments
|
|
3227
|
+
}));
|
|
3228
|
+
const stopReason = choice?.finish_reason === "tool_calls" ? "tool_use" : choice?.finish_reason === "stop" ? "stop" : "unknown";
|
|
3229
|
+
return {
|
|
3230
|
+
content,
|
|
3231
|
+
toolCalls,
|
|
3232
|
+
stopReason,
|
|
3233
|
+
usage: data.usage ? {
|
|
3234
|
+
promptTokens: data.usage.prompt_tokens,
|
|
3235
|
+
completionTokens: data.usage.completion_tokens
|
|
3236
|
+
} : void 0
|
|
3237
|
+
};
|
|
3238
|
+
}
|
|
3239
|
+
async function callAnthropicWithTools(messages, tools, signal) {
|
|
3240
|
+
const config = currentConfig;
|
|
3241
|
+
const url = `${config.baseUrl}/messages`;
|
|
3242
|
+
const systemContent = messages.find((m) => m.role === "system")?.content ?? "";
|
|
3243
|
+
const nonSystemMessages = messages.filter((m) => m.role !== "system");
|
|
3244
|
+
const anthropicMessages = [];
|
|
3245
|
+
for (const msg of nonSystemMessages) {
|
|
3246
|
+
if (msg.role === "user") {
|
|
3247
|
+
anthropicMessages.push({ role: "user", content: msg.content });
|
|
3248
|
+
} else if (msg.role === "assistant") {
|
|
3249
|
+
const contentBlocks = [];
|
|
3250
|
+
if (msg.content) contentBlocks.push({ type: "text", text: msg.content });
|
|
3251
|
+
if (msg.toolCalls) {
|
|
3252
|
+
for (const tc of msg.toolCalls) {
|
|
3253
|
+
contentBlocks.push({
|
|
3254
|
+
type: "tool_use",
|
|
3255
|
+
id: tc.id,
|
|
3256
|
+
name: tc.name,
|
|
3257
|
+
input: JSON.parse(tc.arguments)
|
|
3258
|
+
});
|
|
3259
|
+
}
|
|
3260
|
+
}
|
|
3261
|
+
anthropicMessages.push({ role: "assistant", content: contentBlocks });
|
|
3262
|
+
} else if (msg.role === "tool") {
|
|
3263
|
+
anthropicMessages.push({
|
|
3264
|
+
role: "user",
|
|
3265
|
+
content: [{
|
|
3266
|
+
type: "tool_result",
|
|
3267
|
+
tool_use_id: msg.toolCallId,
|
|
3268
|
+
content: msg.content
|
|
3269
|
+
}]
|
|
3270
|
+
});
|
|
3271
|
+
}
|
|
3272
|
+
}
|
|
3273
|
+
const anthropicTools = tools.map((t) => ({
|
|
3274
|
+
name: t.name,
|
|
3275
|
+
description: t.description,
|
|
3276
|
+
input_schema: t.parameters
|
|
3277
|
+
}));
|
|
3278
|
+
const fetchSignal = signal ? AbortSignal.any([signal, AbortSignal.timeout(LLM_CALL_TIMEOUT_MS)]) : AbortSignal.timeout(LLM_CALL_TIMEOUT_MS);
|
|
3279
|
+
const response = await fetch(url, {
|
|
3280
|
+
signal: fetchSignal,
|
|
3281
|
+
method: "POST",
|
|
3282
|
+
headers: {
|
|
3283
|
+
"Content-Type": "application/json",
|
|
3284
|
+
"x-api-key": config.apiKey,
|
|
3285
|
+
"anthropic-version": "2023-06-01"
|
|
3286
|
+
},
|
|
3287
|
+
body: JSON.stringify({
|
|
3288
|
+
model: config.model,
|
|
3289
|
+
system: systemContent,
|
|
3290
|
+
messages: anthropicMessages,
|
|
3291
|
+
tools: anthropicTools.length > 0 ? anthropicTools : void 0,
|
|
3292
|
+
temperature: 0.3,
|
|
3293
|
+
max_tokens: 2048
|
|
3294
|
+
})
|
|
3295
|
+
});
|
|
3296
|
+
if (!response.ok) {
|
|
3297
|
+
const error = await readResponseText(response, signal, 64 * 1024).catch(() => "unknown error");
|
|
3298
|
+
throw new Error(`Anthropic tool-call API error (${response.status}): ${error}`);
|
|
3299
|
+
}
|
|
3300
|
+
const data = await readResponseJson(response, signal);
|
|
3301
|
+
let content = "";
|
|
3302
|
+
const toolCalls = [];
|
|
3303
|
+
for (const block of data.content) {
|
|
3304
|
+
if (block.type === "text" && block.text) {
|
|
3305
|
+
content += block.text;
|
|
3306
|
+
} else if (block.type === "tool_use" && block.id && block.name) {
|
|
3307
|
+
toolCalls.push({
|
|
3308
|
+
id: block.id,
|
|
3309
|
+
name: block.name,
|
|
3310
|
+
arguments: JSON.stringify(block.input ?? {})
|
|
3311
|
+
});
|
|
3312
|
+
}
|
|
3313
|
+
}
|
|
3314
|
+
const stopReason = data.stop_reason === "tool_use" ? "tool_use" : data.stop_reason === "end_turn" ? "end_turn" : "unknown";
|
|
3315
|
+
return {
|
|
3316
|
+
content,
|
|
3317
|
+
toolCalls,
|
|
3318
|
+
stopReason,
|
|
3319
|
+
usage: data.usage ? {
|
|
3320
|
+
promptTokens: data.usage.input_tokens,
|
|
3321
|
+
completionTokens: data.usage.output_tokens
|
|
3322
|
+
} : void 0
|
|
3323
|
+
};
|
|
3324
|
+
}
|
|
3325
|
+
async function* callOpenAIWithToolsStream(messages, tools) {
|
|
3326
|
+
const config = currentConfig;
|
|
3327
|
+
let base = config.baseUrl.replace(/\/+$/, "");
|
|
3328
|
+
if (!base.endsWith("/v1")) base += "/v1";
|
|
3329
|
+
const url = `${base}/chat/completions`;
|
|
3330
|
+
const openaiMessages = messages.map((msg) => {
|
|
3331
|
+
if (msg.role === "tool") {
|
|
3332
|
+
return { role: "tool", content: msg.content, tool_call_id: msg.toolCallId };
|
|
3333
|
+
}
|
|
3334
|
+
if (msg.role === "assistant" && msg.toolCalls && msg.toolCalls.length > 0) {
|
|
3335
|
+
return {
|
|
3336
|
+
role: "assistant",
|
|
3337
|
+
content: msg.content || null,
|
|
3338
|
+
tool_calls: msg.toolCalls.map((tc) => ({
|
|
3339
|
+
id: tc.id,
|
|
3340
|
+
type: "function",
|
|
3341
|
+
function: { name: tc.name, arguments: tc.arguments }
|
|
3342
|
+
}))
|
|
3343
|
+
};
|
|
3344
|
+
}
|
|
3345
|
+
return { role: msg.role, content: msg.content };
|
|
3346
|
+
});
|
|
3347
|
+
const openaiTools = tools.map((t) => ({
|
|
3348
|
+
type: "function",
|
|
3349
|
+
function: { name: t.name, description: t.description, parameters: t.parameters }
|
|
3350
|
+
}));
|
|
3351
|
+
const response = await fetch(url, {
|
|
3352
|
+
signal: AbortSignal.timeout(LLM_CALL_TIMEOUT_MS * 2),
|
|
3353
|
+
// longer timeout for streaming
|
|
3354
|
+
method: "POST",
|
|
3355
|
+
headers: {
|
|
3356
|
+
"Content-Type": "application/json",
|
|
3357
|
+
"Authorization": `Bearer ${config.apiKey}`
|
|
3358
|
+
},
|
|
3359
|
+
body: JSON.stringify({
|
|
3360
|
+
model: config.model,
|
|
3361
|
+
messages: openaiMessages,
|
|
3362
|
+
tools: openaiTools.length > 0 ? openaiTools : void 0,
|
|
3363
|
+
temperature: 0.3,
|
|
3364
|
+
max_tokens: 2048,
|
|
3365
|
+
stream: true
|
|
3366
|
+
})
|
|
3367
|
+
});
|
|
3368
|
+
if (!response.ok) {
|
|
3369
|
+
const error = await response.text().catch(() => "unknown error");
|
|
3370
|
+
throw new Error(`LLM streaming API error (${response.status}): ${error}`);
|
|
3371
|
+
}
|
|
3372
|
+
const reader = response.body?.getReader();
|
|
3373
|
+
if (!reader) throw new Error("No response body for streaming");
|
|
3374
|
+
const decoder = new TextDecoder();
|
|
3375
|
+
let fullContent = "";
|
|
3376
|
+
const toolCallMap = /* @__PURE__ */ new Map();
|
|
3377
|
+
let finishReason = "unknown";
|
|
3378
|
+
let buffer = "";
|
|
3379
|
+
try {
|
|
3380
|
+
while (true) {
|
|
3381
|
+
const { done, value } = await reader.read();
|
|
3382
|
+
if (done) break;
|
|
3383
|
+
buffer += decoder.decode(value, { stream: true });
|
|
3384
|
+
const lines = buffer.split("\n");
|
|
3385
|
+
buffer = lines.pop() ?? "";
|
|
3386
|
+
for (const line of lines) {
|
|
3387
|
+
const trimmed = line.trim();
|
|
3388
|
+
if (!trimmed || trimmed === "data: [DONE]") continue;
|
|
3389
|
+
if (!trimmed.startsWith("data: ")) continue;
|
|
3390
|
+
try {
|
|
3391
|
+
const chunk = JSON.parse(trimmed.slice(6));
|
|
3392
|
+
const delta = chunk.choices?.[0]?.delta;
|
|
3393
|
+
if (!delta) continue;
|
|
3394
|
+
if (delta.content) {
|
|
3395
|
+
fullContent += delta.content;
|
|
3396
|
+
yield { type: "text", content: delta.content };
|
|
3397
|
+
}
|
|
3398
|
+
if (delta.tool_calls) {
|
|
3399
|
+
for (const tc of delta.tool_calls) {
|
|
3400
|
+
const idx = tc.index ?? 0;
|
|
3401
|
+
if (!toolCallMap.has(idx)) {
|
|
3402
|
+
toolCallMap.set(idx, {
|
|
3403
|
+
id: tc.id ?? "",
|
|
3404
|
+
name: tc.function?.name ?? "",
|
|
3405
|
+
arguments: tc.function?.arguments ?? ""
|
|
3406
|
+
});
|
|
3407
|
+
} else {
|
|
3408
|
+
const existing = toolCallMap.get(idx);
|
|
3409
|
+
if (tc.id) existing.id = tc.id;
|
|
3410
|
+
if (tc.function?.name) existing.name = tc.function.name;
|
|
3411
|
+
if (tc.function?.arguments) existing.arguments += tc.function.arguments;
|
|
3412
|
+
}
|
|
3413
|
+
}
|
|
3414
|
+
}
|
|
3415
|
+
if (chunk.choices?.[0]?.finish_reason) {
|
|
3416
|
+
finishReason = chunk.choices[0].finish_reason;
|
|
3417
|
+
}
|
|
3418
|
+
} catch {
|
|
3419
|
+
}
|
|
3420
|
+
}
|
|
3421
|
+
}
|
|
3422
|
+
} finally {
|
|
3423
|
+
reader.releaseLock();
|
|
3424
|
+
}
|
|
3425
|
+
const toolCalls = [...toolCallMap.values()].map((tc) => ({
|
|
3426
|
+
id: tc.id,
|
|
3427
|
+
name: tc.name,
|
|
3428
|
+
arguments: tc.arguments
|
|
3429
|
+
}));
|
|
3430
|
+
for (const tc of toolCalls) {
|
|
3431
|
+
yield { type: "tool_call", toolCall: tc };
|
|
3432
|
+
}
|
|
3433
|
+
const stopReason = finishReason === "tool_calls" ? "tool_use" : finishReason === "stop" ? "stop" : "unknown";
|
|
3434
|
+
yield {
|
|
3435
|
+
type: "done",
|
|
3436
|
+
response: {
|
|
3437
|
+
content: fullContent,
|
|
3438
|
+
toolCalls,
|
|
3439
|
+
stopReason
|
|
3440
|
+
}
|
|
3441
|
+
};
|
|
3442
|
+
}
|
|
3443
|
+
async function* callAnthropicWithToolsStream(messages, tools) {
|
|
3444
|
+
const config = currentConfig;
|
|
3445
|
+
const url = `${config.baseUrl}/messages`;
|
|
3446
|
+
const systemContent = messages.find((m) => m.role === "system")?.content ?? "";
|
|
3447
|
+
const nonSystemMessages = messages.filter((m) => m.role !== "system");
|
|
3448
|
+
const anthropicMessages = [];
|
|
3449
|
+
for (const msg of nonSystemMessages) {
|
|
3450
|
+
if (msg.role === "user") {
|
|
3451
|
+
anthropicMessages.push({ role: "user", content: msg.content });
|
|
3452
|
+
} else if (msg.role === "assistant") {
|
|
3453
|
+
const contentBlocks = [];
|
|
3454
|
+
if (msg.content) contentBlocks.push({ type: "text", text: msg.content });
|
|
3455
|
+
if (msg.toolCalls) {
|
|
3456
|
+
for (const tc of msg.toolCalls) {
|
|
3457
|
+
contentBlocks.push({
|
|
3458
|
+
type: "tool_use",
|
|
3459
|
+
id: tc.id,
|
|
3460
|
+
name: tc.name,
|
|
3461
|
+
input: JSON.parse(tc.arguments)
|
|
3462
|
+
});
|
|
3463
|
+
}
|
|
3464
|
+
}
|
|
3465
|
+
anthropicMessages.push({ role: "assistant", content: contentBlocks });
|
|
3466
|
+
} else if (msg.role === "tool") {
|
|
3467
|
+
anthropicMessages.push({
|
|
3468
|
+
role: "user",
|
|
3469
|
+
content: [{
|
|
3470
|
+
type: "tool_result",
|
|
3471
|
+
tool_use_id: msg.toolCallId,
|
|
3472
|
+
content: msg.content
|
|
3473
|
+
}]
|
|
3474
|
+
});
|
|
3475
|
+
}
|
|
3476
|
+
}
|
|
3477
|
+
const anthropicTools = tools.map((t) => ({
|
|
3478
|
+
name: t.name,
|
|
3479
|
+
description: t.description,
|
|
3480
|
+
input_schema: t.parameters
|
|
3481
|
+
}));
|
|
3482
|
+
const response = await fetch(url, {
|
|
3483
|
+
signal: AbortSignal.timeout(LLM_CALL_TIMEOUT_MS * 2),
|
|
3484
|
+
method: "POST",
|
|
3485
|
+
headers: {
|
|
3486
|
+
"Content-Type": "application/json",
|
|
3487
|
+
"x-api-key": config.apiKey,
|
|
3488
|
+
"anthropic-version": "2023-06-01"
|
|
3489
|
+
},
|
|
3490
|
+
body: JSON.stringify({
|
|
3491
|
+
model: config.model,
|
|
3492
|
+
system: systemContent,
|
|
3493
|
+
messages: anthropicMessages,
|
|
3494
|
+
tools: anthropicTools.length > 0 ? anthropicTools : void 0,
|
|
3495
|
+
temperature: 0.3,
|
|
3496
|
+
max_tokens: 2048,
|
|
3497
|
+
stream: true
|
|
3498
|
+
})
|
|
3499
|
+
});
|
|
3500
|
+
if (!response.ok) {
|
|
3501
|
+
const error = await response.text().catch(() => "unknown error");
|
|
3502
|
+
throw new Error(`Anthropic streaming API error (${response.status}): ${error}`);
|
|
3503
|
+
}
|
|
3504
|
+
const reader = response.body?.getReader();
|
|
3505
|
+
if (!reader) throw new Error("No response body for streaming");
|
|
3506
|
+
const decoder = new TextDecoder();
|
|
3507
|
+
let fullContent = "";
|
|
3508
|
+
const toolCalls = [];
|
|
3509
|
+
let currentToolId = "";
|
|
3510
|
+
let currentToolName = "";
|
|
3511
|
+
let currentToolInput = "";
|
|
3512
|
+
let stopReason = "unknown";
|
|
3513
|
+
let buffer = "";
|
|
3514
|
+
try {
|
|
3515
|
+
while (true) {
|
|
3516
|
+
const { done, value } = await reader.read();
|
|
3517
|
+
if (done) break;
|
|
3518
|
+
buffer += decoder.decode(value, { stream: true });
|
|
3519
|
+
const lines = buffer.split("\n");
|
|
3520
|
+
buffer = lines.pop() ?? "";
|
|
3521
|
+
for (const line of lines) {
|
|
3522
|
+
const trimmed = line.trim();
|
|
3523
|
+
if (!trimmed.startsWith("data: ")) continue;
|
|
3524
|
+
try {
|
|
3525
|
+
const event = JSON.parse(trimmed.slice(6));
|
|
3526
|
+
if (event.type === "content_block_delta") {
|
|
3527
|
+
const delta = event.delta;
|
|
3528
|
+
if (delta?.type === "text_delta" && delta.text) {
|
|
3529
|
+
fullContent += delta.text;
|
|
3530
|
+
yield { type: "text", content: delta.text };
|
|
3531
|
+
} else if (delta?.type === "input_json_delta" && delta.partial_json) {
|
|
3532
|
+
currentToolInput += delta.partial_json;
|
|
3533
|
+
}
|
|
3534
|
+
} else if (event.type === "content_block_start") {
|
|
3535
|
+
const block = event.content_block;
|
|
3536
|
+
if (block?.type === "tool_use") {
|
|
3537
|
+
currentToolId = block.id ?? "";
|
|
3538
|
+
currentToolName = block.name ?? "";
|
|
3539
|
+
currentToolInput = "";
|
|
3540
|
+
}
|
|
3541
|
+
} else if (event.type === "content_block_stop") {
|
|
3542
|
+
if (currentToolId && currentToolName) {
|
|
3543
|
+
const tc = {
|
|
3544
|
+
id: currentToolId,
|
|
3545
|
+
name: currentToolName,
|
|
3546
|
+
arguments: currentToolInput || "{}"
|
|
3547
|
+
};
|
|
3548
|
+
toolCalls.push(tc);
|
|
3549
|
+
yield { type: "tool_call", toolCall: tc };
|
|
3550
|
+
currentToolId = "";
|
|
3551
|
+
currentToolName = "";
|
|
3552
|
+
currentToolInput = "";
|
|
3553
|
+
}
|
|
3554
|
+
} else if (event.type === "message_delta") {
|
|
3555
|
+
if (event.delta?.stop_reason) {
|
|
3556
|
+
stopReason = event.delta.stop_reason;
|
|
3557
|
+
}
|
|
3558
|
+
}
|
|
3559
|
+
} catch {
|
|
3560
|
+
}
|
|
3561
|
+
}
|
|
3562
|
+
}
|
|
3563
|
+
} finally {
|
|
3564
|
+
reader.releaseLock();
|
|
3565
|
+
}
|
|
3566
|
+
const mappedStopReason = stopReason === "tool_use" ? "tool_use" : stopReason === "end_turn" ? "end_turn" : "unknown";
|
|
3567
|
+
yield {
|
|
3568
|
+
type: "done",
|
|
3569
|
+
response: {
|
|
3570
|
+
content: fullContent,
|
|
3571
|
+
toolCalls,
|
|
3572
|
+
stopReason: mappedStopReason
|
|
3573
|
+
}
|
|
3574
|
+
};
|
|
3575
|
+
}
|
|
3576
|
+
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
3577
|
var init_provider2 = __esm({
|
|
3096
3578
|
"src/llm/provider.ts"() {
|
|
3097
3579
|
"use strict";
|
|
@@ -3099,6 +3581,7 @@ var init_provider2 = __esm({
|
|
|
3099
3581
|
LLM_TIMEOUT_DEFAULT_MS = 3e4;
|
|
3100
3582
|
LLM_TIMEOUT_MIN_MS = 1e3;
|
|
3101
3583
|
LLM_TIMEOUT_MAX_MS = 3e5;
|
|
3584
|
+
MAX_LLM_RESPONSE_BYTES = 2 * 1024 * 1024;
|
|
3102
3585
|
LLM_CALL_TIMEOUT_MS = parseLLMTimeoutMs(process.env.MEMORIX_LLM_TIMEOUT_MS);
|
|
3103
3586
|
PROVIDER_DEFAULTS = {
|
|
3104
3587
|
openai: { baseUrl: "https://api.openai.com/v1", model: "gpt-4.1-nano" },
|
|
@@ -3185,7 +3668,7 @@ function idPriority(id) {
|
|
|
3185
3668
|
return 2;
|
|
3186
3669
|
}
|
|
3187
3670
|
function getRegistryPath(baseDir) {
|
|
3188
|
-
return path8.join(baseDir ?? registryDir ??
|
|
3671
|
+
return path8.join(baseDir ?? registryDir ?? DEFAULT_DATA_DIR, ALIAS_FILE);
|
|
3189
3672
|
}
|
|
3190
3673
|
async function loadRegistry(baseDir) {
|
|
3191
3674
|
if (registryCache) return registryCache;
|
|
@@ -3357,12 +3840,12 @@ function initAliasRegistry(dataDir) {
|
|
|
3357
3840
|
function resetAliasCache() {
|
|
3358
3841
|
registryCache = null;
|
|
3359
3842
|
}
|
|
3360
|
-
var
|
|
3843
|
+
var DEFAULT_DATA_DIR, ALIAS_FILE, registryCache, registryDir;
|
|
3361
3844
|
var init_aliases = __esm({
|
|
3362
3845
|
"src/project/aliases.ts"() {
|
|
3363
3846
|
"use strict";
|
|
3364
3847
|
init_esm_shims();
|
|
3365
|
-
|
|
3848
|
+
DEFAULT_DATA_DIR = process.env.MEMORIX_DATA_DIR || path8.join(os2.homedir(), ".memorix", "data");
|
|
3366
3849
|
ALIAS_FILE = ".project-aliases.json";
|
|
3367
3850
|
registryCache = null;
|
|
3368
3851
|
registryDir = null;
|
|
@@ -3939,7 +4422,7 @@ async function searchObservations(options) {
|
|
|
3939
4422
|
time: formatTime(doc.createdAt),
|
|
3940
4423
|
rawTime: doc.createdAt,
|
|
3941
4424
|
type: obsType,
|
|
3942
|
-
icon: OBSERVATION_ICONS[obsType] ?? "
|
|
4425
|
+
icon: OBSERVATION_ICONS[obsType] ?? "[UNKNOWN]",
|
|
3943
4426
|
title: doc.title,
|
|
3944
4427
|
tokens: doc.tokens,
|
|
3945
4428
|
score: (hit.score ?? 1) * recencyBoost,
|
|
@@ -4164,7 +4647,7 @@ async function searchObservations(options) {
|
|
|
4164
4647
|
} else if (isSynthesized) {
|
|
4165
4648
|
entry.matchedFields = ["synthesized", ...reasons];
|
|
4166
4649
|
} else if (isCore) {
|
|
4167
|
-
entry.matchedFields = ["
|
|
4650
|
+
entry.matchedFields = ["core core", ...reasons];
|
|
4168
4651
|
} else {
|
|
4169
4652
|
entry.matchedFields = reasons;
|
|
4170
4653
|
}
|
|
@@ -4212,7 +4695,7 @@ async function getTimeline(anchorId, projectId, depthBefore = 3, depthAfter = 3)
|
|
|
4212
4695
|
id: obs.id,
|
|
4213
4696
|
time: formatTime(obs.createdAt),
|
|
4214
4697
|
type: obsType,
|
|
4215
|
-
icon: OBSERVATION_ICONS[obsType] ?? "
|
|
4698
|
+
icon: OBSERVATION_ICONS[obsType] ?? "[UNKNOWN]",
|
|
4216
4699
|
title: obs.title,
|
|
4217
4700
|
tokens: obs.tokens,
|
|
4218
4701
|
source: obs.source || void 0,
|
|
@@ -5626,10 +6109,10 @@ function resolveEvidenceBasis(fields) {
|
|
|
5626
6109
|
function evidenceBasisLine(basis, commitHash) {
|
|
5627
6110
|
if (basis === "repository") {
|
|
5628
6111
|
const commit = commitHash ? ` \u2014 commit ${commitHash.substring(0, 7)}` : "";
|
|
5629
|
-
return
|
|
6112
|
+
return `[OK] Repository-backed${commit}`;
|
|
5630
6113
|
}
|
|
5631
6114
|
if (basis === "synthesized") {
|
|
5632
|
-
return "
|
|
6115
|
+
return "[SYNTHESIZED] Synthesized \u2014 explicit analysis citing repository evidence";
|
|
5633
6116
|
}
|
|
5634
6117
|
return "";
|
|
5635
6118
|
}
|
|
@@ -6157,10 +6640,10 @@ var init_extract = __esm({
|
|
|
6157
6640
|
pattern: /\b([A-Z][a-zA-Z_-]{2,30})\s*[:=]\s*([^\n,;]{2,60})/g,
|
|
6158
6641
|
format: (m) => `${m[1]}: ${m[2].trim()}`
|
|
6159
6642
|
},
|
|
6160
|
-
// Arrow notation (e.g., "MySQL
|
|
6643
|
+
// Arrow notation (e.g., "MySQL -> PostgreSQL", "v1.0 -> v2.0")
|
|
6161
6644
|
{
|
|
6162
|
-
pattern: /\b(\S{2,30})\s*
|
|
6163
|
-
format: (m) => `${m[1]}
|
|
6645
|
+
pattern: /\b(\S{2,30})\s*(?:->|=>|>)\s*(\S{2,30})/g,
|
|
6646
|
+
format: (m) => `${m[1]} -> ${m[2]}`
|
|
6164
6647
|
},
|
|
6165
6648
|
// Version numbers (e.g., "v1.2.3", "version 2.0")
|
|
6166
6649
|
{
|
|
@@ -6820,16 +7303,36 @@ function getMetricsSummary() {
|
|
|
6820
7303
|
async function runFormation(input, config) {
|
|
6821
7304
|
const startTime = Date.now();
|
|
6822
7305
|
let stagesCompleted = 0;
|
|
7306
|
+
const stageDurationsMs = {};
|
|
7307
|
+
const emitStageEvent = (stage, status, stageDurationMs) => {
|
|
7308
|
+
try {
|
|
7309
|
+
config.onStageEvent?.({
|
|
7310
|
+
stage,
|
|
7311
|
+
status,
|
|
7312
|
+
stageDurationMs,
|
|
7313
|
+
totalElapsedMs: Date.now() - startTime
|
|
7314
|
+
});
|
|
7315
|
+
} catch {
|
|
7316
|
+
}
|
|
7317
|
+
};
|
|
6823
7318
|
const existingEntities = config.getEntityNames();
|
|
7319
|
+
const extractStartTime = Date.now();
|
|
7320
|
+
emitStageEvent("extract", "start");
|
|
6824
7321
|
const extraction = await runExtract(input, existingEntities, config.useLLM);
|
|
7322
|
+
stageDurationsMs.extract = Date.now() - extractStartTime;
|
|
7323
|
+
emitStageEvent("extract", "success", stageDurationsMs.extract);
|
|
6825
7324
|
stagesCompleted = 1;
|
|
6826
7325
|
let resolution;
|
|
6827
7326
|
if (input.topicKey) {
|
|
7327
|
+
stageDurationsMs.resolve = 0;
|
|
7328
|
+
emitStageEvent("resolve", "skipped", 0);
|
|
6828
7329
|
resolution = {
|
|
6829
7330
|
action: "new",
|
|
6830
7331
|
reason: "TopicKey upsert \u2014 bypasses resolve stage"
|
|
6831
7332
|
};
|
|
6832
7333
|
} else {
|
|
7334
|
+
const resolveStartTime = Date.now();
|
|
7335
|
+
emitStageEvent("resolve", "start");
|
|
6833
7336
|
resolution = await runResolve(
|
|
6834
7337
|
extraction,
|
|
6835
7338
|
input.projectId,
|
|
@@ -6837,9 +7340,15 @@ async function runFormation(input, config) {
|
|
|
6837
7340
|
config.getObservation,
|
|
6838
7341
|
config.useLLM
|
|
6839
7342
|
);
|
|
7343
|
+
stageDurationsMs.resolve = Date.now() - resolveStartTime;
|
|
7344
|
+
emitStageEvent("resolve", "success", stageDurationsMs.resolve);
|
|
6840
7345
|
}
|
|
6841
7346
|
stagesCompleted = 2;
|
|
7347
|
+
const evaluateStartTime = Date.now();
|
|
7348
|
+
emitStageEvent("evaluate", "start");
|
|
6842
7349
|
const evaluation = runEvaluate(extraction);
|
|
7350
|
+
stageDurationsMs.evaluate = Date.now() - evaluateStartTime;
|
|
7351
|
+
emitStageEvent("evaluate", "success", stageDurationsMs.evaluate);
|
|
6843
7352
|
stagesCompleted = 3;
|
|
6844
7353
|
const durationMs = Date.now() - startTime;
|
|
6845
7354
|
const formed = {
|
|
@@ -6858,7 +7367,8 @@ async function runFormation(input, config) {
|
|
|
6858
7367
|
mode: config.useLLM ? "llm" : "rules",
|
|
6859
7368
|
durationMs,
|
|
6860
7369
|
stagesCompleted,
|
|
6861
|
-
shadow: config.mode === "shadow"
|
|
7370
|
+
shadow: config.mode === "shadow",
|
|
7371
|
+
stageDurationsMs
|
|
6862
7372
|
},
|
|
6863
7373
|
// Governance fields
|
|
6864
7374
|
governance: {
|
|
@@ -7139,7 +7649,7 @@ async function getSessionContext(projectDir2, projectId, limit = 3) {
|
|
|
7139
7649
|
lines.push("*Recent activity signals and search guidance for this session.*");
|
|
7140
7650
|
if (l1HookObs.length > 0) {
|
|
7141
7651
|
for (const obs of l1HookObs) {
|
|
7142
|
-
lines.push(
|
|
7652
|
+
lines.push(`[HOOK] ${redactCredentials(obs.title)}`);
|
|
7143
7653
|
}
|
|
7144
7654
|
lines.push("");
|
|
7145
7655
|
}
|
|
@@ -7157,7 +7667,7 @@ async function getSessionContext(projectDir2, projectId, limit = 3) {
|
|
|
7157
7667
|
hints.push(`${totalHookCount} hook trace(s) available \u2014 use \`memorix_timeline\` for activity expansion`);
|
|
7158
7668
|
}
|
|
7159
7669
|
for (const hint of hints) {
|
|
7160
|
-
lines.push(
|
|
7670
|
+
lines.push(`[TIP] ${hint}`);
|
|
7161
7671
|
}
|
|
7162
7672
|
lines.push("");
|
|
7163
7673
|
}
|
|
@@ -7184,7 +7694,7 @@ async function getSessionContext(projectDir2, projectId, limit = 3) {
|
|
|
7184
7694
|
lines.push("## Key Project Memories");
|
|
7185
7695
|
lines.push("*Durable working context \u2014 explicit decisions, gotchas, and discoveries.*");
|
|
7186
7696
|
for (const obs of l2Obs) {
|
|
7187
|
-
const emoji = TYPE_EMOJI[obs.type] ?? "
|
|
7697
|
+
const emoji = TYPE_EMOJI[obs.type] ?? "[PIN]";
|
|
7188
7698
|
const fact = obs.facts?.[0] ? ` \u2014 ${redactCredentials(obs.facts[0])}` : "";
|
|
7189
7699
|
lines.push(`${emoji} ${redactCredentials(obs.title)}${fact}`);
|
|
7190
7700
|
}
|
|
@@ -7204,10 +7714,10 @@ async function getSessionContext(projectDir2, projectId, limit = 3) {
|
|
|
7204
7714
|
}
|
|
7205
7715
|
const l3Lines = [];
|
|
7206
7716
|
if (l3GitCount > 0) {
|
|
7207
|
-
l3Lines.push(
|
|
7717
|
+
l3Lines.push(`[PIN] ${l3GitCount} git-memory item(s) \u2014 use \`memorix_search\` to retrieve repository evidence`);
|
|
7208
7718
|
}
|
|
7209
7719
|
if (totalHookCount > 0) {
|
|
7210
|
-
l3Lines.push(
|
|
7720
|
+
l3Lines.push(`[HOOK] ${totalHookCount} hook trace(s) \u2014 use \`memorix_timeline\` for full activity expansion`);
|
|
7211
7721
|
}
|
|
7212
7722
|
if (l3Lines.length > 0) {
|
|
7213
7723
|
lines.push("## L3 Evidence");
|
|
@@ -7247,15 +7757,15 @@ var init_session = __esm({
|
|
|
7247
7757
|
init_secret_filter();
|
|
7248
7758
|
PRIORITY_TYPES = /* @__PURE__ */ new Set(["gotcha", "decision", "problem-solution", "trade-off", "discovery"]);
|
|
7249
7759
|
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": "
|
|
7760
|
+
"gotcha": "[DISCOVERY]",
|
|
7761
|
+
"decision": "[WHY]",
|
|
7762
|
+
"problem-solution": "[FIX]",
|
|
7763
|
+
"trade-off": "[TRADEOFF]",
|
|
7764
|
+
"discovery": "[DISCOVERY]",
|
|
7765
|
+
"how-it-works": "[INFO]",
|
|
7766
|
+
"what-changed": "[CHANGE]",
|
|
7767
|
+
"why-it-exists": "[DECISION]",
|
|
7768
|
+
"session-request": "[SESSION]"
|
|
7259
7769
|
};
|
|
7260
7770
|
TYPE_WEIGHTS2 = {
|
|
7261
7771
|
"gotcha": 6,
|
|
@@ -7797,7 +8307,7 @@ ${narrative}`;
|
|
|
7797
8307
|
lines.push("");
|
|
7798
8308
|
}
|
|
7799
8309
|
if (gotchas.length > 0) {
|
|
7800
|
-
lines.push("##
|
|
8310
|
+
lines.push("## [WARN] Critical Gotchas");
|
|
7801
8311
|
lines.push("");
|
|
7802
8312
|
for (const g of gotchas) {
|
|
7803
8313
|
lines.push(`### ${g.title}`);
|
|
@@ -7809,7 +8319,7 @@ ${narrative}`;
|
|
|
7809
8319
|
}
|
|
7810
8320
|
}
|
|
7811
8321
|
if (decisions.length > 0) {
|
|
7812
|
-
lines.push("##
|
|
8322
|
+
lines.push("## [BUILD] Architecture Decisions");
|
|
7813
8323
|
lines.push("");
|
|
7814
8324
|
for (const d of decisions) {
|
|
7815
8325
|
lines.push(`### ${d.title}`);
|
|
@@ -7821,7 +8331,7 @@ ${narrative}`;
|
|
|
7821
8331
|
}
|
|
7822
8332
|
}
|
|
7823
8333
|
if (howItWorks.length > 0) {
|
|
7824
|
-
lines.push("##
|
|
8334
|
+
lines.push("## [DOCS] How It Works");
|
|
7825
8335
|
lines.push("");
|
|
7826
8336
|
for (const h of howItWorks) {
|
|
7827
8337
|
lines.push(`### ${h.title}`);
|
|
@@ -7830,7 +8340,7 @@ ${narrative}`;
|
|
|
7830
8340
|
}
|
|
7831
8341
|
}
|
|
7832
8342
|
if (problems.length > 0) {
|
|
7833
|
-
lines.push("##
|
|
8343
|
+
lines.push("## [TOOL] Common Problems & Solutions");
|
|
7834
8344
|
lines.push("");
|
|
7835
8345
|
for (const p of problems) {
|
|
7836
8346
|
lines.push(`### ${p.title}`);
|
|
@@ -7842,7 +8352,7 @@ ${narrative}`;
|
|
|
7842
8352
|
}
|
|
7843
8353
|
}
|
|
7844
8354
|
if (tradeoffs.length > 0) {
|
|
7845
|
-
lines.push("##
|
|
8355
|
+
lines.push("## [TRADEOFF] Trade-offs");
|
|
7846
8356
|
lines.push("");
|
|
7847
8357
|
for (const t of tradeoffs) {
|
|
7848
8358
|
lines.push(`### ${t.title}`);
|
|
@@ -7851,7 +8361,7 @@ ${narrative}`;
|
|
|
7851
8361
|
}
|
|
7852
8362
|
}
|
|
7853
8363
|
if (others.length > 0) {
|
|
7854
|
-
lines.push("##
|
|
8364
|
+
lines.push("## [PLAN] Notes");
|
|
7855
8365
|
lines.push("");
|
|
7856
8366
|
for (const o of others.slice(0, 5)) {
|
|
7857
8367
|
lines.push(`- **${o.title}**: ${o.narrative?.split("\n")[0] || ""}`);
|
|
@@ -7859,13 +8369,13 @@ ${narrative}`;
|
|
|
7859
8369
|
lines.push("");
|
|
7860
8370
|
}
|
|
7861
8371
|
if (allConcepts.length > 0) {
|
|
7862
|
-
lines.push("##
|
|
8372
|
+
lines.push("## [TAG] Related Concepts");
|
|
7863
8373
|
lines.push("");
|
|
7864
8374
|
lines.push(allConcepts.map((c) => `\`${c}\``).join(", "));
|
|
7865
8375
|
lines.push("");
|
|
7866
8376
|
}
|
|
7867
8377
|
if (allFacts.length > 0) {
|
|
7868
|
-
lines.push("##
|
|
8378
|
+
lines.push("## [PIN] Quick Facts");
|
|
7869
8379
|
lines.push("");
|
|
7870
8380
|
for (const f of allFacts.slice(0, 15)) {
|
|
7871
8381
|
lines.push(`- ${f}`);
|
|
@@ -9096,7 +9606,7 @@ async function exportAsMarkdown(projectDir2, projectId) {
|
|
|
9096
9606
|
if (Object.keys(data.stats.typeBreakdown).length > 0) {
|
|
9097
9607
|
lines.push("## Type Distribution");
|
|
9098
9608
|
for (const [type, count2] of Object.entries(data.stats.typeBreakdown).sort((a, b) => b[1] - a[1])) {
|
|
9099
|
-
const icon = OBSERVATION_ICONS2[type] ?? "
|
|
9609
|
+
const icon = OBSERVATION_ICONS2[type] ?? "[UNKNOWN]";
|
|
9100
9610
|
lines.push(`- ${icon} ${type}: ${count2}`);
|
|
9101
9611
|
}
|
|
9102
9612
|
lines.push("");
|
|
@@ -9104,7 +9614,7 @@ async function exportAsMarkdown(projectDir2, projectId) {
|
|
|
9104
9614
|
if (data.sessions.length > 0) {
|
|
9105
9615
|
lines.push("## Sessions");
|
|
9106
9616
|
for (const s of data.sessions) {
|
|
9107
|
-
const status = s.status === "active" ? "
|
|
9617
|
+
const status = s.status === "active" ? "[CHANGE]" : "[OK]";
|
|
9108
9618
|
const agent = s.agent ? ` [${s.agent}]` : "";
|
|
9109
9619
|
lines.push(`### ${status} ${s.id}${agent}`);
|
|
9110
9620
|
lines.push(`Started: ${s.startedAt}${s.endedAt ? ` | Ended: ${s.endedAt}` : ""}`);
|
|
@@ -9124,7 +9634,7 @@ async function exportAsMarkdown(projectDir2, projectId) {
|
|
|
9124
9634
|
for (const [entity, observations2] of byEntity) {
|
|
9125
9635
|
lines.push(`### ${entity}`);
|
|
9126
9636
|
for (const obs of observations2) {
|
|
9127
|
-
const icon = OBSERVATION_ICONS2[obs.type] ?? "
|
|
9637
|
+
const icon = OBSERVATION_ICONS2[obs.type] ?? "[UNKNOWN]";
|
|
9128
9638
|
lines.push(`#### ${icon} #${obs.id} ${obs.title}`);
|
|
9129
9639
|
lines.push(`Type: ${obs.type} | Created: ${obs.createdAt}${obs.topicKey ? ` | Topic: ${obs.topicKey}` : ""}${obs.revisionCount && obs.revisionCount > 1 ? ` | Rev: ${obs.revisionCount}` : ""}`);
|
|
9130
9640
|
lines.push("");
|
|
@@ -9190,15 +9700,15 @@ var init_export_import = __esm({
|
|
|
9190
9700
|
init_obs_store();
|
|
9191
9701
|
init_session_store();
|
|
9192
9702
|
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": "
|
|
9703
|
+
"session-request": "[SESSION]",
|
|
9704
|
+
"gotcha": "[GOTCHA]",
|
|
9705
|
+
"problem-solution": "[FIX]",
|
|
9706
|
+
"how-it-works": "[INFO]",
|
|
9707
|
+
"what-changed": "[CHANGE]",
|
|
9708
|
+
"discovery": "[DISCOVERY]",
|
|
9709
|
+
"why-it-exists": "[WHY]",
|
|
9710
|
+
"decision": "[DECISION]",
|
|
9711
|
+
"trade-off": "[TRADEOFF]"
|
|
9202
9712
|
};
|
|
9203
9713
|
}
|
|
9204
9714
|
});
|
|
@@ -9281,6 +9791,12 @@ function sendError(res, message, status = 500) {
|
|
|
9281
9791
|
function filterByProject(items, projectId) {
|
|
9282
9792
|
return items.filter((item) => item.projectId === projectId);
|
|
9283
9793
|
}
|
|
9794
|
+
function isActiveStatus(status) {
|
|
9795
|
+
return (status ?? "active") === "active";
|
|
9796
|
+
}
|
|
9797
|
+
function filterActiveByProject(items, projectId) {
|
|
9798
|
+
return items.filter((item) => item.projectId === projectId && isActiveStatus(item.status));
|
|
9799
|
+
}
|
|
9284
9800
|
function computeProjectGraphCounts(allEntities, allRelations, projectObs) {
|
|
9285
9801
|
const entityNames = new Set(
|
|
9286
9802
|
projectObs.filter((o) => (o.status ?? "active") === "active" && o.entityName).map((o) => o.entityName)
|
|
@@ -9313,6 +9829,7 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
|
|
|
9313
9829
|
const allObs = await getObservationStore().loadAll();
|
|
9314
9830
|
const projectSet = /* @__PURE__ */ new Map();
|
|
9315
9831
|
for (const obs of allObs) {
|
|
9832
|
+
if (!isActiveStatus(obs.status)) continue;
|
|
9316
9833
|
if (obs.projectId) {
|
|
9317
9834
|
projectSet.set(obs.projectId, (projectSet.get(obs.projectId) || 0) + 1);
|
|
9318
9835
|
}
|
|
@@ -9370,7 +9887,7 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
|
|
|
9370
9887
|
}
|
|
9371
9888
|
case "/observations": {
|
|
9372
9889
|
const allObs = await getObservationStore().loadAll();
|
|
9373
|
-
const observations2 =
|
|
9890
|
+
const observations2 = filterActiveByProject(allObs, effectiveProjectId);
|
|
9374
9891
|
sendJson(res, observations2);
|
|
9375
9892
|
break;
|
|
9376
9893
|
}
|
|
@@ -9384,7 +9901,10 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
|
|
|
9384
9901
|
await initGraphStore(effectiveDataDir);
|
|
9385
9902
|
const graph = { entities: getGraphStore().loadEntities(), relations: getGraphStore().loadRelations() };
|
|
9386
9903
|
const allObs = await getObservationStore().loadAll();
|
|
9387
|
-
const observations2 =
|
|
9904
|
+
const observations2 = filterActiveByProject(
|
|
9905
|
+
allObs,
|
|
9906
|
+
effectiveProjectId
|
|
9907
|
+
);
|
|
9388
9908
|
const nextId2 = await getObservationStore().loadIdCounter();
|
|
9389
9909
|
const projectGraphCounts = computeProjectGraphCounts(graph.entities, graph.relations, observations2);
|
|
9390
9910
|
const typeCounts = {};
|
|
@@ -9469,7 +9989,7 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
|
|
|
9469
9989
|
}
|
|
9470
9990
|
case "/retention": {
|
|
9471
9991
|
const allObs = await getObservationStore().loadAll();
|
|
9472
|
-
const observations2 =
|
|
9992
|
+
const observations2 = filterActiveByProject(allObs, effectiveProjectId);
|
|
9473
9993
|
const now = Date.now();
|
|
9474
9994
|
const scored = observations2.map((obs) => {
|
|
9475
9995
|
const age = now - new Date(obs.createdAt || now).getTime();
|
|
@@ -9694,7 +10214,7 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
|
|
|
9694
10214
|
await initGraphStore(effectiveDataDir);
|
|
9695
10215
|
const fullGraph = { entities: getGraphStore().loadEntities(), relations: getGraphStore().loadRelations() };
|
|
9696
10216
|
const allObs = await getObservationStore().loadAll();
|
|
9697
|
-
const observations2 =
|
|
10217
|
+
const observations2 = filterActiveByProject(allObs, effectiveProjectId);
|
|
9698
10218
|
const nextId2 = await getObservationStore().loadIdCounter();
|
|
9699
10219
|
const exportEntityNames = new Set(
|
|
9700
10220
|
observations2.filter((o) => (o.status ?? "active") === "active" && o.entityName).map((o) => o.entityName)
|
|
@@ -9757,6 +10277,131 @@ function openBrowser(url) {
|
|
|
9757
10277
|
exec(cmd, () => {
|
|
9758
10278
|
});
|
|
9759
10279
|
}
|
|
10280
|
+
function parseJsonField(value, fallback) {
|
|
10281
|
+
if (typeof value !== "string") return value ?? fallback;
|
|
10282
|
+
try {
|
|
10283
|
+
return JSON.parse(value || JSON.stringify(fallback));
|
|
10284
|
+
} catch {
|
|
10285
|
+
return fallback;
|
|
10286
|
+
}
|
|
10287
|
+
}
|
|
10288
|
+
function normalizeDashboardAgent(teamStore, projectId, agent) {
|
|
10289
|
+
const id = agent.agent_id ?? agent.id ?? "";
|
|
10290
|
+
const agentProjectId = agent.project_id ?? agent.projectId ?? projectId;
|
|
10291
|
+
return {
|
|
10292
|
+
id,
|
|
10293
|
+
projectId: agentProjectId,
|
|
10294
|
+
instanceId: agent.instance_id ?? agent.instanceId,
|
|
10295
|
+
agentType: agent.agent_type ?? agent.agentType,
|
|
10296
|
+
name: agent.name,
|
|
10297
|
+
role: agent.role,
|
|
10298
|
+
capabilities: parseJsonField(agent.capabilities, []),
|
|
10299
|
+
status: agent.status,
|
|
10300
|
+
joinedAt: agent.joined_at ?? agent.joinedAt,
|
|
10301
|
+
lastSeenAt: agent.last_heartbeat ?? agent.last_seen_at ?? agent.lastSeenAt,
|
|
10302
|
+
leftAt: agent.left_at ?? agent.leftAt,
|
|
10303
|
+
unread: id ? teamStore.getUnreadCount(agentProjectId, id) : 0,
|
|
10304
|
+
source: agent.source || "sqlite"
|
|
10305
|
+
};
|
|
10306
|
+
}
|
|
10307
|
+
function normalizeDashboardLock(lock) {
|
|
10308
|
+
return {
|
|
10309
|
+
file: lock.file,
|
|
10310
|
+
projectId: lock.project_id ?? lock.projectId,
|
|
10311
|
+
lockedBy: lock.locked_by ?? lock.lockedBy,
|
|
10312
|
+
lockedAt: lock.locked_at ?? lock.lockedAt,
|
|
10313
|
+
expiresAt: lock.expires_at ?? lock.expiresAt
|
|
10314
|
+
};
|
|
10315
|
+
}
|
|
10316
|
+
function normalizeDashboardTask(task) {
|
|
10317
|
+
return {
|
|
10318
|
+
id: task.task_id ?? task.id,
|
|
10319
|
+
projectId: task.project_id ?? task.projectId,
|
|
10320
|
+
description: task.description,
|
|
10321
|
+
status: task.status,
|
|
10322
|
+
assignee: task.assignee_agent_id ?? task.assignee,
|
|
10323
|
+
result: task.result,
|
|
10324
|
+
metadata: parseJsonField(task.metadata, null),
|
|
10325
|
+
createdBy: task.created_by ?? task.createdBy,
|
|
10326
|
+
createdAt: task.created_at ?? task.createdAt,
|
|
10327
|
+
updatedAt: task.updated_at ?? task.updatedAt,
|
|
10328
|
+
deps: task.deps || [],
|
|
10329
|
+
requiredRole: task.required_role ?? task.requiredRole ?? null,
|
|
10330
|
+
preferredRole: task.preferred_role ?? task.preferredRole ?? null
|
|
10331
|
+
};
|
|
10332
|
+
}
|
|
10333
|
+
async function buildTeamSnapshot(dataDir, projectId, scope, mode) {
|
|
10334
|
+
try {
|
|
10335
|
+
const { initTeamStore: initTeamStore2 } = await Promise.resolve().then(() => (init_team_store(), team_store_exports));
|
|
10336
|
+
const teamStore = await initTeamStore2(dataDir);
|
|
10337
|
+
const effectiveProjectId = scope === "global" ? void 0 : projectId;
|
|
10338
|
+
const rawAgents = effectiveProjectId ? teamStore.listAgents(effectiveProjectId) : teamStore.listAllAgents();
|
|
10339
|
+
const rawLocks = effectiveProjectId ? teamStore.listLocks(effectiveProjectId) : teamStore.listAllLocks();
|
|
10340
|
+
const rawTasks = effectiveProjectId ? teamStore.listTasks(effectiveProjectId) : teamStore.listAllTasks();
|
|
10341
|
+
const available = effectiveProjectId ? teamStore.listTasks(effectiveProjectId, { available: true }) : teamStore.listAllTasks({ available: true });
|
|
10342
|
+
const agents = rawAgents.map((agent) => normalizeDashboardAgent(teamStore, projectId, agent));
|
|
10343
|
+
const locks = rawLocks.map(normalizeDashboardLock);
|
|
10344
|
+
const tasks = rawTasks.map(normalizeDashboardTask);
|
|
10345
|
+
const recentWindowMs = 7 * 24 * 60 * 60 * 1e3;
|
|
10346
|
+
const now = Date.now();
|
|
10347
|
+
const withTier = agents.map((agent) => {
|
|
10348
|
+
if (agent.status === "active") return { ...agent, activityTier: "active" };
|
|
10349
|
+
const seen = Date.parse(agent.lastSeenAt ?? "") || 0;
|
|
10350
|
+
return { ...agent, activityTier: now - seen <= recentWindowMs ? "recent" : "historical" };
|
|
10351
|
+
});
|
|
10352
|
+
const activeCount = withTier.filter((agent) => agent.activityTier === "active").length;
|
|
10353
|
+
const recentCount = withTier.filter((agent) => agent.activityTier === "recent").length;
|
|
10354
|
+
const historicalCount = withTier.filter((agent) => agent.activityTier === "historical").length;
|
|
10355
|
+
const roles = effectiveProjectId ? teamStore.listRoles(effectiveProjectId) : [];
|
|
10356
|
+
const roleOccupancy = effectiveProjectId ? teamStore.getRoleOccupancy(effectiveProjectId) : [];
|
|
10357
|
+
const handoffs = effectiveProjectId ? teamStore.listHandoffs(effectiveProjectId) : [];
|
|
10358
|
+
return {
|
|
10359
|
+
mode,
|
|
10360
|
+
readOnly: mode === "standalone",
|
|
10361
|
+
scope,
|
|
10362
|
+
agents: withTier,
|
|
10363
|
+
activeCount,
|
|
10364
|
+
recentCount,
|
|
10365
|
+
historicalCount,
|
|
10366
|
+
totalAgents: withTier.length,
|
|
10367
|
+
recentWindowDays: 7,
|
|
10368
|
+
locks,
|
|
10369
|
+
tasks,
|
|
10370
|
+
availableTasks: available.length,
|
|
10371
|
+
sessions: 0,
|
|
10372
|
+
roles,
|
|
10373
|
+
roleOccupancy,
|
|
10374
|
+
handoffs,
|
|
10375
|
+
openTasks: tasks.filter((task) => task.status === "pending" || task.status === "in_progress").length,
|
|
10376
|
+
openHandoffs: handoffs.filter((handoff) => handoff.handoff_status === "open" || handoff.handoffStatus === "open").length,
|
|
10377
|
+
totalUnread: withTier.reduce((sum, agent) => sum + (agent.unread || 0), 0),
|
|
10378
|
+
activeSessions: activeCount
|
|
10379
|
+
};
|
|
10380
|
+
} catch {
|
|
10381
|
+
return {
|
|
10382
|
+
mode,
|
|
10383
|
+
readOnly: mode === "standalone",
|
|
10384
|
+
scope,
|
|
10385
|
+
agents: [],
|
|
10386
|
+
activeCount: 0,
|
|
10387
|
+
recentCount: 0,
|
|
10388
|
+
historicalCount: 0,
|
|
10389
|
+
totalAgents: 0,
|
|
10390
|
+
recentWindowDays: 7,
|
|
10391
|
+
locks: [],
|
|
10392
|
+
tasks: [],
|
|
10393
|
+
availableTasks: 0,
|
|
10394
|
+
sessions: 0,
|
|
10395
|
+
roles: [],
|
|
10396
|
+
roleOccupancy: [],
|
|
10397
|
+
handoffs: [],
|
|
10398
|
+
openTasks: 0,
|
|
10399
|
+
openHandoffs: 0,
|
|
10400
|
+
totalUnread: 0,
|
|
10401
|
+
activeSessions: 0
|
|
10402
|
+
};
|
|
10403
|
+
}
|
|
10404
|
+
}
|
|
9760
10405
|
function readBody(req) {
|
|
9761
10406
|
return new Promise((resolve, reject) => {
|
|
9762
10407
|
const chunks = [];
|
|
@@ -9795,7 +10440,9 @@ async function startDashboard(dataDir, port, staticDir, projectId, projectName,
|
|
|
9795
10440
|
}
|
|
9796
10441
|
if (url.startsWith("/api/team")) {
|
|
9797
10442
|
if (!teamInstances) {
|
|
9798
|
-
|
|
10443
|
+
const parsedUrl = new URL(url, `http://127.0.0.1:${port}`);
|
|
10444
|
+
const scope = parsedUrl.searchParams.get("scope") || "project";
|
|
10445
|
+
sendJson(res, await buildTeamSnapshot(state.dataDir, state.projectId, scope, state.mode));
|
|
9799
10446
|
return;
|
|
9800
10447
|
}
|
|
9801
10448
|
try {
|
|
@@ -10583,7 +11230,7 @@ function buildReviewIterationHint(iteration, maxIterations, budgetRemaining) {
|
|
|
10583
11230
|
if (iteration >= maxIterations) {
|
|
10584
11231
|
return `
|
|
10585
11232
|
|
|
10586
|
-
|
|
11233
|
+
[WARN] This is the FINAL review iteration (${iteration}/${maxIterations}). Do NOT create more tasks. Summarize remaining issues and exit.`;
|
|
10587
11234
|
}
|
|
10588
11235
|
return `
|
|
10589
11236
|
|
|
@@ -11617,6 +12264,7 @@ At the **beginning of every conversation**, BEFORE responding to the user:
|
|
|
11617
12264
|
|
|
11618
12265
|
**Important:** \`projectRoot\` is a detection anchor only; Git remains the source of truth for project identity.
|
|
11619
12266
|
In HTTP control-plane mode (\`memorix serve-http\` / \`memorix background start\`), explicit \`projectRoot\` binding is required for correct multi-project isolation.
|
|
12267
|
+
\`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
12268
|
|
|
11621
12269
|
## RULE 2: Store Important Context
|
|
11622
12270
|
|
|
@@ -11717,7 +12365,7 @@ This file contains the **minimum operating rules** for Memorix memory tools. It
|
|
|
11717
12365
|
For authoritative, up-to-date details on:
|
|
11718
12366
|
- **Support tiers** (core / extended / community) and what "installed" vs "runtime-ready" means
|
|
11719
12367
|
- **HTTP control-plane binding** and \`projectRoot\` isolation rules
|
|
11720
|
-
- **
|
|
12368
|
+
- **Opt-in team semantics** (\`joinTeam\`, \`team_manage join\`, roles, task claim, handoff validation)
|
|
11721
12369
|
- **Install vs runtime-ready distinction** \u2014 hook config written \u2260 agent will execute it
|
|
11722
12370
|
- **Agent-specific caveats** (Copilot project-level only, OpenCode plugin lifecycle, etc.)
|
|
11723
12371
|
|
|
@@ -11733,7 +12381,7 @@ async function uninstallHooks(agent, projectRoot, global = false) {
|
|
|
11733
12381
|
const configPath = global ? getGlobalConfigPath(agent) : getProjectConfigPath(agent, projectRoot);
|
|
11734
12382
|
let success = false;
|
|
11735
12383
|
try {
|
|
11736
|
-
if (agent === "kiro") {
|
|
12384
|
+
if (agent === "kiro" || agent === "opencode") {
|
|
11737
12385
|
await fs14.unlink(configPath);
|
|
11738
12386
|
success = true;
|
|
11739
12387
|
} else {
|
|
@@ -12026,6 +12674,7 @@ init_obs_store();
|
|
|
12026
12674
|
init_orama_store();
|
|
12027
12675
|
init_mini_skill_store();
|
|
12028
12676
|
init_session_store();
|
|
12677
|
+
import { createHash as createHash4 } from "crypto";
|
|
12029
12678
|
import { watchFile } from "fs";
|
|
12030
12679
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
12031
12680
|
import { z as z2 } from "zod";
|
|
@@ -12295,7 +12944,7 @@ function formatTimeline(timeline) {
|
|
|
12295
12944
|
const anchorEffectiveSource = resolveSourceDetail(anchor.sourceDetail, anchor.source);
|
|
12296
12945
|
if (hasSrc && anchorEffectiveSource) {
|
|
12297
12946
|
const anchorBasis = resolveEvidenceBasis({ sourceDetail: anchor.sourceDetail, source: anchor.source });
|
|
12298
|
-
const basisSuffix = anchorBasis === "repository" ? " \u2014
|
|
12947
|
+
const basisSuffix = anchorBasis === "repository" ? " \u2014 [OK] repository-backed" : anchorBasis === "synthesized" ? " \u2014 [SYNTHESIZED] synthesized" : "";
|
|
12299
12948
|
lines.push(`*Expanding: ${sourceKindLabel(anchorEffectiveSource)}${basisSuffix}*`);
|
|
12300
12949
|
}
|
|
12301
12950
|
lines.push("");
|
|
@@ -12375,38 +13024,38 @@ function buildProvenanceHeader(sourceDetail, valueCategory, source, commitHash,
|
|
|
12375
13024
|
lines.push(verificationLine);
|
|
12376
13025
|
}
|
|
12377
13026
|
if (valueCategory === "core") {
|
|
12378
|
-
lines.push("
|
|
13027
|
+
lines.push("[CORE] Core \u2014 immune to decay");
|
|
12379
13028
|
} else if (valueCategory === "ephemeral") {
|
|
12380
|
-
lines.push("
|
|
13029
|
+
lines.push("[WARN] Ephemeral \u2014 short-lived signal");
|
|
12381
13030
|
}
|
|
12382
13031
|
return lines.join("\n");
|
|
12383
13032
|
}
|
|
12384
13033
|
function sourceKindLabel(sd) {
|
|
12385
|
-
if (sd === "git-ingest") return "
|
|
12386
|
-
if (sd === "hook") return "
|
|
12387
|
-
return "
|
|
13034
|
+
if (sd === "git-ingest") return "[PIN] Git Repository Evidence";
|
|
13035
|
+
if (sd === "hook") return "[HOOK] Hook Trace";
|
|
13036
|
+
return "[STORE] Explicit Working Memory";
|
|
12388
13037
|
}
|
|
12389
13038
|
function getTypeIcon(type) {
|
|
12390
13039
|
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": "
|
|
13040
|
+
"session-request": "[SESSION]",
|
|
13041
|
+
"gotcha": "[GOTCHA]",
|
|
13042
|
+
"problem-solution": "[FIX]",
|
|
13043
|
+
"how-it-works": "[INFO]",
|
|
13044
|
+
"what-changed": "[CHANGE]",
|
|
13045
|
+
"discovery": "[DISCOVERY]",
|
|
13046
|
+
"why-it-exists": "[WHY]",
|
|
13047
|
+
"decision": "[DECISION]",
|
|
13048
|
+
"trade-off": "[TRADEOFF]",
|
|
13049
|
+
"reasoning": "[REASONING]"
|
|
12401
13050
|
};
|
|
12402
|
-
return icons[type] ?? "
|
|
13051
|
+
return icons[type] ?? "[UNKNOWN]";
|
|
12403
13052
|
}
|
|
12404
13053
|
function getProgressiveDisclosureHint(hasProject) {
|
|
12405
13054
|
const lines = [
|
|
12406
|
-
"
|
|
13055
|
+
"[TIP] **Progressive Disclosure:** This index shows WHAT exists and retrieval COST.",
|
|
12407
13056
|
"- Use `memorix_detail` with typed refs (obs:42, skill:3) to fetch full details",
|
|
12408
13057
|
"- Use `memorix_timeline` to see chronological context around an observation",
|
|
12409
|
-
"- Critical types (
|
|
13058
|
+
"- Critical types ([GOTCHA] gotcha, [DECISION] decision, [TRADEOFF] trade-off) are often worth fetching immediately"
|
|
12410
13059
|
];
|
|
12411
13060
|
if (hasProject) {
|
|
12412
13061
|
lines.push("- For global results, prefer `memorix_detail refs=[{ id, projectId }]` to avoid cross-project ID ambiguity");
|
|
@@ -12417,7 +13066,7 @@ function formatEntryRef(entry) {
|
|
|
12417
13066
|
return entry.documentType === "mini-skill" ? `skill:${entry.id}` : `obs:${entry.id}`;
|
|
12418
13067
|
}
|
|
12419
13068
|
function formatLayerBadge(layer) {
|
|
12420
|
-
if (layer === "promoted") return "
|
|
13069
|
+
if (layer === "promoted") return "core";
|
|
12421
13070
|
if (layer === "evidence") return "ev";
|
|
12422
13071
|
return "-";
|
|
12423
13072
|
}
|
|
@@ -12590,7 +13239,7 @@ async function compactDetail(idsOrRefs) {
|
|
|
12590
13239
|
(o) => o.source === "git" && projectIds.has(o.projectId) && o.commitHash && obs.relatedCommits.includes(o.commitHash)
|
|
12591
13240
|
);
|
|
12592
13241
|
for (const gm of gitMems) {
|
|
12593
|
-
refs2.push(` \u2192 #${gm.id}
|
|
13242
|
+
refs2.push(` \u2192 #${gm.id} [CHANGE] ${gm.title}`);
|
|
12594
13243
|
}
|
|
12595
13244
|
}
|
|
12596
13245
|
if (obs.relatedEntities && obs.relatedEntities.length > 0) {
|
|
@@ -12603,7 +13252,7 @@ async function compactDetail(idsOrRefs) {
|
|
|
12603
13252
|
if (analysis.length > 0) {
|
|
12604
13253
|
refs2.push("Analysis:");
|
|
12605
13254
|
for (const r of analysis) {
|
|
12606
|
-
refs2.push(` \u2192 #${r.id} ${r.type === "reasoning" ? "
|
|
13255
|
+
refs2.push(` \u2192 #${r.id} ${r.type === "reasoning" ? "[REASONING]" : "[DECISION]"} ${r.title}`);
|
|
12607
13256
|
}
|
|
12608
13257
|
}
|
|
12609
13258
|
}
|
|
@@ -12614,7 +13263,7 @@ async function compactDetail(idsOrRefs) {
|
|
|
12614
13263
|
if (gitMems.length > 0) {
|
|
12615
13264
|
refs2.push("Repository evidence:");
|
|
12616
13265
|
for (const g of gitMems) {
|
|
12617
|
-
refs2.push(` \u2192 #${g.id}
|
|
13266
|
+
refs2.push(` \u2192 #${g.id} [CHANGE] ${g.title}`);
|
|
12618
13267
|
}
|
|
12619
13268
|
}
|
|
12620
13269
|
}
|
|
@@ -12653,7 +13302,7 @@ async function compactDetail(idsOrRefs) {
|
|
|
12653
13302
|
}
|
|
12654
13303
|
function formatMiniSkillDetail(skill, provenanceStatus) {
|
|
12655
13304
|
const lines = [];
|
|
12656
|
-
lines.push(`S${skill.id}
|
|
13305
|
+
lines.push(`S${skill.id} core ${skill.title}`);
|
|
12657
13306
|
lines.push("=".repeat(50));
|
|
12658
13307
|
lines.push(`Type: promoted knowledge (mini-skill)`);
|
|
12659
13308
|
lines.push(`Entity: ${skill.sourceEntity}`);
|
|
@@ -14697,47 +15346,47 @@ var WorkspaceSyncEngine = class _WorkspaceSyncEngine {
|
|
|
14697
15346
|
}
|
|
14698
15347
|
const lines = [];
|
|
14699
15348
|
if (applyResult.success) {
|
|
14700
|
-
lines.push(
|
|
15349
|
+
lines.push(`[OK] Applied ${applyResult.filesWritten.length} file(s) for ${target}`);
|
|
14701
15350
|
for (const f of applyResult.filesWritten) {
|
|
14702
15351
|
lines.push(` \u2192 ${f}`);
|
|
14703
15352
|
}
|
|
14704
15353
|
if (skillResult.copied.length > 0) {
|
|
14705
15354
|
lines.push(`
|
|
14706
|
-
|
|
15355
|
+
[SKILL] Copied ${skillResult.copied.length} skill(s):`);
|
|
14707
15356
|
for (const sk of skillResult.copied) {
|
|
14708
15357
|
lines.push(` \u2192 ${sk}`);
|
|
14709
15358
|
}
|
|
14710
15359
|
}
|
|
14711
15360
|
if (skillResult.skipped.length > 0) {
|
|
14712
15361
|
lines.push(`
|
|
14713
|
-
|
|
15362
|
+
[SKIP] Skipped ${skillResult.skipped.length} skill(s):`);
|
|
14714
15363
|
for (const sk of skillResult.skipped) {
|
|
14715
15364
|
lines.push(` \u2192 ${sk}`);
|
|
14716
15365
|
}
|
|
14717
15366
|
}
|
|
14718
15367
|
if (syncResult.skills.conflicts.length > 0) {
|
|
14719
15368
|
lines.push(`
|
|
14720
|
-
|
|
15369
|
+
[WARN] Name conflicts (${syncResult.skills.conflicts.length}):`);
|
|
14721
15370
|
for (const c of syncResult.skills.conflicts) {
|
|
14722
15371
|
lines.push(` \u2192 "${c.name}": kept ${c.kept.sourceAgent}, skipped ${c.skipped.sourceAgent}`);
|
|
14723
15372
|
}
|
|
14724
15373
|
}
|
|
14725
15374
|
if (applyResult.backups.length > 0) {
|
|
14726
15375
|
lines.push(`
|
|
14727
|
-
|
|
15376
|
+
[PACKAGE] Backups created (${applyResult.backups.length}):`);
|
|
14728
15377
|
for (const b of applyResult.backups) {
|
|
14729
15378
|
lines.push(` ${b.originalPath} \u2192 ${b.backupPath}`);
|
|
14730
15379
|
}
|
|
14731
15380
|
}
|
|
14732
15381
|
applier.cleanBackups(applyResult.backups);
|
|
14733
15382
|
} else {
|
|
14734
|
-
lines.push(
|
|
15383
|
+
lines.push(`[ERROR] Apply failed for ${target}`);
|
|
14735
15384
|
for (const e of applyResult.errors) {
|
|
14736
15385
|
lines.push(` Error: ${e}`);
|
|
14737
15386
|
}
|
|
14738
15387
|
if (applyResult.backups.length > 0) {
|
|
14739
15388
|
lines.push(`
|
|
14740
|
-
|
|
15389
|
+
[UPDATED] Rolled back ${applyResult.backups.length} file(s)`);
|
|
14741
15390
|
}
|
|
14742
15391
|
}
|
|
14743
15392
|
return {
|
|
@@ -14763,11 +15412,109 @@ var WorkspaceSyncEngine = class _WorkspaceSyncEngine {
|
|
|
14763
15412
|
}
|
|
14764
15413
|
};
|
|
14765
15414
|
|
|
15415
|
+
// src/server/tool-profile.ts
|
|
15416
|
+
init_esm_shims();
|
|
15417
|
+
var TOOL_PROFILES = Object.freeze({
|
|
15418
|
+
// ── lite: core cross-agent memory — always available ──────────────
|
|
15419
|
+
memorix_store: ["lite", "team", "full"],
|
|
15420
|
+
memorix_search: ["lite", "team", "full"],
|
|
15421
|
+
memorix_detail: ["lite", "team", "full"],
|
|
15422
|
+
memorix_resolve: ["lite", "team", "full"],
|
|
15423
|
+
memorix_timeline: ["lite", "team", "full"],
|
|
15424
|
+
memorix_suggest_topic_key: ["lite", "team", "full"],
|
|
15425
|
+
memorix_session_start: ["lite", "team", "full"],
|
|
15426
|
+
memorix_session_end: ["lite", "team", "full"],
|
|
15427
|
+
memorix_session_context: ["lite", "team", "full"],
|
|
15428
|
+
memorix_store_reasoning: ["lite", "team", "full"],
|
|
15429
|
+
memorix_search_reasoning: ["lite", "team", "full"],
|
|
15430
|
+
memorix_transfer: ["lite", "team", "full"],
|
|
15431
|
+
memorix_retention: ["lite", "team", "full"],
|
|
15432
|
+
// ── team: autonomous agent team surfaces — HTTP default ───────────
|
|
15433
|
+
memorix_dashboard: ["team", "full"],
|
|
15434
|
+
memorix_handoff: ["team", "full"],
|
|
15435
|
+
memorix_poll: ["team", "full"],
|
|
15436
|
+
team_manage: ["team", "full"],
|
|
15437
|
+
team_message: ["team", "full"],
|
|
15438
|
+
team_task: ["team", "full"],
|
|
15439
|
+
team_file_lock: ["team", "full"],
|
|
15440
|
+
// ── full: advanced / specialized — opt-in only ───────────────────
|
|
15441
|
+
memorix_audit_project: ["full"],
|
|
15442
|
+
memorix_deduplicate: ["full"],
|
|
15443
|
+
memorix_consolidate: ["full"],
|
|
15444
|
+
memorix_formation_metrics: ["full"],
|
|
15445
|
+
memorix_skills: ["full"],
|
|
15446
|
+
memorix_promote: ["full"],
|
|
15447
|
+
memorix_rules_sync: ["full"],
|
|
15448
|
+
memorix_workspace_sync: ["full"],
|
|
15449
|
+
memorix_ingest_image: ["full"],
|
|
15450
|
+
// ── MCP Official Memory Server compatibility (KG tools) ──────────
|
|
15451
|
+
// These are only useful to users specifically migrating from the
|
|
15452
|
+
// reference mcp-memory server. Hide them unless explicitly enabled.
|
|
15453
|
+
create_entities: ["full"],
|
|
15454
|
+
create_relations: ["full"],
|
|
15455
|
+
add_observations: ["full"],
|
|
15456
|
+
delete_entities: ["full"],
|
|
15457
|
+
delete_observations: ["full"],
|
|
15458
|
+
delete_relations: ["full"],
|
|
15459
|
+
read_graph: ["full"],
|
|
15460
|
+
search_nodes: ["full"],
|
|
15461
|
+
open_nodes: ["full"]
|
|
15462
|
+
});
|
|
15463
|
+
function isToolInProfile(toolName, profile) {
|
|
15464
|
+
const profiles = TOOL_PROFILES[toolName];
|
|
15465
|
+
if (!profiles) {
|
|
15466
|
+
return profile === "full";
|
|
15467
|
+
}
|
|
15468
|
+
return profiles.includes(profile);
|
|
15469
|
+
}
|
|
15470
|
+
function resolveToolProfile(opts) {
|
|
15471
|
+
const normalize = (v) => {
|
|
15472
|
+
if (!v) return null;
|
|
15473
|
+
const s = String(v).trim().toLowerCase();
|
|
15474
|
+
if (s === "lite" || s === "team" || s === "full") return s;
|
|
15475
|
+
return null;
|
|
15476
|
+
};
|
|
15477
|
+
return normalize(opts.explicit) ?? normalize(opts.envValue) ?? opts.fallback;
|
|
15478
|
+
}
|
|
15479
|
+
function describeProfile(profile) {
|
|
15480
|
+
switch (profile) {
|
|
15481
|
+
case "lite":
|
|
15482
|
+
return "lite (core memory + sessions, ~13 tools)";
|
|
15483
|
+
case "team":
|
|
15484
|
+
return "team (lite + agent team tools + dashboard, ~20 tools)";
|
|
15485
|
+
case "full":
|
|
15486
|
+
return "full (all tools including advanced / KG-compat)";
|
|
15487
|
+
}
|
|
15488
|
+
}
|
|
15489
|
+
|
|
14766
15490
|
// src/server.ts
|
|
14767
15491
|
init_provider2();
|
|
14768
15492
|
init_memory_manager();
|
|
14769
15493
|
init_formation();
|
|
14770
|
-
|
|
15494
|
+
|
|
15495
|
+
// src/server/formation-timeout.ts
|
|
15496
|
+
init_esm_shims();
|
|
15497
|
+
var DEFAULT_FORMATION_TIMEOUT_MS = 12e3;
|
|
15498
|
+
var FORMATION_TIMEOUT_MIN_MS = 1e3;
|
|
15499
|
+
var FORMATION_TIMEOUT_MAX_MS = 3e5;
|
|
15500
|
+
function parseFormationTimeoutMs(raw) {
|
|
15501
|
+
const value = raw?.trim();
|
|
15502
|
+
if (!value) return DEFAULT_FORMATION_TIMEOUT_MS;
|
|
15503
|
+
const parsed = Number(value);
|
|
15504
|
+
if (!Number.isInteger(parsed) || Number.isNaN(parsed)) {
|
|
15505
|
+
console.warn(
|
|
15506
|
+
`[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.`
|
|
15507
|
+
);
|
|
15508
|
+
return DEFAULT_FORMATION_TIMEOUT_MS;
|
|
15509
|
+
}
|
|
15510
|
+
if (parsed < FORMATION_TIMEOUT_MIN_MS) return FORMATION_TIMEOUT_MIN_MS;
|
|
15511
|
+
if (parsed > FORMATION_TIMEOUT_MAX_MS) return FORMATION_TIMEOUT_MAX_MS;
|
|
15512
|
+
return parsed;
|
|
15513
|
+
}
|
|
15514
|
+
|
|
15515
|
+
// src/server.ts
|
|
15516
|
+
var FORMATION_TIMEOUT_MS = parseFormationTimeoutMs(process.env.MEMORIX_FORMATION_TIMEOUT_MS);
|
|
15517
|
+
var COMPACT_ON_WRITE_TIMEOUT_MS = 12e3;
|
|
14771
15518
|
var COMPRESSION_TIMEOUT_MS = 5e3;
|
|
14772
15519
|
var DEDUP_PER_PAIR_TIMEOUT_MS = 5e3;
|
|
14773
15520
|
function withTimeout(promise, ms, label) {
|
|
@@ -14779,6 +15526,11 @@ function withTimeout(promise, ms, label) {
|
|
|
14779
15526
|
})
|
|
14780
15527
|
]).finally(() => clearTimeout(timer));
|
|
14781
15528
|
}
|
|
15529
|
+
function formatFormationStageDurations(stageDurationsMs) {
|
|
15530
|
+
const orderedStages = ["extract", "resolve", "evaluate"];
|
|
15531
|
+
const parts = orderedStages.filter((stage) => stageDurationsMs[stage] !== void 0).map((stage) => `${stage}=${stageDurationsMs[stage]}ms`);
|
|
15532
|
+
return parts.join(", ");
|
|
15533
|
+
}
|
|
14782
15534
|
var lastInternalWriteMs = 0;
|
|
14783
15535
|
var markInternalWrite = () => {
|
|
14784
15536
|
lastInternalWriteMs = Date.now();
|
|
@@ -14870,11 +15622,21 @@ function coerceObjectArray(val) {
|
|
|
14870
15622
|
}
|
|
14871
15623
|
return [];
|
|
14872
15624
|
}
|
|
15625
|
+
function createDeterministicInstanceId(projectId, agentType, agentName) {
|
|
15626
|
+
const digest = createHash4("sha256").update(projectId).update("\n").update(agentType).update("\n").update(agentName ?? "").digest("hex").slice(0, 24);
|
|
15627
|
+
return `auto-${digest}`;
|
|
15628
|
+
}
|
|
14873
15629
|
async function createMemorixServer(cwd, existingServer, sharedTeam, options = {}) {
|
|
14874
15630
|
const allowUntrackedFallback = options.allowUntrackedFallback ?? true;
|
|
14875
15631
|
const deferProjectInitUntilBound = options.deferProjectInitUntilBound ?? false;
|
|
14876
15632
|
const dashboardMode = options.dashboardMode ?? (sharedTeam ? "control-plane" : "standalone");
|
|
14877
15633
|
const configuredDashboardPort = options.dashboardPort ?? (dashboardMode === "control-plane" ? 3211 : 3210);
|
|
15634
|
+
const toolProfile = resolveToolProfile({
|
|
15635
|
+
explicit: options.toolProfile,
|
|
15636
|
+
envValue: process.env.MEMORIX_MODE,
|
|
15637
|
+
fallback: sharedTeam ? "team" : "lite"
|
|
15638
|
+
});
|
|
15639
|
+
const teamFeaturesEnabled = isToolInProfile("team_manage", toolProfile);
|
|
14878
15640
|
const detectedProject = detectProject(cwd);
|
|
14879
15641
|
let rawProject;
|
|
14880
15642
|
let projectResolved = true;
|
|
@@ -14949,6 +15711,9 @@ async function createMemorixServer(cwd, existingServer, sharedTeam, options = {}
|
|
|
14949
15711
|
} else {
|
|
14950
15712
|
console.error(`[memorix] LLM mode: off (set MEMORIX_LLM_API_KEY or OPENAI_API_KEY to enable)`);
|
|
14951
15713
|
}
|
|
15714
|
+
if (logPrefix === "startup") {
|
|
15715
|
+
console.error(`[memorix] Tool profile: ${describeProfile(toolProfile)}`);
|
|
15716
|
+
}
|
|
14952
15717
|
if (logPrefix === "startup") {
|
|
14953
15718
|
console.error(`[memorix] Project: ${project.id} (${project.name})`);
|
|
14954
15719
|
console.error(`[memorix] Data dir: ${projectDir2}`);
|
|
@@ -15009,13 +15774,20 @@ The path should point to a directory containing a .git folder.`
|
|
|
15009
15774
|
};
|
|
15010
15775
|
const server = existingServer ?? new McpServer({
|
|
15011
15776
|
name: "memorix",
|
|
15012
|
-
version: true ? "1.0.
|
|
15777
|
+
version: true ? "1.0.8" : "1.0.1"
|
|
15778
|
+
});
|
|
15779
|
+
const originalRegisterTool = server.registerTool.bind(server);
|
|
15780
|
+
server.registerTool = ((name, ...args) => {
|
|
15781
|
+
if (!isToolInProfile(name, toolProfile)) {
|
|
15782
|
+
return void 0;
|
|
15783
|
+
}
|
|
15784
|
+
return originalRegisterTool(name, ...args);
|
|
15013
15785
|
});
|
|
15014
15786
|
server.registerTool(
|
|
15015
15787
|
"memorix_store",
|
|
15016
15788
|
{
|
|
15017
15789
|
title: "Store Memory",
|
|
15018
|
-
description: "Store a new observation/memory. Automatically indexed for search. Use type to classify: gotcha (
|
|
15790
|
+
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
15791
|
inputSchema: {
|
|
15020
15792
|
entityName: z2.string().describe('The entity this observation belongs to (e.g., "auth-module", "port-config")'),
|
|
15021
15793
|
type: z2.enum(OBSERVATION_TYPES).describe("Observation type for classification"),
|
|
@@ -15060,6 +15832,19 @@ The path should point to a directory containing a .git folder.`
|
|
|
15060
15832
|
let formationResult = null;
|
|
15061
15833
|
let formationNote = "";
|
|
15062
15834
|
if (useFormation && !topicKey && !progress) {
|
|
15835
|
+
let currentFormationStage = "setup";
|
|
15836
|
+
const completedFormationStages = {};
|
|
15837
|
+
const formationStartTime = Date.now();
|
|
15838
|
+
const onFormationStageEvent = (event) => {
|
|
15839
|
+
if (event.status === "start") {
|
|
15840
|
+
currentFormationStage = event.stage;
|
|
15841
|
+
return;
|
|
15842
|
+
}
|
|
15843
|
+
currentFormationStage = event.stage;
|
|
15844
|
+
if (event.stageDurationMs !== void 0) {
|
|
15845
|
+
completedFormationStages[event.stage] = event.stageDurationMs;
|
|
15846
|
+
}
|
|
15847
|
+
};
|
|
15063
15848
|
try {
|
|
15064
15849
|
const formationConfig = {
|
|
15065
15850
|
mode: "active",
|
|
@@ -15093,7 +15878,8 @@ The path should point to a directory containing a .git folder.`
|
|
|
15093
15878
|
topicKey: o.topicKey
|
|
15094
15879
|
};
|
|
15095
15880
|
},
|
|
15096
|
-
getEntityNames: () => graphManager.getEntityNames()
|
|
15881
|
+
getEntityNames: () => graphManager.getEntityNames(),
|
|
15882
|
+
onStageEvent: onFormationStageEvent
|
|
15097
15883
|
};
|
|
15098
15884
|
formationResult = await withTimeout(
|
|
15099
15885
|
runFormation({
|
|
@@ -15108,7 +15894,7 @@ The path should point to a directory containing a .git folder.`
|
|
|
15108
15894
|
FORMATION_TIMEOUT_MS,
|
|
15109
15895
|
"Formation pipeline"
|
|
15110
15896
|
);
|
|
15111
|
-
const modeIcon = "
|
|
15897
|
+
const modeIcon = "[FAST]";
|
|
15112
15898
|
formationNote = `
|
|
15113
15899
|
${modeIcon} Formation[active]: ${formationResult.evaluation.category} (${formationResult.evaluation.score.toFixed(2)}) | ${formationResult.resolution.action} | ${formationResult.pipeline.durationMs}ms`;
|
|
15114
15900
|
if (formationResult.extraction.extractedFacts.length > 0) {
|
|
@@ -15119,8 +15905,16 @@ ${modeIcon} Formation[active]: ${formationResult.evaluation.category} (${formati
|
|
|
15119
15905
|
if (formationResult.extraction.typeCorrected) formationNote += ` | type\u2192${formationResult.type}`;
|
|
15120
15906
|
} catch (formationErr) {
|
|
15121
15907
|
const isTimeout = formationErr instanceof Error && formationErr.message.includes("timed out");
|
|
15908
|
+
const elapsedMs = Date.now() - formationStartTime;
|
|
15909
|
+
const stageSummary = formatFormationStageDurations(completedFormationStages);
|
|
15910
|
+
console.error(
|
|
15911
|
+
`[memorix] Formation ${isTimeout ? "timed out" : "failed"} in memorix_store after ${elapsedMs}ms/${FORMATION_TIMEOUT_MS}ms at stage ${currentFormationStage}${stageSummary ? ` | completed: ${stageSummary}` : ""}`
|
|
15912
|
+
);
|
|
15913
|
+
if (!isTimeout && formationErr instanceof Error) {
|
|
15914
|
+
console.error(`[memorix] Formation error: ${formationErr.message}`);
|
|
15915
|
+
}
|
|
15122
15916
|
formationNote = `
|
|
15123
|
-
|
|
15917
|
+
[WARN] Formation ${isTimeout ? "timed out" : "failed"} \u2014 storing base observation without enrichment`;
|
|
15124
15918
|
}
|
|
15125
15919
|
}
|
|
15126
15920
|
if (useFormation && formationResult && formationResult.resolution.action !== "new") {
|
|
@@ -15146,7 +15940,7 @@ ${modeIcon} Formation[active]: ${formationResult.evaluation.category} (${formati
|
|
|
15146
15940
|
return {
|
|
15147
15941
|
content: [{
|
|
15148
15942
|
type: "text",
|
|
15149
|
-
text:
|
|
15943
|
+
text: `[UPDATED] Formation MERGE: merged into #${targetId} (${reason})${formationNote}`
|
|
15150
15944
|
}]
|
|
15151
15945
|
};
|
|
15152
15946
|
}
|
|
@@ -15171,7 +15965,7 @@ ${modeIcon} Formation[active]: ${formationResult.evaluation.category} (${formati
|
|
|
15171
15965
|
return {
|
|
15172
15966
|
content: [{
|
|
15173
15967
|
type: "text",
|
|
15174
|
-
text:
|
|
15968
|
+
text: `[UPDATED] Formation EVOLVE: evolved #${targetId} (${reason})${formationNote}`
|
|
15175
15969
|
}]
|
|
15176
15970
|
};
|
|
15177
15971
|
}
|
|
@@ -15179,7 +15973,7 @@ ${modeIcon} Formation[active]: ${formationResult.evaluation.category} (${formati
|
|
|
15179
15973
|
return {
|
|
15180
15974
|
content: [{
|
|
15181
15975
|
type: "text",
|
|
15182
|
-
text:
|
|
15976
|
+
text: `[SKIP] Formation DISCARD: ${reason}${formationNote}`
|
|
15183
15977
|
}]
|
|
15184
15978
|
};
|
|
15185
15979
|
}
|
|
@@ -15210,7 +16004,7 @@ ${modeIcon} Formation[active]: ${formationResult.evaluation.category} (${formati
|
|
|
15210
16004
|
{ title, narrative, facts: safeFacts ?? [] },
|
|
15211
16005
|
existingMemories
|
|
15212
16006
|
),
|
|
15213
|
-
|
|
16007
|
+
COMPACT_ON_WRITE_TIMEOUT_MS,
|
|
15214
16008
|
"Compact-on-write"
|
|
15215
16009
|
);
|
|
15216
16010
|
if (decision.action === "UPDATE" && decision.targetId) {
|
|
@@ -15231,7 +16025,7 @@ ${modeIcon} Formation[active]: ${formationResult.evaluation.category} (${formati
|
|
|
15231
16025
|
sourceDetail: "explicit",
|
|
15232
16026
|
createdByAgentId: currentAgentId
|
|
15233
16027
|
});
|
|
15234
|
-
compactAction =
|
|
16028
|
+
compactAction = `[UPDATED] Compact UPDATE: merged into #${decision.targetId} (${decision.reason})`;
|
|
15235
16029
|
compactMerged = true;
|
|
15236
16030
|
return {
|
|
15237
16031
|
content: [{
|
|
@@ -15245,7 +16039,7 @@ Mode: ${decision.usedLLM ? "LLM" : "heuristic"}`
|
|
|
15245
16039
|
return {
|
|
15246
16040
|
content: [{
|
|
15247
16041
|
type: "text",
|
|
15248
|
-
text:
|
|
16042
|
+
text: `[SKIP] Compact SKIP: ${decision.reason}
|
|
15249
16043
|
Existing memory #${decision.targetId} already covers this.
|
|
15250
16044
|
Mode: ${decision.usedLLM ? "LLM" : "heuristic"}`
|
|
15251
16045
|
}]
|
|
@@ -15316,7 +16110,7 @@ Mode: ${decision.usedLLM ? "LLM" : "heuristic"}`
|
|
|
15316
16110
|
const attrCheck = await checkProjectAttribution(entityName, project.id, getAllObservations());
|
|
15317
16111
|
if (attrCheck.suspicious) {
|
|
15318
16112
|
attributionWarning = `
|
|
15319
|
-
|
|
16113
|
+
[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
16114
|
}
|
|
15321
16115
|
} catch {
|
|
15322
16116
|
}
|
|
@@ -15354,7 +16148,7 @@ Mode: ${decision.usedLLM ? "LLM" : "heuristic"}`
|
|
|
15354
16148
|
if (upserted) enrichmentParts.push(`topic upserted (rev ${obs.revisionCount ?? 1})`);
|
|
15355
16149
|
const enrichment = enrichmentParts.length > 0 ? `
|
|
15356
16150
|
Auto-enriched: ${enrichmentParts.join(", ")}` : "";
|
|
15357
|
-
const action = upserted ? "
|
|
16151
|
+
const action = upserted ? "[UPDATED] Updated" : "[OK] Stored";
|
|
15358
16152
|
if (!useFormation && !topicKey && !progress) {
|
|
15359
16153
|
const shadowFormation = async () => {
|
|
15360
16154
|
let oldCompactDecision = null;
|
|
@@ -15583,13 +16377,13 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
|
|
|
15583
16377
|
const result = await resolveObservations2(safeIds, status ?? "resolved");
|
|
15584
16378
|
const parts = [];
|
|
15585
16379
|
if (result.resolved.length > 0) {
|
|
15586
|
-
parts.push(
|
|
16380
|
+
parts.push(`[OK] Resolved ${result.resolved.length} observation(s): #${result.resolved.join(", #")}`);
|
|
15587
16381
|
}
|
|
15588
16382
|
if (result.notFound.length > 0) {
|
|
15589
|
-
parts.push(
|
|
16383
|
+
parts.push(`[WARN] Not found: #${result.notFound.join(", #")}`);
|
|
15590
16384
|
}
|
|
15591
16385
|
parts.push('\nResolved memories are hidden from default search. Use status="all" to include them.');
|
|
15592
|
-
parts.push('
|
|
16386
|
+
parts.push('[STATS] Run `memorix_retention` with `action: "report"` to check remaining cleanup status.');
|
|
15593
16387
|
return {
|
|
15594
16388
|
content: [{ type: "text", text: parts.join("\n") }]
|
|
15595
16389
|
};
|
|
@@ -15642,7 +16436,7 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
|
|
|
15642
16436
|
const attrCheck = await checkProjectAttribution(entityName, project.id, getAllObservations());
|
|
15643
16437
|
if (attrCheck.suspicious) {
|
|
15644
16438
|
reasoningAttributionWarning = `
|
|
15645
|
-
|
|
16439
|
+
[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
16440
|
}
|
|
15647
16441
|
} catch {
|
|
15648
16442
|
}
|
|
@@ -15663,12 +16457,12 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
|
|
|
15663
16457
|
createdByAgentId: currentAgentId
|
|
15664
16458
|
});
|
|
15665
16459
|
await graphManager.addObservations([
|
|
15666
|
-
{ entityName, contents: [`[#${obs.id}]
|
|
16460
|
+
{ entityName, contents: [`[#${obs.id}] [REASONING] ${decision}`] }
|
|
15667
16461
|
]);
|
|
15668
16462
|
return {
|
|
15669
16463
|
content: [{
|
|
15670
16464
|
type: "text",
|
|
15671
|
-
text:
|
|
16465
|
+
text: `[REASONING] Reasoning trace stored #${obs.id}: "${decision}"
|
|
15672
16466
|
Entity: ${entityName} | ${facts.length} facts | ${obs.tokens} tokens${reasoningAttributionWarning}`
|
|
15673
16467
|
}]
|
|
15674
16468
|
};
|
|
@@ -15705,7 +16499,7 @@ Entity: ${entityName} | ${facts.length} facts | ${obs.tokens} tokens${reasoningA
|
|
|
15705
16499
|
return {
|
|
15706
16500
|
content: [{
|
|
15707
16501
|
type: "text",
|
|
15708
|
-
text:
|
|
16502
|
+
text: `[OK] No suspicious observations found in project "${project.id}" (threshold: ${minCount}).`
|
|
15709
16503
|
}]
|
|
15710
16504
|
};
|
|
15711
16505
|
}
|
|
@@ -15765,7 +16559,7 @@ Entity: ${entityName} | ${facts.length} facts | ${obs.tokens} tokens${reasoningA
|
|
|
15765
16559
|
};
|
|
15766
16560
|
}
|
|
15767
16561
|
return {
|
|
15768
|
-
content: [{ type: "text", text:
|
|
16562
|
+
content: [{ type: "text", text: `[REASONING] Reasoning Traces:
|
|
15769
16563
|
${result.formatted}` }]
|
|
15770
16564
|
};
|
|
15771
16565
|
}
|
|
@@ -15790,7 +16584,7 @@ ${result.formatted}` }]
|
|
|
15790
16584
|
return {
|
|
15791
16585
|
content: [{
|
|
15792
16586
|
type: "text",
|
|
15793
|
-
text: "
|
|
16587
|
+
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
16588
|
}]
|
|
15795
16589
|
};
|
|
15796
16590
|
}
|
|
@@ -15825,29 +16619,29 @@ ${result.formatted}` }]
|
|
|
15825
16619
|
[{ id: older.id, title: older.title, narrative: older.narrative, facts: older.facts.join("\n") }]
|
|
15826
16620
|
);
|
|
15827
16621
|
if (decision && decision.action === "UPDATE" && decision.targetId) {
|
|
15828
|
-
actions.push(
|
|
16622
|
+
actions.push(`[UPDATED] #${older.id} "${older.title}" \u2192 superseded by #${newer.id} (${decision.reason})${decision.usedLLM ? " [LLM]" : " [heuristic]"}`);
|
|
15829
16623
|
toResolve.push(older.id);
|
|
15830
16624
|
} else if (decision && decision.action === "NONE") {
|
|
15831
|
-
actions.push(
|
|
16625
|
+
actions.push(`[DELETE] #${newer.id} "${newer.title}" \u2192 redundant (${decision.reason})${decision.usedLLM ? " [LLM]" : " [heuristic]"}`);
|
|
15832
16626
|
toResolve.push(newer.id);
|
|
15833
16627
|
} else if (decision && decision.action === "DELETE") {
|
|
15834
|
-
actions.push(
|
|
16628
|
+
actions.push(`[ERROR] #${decision.targetId ?? older.id} \u2192 outdated (${decision.reason})${decision.usedLLM ? " [LLM]" : " [heuristic]"}`);
|
|
15835
16629
|
toResolve.push(decision.targetId ?? older.id);
|
|
15836
16630
|
}
|
|
15837
16631
|
} catch (dedupErr) {
|
|
15838
|
-
actions.push(
|
|
16632
|
+
actions.push(`[WARN] comparison failed: ${dedupErr?.message ?? dedupErr}`);
|
|
15839
16633
|
}
|
|
15840
16634
|
}
|
|
15841
16635
|
}
|
|
15842
16636
|
}
|
|
15843
16637
|
if (actions.length === 0) {
|
|
15844
|
-
return { content: [{ type: "text", text:
|
|
16638
|
+
return { content: [{ type: "text", text: `[OK] Scanned ${candidates.length} memories across ${byEntity.size} entities \u2014 no duplicates found.` }] };
|
|
15845
16639
|
}
|
|
15846
16640
|
if (dryRun) {
|
|
15847
16641
|
return {
|
|
15848
16642
|
content: [{
|
|
15849
16643
|
type: "text",
|
|
15850
|
-
text:
|
|
16644
|
+
text: `[SEARCH] DRY RUN \u2014 ${actions.length} action(s) found:
|
|
15851
16645
|
|
|
15852
16646
|
${actions.join("\n")}
|
|
15853
16647
|
|
|
@@ -15860,7 +16654,7 @@ Run with dryRun=false to apply.`
|
|
|
15860
16654
|
return {
|
|
15861
16655
|
content: [{
|
|
15862
16656
|
type: "text",
|
|
15863
|
-
text:
|
|
16657
|
+
text: `[CLEANUP] Deduplicated: resolved ${unique.length} memory(ies)
|
|
15864
16658
|
|
|
15865
16659
|
${actions.join("\n")}`
|
|
15866
16660
|
}]
|
|
@@ -15976,11 +16770,11 @@ ${actions.join("\n")}`
|
|
|
15976
16770
|
const result = await archiveExpired2(projectDir2, void 0, accessMap);
|
|
15977
16771
|
if (result.archived === 0) {
|
|
15978
16772
|
return {
|
|
15979
|
-
content: [{ type: "text", text: "
|
|
16773
|
+
content: [{ type: "text", text: "[OK] No expired observations to archive. All memories are within their retention period." }]
|
|
15980
16774
|
};
|
|
15981
16775
|
}
|
|
15982
16776
|
return {
|
|
15983
|
-
content: [{ type: "text", text:
|
|
16777
|
+
content: [{ type: "text", text: `[ARCHIVED] Archived ${result.archived} expired observations (status set to 'archived' in-place)
|
|
15984
16778
|
${result.remaining} active observations remaining.
|
|
15985
16779
|
|
|
15986
16780
|
Archived memories are hidden from default search but can be found with status: "all".` }]
|
|
@@ -16015,7 +16809,7 @@ Archived memories are hidden from default search but can be found with status: "
|
|
|
16015
16809
|
const staleDocs = docs.filter((d) => getRetentionZone2(d) === "stale");
|
|
16016
16810
|
if (staleDocs.length === 0) {
|
|
16017
16811
|
return {
|
|
16018
|
-
content: [{ type: "text", text: "
|
|
16812
|
+
content: [{ type: "text", text: "[OK] No stale observations. All active memories are within 50% of their retention period." }]
|
|
16019
16813
|
};
|
|
16020
16814
|
}
|
|
16021
16815
|
const staleLines = [
|
|
@@ -16033,7 +16827,7 @@ Archived memories are hidden from default search but can be found with status: "
|
|
|
16033
16827
|
);
|
|
16034
16828
|
}
|
|
16035
16829
|
staleLines.push("");
|
|
16036
|
-
staleLines.push(">
|
|
16830
|
+
staleLines.push("> [TIP] Stale = past 50% of effective retention. Review or access to keep; otherwise will become archive candidates.");
|
|
16037
16831
|
const staleIds = staleDocs.map((d) => d.observationId);
|
|
16038
16832
|
staleLines.push("");
|
|
16039
16833
|
staleLines.push("### Suggested Actions");
|
|
@@ -16085,11 +16879,11 @@ Archived memories are hidden from default search but can be found with status: "
|
|
|
16085
16879
|
const candidateIds = candidates.map((c) => c.observationId);
|
|
16086
16880
|
lines.push("");
|
|
16087
16881
|
lines.push(`Candidate IDs: [${candidateIds.slice(0, 20).join(", ")}]${candidateIds.length > 20 ? ` \u2026 (${candidateIds.length} total)` : ""}`);
|
|
16088
|
-
lines.push(`>
|
|
16882
|
+
lines.push(`> [TIP] Use \`memorix_retention\` with \`action: "archive"\` to move all, or \`memorix_resolve\` with specific IDs.`);
|
|
16089
16883
|
lines.push("");
|
|
16090
16884
|
}
|
|
16091
16885
|
if (summary.stale > 0) {
|
|
16092
|
-
lines.push(`>
|
|
16886
|
+
lines.push(`> [TASK] ${summary.stale} stale observation(s) \u2014 use \`memorix_retention\` with \`action: "stale"\` for full details.`);
|
|
16093
16887
|
lines.push("");
|
|
16094
16888
|
}
|
|
16095
16889
|
lines.push(`### Top 5 Most Relevant`);
|
|
@@ -16120,12 +16914,12 @@ Archived memories are hidden from default search but can be found with status: "
|
|
|
16120
16914
|
return {
|
|
16121
16915
|
content: [{
|
|
16122
16916
|
type: "text",
|
|
16123
|
-
text: "
|
|
16917
|
+
text: "[STATS] Formation Pipeline: No metrics collected yet.\nStore some observations to start collecting runtime data."
|
|
16124
16918
|
}]
|
|
16125
16919
|
};
|
|
16126
16920
|
}
|
|
16127
16921
|
const lines = [
|
|
16128
|
-
"
|
|
16922
|
+
"[STATS] **Formation Pipeline Metrics**",
|
|
16129
16923
|
"",
|
|
16130
16924
|
`**Total observations processed:** ${summary.total}`,
|
|
16131
16925
|
`**Average value score:** ${summary.avgValueScore.toFixed(3)}`,
|
|
@@ -16141,7 +16935,7 @@ Archived memories are hidden from default search but can be found with status: "
|
|
|
16141
16935
|
];
|
|
16142
16936
|
for (const [cat, count2] of Object.entries(summary.categoryBreakdown)) {
|
|
16143
16937
|
const pct = (count2 / summary.total * 100).toFixed(1);
|
|
16144
|
-
const icon = cat === "core" ? "
|
|
16938
|
+
const icon = cat === "core" ? "[CHANGE]" : cat === "contextual" ? "[FIX]" : "[GOTCHA]";
|
|
16145
16939
|
lines.push(`- ${icon} **${cat}:** ${count2} (${pct}%)`);
|
|
16146
16940
|
}
|
|
16147
16941
|
lines.push("", "### Resolution Actions");
|
|
@@ -16183,7 +16977,7 @@ Archived memories are hidden from default search but can be found with status: "
|
|
|
16183
16977
|
};
|
|
16184
16978
|
}
|
|
16185
16979
|
);
|
|
16186
|
-
let enableKG =
|
|
16980
|
+
let enableKG = isToolInProfile("create_entities", toolProfile);
|
|
16187
16981
|
try {
|
|
16188
16982
|
const { homedir: homedir21 } = await import("os");
|
|
16189
16983
|
const { join: join23 } = await import("path");
|
|
@@ -16495,7 +17289,7 @@ Archived memories are hidden from default search but can be found with status: "
|
|
|
16495
17289
|
lines2.push("- No skills found");
|
|
16496
17290
|
}
|
|
16497
17291
|
if (scan.skillConflicts.length > 0) {
|
|
16498
|
-
lines2.push("", `###
|
|
17292
|
+
lines2.push("", `### [WARN] Skill Name Conflicts`);
|
|
16499
17293
|
for (const c of scan.skillConflicts) {
|
|
16500
17294
|
lines2.push(`- **${c.name}**: kept from ${c.kept.sourceAgent}, duplicate in ${c.skipped.sourceAgent}`);
|
|
16501
17295
|
}
|
|
@@ -16641,9 +17435,9 @@ ${skill.content}` }]
|
|
|
16641
17435
|
if (write && target) {
|
|
16642
17436
|
const path18 = engine.writeSkill(sk, target);
|
|
16643
17437
|
if (path18) {
|
|
16644
|
-
lines.push(`-
|
|
17438
|
+
lines.push(`- [OK] **Written**: \`${path18}\``);
|
|
16645
17439
|
} else {
|
|
16646
|
-
lines.push(`-
|
|
17440
|
+
lines.push(`- [ERROR] Failed to write`);
|
|
16647
17441
|
}
|
|
16648
17442
|
}
|
|
16649
17443
|
lines.push("");
|
|
@@ -16700,7 +17494,7 @@ ${skill.content}` }]
|
|
|
16700
17494
|
if (!deleted) {
|
|
16701
17495
|
return { content: [{ type: "text", text: `Mini-skill #${skillId} not found.` }], isError: true };
|
|
16702
17496
|
}
|
|
16703
|
-
return { content: [{ type: "text", text:
|
|
17497
|
+
return { content: [{ type: "text", text: `[OK] Deleted mini-skill #${skillId}.` }] };
|
|
16704
17498
|
}
|
|
16705
17499
|
if (!observationIds || observationIds.length === 0) {
|
|
16706
17500
|
return { content: [{ type: "text", text: "Error: `observationIds` is required for promote action. Use `memorix_search` to find observation IDs." }], isError: true };
|
|
@@ -16717,7 +17511,7 @@ ${skill.content}` }]
|
|
|
16717
17511
|
}
|
|
16718
17512
|
const skill = await promoteToMiniSkill2(projectDir2, project.id, matched, { trigger, instruction, tags });
|
|
16719
17513
|
const lines = [
|
|
16720
|
-
|
|
17514
|
+
`[OK] Created mini-skill #${skill.id}`,
|
|
16721
17515
|
"",
|
|
16722
17516
|
`**${skill.title}**`,
|
|
16723
17517
|
`**Do**: ${skill.instruction}`,
|
|
@@ -16748,7 +17542,7 @@ ${skill.content}` }]
|
|
|
16748
17542
|
if (action === "preview") {
|
|
16749
17543
|
const clusters = await findConsolidationCandidates2(projectDir2, project.id, { threshold: safeThreshold });
|
|
16750
17544
|
if (clusters.length === 0) {
|
|
16751
|
-
return { content: [{ type: "text", text: "
|
|
17545
|
+
return { content: [{ type: "text", text: "[OK] No consolidation candidates found. Your memories are already clean!" }] };
|
|
16752
17546
|
}
|
|
16753
17547
|
const lines2 = [`## Consolidation Preview`, `Found **${clusters.length}** clusters to merge:`, ""];
|
|
16754
17548
|
for (let i = 0; i < clusters.length; i++) {
|
|
@@ -16764,7 +17558,7 @@ ${skill.content}` }]
|
|
|
16764
17558
|
}
|
|
16765
17559
|
const result = await executeConsolidation2(projectDir2, project.id, { threshold: safeThreshold });
|
|
16766
17560
|
if (result.clustersFound === 0) {
|
|
16767
|
-
return { content: [{ type: "text", text: "
|
|
17561
|
+
return { content: [{ type: "text", text: "[OK] No consolidation needed. Memories are already clean!" }] };
|
|
16768
17562
|
}
|
|
16769
17563
|
const lines = [
|
|
16770
17564
|
`## Consolidation Complete`,
|
|
@@ -16783,18 +17577,20 @@ ${skill.content}` }]
|
|
|
16783
17577
|
"memorix_session_start",
|
|
16784
17578
|
{
|
|
16785
17579
|
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.",
|
|
17580
|
+
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
17581
|
inputSchema: {
|
|
16788
17582
|
sessionId: z2.string().optional().describe("Custom session ID (auto-generated if omitted)"),
|
|
16789
17583
|
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
|
|
17584
|
+
agentType: z2.string().optional().describe('Agent type used for optional Agent Team identity mapping (e.g., "windsurf", "cursor").'),
|
|
17585
|
+
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."),
|
|
17586
|
+
joinTeam: z2.boolean().optional().describe("If true, also join the autonomous agent team for this session. Defaults to false."),
|
|
17587
|
+
role: z2.string().optional().describe("Explicit role override used only when joinTeam=true."),
|
|
16792
17588
|
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."
|
|
17589
|
+
"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
17590
|
)
|
|
16795
17591
|
}
|
|
16796
17592
|
},
|
|
16797
|
-
async ({ sessionId, agent, agentType, instanceId, projectRoot: explicitRoot }) => {
|
|
17593
|
+
async ({ sessionId, agent, agentType, instanceId, joinTeam, role, projectRoot: explicitRoot }) => {
|
|
16798
17594
|
currentAgentId = void 0;
|
|
16799
17595
|
if (explicitRoot && typeof explicitRoot === "string") {
|
|
16800
17596
|
let bound = await switchProject(explicitRoot);
|
|
@@ -16841,18 +17637,27 @@ Ensure the path points to a directory containing a .git folder (or a subdirector
|
|
|
16841
17637
|
const { startSession: startSession2 } = await Promise.resolve().then(() => (init_session(), session_exports));
|
|
16842
17638
|
const result = await startSession2(projectDir2, project.id, { sessionId, agent });
|
|
16843
17639
|
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)";
|
|
17640
|
+
const shouldJoinTeam = !!joinTeam;
|
|
16844
17641
|
let registeredAgent = null;
|
|
16845
17642
|
let watermarkInfo = "";
|
|
16846
17643
|
let rescueInfo = "";
|
|
17644
|
+
let teamJoinNotice = "";
|
|
16847
17645
|
try {
|
|
16848
|
-
if (
|
|
17646
|
+
if (!teamFeaturesEnabled && shouldJoinTeam) {
|
|
17647
|
+
teamJoinNotice = "Team join skipped: the current tool profile does not expose Agent Team tools.";
|
|
17648
|
+
} else if (shouldJoinTeam && typeof teamStore !== "undefined" && (agent || agentType)) {
|
|
16849
17649
|
const { AGENT_TYPE_ROLE_MAP: AGENT_TYPE_ROLE_MAP2 } = await Promise.resolve().then(() => (init_team_store(), team_store_exports));
|
|
16850
17650
|
const resolvedAgentType = agentType || agent || "unknown";
|
|
16851
|
-
const resolvedRole = (resolvedAgentType ? AGENT_TYPE_ROLE_MAP2[resolvedAgentType] : void 0) || "engineer";
|
|
17651
|
+
const resolvedRole = role || (resolvedAgentType ? AGENT_TYPE_ROLE_MAP2[resolvedAgentType] : void 0) || "engineer";
|
|
17652
|
+
const resolvedInstanceId = instanceId || createDeterministicInstanceId(
|
|
17653
|
+
project.id,
|
|
17654
|
+
resolvedAgentType,
|
|
17655
|
+
agent || agentType || void 0
|
|
17656
|
+
);
|
|
16852
17657
|
registeredAgent = teamStore.registerAgent({
|
|
16853
17658
|
projectId: project.id,
|
|
16854
17659
|
agentType: resolvedAgentType,
|
|
16855
|
-
instanceId:
|
|
17660
|
+
instanceId: resolvedInstanceId,
|
|
16856
17661
|
name: agent || agentType || void 0,
|
|
16857
17662
|
role: resolvedRole
|
|
16858
17663
|
});
|
|
@@ -16866,32 +17671,36 @@ Ensure the path points to a directory containing a .git folder (or a subdirector
|
|
|
16866
17671
|
));
|
|
16867
17672
|
const wm = computeWatermark2(lastSeen, currentGen, projectObs.length);
|
|
16868
17673
|
if (wm.newObservationCount > 0) {
|
|
16869
|
-
watermarkInfo =
|
|
17674
|
+
watermarkInfo = `[STATS] ${wm.newObservationCount} new observation(s) in this project since your last session.`;
|
|
16870
17675
|
}
|
|
16871
17676
|
teamStore.updateWatermark(registeredAgent.agent_id, currentGen);
|
|
16872
17677
|
const STALE_TTL_MS = 5 * 60 * 1e3;
|
|
16873
17678
|
const rescuedAgentIds = teamStore.detectAndMarkStale(project.id, STALE_TTL_MS);
|
|
16874
17679
|
if (rescuedAgentIds.length > 0) {
|
|
16875
|
-
rescueInfo =
|
|
17680
|
+
rescueInfo = `[RESCUE] ${rescuedAgentIds.length} stale agent(s) detected and rescued.`;
|
|
16876
17681
|
}
|
|
16877
17682
|
const availableTasks = teamStore.listTasks(project.id, { available: true });
|
|
16878
17683
|
if (availableTasks.length > 0) {
|
|
16879
17684
|
rescueInfo += rescueInfo ? "\n" : "";
|
|
16880
|
-
rescueInfo +=
|
|
17685
|
+
rescueInfo += `[TASK] ${availableTasks.length} task(s) available to claim. Use memorix_poll for details.`;
|
|
16881
17686
|
}
|
|
17687
|
+
} else if (shouldJoinTeam) {
|
|
17688
|
+
teamJoinNotice = "Team join skipped: pass `agent` or `agentType` to create an Agent Team identity.";
|
|
16882
17689
|
}
|
|
16883
17690
|
} catch {
|
|
16884
17691
|
}
|
|
16885
17692
|
const lines = [
|
|
16886
|
-
|
|
17693
|
+
`[OK] Session started: ${result.session.id}`,
|
|
16887
17694
|
`Project: ${project.name} (${project.id})`,
|
|
16888
17695
|
result.session.agent ? `Agent: ${result.session.agent}` : "",
|
|
16889
17696
|
registeredAgent ? `Agent ID: ${registeredAgent.agent_id} (instance: ${registeredAgent.instance_id})` : "",
|
|
17697
|
+
!registeredAgent ? "Team identity: not joined (memory/session context only)" : "",
|
|
16890
17698
|
llmStatus,
|
|
16891
|
-
|
|
16892
|
-
|
|
17699
|
+
teamJoinNotice,
|
|
17700
|
+
registeredAgent ? watermarkInfo : "",
|
|
17701
|
+
registeredAgent ? rescueInfo : "",
|
|
16893
17702
|
"",
|
|
16894
|
-
"
|
|
17703
|
+
"[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
17704
|
""
|
|
16896
17705
|
];
|
|
16897
17706
|
try {
|
|
@@ -16931,27 +17740,27 @@ ${s.instruction}`.toLowerCase();
|
|
|
16931
17740
|
} catch {
|
|
16932
17741
|
}
|
|
16933
17742
|
if (result.previousContext) {
|
|
16934
|
-
lines.push("---", "
|
|
17743
|
+
lines.push("---", "[TASK] **Context from previous sessions:**", "", result.previousContext);
|
|
16935
17744
|
} else {
|
|
16936
17745
|
lines.push("No previous session context found. This appears to be a fresh project.");
|
|
16937
17746
|
}
|
|
16938
17747
|
try {
|
|
16939
|
-
if (typeof teamStore !== "undefined") {
|
|
17748
|
+
if (registeredAgent && teamFeaturesEnabled && typeof teamStore !== "undefined") {
|
|
16940
17749
|
const activeAgents = teamStore.listAgents(project.id, { status: "active" });
|
|
16941
17750
|
if (activeAgents.length > 0) {
|
|
16942
|
-
lines.push("", "---", "
|
|
17751
|
+
lines.push("", "---", "[TEAM] **Team Status:**");
|
|
16943
17752
|
for (const a of activeAgents) {
|
|
16944
|
-
lines.push(`-
|
|
17753
|
+
lines.push(`- [CHANGE] ${a.name}${a.role ? ` (${a.role})` : ""}`);
|
|
16945
17754
|
}
|
|
16946
17755
|
const locks = teamStore.listLocks(project.id);
|
|
16947
17756
|
if (locks.length > 0) {
|
|
16948
|
-
lines.push("", "
|
|
17757
|
+
lines.push("", "[LOCK] **Locked files:**");
|
|
16949
17758
|
for (const l of locks) {
|
|
16950
17759
|
const owner = teamStore.getAgent(l.locked_by);
|
|
16951
17760
|
lines.push(`- ${l.file} \u2014 ${owner?.name ?? l.locked_by.slice(0, 8)}`);
|
|
16952
17761
|
}
|
|
16953
17762
|
}
|
|
16954
|
-
lines.push("", "
|
|
17763
|
+
lines.push("", "[TIP] Use `team_manage` to register, `team_message` to check inbox, `team_task` to see tasks.");
|
|
16955
17764
|
}
|
|
16956
17765
|
}
|
|
16957
17766
|
} catch {
|
|
@@ -16965,7 +17774,7 @@ ${s.instruction}`.toLowerCase();
|
|
|
16965
17774
|
"memorix_session_end",
|
|
16966
17775
|
{
|
|
16967
17776
|
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-
|
|
17777
|
+
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
17778
|
inputSchema: {
|
|
16970
17779
|
sessionId: z2.string().describe("Session ID to close (from memorix_session_start)"),
|
|
16971
17780
|
summary: z2.string().optional().describe("Structured session summary (Goal/Discoveries/Accomplished/Files format)")
|
|
@@ -16983,7 +17792,7 @@ ${s.instruction}`.toLowerCase();
|
|
|
16983
17792
|
return {
|
|
16984
17793
|
content: [{
|
|
16985
17794
|
type: "text",
|
|
16986
|
-
text:
|
|
17795
|
+
text: `[OK] Session "${sessionId}" completed.
|
|
16987
17796
|
Duration: ${session.startedAt} \u2192 ${session.endedAt}
|
|
16988
17797
|
${summary ? "Summary saved for next session context injection." : "No summary provided \u2014 consider adding one for better cross-session context."}`
|
|
16989
17798
|
}]
|
|
@@ -17056,7 +17865,7 @@ ${json}
|
|
|
17056
17865
|
}]
|
|
17057
17866
|
};
|
|
17058
17867
|
}
|
|
17059
|
-
if (!jsonStr) return { content: [{ type: "text", text: "
|
|
17868
|
+
if (!jsonStr) return { content: [{ type: "text", text: "[ERROR] data is required for import" }], isError: true };
|
|
17060
17869
|
const { importFromJson: importFromJson2 } = await Promise.resolve().then(() => (init_export_import(), export_import_exports));
|
|
17061
17870
|
let parsed;
|
|
17062
17871
|
try {
|
|
@@ -17188,9 +17997,7 @@ ${json}
|
|
|
17188
17997
|
}
|
|
17189
17998
|
}
|
|
17190
17999
|
console.error(`[memorix] Dashboard staticDir: ${staticDir}`);
|
|
17191
|
-
startDashboard2(projectDir2, portNum, staticDir, project.id, project.name, false, {
|
|
17192
|
-
teamStore
|
|
17193
|
-
}, project.rootPath, projectResolved).then(() => {
|
|
18000
|
+
startDashboard2(projectDir2, portNum, staticDir, project.id, project.name, false, void 0, project.rootPath, projectResolved).then(() => {
|
|
17194
18001
|
dashboardRunning = true;
|
|
17195
18002
|
}).catch((err) => {
|
|
17196
18003
|
console.error("[memorix] Dashboard error:", err);
|
|
@@ -17240,16 +18047,20 @@ ${json}
|
|
|
17240
18047
|
}
|
|
17241
18048
|
}
|
|
17242
18049
|
);
|
|
17243
|
-
const { initTeamStore: initTeamStore2 } = await Promise.resolve().then(() => (init_team_store(), team_store_exports));
|
|
17244
18050
|
let teamStore;
|
|
17245
|
-
|
|
17246
|
-
|
|
17247
|
-
|
|
17248
|
-
|
|
17249
|
-
|
|
17250
|
-
|
|
17251
|
-
|
|
17252
|
-
|
|
18051
|
+
let initTeamStoreForProject;
|
|
18052
|
+
if (teamFeaturesEnabled) {
|
|
18053
|
+
const { initTeamStore: initTeamStore2 } = await Promise.resolve().then(() => (init_team_store(), team_store_exports));
|
|
18054
|
+
initTeamStoreForProject = initTeamStore2;
|
|
18055
|
+
if (sharedTeam?.teamStore) {
|
|
18056
|
+
teamStore = sharedTeam.teamStore;
|
|
18057
|
+
} else {
|
|
18058
|
+
teamStore = await initTeamStore2(projectDir2);
|
|
18059
|
+
}
|
|
18060
|
+
if (!teamStore.getEventBus()) {
|
|
18061
|
+
const { TeamEventBus: TeamEventBus2 } = await Promise.resolve().then(() => (init_event_bus(), event_bus_exports));
|
|
18062
|
+
teamStore.setEventBus(new TeamEventBus2());
|
|
18063
|
+
}
|
|
17253
18064
|
}
|
|
17254
18065
|
server.registerTool(
|
|
17255
18066
|
"team_manage",
|
|
@@ -17284,13 +18095,15 @@ ${json}
|
|
|
17284
18095
|
role: resolvedRole,
|
|
17285
18096
|
capabilities: capabilities ? coerceStringArray(capabilities) : void 0
|
|
17286
18097
|
});
|
|
18098
|
+
currentAgentId = agent.agent_id;
|
|
17287
18099
|
const caps = agent.capabilities ? JSON.parse(agent.capabilities) : [];
|
|
17288
18100
|
return {
|
|
17289
18101
|
content: [{
|
|
17290
18102
|
type: "text",
|
|
17291
|
-
text: `Joined
|
|
18103
|
+
text: `Joined Agent Team as "${agent.name}" (ID: ${agent.agent_id})
|
|
17292
18104
|
Instance ID: ${agent.instance_id}
|
|
17293
18105
|
Role: ${agent.role}
|
|
18106
|
+
This session now attributes Agent Team activity to that identity.
|
|
17294
18107
|
Active agents: ${teamStore.getActiveCount(project.id)}`
|
|
17295
18108
|
}]
|
|
17296
18109
|
};
|
|
@@ -17298,6 +18111,7 @@ Active agents: ${teamStore.getActiveCount(project.id)}`
|
|
|
17298
18111
|
if (action === "leave") {
|
|
17299
18112
|
if (!agentId) return { content: [{ type: "text", text: "agentId is required for leave" }], isError: true };
|
|
17300
18113
|
const left = teamStore.leaveAgent(agentId);
|
|
18114
|
+
if (currentAgentId === agentId) currentAgentId = void 0;
|
|
17301
18115
|
if (!left) return { content: [{ type: "text", text: "Agent not found" }] };
|
|
17302
18116
|
const releasedLocks = teamStore.releaseAllLocks(agentId);
|
|
17303
18117
|
const releasedTasks = teamStore.releaseTasksByAgent(agentId);
|
|
@@ -17321,7 +18135,7 @@ Active agents: ${teamStore.getActiveCount(project.id)}`
|
|
|
17321
18135
|
const lines = occupancy2.map(({ role: role2, activeAgents, vacant }) => {
|
|
17322
18136
|
const agentTypes = JSON.parse(role2.preferred_agent_types);
|
|
17323
18137
|
const agentNames = activeAgents.map((a) => a.name).join(", ") || "vacant";
|
|
17324
|
-
return `${role2.label} (${role2.role_id.split(":").pop()})
|
|
18138
|
+
return `${role2.label} (${role2.role_id.split(":").pop()}) - ${activeAgents.length}/${role2.max_concurrent} filled, ${vacant} vacant
|
|
17325
18139
|
Agents: ${agentNames}
|
|
17326
18140
|
Preferred types: ${agentTypes.join(", ") || "any"}${role2.description ? "\n " + role2.description : ""}`;
|
|
17327
18141
|
});
|
|
@@ -17353,11 +18167,11 @@ ${lines.join("\n\n")}` }] };
|
|
|
17353
18167
|
}
|
|
17354
18168
|
const roleLines = occupancy.map(({ role: role2, activeAgents, vacant }) => {
|
|
17355
18169
|
const agentNames = activeAgents.map((a) => a.name).join(", ") || "vacant";
|
|
17356
|
-
return `${role2.label}: ${activeAgents.length}/${role2.max_concurrent}
|
|
18170
|
+
return `${role2.label}: ${activeAgents.length}/${role2.max_concurrent} - ${agentNames}${vacant > 0 ? ` (${vacant} slot${vacant > 1 ? "s" : ""} open)` : ""}`;
|
|
17357
18171
|
});
|
|
17358
18172
|
const agentLines = agents.map((a) => {
|
|
17359
18173
|
const caps = a.capabilities ? JSON.parse(a.capabilities) : [];
|
|
17360
|
-
return `${a.status === "active" ? "
|
|
18174
|
+
return `${a.status === "active" ? "[active]" : "[inactive]"} ${a.name} (${a.agent_id.slice(0, 8)}) - ${a.role ?? "no role"} [${caps.join(", ") || "-"}]`;
|
|
17361
18175
|
});
|
|
17362
18176
|
return {
|
|
17363
18177
|
content: [{
|
|
@@ -17386,10 +18200,10 @@ ${agentLines.join("\n")}`
|
|
|
17386
18200
|
},
|
|
17387
18201
|
async ({ action, file, agentId }) => {
|
|
17388
18202
|
if (action === "lock") {
|
|
17389
|
-
if (!file || !agentId) return { content: [{ type: "text", text: "
|
|
18203
|
+
if (!file || !agentId) return { content: [{ type: "text", text: "[ERROR] file and agentId are required for lock" }], isError: true };
|
|
17390
18204
|
const agent = teamStore.getAgent(agentId);
|
|
17391
18205
|
if (!agent || agent.status !== "active") {
|
|
17392
|
-
return { content: [{ type: "text", text:
|
|
18206
|
+
return { content: [{ type: "text", text: `[ERROR] Unknown or inactive agent: ${agentId.slice(0, 8)}\u2026` }], isError: true };
|
|
17393
18207
|
}
|
|
17394
18208
|
const result = teamStore.acquireLock(project.id, file, agentId);
|
|
17395
18209
|
if (result.success) return { content: [{ type: "text", text: `Locked: ${file}` }] };
|
|
@@ -17397,7 +18211,7 @@ ${agentLines.join("\n")}`
|
|
|
17397
18211
|
return { content: [{ type: "text", text: `Denied \u2014 locked by ${owner?.name ?? result.lockedBy.slice(0, 8)}` }], isError: true };
|
|
17398
18212
|
}
|
|
17399
18213
|
if (action === "unlock") {
|
|
17400
|
-
if (!file || !agentId) return { content: [{ type: "text", text: "
|
|
18214
|
+
if (!file || !agentId) return { content: [{ type: "text", text: "[ERROR] file and agentId are required for unlock" }], isError: true };
|
|
17401
18215
|
const released = teamStore.releaseLock(project.id, file, agentId);
|
|
17402
18216
|
return { content: [{ type: "text", text: released ? `Unlocked: ${file}` : `Cannot unlock: not owner or not locked` }] };
|
|
17403
18217
|
}
|
|
@@ -17439,7 +18253,7 @@ ${lines.join("\n")}` }] };
|
|
|
17439
18253
|
async ({ action, description: desc, deps, taskId, agentId, result, status, available, metadata, requiredRole, preferredRole }) => {
|
|
17440
18254
|
try {
|
|
17441
18255
|
if (action === "create") {
|
|
17442
|
-
if (!desc) return { content: [{ type: "text", text: "
|
|
18256
|
+
if (!desc) return { content: [{ type: "text", text: "[ERROR] description is required for create" }], isError: true };
|
|
17443
18257
|
let parsedMeta;
|
|
17444
18258
|
if (metadata) {
|
|
17445
18259
|
try {
|
|
@@ -17451,7 +18265,7 @@ ${lines.join("\n")}` }] };
|
|
|
17451
18265
|
const existingTasks = teamStore.listTasks(project.id);
|
|
17452
18266
|
const guard = checkPipelineGuards2({ existingTasks, newTaskMeta: parsedMeta });
|
|
17453
18267
|
if (!guard.allowed) {
|
|
17454
|
-
return { content: [{ type: "text", text:
|
|
18268
|
+
return { content: [{ type: "text", text: `[ERROR] ${guard.reason}` }], isError: true };
|
|
17455
18269
|
}
|
|
17456
18270
|
const task = teamStore.createTask({
|
|
17457
18271
|
projectId: project.id,
|
|
@@ -17467,19 +18281,19 @@ ${lines.join("\n")}` }] };
|
|
|
17467
18281
|
return { content: [{ type: "text", text: `Task created: ${task.task_id.slice(0, 8)}\u2026 "${desc}"${taskDeps.length > 0 ? ` (depends on ${taskDeps.length})` : ""}${roleInfo}` }] };
|
|
17468
18282
|
}
|
|
17469
18283
|
if (action === "claim") {
|
|
17470
|
-
if (!taskId || !agentId) return { content: [{ type: "text", text: "
|
|
18284
|
+
if (!taskId || !agentId) return { content: [{ type: "text", text: "[ERROR] taskId and agentId required for claim" }], isError: true };
|
|
17471
18285
|
const agent = teamStore.getAgent(agentId);
|
|
17472
|
-
if (!agent || agent.status !== "active") return { content: [{ type: "text", text:
|
|
18286
|
+
if (!agent || agent.status !== "active") return { content: [{ type: "text", text: `[ERROR] Unknown or inactive agent` }], isError: true };
|
|
17473
18287
|
const claimResult = teamStore.claimTask(taskId, agentId);
|
|
17474
|
-
if (!claimResult.success) return { content: [{ type: "text", text:
|
|
18288
|
+
if (!claimResult.success) return { content: [{ type: "text", text: `[ERROR] ${claimResult.reason}` }], isError: true };
|
|
17475
18289
|
const hintSuffix = claimResult.hint ? `
|
|
17476
|
-
|
|
18290
|
+
[WARN] ${claimResult.hint}` : "";
|
|
17477
18291
|
return { content: [{ type: "text", text: `Task claimed by ${agent.name}: "${claimResult.task.description}"${hintSuffix}` }] };
|
|
17478
18292
|
}
|
|
17479
18293
|
if (action === "complete") {
|
|
17480
|
-
if (!taskId || !agentId || !result) return { content: [{ type: "text", text: "
|
|
18294
|
+
if (!taskId || !agentId || !result) return { content: [{ type: "text", text: "[ERROR] taskId, agentId, and result required for complete" }], isError: true };
|
|
17481
18295
|
const completeResult = teamStore.completeTask(taskId, agentId, result);
|
|
17482
|
-
if (!completeResult.success) return { content: [{ type: "text", text:
|
|
18296
|
+
if (!completeResult.success) return { content: [{ type: "text", text: `[ERROR] ${completeResult.reason}` }], isError: true };
|
|
17483
18297
|
const completedTask = teamStore.getTask(taskId);
|
|
17484
18298
|
return { content: [{ type: "text", text: `Task completed: "${completedTask?.description ?? taskId}"
|
|
17485
18299
|
Result: ${result}` }] };
|
|
@@ -17496,7 +18310,7 @@ Result: ${result}` }] };
|
|
|
17496
18310
|
return { content: [{ type: "text", text: `Tasks (${list.length}):
|
|
17497
18311
|
${lines.join("\n")}` }] };
|
|
17498
18312
|
} catch (err) {
|
|
17499
|
-
return { content: [{ type: "text", text:
|
|
18313
|
+
return { content: [{ type: "text", text: `[ERROR] ${err.message}` }], isError: true };
|
|
17500
18314
|
}
|
|
17501
18315
|
}
|
|
17502
18316
|
);
|
|
@@ -17519,9 +18333,9 @@ ${lines.join("\n")}` }] };
|
|
|
17519
18333
|
},
|
|
17520
18334
|
async ({ action, from, to, type: msgType, content, agentId, markRead, toRole, handoffStatus }) => {
|
|
17521
18335
|
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: "
|
|
18336
|
+
if (!from || !msgType || !content) return { content: [{ type: "text", text: "[ERROR] from, type, and content required for send" }], isError: true };
|
|
18337
|
+
if (!to && !toRole) return { content: [{ type: "text", text: "[ERROR] either to (agent ID) or toRole is required for send" }], isError: true };
|
|
18338
|
+
if (content.length > 1e4) return { content: [{ type: "text", text: "[ERROR] Message too large (max 10KB)" }], isError: true };
|
|
17525
18339
|
const msg = teamStore.sendMessage({
|
|
17526
18340
|
projectId: project.id,
|
|
17527
18341
|
senderAgentId: from,
|
|
@@ -17531,13 +18345,13 @@ ${lines.join("\n")}` }] };
|
|
|
17531
18345
|
toRole: toRole ?? null,
|
|
17532
18346
|
handoffStatus: handoffStatus ?? (msgType === "handoff" ? "open" : null)
|
|
17533
18347
|
});
|
|
17534
|
-
if ("error" in msg) return { content: [{ type: "text", text:
|
|
18348
|
+
if ("error" in msg) return { content: [{ type: "text", text: `[ERROR] ${msg.error}` }], isError: true };
|
|
17535
18349
|
const target = to ? `agent ${to.slice(0, 8)}\u2026` : `role ${toRole}`;
|
|
17536
18350
|
return { content: [{ type: "text", text: `Message sent (${msgType}) to ${target} | ID: ${msg.id.slice(0, 8)}\u2026${toRole ? ` [role: ${toRole}]` : ""}` }] };
|
|
17537
18351
|
}
|
|
17538
18352
|
if (action === "broadcast") {
|
|
17539
|
-
if (!from || !msgType || !content) return { content: [{ type: "text", text: "
|
|
17540
|
-
if (content.length > 1e4) return { content: [{ type: "text", text: "
|
|
18353
|
+
if (!from || !msgType || !content) return { content: [{ type: "text", text: "[ERROR] from, type, and content required for broadcast" }], isError: true };
|
|
18354
|
+
if (content.length > 1e4) return { content: [{ type: "text", text: "[ERROR] Message too large (max 10KB)" }], isError: true };
|
|
17541
18355
|
const msg = teamStore.sendMessage({
|
|
17542
18356
|
projectId: project.id,
|
|
17543
18357
|
senderAgentId: from,
|
|
@@ -17545,11 +18359,11 @@ ${lines.join("\n")}` }] };
|
|
|
17545
18359
|
type: msgType,
|
|
17546
18360
|
content
|
|
17547
18361
|
});
|
|
17548
|
-
if ("error" in msg) return { content: [{ type: "text", text:
|
|
18362
|
+
if ("error" in msg) return { content: [{ type: "text", text: `[ERROR] ${msg.error}` }], isError: true };
|
|
17549
18363
|
return { content: [{ type: "text", text: `Broadcast (${msgType}) | ID: ${msg.id.slice(0, 8)}\u2026` }] };
|
|
17550
18364
|
}
|
|
17551
18365
|
const inboxId = agentId || from || "";
|
|
17552
|
-
if (!inboxId) return { content: [{ type: "text", text: "
|
|
18366
|
+
if (!inboxId) return { content: [{ type: "text", text: "[ERROR] agentId required for inbox" }], isError: true };
|
|
17553
18367
|
const inbox = teamStore.getInbox(project.id, inboxId);
|
|
17554
18368
|
const unread = teamStore.getUnreadCount(project.id, inboxId);
|
|
17555
18369
|
if (inbox.length === 0) return { content: [{ type: "text", text: "Inbox empty" }] };
|
|
@@ -17571,7 +18385,7 @@ ${lines.join("\n")}` }] };
|
|
|
17571
18385
|
title: "Team Poll \u2014 Situational Awareness",
|
|
17572
18386
|
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
18387
|
inputSchema: {
|
|
17574
|
-
agentId: z2.string().optional().describe("Your agent ID (from team_manage join or session_start). If omitted, returns project-level overview only."),
|
|
18388
|
+
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
18389
|
markInboxRead: z2.boolean().optional().describe("If true, mark all inbox messages as read after returning them.")
|
|
17576
18390
|
}
|
|
17577
18391
|
},
|
|
@@ -17598,13 +18412,13 @@ ${lines.join("\n")}` }] };
|
|
|
17598
18412
|
}
|
|
17599
18413
|
const lines = [];
|
|
17600
18414
|
if (poll.agent) {
|
|
17601
|
-
lines.push(
|
|
18415
|
+
lines.push(`[AGENT] You: ${poll.agent.agentId.slice(0, 8)}\u2026 (${poll.agent.status})`);
|
|
17602
18416
|
}
|
|
17603
18417
|
if (poll.watermark.newObservationCount > 0) {
|
|
17604
|
-
lines.push(
|
|
18418
|
+
lines.push(`[STATS] ${poll.watermark.newObservationCount} new observation(s) since your last session`);
|
|
17605
18419
|
}
|
|
17606
18420
|
if (poll.inbox.unreadCount > 0) {
|
|
17607
|
-
lines.push(
|
|
18421
|
+
lines.push(`[INBOX] ${poll.inbox.unreadCount} unread message(s)`);
|
|
17608
18422
|
for (const m of poll.inbox.messages.slice(-5)) {
|
|
17609
18423
|
const sender = teamStore.getAgent(m.sender_agent_id);
|
|
17610
18424
|
lines.push(` ${m.read_at ? " " : "*"} [${m.type}] from ${sender?.name ?? m.sender_agent_id.slice(0, 8)}: ${m.content.slice(0, 80)}`);
|
|
@@ -17612,21 +18426,21 @@ ${lines.join("\n")}` }] };
|
|
|
17612
18426
|
}
|
|
17613
18427
|
if (poll.tasks.myInProgress.length > 0) {
|
|
17614
18428
|
lines.push(`
|
|
17615
|
-
|
|
18429
|
+
[TOOL] Your in-progress tasks (${poll.tasks.myInProgress.length}):`);
|
|
17616
18430
|
for (const t of poll.tasks.myInProgress) {
|
|
17617
18431
|
lines.push(` [~] ${t.task_id.slice(0, 8)}\u2026 "${t.description}"`);
|
|
17618
18432
|
}
|
|
17619
18433
|
}
|
|
17620
18434
|
if (poll.tasks.availableToClaim.length > 0) {
|
|
17621
18435
|
lines.push(`
|
|
17622
|
-
|
|
18436
|
+
[TASK] Available to claim (${poll.tasks.availableToClaim.length}):`);
|
|
17623
18437
|
for (const t of poll.tasks.availableToClaim) {
|
|
17624
18438
|
lines.push(` [ ] ${t.task_id.slice(0, 8)}\u2026 "${t.description}"`);
|
|
17625
18439
|
}
|
|
17626
18440
|
}
|
|
17627
18441
|
if (poll.tasks.recentlyCompleted.length > 0) {
|
|
17628
18442
|
lines.push(`
|
|
17629
|
-
|
|
18443
|
+
[OK] Completed (${poll.tasks.recentlyCompleted.length}):`);
|
|
17630
18444
|
for (const t of poll.tasks.recentlyCompleted.slice(-5)) {
|
|
17631
18445
|
const who = t.assignee_agent_id ? teamStore.getAgent(t.assignee_agent_id)?.name ?? t.assignee_agent_id.slice(0, 8) : "?";
|
|
17632
18446
|
lines.push(` [x] ${t.task_id.slice(0, 8)}\u2026 "${t.description}" \u2014 by ${who}`);
|
|
@@ -17634,15 +18448,15 @@ ${lines.join("\n")}` }] };
|
|
|
17634
18448
|
}
|
|
17635
18449
|
if (poll.tasks.recentlyFailed.length > 0) {
|
|
17636
18450
|
lines.push(`
|
|
17637
|
-
|
|
18451
|
+
[ERROR] Failed (${poll.tasks.recentlyFailed.length}):`);
|
|
17638
18452
|
for (const t of poll.tasks.recentlyFailed.slice(-3)) {
|
|
17639
18453
|
lines.push(` [!] ${t.task_id.slice(0, 8)}\u2026 "${t.description}" \u2014 ${t.result?.slice(0, 80) ?? "no reason"}`);
|
|
17640
18454
|
}
|
|
17641
18455
|
}
|
|
17642
18456
|
lines.push(`
|
|
17643
|
-
|
|
18457
|
+
[TEAM] Team: ${poll.team.activeAgents.length} active / ${poll.team.totalAgents} total`);
|
|
17644
18458
|
for (const a of poll.team.activeAgents) {
|
|
17645
|
-
lines.push(`
|
|
18459
|
+
lines.push(` - ${a.name} (${a.agent_type}) \u2014 ${a.role ?? "no role"}`);
|
|
17646
18460
|
}
|
|
17647
18461
|
if (lines.length === 0) {
|
|
17648
18462
|
lines.push('No team activity yet. Use team_manage action="join" to register, then team_task to create tasks.');
|
|
@@ -17656,7 +18470,7 @@ ${lines.join("\n")}` }] };
|
|
|
17656
18470
|
title: "Team Handoff \u2014 Agent Context Transfer",
|
|
17657
18471
|
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
18472
|
inputSchema: {
|
|
17659
|
-
fromAgentId: z2.string().describe("Your agent ID (from team_manage join or session_start)"),
|
|
18473
|
+
fromAgentId: z2.string().describe("Your agent ID (from team_manage join or session_start with joinTeam=true)"),
|
|
17660
18474
|
summary: z2.string().describe("Human-readable summary of what you did and what needs to happen next"),
|
|
17661
18475
|
context: z2.string().describe("Detailed context for the next agent: what was done, current state, known issues, next steps"),
|
|
17662
18476
|
toAgentId: z2.string().optional().describe("Specific recipient agent ID. Omit to broadcast to all."),
|
|
@@ -17682,7 +18496,7 @@ ${lines.join("\n")}` }] };
|
|
|
17682
18496
|
teamStore
|
|
17683
18497
|
);
|
|
17684
18498
|
const lines = [
|
|
17685
|
-
|
|
18499
|
+
`[OK] Handoff created`,
|
|
17686
18500
|
`Observation: #${result.observationId}`,
|
|
17687
18501
|
`From: ${result.fromAgentId.slice(0, 8)}\u2026`,
|
|
17688
18502
|
result.toAgentId ? `To: ${result.toAgentId.slice(0, 8)}\u2026` : "To: broadcast (any agent)",
|
|
@@ -17735,7 +18549,7 @@ Preview: ${analysis.description.slice(0, 300)}${analysis.description.length > 30
|
|
|
17735
18549
|
return {
|
|
17736
18550
|
content: [{
|
|
17737
18551
|
type: "text",
|
|
17738
|
-
text:
|
|
18552
|
+
text: `[ERROR] Image ingestion failed: ${err instanceof Error ? err.message : String(err)}`
|
|
17739
18553
|
}],
|
|
17740
18554
|
isError: true
|
|
17741
18555
|
};
|
|
@@ -17813,7 +18627,7 @@ fi
|
|
|
17813
18627
|
const totalRules = scan.rulesCount;
|
|
17814
18628
|
const totalWorkflows = scan.workflows.length;
|
|
17815
18629
|
if (totalMCP > 0 || totalSkills > 0 || totalRules > 0 || totalWorkflows > 0) {
|
|
17816
|
-
lines.push("", "---", "
|
|
18630
|
+
lines.push("", "---", "[UPDATED] **Cross-Agent Sync Available**");
|
|
17817
18631
|
for (const [agent, servers] of Object.entries(scan.mcpConfigs)) {
|
|
17818
18632
|
const list = servers;
|
|
17819
18633
|
if (list.length > 0) {
|
|
@@ -17831,11 +18645,11 @@ fi
|
|
|
17831
18645
|
lines.push(`- **${agent}** skills: ${names.join(", ")}`);
|
|
17832
18646
|
}
|
|
17833
18647
|
}
|
|
17834
|
-
if (scan.skillConflicts.length > 0) lines.push(`
|
|
18648
|
+
if (scan.skillConflicts.length > 0) lines.push(` [WARN] ${scan.skillConflicts.length} name conflict(s)`);
|
|
17835
18649
|
if (totalRules > 0) lines.push(`- **${totalRules} rule(s)** found`);
|
|
17836
18650
|
if (totalWorkflows > 0) lines.push(`- **${totalWorkflows} workflow(s)** found`);
|
|
17837
18651
|
lines.push("");
|
|
17838
|
-
lines.push("
|
|
18652
|
+
lines.push("[WARN] IMPORTANT INSTRUCTIONS FOR AI AGENT:");
|
|
17839
18653
|
lines.push("1. Tell the user what configs/skills are available from other agents (list them).");
|
|
17840
18654
|
lines.push("2. Ask which ones they want to sync.");
|
|
17841
18655
|
lines.push("3. DO NOT manually copy files or run shell commands to sync.");
|
|
@@ -18004,27 +18818,40 @@ fi
|
|
|
18004
18818
|
} catch {
|
|
18005
18819
|
}
|
|
18006
18820
|
try {
|
|
18821
|
+
if (!initTeamStoreForProject) throw new Error("Team store init unavailable");
|
|
18007
18822
|
if (sharedTeam?.teamStore) {
|
|
18008
|
-
teamStore = await
|
|
18823
|
+
teamStore = await initTeamStoreForProject(canonicalProjectDir);
|
|
18009
18824
|
if (!teamStore.getEventBus()) {
|
|
18010
18825
|
const { TeamEventBus: TeamEventBus2 } = await Promise.resolve().then(() => (init_event_bus(), event_bus_exports));
|
|
18011
18826
|
teamStore.setEventBus(new TeamEventBus2());
|
|
18012
18827
|
}
|
|
18013
18828
|
} else {
|
|
18014
|
-
teamStore = await
|
|
18829
|
+
teamStore = await initTeamStoreForProject(canonicalProjectDir);
|
|
18015
18830
|
}
|
|
18016
18831
|
} catch {
|
|
18017
18832
|
}
|
|
18018
18833
|
await initializeProjectRuntime("switch");
|
|
18019
18834
|
return true;
|
|
18020
18835
|
};
|
|
18836
|
+
const handleTransportClose = () => {
|
|
18837
|
+
const agentId = currentAgentId;
|
|
18838
|
+
currentAgentId = void 0;
|
|
18839
|
+
if (!teamFeaturesEnabled || !agentId) return;
|
|
18840
|
+
try {
|
|
18841
|
+
teamStore.leaveAgent(agentId);
|
|
18842
|
+
teamStore.releaseAllLocks(agentId);
|
|
18843
|
+
teamStore.releaseTasksByAgent(agentId);
|
|
18844
|
+
} catch {
|
|
18845
|
+
}
|
|
18846
|
+
};
|
|
18021
18847
|
return {
|
|
18022
18848
|
server,
|
|
18023
18849
|
graphManager,
|
|
18024
18850
|
projectId: project.id,
|
|
18025
18851
|
deferredInit,
|
|
18026
18852
|
switchProject,
|
|
18027
|
-
isExplicitlyBound: () => explicitProjectBound
|
|
18853
|
+
isExplicitlyBound: () => explicitProjectBound,
|
|
18854
|
+
handleTransportClose
|
|
18028
18855
|
};
|
|
18029
18856
|
}
|
|
18030
18857
|
|