memorix 1.0.7 → 1.0.9

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