memorix 1.0.7 → 1.0.8

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