chat-recall 0.5.3 → 0.5.5

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/mcp.js CHANGED
@@ -6149,6 +6149,24 @@ async function parseSessionFile(sessionPath, maxMessages = 50) {
6149
6149
  }
6150
6150
  const filesToParse = hasSubagentsDir(sessionPath) ? [sessionPath, ...getSubagentPaths(sessionPath)] : [sessionPath];
6151
6151
  let userCount = 0;
6152
+ const seenPrompts = /* @__PURE__ */ new Set();
6153
+ const recordPrompt = (raw, line, dedupe = false) => {
6154
+ if (userCount >= maxMessages)
6155
+ return;
6156
+ const withoutReminders = raw.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "");
6157
+ if (!withoutReminders.trim() || withoutReminders.trim().length <= 10)
6158
+ return;
6159
+ const cleanedText = stripInjectedBanners(withoutReminders).trim();
6160
+ if (cleanedText.length < 10)
6161
+ return;
6162
+ if (dedupe && seenPrompts.has(cleanedText))
6163
+ return;
6164
+ seenPrompts.add(cleanedText);
6165
+ content.userMessages.push({ text: cleanedText, lineNumber: line, contentType: "user" });
6166
+ userCount++;
6167
+ if (!content.firstPrompt)
6168
+ content.firstPrompt = cleanedText.slice(0, 1e3);
6169
+ };
6152
6170
  let assistantCount = 0;
6153
6171
  let toolCount = 0;
6154
6172
  let lineNum = 0;
@@ -6227,19 +6245,11 @@ async function parseSessionFile(sessionPath, maxMessages = 50) {
6227
6245
  text = textParts.join("\n");
6228
6246
  }
6229
6247
  }
6230
- if (text && !text.includes("<system-reminder>") && text.trim().length > 10) {
6231
- const cleanedText = stripInjectedBanners(text).trim();
6232
- if (cleanedText.length < 10)
6233
- continue;
6234
- content.userMessages.push({
6235
- text: cleanedText,
6236
- lineNumber: lineNum,
6237
- contentType: "user"
6238
- });
6239
- userCount++;
6240
- if (!content.firstPrompt) {
6241
- content.firstPrompt = cleanedText.slice(0, 1e3);
6242
- }
6248
+ if (text)
6249
+ recordPrompt(text, lineNum);
6250
+ } else if (msgType === "queue-operation") {
6251
+ if (obj.operation === "enqueue" && typeof obj.content === "string") {
6252
+ recordPrompt(obj.content, lineNum, true);
6243
6253
  }
6244
6254
  } else if (msgType === "assistant") {
6245
6255
  const msg = obj.message;
@@ -8541,6 +8551,9 @@ var init_generic_engine = __esm({
8541
8551
  // ../engine/dist/core/backends/claude.js
8542
8552
  import { existsSync as existsSync10, readdirSync as readdirSync6, readFileSync as readFileSync6, statSync as statSync8, openSync as openSync4, readSync as readSync4, closeSync as closeSync4 } from "fs";
8543
8553
  import { join as join10, basename as basename8 } from "path";
8554
+ function stripReminders(text) {
8555
+ return text.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "").trim();
8556
+ }
8544
8557
  function parseExitCode(text) {
8545
8558
  const m = text.match(/(?:^|\n)\s*(?:exit\s+code|returncode|exit\s+status|exit)\s*[:=]\s*(\d+)\b/i);
8546
8559
  return m ? Number(m[1]) : void 0;
@@ -8849,6 +8862,15 @@ var init_claude2 = __esm({
8849
8862
  }
8850
8863
  const tsIso = typeof obj.timestamp === "string" ? obj.timestamp : void 0;
8851
8864
  const ts = tsIso ? Date.parse(tsIso) || mtime : mtime;
8865
+ if (obj.type === "queue-operation") {
8866
+ if (obj.operation === "enqueue" && typeof obj.content === "string") {
8867
+ const queued = stripReminders(obj.content);
8868
+ if (queued && !queued.startsWith("<task-notification")) {
8869
+ events.push({ kind: "user", ts, tsIso, line: lineNum, text: queued });
8870
+ }
8871
+ }
8872
+ continue;
8873
+ }
8852
8874
  if (obj.type === "user") {
8853
8875
  const msg = obj.message;
8854
8876
  const content = msg?.content;
@@ -8882,9 +8904,9 @@ var init_claude2 = __esm({
8882
8904
  }
8883
8905
  }
8884
8906
  }
8885
- if (text && !text.includes("<system-reminder>")) {
8886
- events.push({ kind: "user", ts, tsIso, line: lineNum, text });
8887
- }
8907
+ const spoken = stripReminders(text);
8908
+ if (spoken)
8909
+ events.push({ kind: "user", ts, tsIso, line: lineNum, text: spoken });
8888
8910
  for (const tr of toolResults) {
8889
8911
  events.push({
8890
8912
  kind: "tool_result",
@@ -10499,23 +10521,24 @@ var init_codex2 = __esm({
10499
10521
  return null;
10500
10522
  }
10501
10523
  /**
10502
- * Stream a Codex JSONL rollout into canonical events. Codex separates:
10503
- * - event_msg + payload.type='user_message' → user
10504
- * - response_item + payload.type='message' → assistant_text
10505
- * - response_item + payload.type='function_call' → tool_use
10506
- * - response_item + payload.type='function_call_output' → tool_result
10507
- *
10508
- * Filters Codex's wrapper-bearing user messages (environment_context etc.).
10509
- *
10510
- * **Subagent fan-out:** Codex orchestrates work via `spawn_agent` calls
10511
- * that produce sibling rollout files in the same YYYY/MM/DD directory.
10512
- * Sub-agent rollouts carry `payload.source.subagent.thread_spawn` linking
10513
- * back to the parent. The actual file edits often happen inside those
10514
- * subagent rollouts — without folding their events into the parent's
10515
- * stream, replay/scan would see the parent as edit-free. We append every
10516
- * subagent rollout's events here so the generic engine sees one unified
10517
- * stream per user-facing session.
10518
- */
10524
+ * Stream a Codex JSONL rollout into canonical events. Codex separates:
10525
+ * - event_msg + payload.type='user_message' → user
10526
+ * - response_item + payload.type='message' → user OR assistant_text,
10527
+ * by payload.role
10528
+ * - response_item + payload.type='function_call' → tool_use
10529
+ * - response_item + payload.type='function_call_output' → tool_result
10530
+ *
10531
+ * Filters Codex's wrapper-bearing user messages (environment_context etc.).
10532
+ *
10533
+ * **Subagent fan-out:** Codex orchestrates work via `spawn_agent` calls
10534
+ * that produce sibling rollout files in the same YYYY/MM/DD directory.
10535
+ * Sub-agent rollouts carry `payload.source.subagent.thread_spawn` linking
10536
+ * back to the parent. The actual file edits often happen inside those
10537
+ * subagent rollouts — without folding their events into the parent's
10538
+ * stream, replay/scan would see the parent as edit-free. We append every
10539
+ * subagent rollout's events here so the generic engine sees one unified
10540
+ * stream per user-facing session.
10541
+ */
10519
10542
  readEvents(rawId) {
10520
10543
  const located = findCodexSessionFile(this.toRawId(rawId));
10521
10544
  if (!located)
@@ -10585,6 +10608,11 @@ var init_codex2 = __esm({
10585
10608
  if (text && !isCodexInjectedWrapper2(text)) {
10586
10609
  events.push({ kind: "user", ts, tsIso, line: lineNum, text });
10587
10610
  }
10611
+ } else if (obj.type === "response_item" && payload.type === "message" && payload.role === "user") {
10612
+ const text = flattenContent(payload.content).trim();
10613
+ if (text && !isCodexInjectedWrapper2(text)) {
10614
+ events.push({ kind: "user", ts, tsIso, line: lineNum, text });
10615
+ }
10588
10616
  } else if (obj.type === "response_item" && payload.type === "message" && payload.role === "assistant") {
10589
10617
  const text = flattenContent(payload.content).trim();
10590
10618
  if (text)
@@ -17008,7 +17036,9 @@ var init_extractor_version = __esm({
17008
17036
  "use strict";
17009
17037
  BASE_EXTRACTOR_VERSION = 2;
17010
17038
  TOOL_EXTRACTOR_BUMP = {
17011
- agy: 2
17039
+ agy: 2,
17040
+ claude: 1,
17041
+ codex: 1
17012
17042
  };
17013
17043
  EXTRACTOR_VERSION = BASE_EXTRACTOR_VERSION;
17014
17044
  }
@@ -17326,7 +17356,7 @@ var init_http = __esm({
17326
17356
  CLI_VERSION = JSON.parse(readFileSync31(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION;
17327
17357
  } catch {
17328
17358
  try {
17329
- if (true) CLI_VERSION = "0.5.3";
17359
+ if (true) CLI_VERSION = "0.5.5";
17330
17360
  } catch {
17331
17361
  }
17332
17362
  }
@@ -17900,6 +17930,17 @@ async function syncToTarget(cred, opts = {}) {
17900
17930
  if (res.status === 401 || res.status === 403) {
17901
17931
  throw new Error(`server rejected this machine's device token (HTTP ${res.status} \u2014 revoked?). Reconnect with: chat-recall login ${base}`);
17902
17932
  }
17933
+ if (res.status === 402) {
17934
+ let detail = text;
17935
+ try {
17936
+ const body2 = JSON.parse(text);
17937
+ detail = body2.error ?? text;
17938
+ if (body2.resetsAt) detail += ` (resets ${new Date(body2.resetsAt).toISOString().slice(0, 10)})`;
17939
+ if (body2.upgradeUrl) detail += ` \u2014 ${body2.upgradeUrl}`;
17940
+ } catch {
17941
+ }
17942
+ throw new Error(`sync paused by the server: ${detail}`);
17943
+ }
17903
17944
  if (!retryable) throw new Error(`sync failed: HTTP ${res.status} ${text}`);
17904
17945
  if (attempt >= RETRY_DELAYS_MS.length) throw new Error(`sync failed after ${attempt + 1} attempts: HTTP ${res.status} ${text}`);
17905
17946
  const retryAfterS = Number(res.headers.get("retry-after"));
@@ -18784,7 +18825,7 @@ var init_sync_client = __esm({
18784
18825
  ownVersion = JSON.parse(readFileSync32(join41(dir, "../package.json"), "utf-8")).version || ownVersion;
18785
18826
  } catch {
18786
18827
  try {
18787
- if (true) ownVersion = "0.5.3";
18828
+ if (true) ownVersion = "0.5.5";
18788
18829
  } catch {
18789
18830
  }
18790
18831
  }
@@ -19625,6 +19666,14 @@ async function collectCode(opts) {
19625
19666
  agentPrompt: manifestPrompt(v.violation_type ?? "", file, v.line ?? null, v.description ?? "")
19626
19667
  });
19627
19668
  }
19669
+ {
19670
+ const before = findings.length;
19671
+ const own = findings.filter((f) => !f.file || !isGenerated(f.file));
19672
+ if (own.length !== before) {
19673
+ findings.splice(0, before, ...own);
19674
+ log6(`dropped ${before - own.length} finding(s) in vendored/generated files`);
19675
+ }
19676
+ }
19628
19677
  const godSet = new Set((coup?.god_modules ?? []).map((m) => rel(m.file)));
19629
19678
  const cloneSet = /* @__PURE__ */ new Set();
19630
19679
  for (const g of clones?.groups ?? [])
@@ -19710,7 +19759,9 @@ async function collectCode(opts) {
19710
19759
  }
19711
19760
  for (const g of clones?.groups ?? []) {
19712
19761
  if ((g.count ?? 0) >= 3 || (g.lines ?? 0) >= 12) {
19713
- const fns = (g.functions ?? []).filter((x) => x && typeof x === "object");
19762
+ const fns = (g.functions ?? []).filter((x) => x && typeof x === "object").filter((x) => !isGenerated(rel(x.file)));
19763
+ if (fns.length < 2)
19764
+ continue;
19714
19765
  const names = [...new Set(fns.map((x) => x.name))];
19715
19766
  const locs = fns.map((x) => rel(x.file)).slice(0, 5);
19716
19767
  pushAction((g.lines ?? 0) * (g.count ?? 0) >= 150 ? 1 : 2, "duplication", `${names.join(", ")} copy-pasted ${g.count}\xD7 (${g.lines} lines each)`, "Extract one shared implementation and replace the copies.", locs.map((f) => ({ file: f })), clonePrompt(names, g.lines ?? 0, g.count ?? 0, locs));
@@ -19729,10 +19780,14 @@ async function collectCode(opts) {
19729
19780
  }
19730
19781
  for (const m of (coup?.god_modules ?? []).slice(0, 5)) {
19731
19782
  const file = rel(m.file);
19783
+ if (isGenerated(file))
19784
+ continue;
19732
19785
  pushAction(2, "modularity", `God module ${file} (fan-in ${m.fan_in}, fan-out ${m.fan_out})`, "Split into cohesive sub-modules; introduce interfaces to cut fan-out.", [{ file }], couplingPrompt(file, m.fan_in ?? 0, m.fan_out ?? 0, Math.round((m.instability ?? 0) * 100) / 100));
19733
19786
  }
19734
19787
  for (const c of (cyc?.cycles ?? []).slice(0, 5)) {
19735
- const chain = (c.files ?? []).map(rel);
19788
+ const chain = (c.files ?? []).map(rel).filter((f) => !isGenerated(f));
19789
+ if (chain.length < 2)
19790
+ continue;
19736
19791
  pushAction(2, "architecture", `Circular dependency (${chain.length} files)`, "Break the cycle: extract the shared type/interface into a separate module.", chain.slice(0, 4).map((f) => ({ file: f })), cyclePrompt(chain.slice(0, 6)));
19737
19792
  }
19738
19793
  for (const h of hotspotsAll.slice(0, 5)) {
@@ -19871,7 +19926,31 @@ var init_collector = __esm({
19871
19926
  "migration_parity",
19872
19927
  "manifest_compliance"
19873
19928
  ];
19874
- GENERATED = ["generated/", ".pb.go", ".gen.", "_pb2.py", "/gen/", ".g.dart", "/node_modules/", "/vendor/", ".lock"];
19929
+ GENERATED = [
19930
+ "generated/",
19931
+ "__generated__/",
19932
+ ".pb.go",
19933
+ ".gen.",
19934
+ "_pb2.py",
19935
+ "/gen/",
19936
+ ".g.dart",
19937
+ "/node_modules/",
19938
+ "/vendor/",
19939
+ "/third_party/",
19940
+ "/site-packages/",
19941
+ ".lock",
19942
+ "/dist/",
19943
+ "/build/",
19944
+ "/out/",
19945
+ "/target/",
19946
+ "/.next/",
19947
+ "/coverage/",
19948
+ ".min.js",
19949
+ ".min.css",
19950
+ ".bundle.js",
19951
+ "-bundle.js",
19952
+ ".map"
19953
+ ];
19875
19954
  CF_RE = /\b(if|for|while|switch|case|catch|elif|when|loop|&&|\|\||\?)\b|\?/g;
19876
19955
  DEPTH = 2;
19877
19956
  MCP_OUT_MAX_BYTES = 64 * 1024 * 1024;
@@ -20362,6 +20441,46 @@ var init_service_installer = __esm({
20362
20441
 
20363
20442
  // src/mcp.ts
20364
20443
  import { config } from "dotenv";
20444
+
20445
+ // src/mcp-diagnostics.ts
20446
+ async function buildHttpError(path, res) {
20447
+ const status = res.status;
20448
+ let body = null;
20449
+ try {
20450
+ body = await res.clone().json();
20451
+ } catch {
20452
+ }
20453
+ const str = (k) => typeof body?.[k] === "string" ? body[k] : "";
20454
+ if (status === 402) {
20455
+ const parts = [str("error") || "payment required"];
20456
+ if (str("detail")) parts.push(str("detail"));
20457
+ if (str("feature")) {
20458
+ parts.push(`This is the "${str("feature")}" capability${str("requires") ? `, on the ${str("requires")} plan` : ""}.`);
20459
+ }
20460
+ if (str("upgradeUrl")) parts.push(`Upgrade: ${str("upgradeUrl")}`);
20461
+ if (str("checkoutHint")) parts.push(str("checkoutHint"));
20462
+ if (str("resendHint")) parts.push(str("resendHint"));
20463
+ parts.push("Nothing has been deleted. The free plan keeps sync and recent-history search working.");
20464
+ return new Error(`server ${path}: ${parts.join(" ")}`);
20465
+ }
20466
+ const hint = status === 401 || status === 403 ? "auth failed \u2014 re-run `chat-recall login <server-url>`" : status === 404 ? "not found \u2014 the id may be wrong, or the server is older than this CLI" : status === 429 ? "rate limited \u2014 retry in a moment" : status >= 500 ? "server error \u2014 check the chat-recall server logs" : str("error") || str("detail") || "request rejected";
20467
+ return new Error(`server ${path}: HTTP ${status} (${hint})`);
20468
+ }
20469
+ function stalenessBanner(state) {
20470
+ if (!state || state.entitled) return null;
20471
+ const since = state.periodEnd ? new Date(state.periodEnd).toISOString().slice(0, 10) : null;
20472
+ const days = state.periodEnd ? Math.max(0, Math.floor((Date.now() - state.periodEnd) / 864e5)) : null;
20473
+ const reason = state.status === "trialing" ? "the trial has ended" : state.status === "past_due" ? "a payment has not gone through" : "the subscription has lapsed";
20474
+ return [
20475
+ `\u26A0 FREE PLAN \u2014 ${reason}.`,
20476
+ "Search and lists cover only the recent window (default 7 days); older history is stored but locked.",
20477
+ since ? `The plan lapsed ${since}${days !== null ? ` (${days} days ago)` : ""}.` : "",
20478
+ "Do not tell the user their older work does not exist; say it is locked behind the free plan and unlocks on upgrade.",
20479
+ "Sync continues under a monthly quota, export always works, and nothing has been deleted."
20480
+ ].filter(Boolean).join(" ");
20481
+ }
20482
+
20483
+ // src/mcp.ts
20365
20484
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
20366
20485
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
20367
20486
  import {
@@ -20699,10 +20818,6 @@ function remoteCredentials() {
20699
20818
  return null;
20700
20819
  }
20701
20820
  }
20702
- function httpError(path, status) {
20703
- const hint = status === 401 || status === 403 ? "auth failed \u2014 re-run `chat-recall login <server-url>`" : status === 404 ? "not found \u2014 the id may be wrong, or the server is older than this CLI" : status === 429 ? "rate limited \u2014 retry in a moment" : status >= 500 ? "server error \u2014 check the chat-recall server logs" : "request rejected";
20704
- return new Error(`server ${path}: HTTP ${status} (${hint})`);
20705
- }
20706
20821
  async function remotePost(path, body) {
20707
20822
  const cred = remoteCredentials();
20708
20823
  if (!cred) throw new Error('scope "server" needs a login \u2014 run `chat-recall login <server-url>` first.');
@@ -20711,7 +20826,7 @@ async function remotePost(path, body) {
20711
20826
  headers: { "content-type": "application/json", authorization: `Bearer ${cred.token}` },
20712
20827
  body: JSON.stringify(body)
20713
20828
  });
20714
- if (!res.ok) throw httpError(path, res.status);
20829
+ if (!res.ok) throw await buildHttpError(path, res);
20715
20830
  return res.json();
20716
20831
  }
20717
20832
  async function remoteGet(path) {
@@ -20720,7 +20835,7 @@ async function remoteGet(path) {
20720
20835
  const res = await fetch(cred.base + path, {
20721
20836
  headers: { authorization: `Bearer ${cred.token}` }
20722
20837
  });
20723
- if (!res.ok) throw httpError(path, res.status);
20838
+ if (!res.ok) throw await buildHttpError(path, res);
20724
20839
  return res.json();
20725
20840
  }
20726
20841
  async function remotePatch(path, body) {
@@ -20731,7 +20846,7 @@ async function remotePatch(path, body) {
20731
20846
  headers: { "content-type": "application/json", authorization: `Bearer ${cred.token}` },
20732
20847
  body: JSON.stringify(body)
20733
20848
  });
20734
- if (!res.ok) throw httpError(path, res.status);
20849
+ if (!res.ok) throw await buildHttpError(path, res);
20735
20850
  return res.json();
20736
20851
  }
20737
20852
  function requireRemote() {
@@ -20777,7 +20892,7 @@ Re-run with a longer prefix or the full id.`;
20777
20892
  }
20778
20893
  return { status: res.status, data: null, message };
20779
20894
  }
20780
- if (!res.ok) throw httpError(path, res.status);
20895
+ if (!res.ok) throw await buildHttpError(path, res);
20781
20896
  return { status: res.status, data: await res.json() };
20782
20897
  }
20783
20898
  var RecallSearchSchema = z.object({
@@ -20915,7 +21030,12 @@ var RecallTaskUpdateSchema = z.object({
20915
21030
  status: z.enum(TASK_STATUSES).optional(),
20916
21031
  assignee: z.string().optional().describe("Reassign to this user id (sub); empty string unassigns"),
20917
21032
  title: z.string().optional(),
20918
- comment: z.string().optional().describe("Add a comment to the task")
21033
+ comment: z.string().optional().describe("Add a comment to the task"),
21034
+ linked_session_id: z.string().nullable().optional().describe("Session id to attach to the task (null detaches). Pass YOUR OWN current session id when you start working on the task.")
21035
+ });
21036
+ var RecallTaskCommentSchema = z.object({
21037
+ task_id: z.string().describe("Task id (t_\u2026) from recall_tasks"),
21038
+ body: z.string().describe("The comment text")
20919
21039
  });
20920
21040
  var RecallKGQuerySchema = z.object({
20921
21041
  entity: z.string().describe('Entity name to query (e.g., "Alice", "chat-recall", "PostgreSQL")'),
@@ -21053,12 +21173,47 @@ var server = new Server(
21053
21173
  }
21054
21174
  }
21055
21175
  );
21176
+ var LEAN_TOOLS = /* @__PURE__ */ new Set([
21177
+ // Resume and cold start — the reason most people reach for this at all.
21178
+ "recall_smart_resume",
21179
+ "recall_wake_up",
21180
+ "recall_recent",
21181
+ // Find things.
21182
+ "recall_search",
21183
+ "recall_memory_search",
21184
+ "recall_user_prompts",
21185
+ // Read one session.
21186
+ "recall_context",
21187
+ "recall_summary",
21188
+ "recall_show",
21189
+ // What changed.
21190
+ "recall_edits_timeline",
21191
+ "recall_diff",
21192
+ "recall_commits",
21193
+ // Project state and tasks.
21194
+ "recall_project_context",
21195
+ "recall_tasks",
21196
+ "recall_task_create",
21197
+ "recall_task_update",
21198
+ // Durable memory.
21199
+ "recall_kg_query",
21200
+ "recall_kg_add",
21201
+ "recall_decision_record",
21202
+ "recall_diary_write",
21203
+ "recall_diary_read",
21204
+ // Health, and the one that pays out on day one.
21205
+ "recall_status",
21206
+ "recall_index",
21207
+ "recall_security_summary"
21208
+ ]);
21209
+ function toolProfile() {
21210
+ return (process.env.CHAT_RECALL_MCP_PROFILE || "").toLowerCase() === "full" ? "full" : "lean";
21211
+ }
21056
21212
  server.setRequestHandler(ListToolsRequestSchema, async () => {
21057
- return {
21058
- tools: [
21059
- {
21060
- name: "recall_search",
21061
- description: `Search for relevant past sessions to resume.
21213
+ const all = [
21214
+ {
21215
+ name: "recall_search",
21216
+ description: `Search for relevant past sessions to resume.
21062
21217
 
21063
21218
  Find past conversations matching your current task. Returns session IDs that
21064
21219
  can be used with \`claude --resume <session_id>\`.
@@ -21069,45 +21224,45 @@ Three modes on one tool:
21069
21224
  first prompt as the search text, excludes itself, groups hits by project).
21070
21225
  - \`include_outcome: true\` \u2014 append a per-result outcome one-liner (shipped /
21071
21226
  interrupted / abandoned + edit stats) to judge whether each hit is worth resuming.`,
21072
- inputSchema: {
21073
- type: "object",
21074
- properties: {
21075
- query: { type: "string", description: `What you're looking for (e.g., "OAuth implementation"). Required unless like_session is set.` },
21076
- like_session: { type: "string", description: "Find sessions similar to this session id instead of a free-text query." },
21077
- include_outcome: { type: "boolean", default: false, description: "Append per-result outcome one-liners (status + edit stats)." },
21078
- top_k: { type: "number", default: 5, description: "Number of results to return" },
21079
- project_filter: { type: "string", description: "Optional filter by project path substring" },
21080
- skip_ranking: { type: "boolean", default: false, description: "Skip Claude ranking for faster results" },
21081
- provider: { type: "string", enum: ["ollama", "gemini"], default: "ollama", description: "Embedding provider" },
21082
- scope: { type: "string", enum: ["local", "server"], default: "local", description: "Accepted for back-compat; search always runs against the synced server." }
21083
- }
21227
+ inputSchema: {
21228
+ type: "object",
21229
+ properties: {
21230
+ query: { type: "string", description: `What you're looking for (e.g., "OAuth implementation"). Required unless like_session is set.` },
21231
+ like_session: { type: "string", description: "Find sessions similar to this session id instead of a free-text query." },
21232
+ include_outcome: { type: "boolean", default: false, description: "Append per-result outcome one-liners (status + edit stats)." },
21233
+ top_k: { type: "number", default: 5, description: "Number of results to return" },
21234
+ project_filter: { type: "string", description: "Optional filter by project path substring" },
21235
+ skip_ranking: { type: "boolean", default: false, description: "Skip Claude ranking for faster results" },
21236
+ provider: { type: "string", enum: ["ollama", "gemini"], default: "ollama", description: "Embedding provider" },
21237
+ scope: { type: "string", enum: ["local", "server"], default: "local", description: "Accepted for back-compat; search always runs against the synced server." }
21084
21238
  }
21085
- },
21086
- {
21087
- name: "recall_index",
21088
- description: `Index all Claude Code sessions for semantic search.
21239
+ }
21240
+ },
21241
+ {
21242
+ name: "recall_index",
21243
+ description: `Index all Claude Code sessions for semantic search.
21089
21244
 
21090
21245
  Scans ~/.claude/projects/ and creates embeddings for all sessions.
21091
21246
  By default, only indexes new or changed sessions.`,
21092
- inputSchema: {
21093
- type: "object",
21094
- properties: {
21095
- force: { type: "boolean", default: false, description: "Force re-index all sessions" },
21096
- provider: { type: "string", enum: ["ollama", "gemini"], default: "ollama", description: "Embedding provider" }
21097
- }
21098
- }
21099
- },
21100
- {
21101
- name: "recall_status",
21102
- description: "Show index status and statistics: synced sessions, chunks, freshness, top projects, plus the full memory-system breakdown (items/chunks per source type, per AI tool, link count).",
21103
- inputSchema: {
21104
- type: "object",
21105
- properties: {}
21247
+ inputSchema: {
21248
+ type: "object",
21249
+ properties: {
21250
+ force: { type: "boolean", default: false, description: "Force re-index all sessions" },
21251
+ provider: { type: "string", enum: ["ollama", "gemini"], default: "ollama", description: "Embedding provider" }
21106
21252
  }
21107
- },
21108
- {
21109
- name: "recall_show",
21110
- description: `Get conversation content from a specific session \u2014 or the full text of a plan.
21253
+ }
21254
+ },
21255
+ {
21256
+ name: "recall_status",
21257
+ description: "Show index status and statistics: synced sessions, chunks, freshness, top projects, plus the full memory-system breakdown (items/chunks per source type, per AI tool, link count).",
21258
+ inputSchema: {
21259
+ type: "object",
21260
+ properties: {}
21261
+ }
21262
+ },
21263
+ {
21264
+ name: "recall_show",
21265
+ description: `Get conversation content from a specific session \u2014 or the full text of a plan.
21111
21266
 
21112
21267
  Use this after recall_search to get full context from a session. Also accepts a
21113
21268
  plan id (the plan filename without .md, as returned by
@@ -21116,57 +21271,57 @@ recall_memory_search(source_types:['plan'])) and renders the complete plan text.
21116
21271
  Set \`from_end: N\` to fetch the last N messages (no line-number guessing).
21117
21272
  Set \`include_code: true\` to keep code blocks instead of redacting them \u2014 useful
21118
21273
  when the user is asking "what did we change?" and the diffs/SQL/commands matter.`,
21119
- inputSchema: {
21120
- type: "object",
21121
- properties: {
21122
- session_id: { type: "string", description: "Session ID from search results, or a plan id (filename without .md)" },
21123
- around_line: { type: "number", description: "Optional line number to show context around" },
21124
- max_messages: { type: "number", default: 10, description: "Maximum messages to return" },
21125
- from_end: { type: "number", description: "Return the last N messages (alternative to around_line)." },
21126
- include_code: { type: "boolean", default: false, description: "Keep code blocks in assistant messages." }
21127
- },
21128
- required: ["session_id"]
21129
- }
21130
- },
21131
- {
21132
- name: "recall_recent",
21133
- description: `List recent Claude Code sessions.
21274
+ inputSchema: {
21275
+ type: "object",
21276
+ properties: {
21277
+ session_id: { type: "string", description: "Session ID from search results, or a plan id (filename without .md)" },
21278
+ around_line: { type: "number", description: "Optional line number to show context around" },
21279
+ max_messages: { type: "number", default: 10, description: "Maximum messages to return" },
21280
+ from_end: { type: "number", description: "Return the last N messages (alternative to around_line)." },
21281
+ include_code: { type: "boolean", default: false, description: "Keep code blocks in assistant messages." }
21282
+ },
21283
+ required: ["session_id"]
21284
+ }
21285
+ },
21286
+ {
21287
+ name: "recall_recent",
21288
+ description: `List recent Claude Code sessions.
21134
21289
 
21135
21290
  Shows your most recent conversations across all projects or filtered by project.
21136
21291
  Use this when the user says "continue our last conversation" or wants to see recent work.
21137
21292
 
21138
21293
  Pass \`since_hours: N\` to restrict to sessions modified in the last N hours.`,
21139
- inputSchema: {
21140
- type: "object",
21141
- properties: {
21142
- project_filter: { type: "string", description: 'Filter by project name (e.g., "inco", "poly")' },
21143
- limit: { type: "number", default: 10, description: "Number of recent sessions to show" },
21144
- since_hours: { type: "number", description: "Only include sessions modified in the last N hours." },
21145
- scope: { type: "string", enum: ["local", "server"], default: "local", description: "server = synced cross-device history (needs chat-recall login)." }
21146
- }
21294
+ inputSchema: {
21295
+ type: "object",
21296
+ properties: {
21297
+ project_filter: { type: "string", description: 'Filter by project name (e.g., "inco", "poly")' },
21298
+ limit: { type: "number", default: 10, description: "Number of recent sessions to show" },
21299
+ since_hours: { type: "number", description: "Only include sessions modified in the last N hours." },
21300
+ scope: { type: "string", enum: ["local", "server"], default: "local", description: "server = synced cross-device history (needs chat-recall login)." }
21147
21301
  }
21148
- },
21149
- {
21150
- name: "recall_rename_session",
21151
- description: `Give a conversation a memorable name \u2014 or rename it \u2014 so you (and the user) can find and resume it later.
21302
+ }
21303
+ },
21304
+ {
21305
+ name: "recall_rename_session",
21306
+ description: `Give a conversation a memorable name \u2014 or rename it \u2014 so you (and the user) can find and resume it later.
21152
21307
 
21153
21308
  Mirrors Claude Code's session naming (\`/rename\`). The name replaces the auto-generated summary in
21154
21309
  recall_recent and the web UI. Use it when the user says "name this conversation X", "call this session Y",
21155
21310
  or "rename this to Z". Pass an empty string to clear the name and revert to the auto title.
21156
21311
 
21157
21312
  The name persists across re-syncs and summary regeneration (it's stored separately from the AI summary).`,
21158
- inputSchema: {
21159
- type: "object",
21160
- properties: {
21161
- session_id: { type: "string", description: "Session ID to name (from recall_recent / recall_search)." },
21162
- name: { type: "string", description: "The name to give this conversation. Empty string clears it." }
21163
- },
21164
- required: ["session_id", "name"]
21165
- }
21166
- },
21167
- {
21168
- name: "recall_edits_timeline",
21169
- description: `Chronological list of file edits across recent sessions, spanning every AI tool
21313
+ inputSchema: {
21314
+ type: "object",
21315
+ properties: {
21316
+ session_id: { type: "string", description: "Session ID to name (from recall_recent / recall_search)." },
21317
+ name: { type: "string", description: "The name to give this conversation. Empty string clears it." }
21318
+ },
21319
+ required: ["session_id", "name"]
21320
+ }
21321
+ },
21322
+ {
21323
+ name: "recall_edits_timeline",
21324
+ description: `Chronological list of file edits across recent sessions, spanning every AI tool
21170
21325
  chat-recall indexes \u2014 Claude Code, Gemini CLI, OpenCode, Codex and Antigravity.
21171
21326
 
21172
21327
  Returns rows shaped like (timestamp, tool, session_id, project, file, op) sorted newest
@@ -21179,28 +21334,28 @@ two hours of edits across every session and every AI tool.
21179
21334
 
21180
21335
  Pass \`group_by: "session"\` (with \`pattern\` + a wide \`since_hours\`, e.g. 720) to
21181
21336
  aggregate per session instead \u2014 "which sessions edited auth.rs in the last month?".`,
21182
- inputSchema: {
21183
- type: "object",
21184
- properties: {
21185
- since_hours: { type: "number", default: 24, description: "Time window in hours" },
21186
- limit: { type: "number", default: 100, description: "Maximum edit rows to return" },
21187
- pattern: { type: "string", description: "Optional substring filter on file path" },
21188
- project_filter: { type: "string", description: "Filter by encoded project directory name" },
21189
- include_reads: { type: "boolean", default: false, description: "Include Read tool_uses too" },
21190
- tools: {
21191
- type: "array",
21192
- items: { type: "string", enum: ["claude", "gemini", "opencode", "codex"] },
21193
- description: "Restrict to specific AI tools. Default: all four."
21194
- },
21195
- group_by_repo: { type: "boolean", default: false, description: "Group results by detected git repo root." },
21196
- view: { type: "string", enum: ["timeline", "summary"], default: "timeline", description: "timeline = chronological edit rows; summary = aggregated rollup over the same window" },
21197
- group_by: { type: "string", enum: ["session"], description: '"session" = aggregate edits per session (which sessions touched files matching pattern).' }
21198
- }
21337
+ inputSchema: {
21338
+ type: "object",
21339
+ properties: {
21340
+ since_hours: { type: "number", default: 24, description: "Time window in hours" },
21341
+ limit: { type: "number", default: 100, description: "Maximum edit rows to return" },
21342
+ pattern: { type: "string", description: "Optional substring filter on file path" },
21343
+ project_filter: { type: "string", description: "Filter by encoded project directory name" },
21344
+ include_reads: { type: "boolean", default: false, description: "Include Read tool_uses too" },
21345
+ tools: {
21346
+ type: "array",
21347
+ items: { type: "string", enum: ["claude", "gemini", "opencode", "codex"] },
21348
+ description: "Restrict to specific AI tools. Default: all four."
21349
+ },
21350
+ group_by_repo: { type: "boolean", default: false, description: "Group results by detected git repo root." },
21351
+ view: { type: "string", enum: ["timeline", "summary"], default: "timeline", description: "timeline = chronological edit rows; summary = aggregated rollup over the same window" },
21352
+ group_by: { type: "string", enum: ["session"], description: '"session" = aggregate edits per session (which sessions touched files matching pattern).' }
21199
21353
  }
21200
- },
21201
- {
21202
- name: "recall_context",
21203
- description: `Get structured context from a session for continuation.
21354
+ }
21355
+ },
21356
+ {
21357
+ name: "recall_context",
21358
+ description: `Get structured context from a session for continuation.
21204
21359
 
21205
21360
  Returns:
21206
21361
  - Your requests/inputs
@@ -21210,17 +21365,17 @@ Returns:
21210
21365
  - Summary if available
21211
21366
 
21212
21367
  Use this to understand what happened in a session before resuming.`,
21213
- inputSchema: {
21214
- type: "object",
21215
- properties: {
21216
- session_id: { type: "string", description: "Session ID to get context from" }
21217
- },
21218
- required: ["session_id"]
21219
- }
21220
- },
21221
- {
21222
- name: "recall_summary",
21223
- description: `Get the summary for a session \u2014 including its OUTCOME classification.
21368
+ inputSchema: {
21369
+ type: "object",
21370
+ properties: {
21371
+ session_id: { type: "string", description: "Session ID to get context from" }
21372
+ },
21373
+ required: ["session_id"]
21374
+ }
21375
+ },
21376
+ {
21377
+ name: "recall_summary",
21378
+ description: `Get the summary for a session \u2014 including its OUTCOME classification.
21224
21379
 
21225
21380
  By default returns a *rich* summary that combines the AI summary (when available)
21226
21381
  with the structured outcome derived from the transcript itself: status (shipped /
@@ -21231,18 +21386,18 @@ markers, and the last assistant claim paired with the user's reaction to it
21231
21386
  This is the triage call when scanning a session list \u2014 it answers the question
21232
21387
  the AI summary doesn't: did this work actually land?
21233
21388
  Pass \`rich: false\` to fall back to the legacy single-line AI summary only.`,
21234
- inputSchema: {
21235
- type: "object",
21236
- properties: {
21237
- session_id: { type: "string", description: "Session ID to get summary for" },
21238
- rich: { type: "boolean", default: true, description: "Include structured outcome alongside AI summary" }
21239
- },
21240
- required: ["session_id"]
21241
- }
21242
- },
21243
- {
21244
- name: "recall_diff",
21245
- description: `Replay a session's Edit/Write/MultiEdit/NotebookEdit tool calls into a unified
21389
+ inputSchema: {
21390
+ type: "object",
21391
+ properties: {
21392
+ session_id: { type: "string", description: "Session ID to get summary for" },
21393
+ rich: { type: "boolean", default: true, description: "Include structured outcome alongside AI summary" }
21394
+ },
21395
+ required: ["session_id"]
21396
+ }
21397
+ },
21398
+ {
21399
+ name: "recall_diff",
21400
+ description: `Replay a session's Edit/Write/MultiEdit/NotebookEdit tool calls into a unified
21246
21401
  diff per file. Lets you see what the agent actually changed \u2014 not just that it
21247
21402
  edited, but how \u2014 without grepping the raw transcript.
21248
21403
 
@@ -21253,21 +21408,21 @@ Pass \`file\` to focus on a single absolute path. Pass \`context_only: true\` fo
21253
21408
  just the per-file stats (lines added/removed) when you don't need the diff body.
21254
21409
  Pass \`files_only: true\` for just the list of files the session touched (grouped
21255
21410
  by extension, with tools used) \u2014 "which files did session X actually touch?".`,
21256
- inputSchema: {
21257
- type: "object",
21258
- properties: {
21259
- session_id: { type: "string", description: "Session ID to replay" },
21260
- file: { type: "string", description: "Absolute file path to focus on (optional)" },
21261
- context_only: { type: "boolean", default: false, description: "Skip diff bodies, return stats only" },
21262
- files_only: { type: "boolean", default: false, description: "Return just the file list (no diffs/stats)" },
21263
- max_diff_chars: { type: "number", default: 4e3, description: "Truncate each per-file unified diff" }
21264
- },
21265
- required: ["session_id"]
21266
- }
21267
- },
21268
- {
21269
- name: "recall_commits",
21270
- description: `Find git commits that landed during a session's edit window, grouped by repo.
21411
+ inputSchema: {
21412
+ type: "object",
21413
+ properties: {
21414
+ session_id: { type: "string", description: "Session ID to replay" },
21415
+ file: { type: "string", description: "Absolute file path to focus on (optional)" },
21416
+ context_only: { type: "boolean", default: false, description: "Skip diff bodies, return stats only" },
21417
+ files_only: { type: "boolean", default: false, description: "Return just the file list (no diffs/stats)" },
21418
+ max_diff_chars: { type: "number", default: 4e3, description: "Truncate each per-file unified diff" }
21419
+ },
21420
+ required: ["session_id"]
21421
+ }
21422
+ },
21423
+ {
21424
+ name: "recall_commits",
21425
+ description: `Find git commits that landed during a session's edit window, grouped by repo.
21271
21426
 
21272
21427
  Multi-repo aware: a session that touched ~/code/personal/k8s_gpu and
21273
21428
  ~/code/personal/munbot returns commits from each repo, with overlap shown
@@ -21275,18 +21430,18 @@ between commit-files and session-touched-files.
21275
21430
 
21276
21431
  Use this to verify "shipped" claims \u2014 if the session edited 18 files but no
21277
21432
  matching commit exists, work probably stayed local.`,
21278
- inputSchema: {
21279
- type: "object",
21280
- properties: {
21281
- session_id: { type: "string", description: "Session ID to look up commits for" },
21282
- buffer_minutes: { type: "number", default: 30, description: "Window padding on each side" }
21283
- },
21284
- required: ["session_id"]
21285
- }
21286
- },
21287
- {
21288
- name: "recall_markers",
21289
- description: `Tag every user prompt in a session with sentiment / corrective markers:
21433
+ inputSchema: {
21434
+ type: "object",
21435
+ properties: {
21436
+ session_id: { type: "string", description: "Session ID to look up commits for" },
21437
+ buffer_minutes: { type: "number", default: 30, description: "Window padding on each side" }
21438
+ },
21439
+ required: ["session_id"]
21440
+ }
21441
+ },
21442
+ {
21443
+ name: "recall_markers",
21444
+ description: `Tag every user prompt in a session with sentiment / corrective markers:
21290
21445
 
21291
21446
  \u2022 interrupt \u2014 user hit ESC mid-response
21292
21447
  \u2022 frustrated \u2014 caps, profanity, "wtf", "ffs"
@@ -21298,43 +21453,43 @@ matching commit exists, work probably stayed local.`,
21298
21453
 
21299
21454
  Returns per-prompt markers + a session-level summary count. Use this to spot
21300
21455
  sessions where things went sideways before reading the whole transcript.`,
21301
- inputSchema: {
21302
- type: "object",
21303
- properties: {
21304
- session_id: { type: "string", description: "Session ID to mark prompts in" },
21305
- limit: { type: "number", default: 200, description: "Maximum prompts to return" }
21306
- },
21307
- required: ["session_id"]
21308
- }
21309
- },
21310
- {
21311
- name: "recall_memory_search",
21312
- description: `Search across all memory types: sessions, plans, tasks, CLAUDE.md files, history, paste cache, and agent diaries.
21456
+ inputSchema: {
21457
+ type: "object",
21458
+ properties: {
21459
+ session_id: { type: "string", description: "Session ID to mark prompts in" },
21460
+ limit: { type: "number", default: 200, description: "Maximum prompts to return" }
21461
+ },
21462
+ required: ["session_id"]
21463
+ }
21464
+ },
21465
+ {
21466
+ name: "recall_memory_search",
21467
+ description: `Search across all memory types: sessions, plans, tasks, CLAUDE.md files, history, paste cache, and agent diaries.
21313
21468
 
21314
21469
  Returns results from any memory source, ranked by relevance. Use source_types to filter.
21315
21470
  Plans and tasks are found here (source_types: ['plan', 'task']); read a full plan
21316
21471
  with recall_show (pass the plan id).`,
21317
- inputSchema: {
21318
- type: "object",
21319
- properties: {
21320
- query: { type: "string", description: "What you're looking for across all memory types" },
21321
- top_k: { type: "number", default: 10, description: "Number of results" },
21322
- source_types: {
21323
- type: "array",
21324
- items: { type: "string", enum: ["session", "plan", "task", "claude_md", "paste", "history", "diary"] },
21325
- description: "Filter by source types (default: all)"
21326
- },
21327
- project_filter: { type: "string", description: "Filter by project path" },
21328
- provider: { type: "string", enum: ["ollama", "gemini"], default: "ollama" },
21329
- scope: { type: "string", enum: ["local", "server"], default: "local", description: "server = synced cross-device history (needs chat-recall login)." },
21330
- semantic: { type: "boolean", default: false, description: "Also run the pgvector semantic tier, not just keyword FTS. Needs an embedder configured server-side; ignored otherwise." }
21472
+ inputSchema: {
21473
+ type: "object",
21474
+ properties: {
21475
+ query: { type: "string", description: "What you're looking for across all memory types" },
21476
+ top_k: { type: "number", default: 10, description: "Number of results" },
21477
+ source_types: {
21478
+ type: "array",
21479
+ items: { type: "string", enum: ["session", "plan", "task", "claude_md", "paste", "history", "diary"] },
21480
+ description: "Filter by source types (default: all)"
21331
21481
  },
21332
- required: ["query"]
21333
- }
21334
- },
21335
- {
21336
- name: "recall_smart_resume",
21337
- description: `Get structured resume context for a session.
21482
+ project_filter: { type: "string", description: "Filter by project path" },
21483
+ provider: { type: "string", enum: ["ollama", "gemini"], default: "ollama" },
21484
+ scope: { type: "string", enum: ["local", "server"], default: "local", description: "server = synced cross-device history (needs chat-recall login)." },
21485
+ semantic: { type: "boolean", default: false, description: "Also run the pgvector semantic tier, not just keyword FTS. Needs an embedder configured server-side; ignored otherwise." }
21486
+ },
21487
+ required: ["query"]
21488
+ }
21489
+ },
21490
+ {
21491
+ name: "recall_smart_resume",
21492
+ description: `Get structured resume context for a session.
21338
21493
 
21339
21494
  Returns:
21340
21495
  - What was done (completed work, decisions made)
@@ -21344,17 +21499,17 @@ Returns:
21344
21499
  - Resume command
21345
21500
 
21346
21501
  Use this instead of recall_context for a more actionable summary when resuming work.`,
21347
- inputSchema: {
21348
- type: "object",
21349
- properties: {
21350
- session_id: { type: "string", description: "Session ID to resume" }
21351
- },
21352
- required: ["session_id"]
21353
- }
21354
- },
21355
- {
21356
- name: "recall_project_context",
21357
- description: `Get the full project dossier as markdown \u2014 rich project context.
21502
+ inputSchema: {
21503
+ type: "object",
21504
+ properties: {
21505
+ session_id: { type: "string", description: "Session ID to resume" }
21506
+ },
21507
+ required: ["session_id"]
21508
+ }
21509
+ },
21510
+ {
21511
+ name: "recall_project_context",
21512
+ description: `Get the full project dossier as markdown \u2014 rich project context.
21358
21513
 
21359
21514
  Aggregates everything the index knows about one project into a single report:
21360
21515
  overview (from CLAUDE.md), tech stack (KG uses), architecture / deployment / security
@@ -21369,20 +21524,20 @@ Accepts EITHER a project path / name substring (e.g. "munbot", "chat-recall",
21369
21524
  - "user:<custom>" (declared in ~/.chat-recall/projects.json)
21370
21525
 
21371
21526
  Use at the START of a new session to understand what's been happening in a project.`,
21372
- inputSchema: {
21373
- type: "object",
21374
- properties: {
21375
- project_path: { type: "string", description: "Project path, name substring, or project_id (git:/ws:/git-local:/user:)" },
21376
- limit: { type: "number", default: 5, description: "Number of recent sessions to include" },
21377
- tasks: { type: "number", default: 20, description: "Max open tasks to list" },
21378
- plans: { type: "number", default: 20, description: "Max plans to list" }
21379
- },
21380
- required: ["project_path"]
21381
- }
21382
- },
21383
- {
21384
- name: "recall_weekly_digest",
21385
- description: `Get a weekly activity digest across all projects.
21527
+ inputSchema: {
21528
+ type: "object",
21529
+ properties: {
21530
+ project_path: { type: "string", description: "Project path, name substring, or project_id (git:/ws:/git-local:/user:)" },
21531
+ limit: { type: "number", default: 5, description: "Number of recent sessions to include" },
21532
+ tasks: { type: "number", default: 20, description: "Max open tasks to list" },
21533
+ plans: { type: "number", default: 20, description: "Max plans to list" }
21534
+ },
21535
+ required: ["project_path"]
21536
+ }
21537
+ },
21538
+ {
21539
+ name: "recall_weekly_digest",
21540
+ description: `Get a weekly activity digest across all projects.
21386
21541
 
21387
21542
  Returns:
21388
21543
  - Session count, total cost, coding time
@@ -21392,16 +21547,16 @@ Returns:
21392
21547
  - Git activity summary
21393
21548
 
21394
21549
  Use to understand overall productivity and spending.`,
21395
- inputSchema: {
21396
- type: "object",
21397
- properties: {
21398
- weeks_back: { type: "number", default: 0, description: "0 = current week, 1 = last week" }
21399
- }
21550
+ inputSchema: {
21551
+ type: "object",
21552
+ properties: {
21553
+ weeks_back: { type: "number", default: 0, description: "0 = current week, 1 = last week" }
21400
21554
  }
21401
- },
21402
- {
21403
- name: "recall_team_activity",
21404
- description: `See what each teammate did, grouped by project \u2014 the team activity view.
21555
+ }
21556
+ },
21557
+ {
21558
+ name: "recall_team_activity",
21559
+ description: `See what each teammate did, grouped by project \u2014 the team activity view.
21405
21560
 
21406
21561
  Returns a per-member \xD7 per-project rollup (session counts + last activity) for
21407
21562
  your team. Scoped to what you're allowed to see: your own work plus teammates'
@@ -21409,210 +21564,236 @@ work on projects they've shared into the team (private projects never appear).
21409
21564
 
21410
21565
  Use for "what has the team been working on", "what did <teammate> do on <project>",
21411
21566
  or standup/status. Filter by project, by member, or by recency (since_days).`,
21412
- inputSchema: {
21413
- type: "object",
21414
- properties: {
21415
- project: { type: "string", description: "Filter to one project_id" },
21416
- member: { type: "string", description: "Filter to one teammate (user id / sub)" },
21417
- since_days: { type: "number", description: "Only activity in the last N days" }
21418
- }
21567
+ inputSchema: {
21568
+ type: "object",
21569
+ properties: {
21570
+ project: { type: "string", description: "Filter to one project_id" },
21571
+ member: { type: "string", description: "Filter to one teammate (user id / sub)" },
21572
+ since_days: { type: "number", description: "Only activity in the last N days" }
21419
21573
  }
21420
- },
21421
- {
21422
- name: "recall_tasks",
21423
- description: `List collaborative team tasks (the shared task board).
21574
+ }
21575
+ },
21576
+ {
21577
+ name: "recall_tasks",
21578
+ description: `List collaborative team tasks (the shared task board).
21424
21579
 
21425
21580
  Team-visible tasks with status, assignee, and project. Use for "what's on the
21426
21581
  team's plate", "my tasks", or to find a task id to update. Filter by mine /
21427
21582
  project / status.`,
21428
- inputSchema: {
21429
- type: "object",
21430
- properties: {
21431
- mine: { type: "boolean", description: "Only tasks assigned to me" },
21432
- project: { type: "string", description: "Filter to one project_id" },
21433
- status: { type: "string", enum: [...TASK_STATUSES], description: "Filter by status" }
21434
- }
21435
- }
21436
- },
21437
- {
21438
- name: "recall_task_create",
21439
- description: `Create a collaborative team task. Optionally assign it to a teammate and
21440
- attach it to a project. Returns the new task id.`,
21441
- inputSchema: {
21442
- type: "object",
21443
- properties: {
21444
- title: { type: "string", description: "Task title" },
21445
- description: { type: "string" },
21446
- project: { type: "string", description: "project_id this task belongs to" },
21447
- assignee: { type: "string", description: "Teammate user id (sub) to assign" }
21448
- },
21449
- required: ["title"]
21450
- }
21451
- },
21452
- {
21453
- name: "recall_task_update",
21454
- description: `Update a team task: change status, reassign, rename, and/or add a comment.
21455
- Pass the task id (t_\u2026) from recall_tasks.`,
21456
- inputSchema: {
21457
- type: "object",
21458
- properties: {
21459
- id: { type: "string", description: "Task id (t_\u2026)" },
21460
- status: { type: "string", enum: [...TASK_STATUSES] },
21461
- assignee: { type: "string", description: "Reassign to this user id (sub)" },
21462
- title: { type: "string" },
21463
- comment: { type: "string", description: "Add a comment" }
21464
- },
21465
- required: ["id"]
21583
+ inputSchema: {
21584
+ type: "object",
21585
+ properties: {
21586
+ mine: { type: "boolean", description: "Only tasks assigned to me" },
21587
+ project: { type: "string", description: "Filter to one project_id" },
21588
+ status: { type: "string", enum: [...TASK_STATUSES], description: "Filter by status" }
21466
21589
  }
21467
- },
21468
- // ── Knowledge Graph Tools ──────────────────────────────────
21469
- {
21470
- name: "recall_kg_query",
21471
- description: `Query the knowledge graph for an entity's relationships.
21590
+ }
21591
+ },
21592
+ {
21593
+ name: "recall_task_create",
21594
+ description: `Create a collaborative team task. Optionally assign it to a teammate and
21595
+ attach it to a project. Returns the new task id.
21596
+
21597
+ When you then START working on a task, use recall_task_update to set status
21598
+ 'in_progress' and link your current session (linked_session_id) \u2014 that is what
21599
+ lets the board verify the work actually shipped.`,
21600
+ inputSchema: {
21601
+ type: "object",
21602
+ properties: {
21603
+ title: { type: "string", description: "Task title" },
21604
+ description: { type: "string" },
21605
+ project: { type: "string", description: "project_id this task belongs to" },
21606
+ assignee: { type: "string", description: "Teammate user id (sub) to assign" }
21607
+ },
21608
+ required: ["title"]
21609
+ }
21610
+ },
21611
+ {
21612
+ name: "recall_task_update",
21613
+ description: `Update a team task: change status, reassign, rename, link a session, and/or
21614
+ add a comment. Pass the task id (t_\u2026) from recall_tasks.
21615
+
21616
+ WORKFLOW \u2014 do this whenever the user asks you to work on a task from the board:
21617
+ 1. When you start, set status 'in_progress' AND pass your own current session id
21618
+ as linked_session_id (the session id you know from your own context; this MCP
21619
+ server cannot see it). The board uses that link to show whether the linked
21620
+ session actually shipped \u2014 files, diff stats, commits.
21621
+ 2. When you finish, set status 'done'.`,
21622
+ inputSchema: {
21623
+ type: "object",
21624
+ properties: {
21625
+ id: { type: "string", description: "Task id (t_\u2026)" },
21626
+ status: { type: "string", enum: [...TASK_STATUSES] },
21627
+ assignee: { type: "string", description: "Reassign to this user id (sub)" },
21628
+ title: { type: "string" },
21629
+ comment: { type: "string", description: "Add a comment" },
21630
+ linked_session_id: { type: "string", description: "Session id to attach \u2014 pass YOUR OWN current session id when you start working on this task, so the board can check whether the work shipped. Null detaches." }
21631
+ },
21632
+ required: ["id"]
21633
+ }
21634
+ },
21635
+ {
21636
+ name: "recall_task_comment",
21637
+ description: `Leave a progress note on a team task \u2014 a comment visible to everyone on the
21638
+ board. Use it to record what you did, what is blocked, or why a status changed,
21639
+ without editing the task itself. Pass the task id (t_\u2026) from recall_tasks.`,
21640
+ inputSchema: {
21641
+ type: "object",
21642
+ properties: {
21643
+ task_id: { type: "string", description: "Task id (t_\u2026) from recall_tasks" },
21644
+ body: { type: "string", description: "The comment text" }
21645
+ },
21646
+ required: ["task_id", "body"]
21647
+ }
21648
+ },
21649
+ // ── Knowledge Graph Tools ──────────────────────────────────
21650
+ {
21651
+ name: "recall_kg_query",
21652
+ description: `Query the knowledge graph for an entity's relationships.
21472
21653
 
21473
21654
  Returns typed facts with temporal validity. E.g. "chat-recall" \u2192 uses TypeScript, has source FTS5.
21474
21655
  Filter by date with as_of to see what was true at a specific point in time.
21475
21656
  Use this to VERIFY facts before asserting them.`,
21476
- inputSchema: {
21477
- type: "object",
21478
- properties: {
21479
- entity: { type: "string", description: 'Entity to query (e.g., "Alice", "chat-recall")' },
21480
- as_of: { type: "string", description: "Date filter (YYYY-MM-DD, optional)" },
21481
- direction: { type: "string", enum: ["outgoing", "incoming", "both"], default: "both" }
21482
- },
21483
- required: ["entity"]
21484
- }
21485
- },
21486
- {
21487
- name: "recall_kg_add",
21488
- description: `Add a fact to the knowledge graph. Subject \u2192 predicate \u2192 object with optional time window.
21657
+ inputSchema: {
21658
+ type: "object",
21659
+ properties: {
21660
+ entity: { type: "string", description: 'Entity to query (e.g., "Alice", "chat-recall")' },
21661
+ as_of: { type: "string", description: "Date filter (YYYY-MM-DD, optional)" },
21662
+ direction: { type: "string", enum: ["outgoing", "incoming", "both"], default: "both" }
21663
+ },
21664
+ required: ["entity"]
21665
+ }
21666
+ },
21667
+ {
21668
+ name: "recall_kg_add",
21669
+ description: `Add a fact to the knowledge graph. Subject \u2192 predicate \u2192 object with optional time window.
21489
21670
 
21490
21671
  E.g. ("chat-recall", "uses", "LanceDB", valid_from="2024-01-15")`,
21491
- inputSchema: {
21492
- type: "object",
21493
- properties: {
21494
- subject: { type: "string", description: "The entity doing/being something" },
21495
- predicate: { type: "string", description: 'Relationship type (e.g., "uses", "works_on", "prefers")' },
21496
- object: { type: "string", description: "The entity being connected to" },
21497
- valid_from: { type: "string", description: "When this became true (YYYY-MM-DD, optional)" },
21498
- source_session: { type: "string", description: "Session ID where this was learned (optional)" }
21499
- },
21500
- required: ["subject", "predicate", "object"]
21501
- }
21502
- },
21503
- {
21504
- name: "recall_kg_invalidate",
21505
- description: `Mark a fact as no longer true. Use when things change \u2014 tools replaced, decisions reversed, people leave.`,
21506
- inputSchema: {
21507
- type: "object",
21508
- properties: {
21509
- subject: { type: "string", description: "Entity" },
21510
- predicate: { type: "string", description: "Relationship" },
21511
- object: { type: "string", description: "Connected entity" },
21512
- ended: { type: "string", description: "When it stopped being true (YYYY-MM-DD, default: today)" }
21513
- },
21514
- required: ["subject", "predicate", "object"]
21672
+ inputSchema: {
21673
+ type: "object",
21674
+ properties: {
21675
+ subject: { type: "string", description: "The entity doing/being something" },
21676
+ predicate: { type: "string", description: 'Relationship type (e.g., "uses", "works_on", "prefers")' },
21677
+ object: { type: "string", description: "The entity being connected to" },
21678
+ valid_from: { type: "string", description: "When this became true (YYYY-MM-DD, optional)" },
21679
+ source_session: { type: "string", description: "Session ID where this was learned (optional)" }
21680
+ },
21681
+ required: ["subject", "predicate", "object"]
21682
+ }
21683
+ },
21684
+ {
21685
+ name: "recall_kg_invalidate",
21686
+ description: `Mark a fact as no longer true. Use when things change \u2014 tools replaced, decisions reversed, people leave.`,
21687
+ inputSchema: {
21688
+ type: "object",
21689
+ properties: {
21690
+ subject: { type: "string", description: "Entity" },
21691
+ predicate: { type: "string", description: "Relationship" },
21692
+ object: { type: "string", description: "Connected entity" },
21693
+ ended: { type: "string", description: "When it stopped being true (YYYY-MM-DD, default: today)" }
21694
+ },
21695
+ required: ["subject", "predicate", "object"]
21696
+ }
21697
+ },
21698
+ {
21699
+ name: "recall_kg_timeline",
21700
+ description: `Get chronological timeline of facts. Shows the story of an entity (or everything) in order.`,
21701
+ inputSchema: {
21702
+ type: "object",
21703
+ properties: {
21704
+ entity: { type: "string", description: "Entity to get timeline for (optional)" },
21705
+ limit: { type: "number", default: 50 }
21515
21706
  }
21516
- },
21707
+ }
21708
+ },
21709
+ {
21710
+ name: "recall_kg_stats",
21711
+ description: "Knowledge graph overview: entities, triples, current vs expired facts, relationship types.",
21712
+ inputSchema: {
21713
+ type: "object",
21714
+ properties: {}
21715
+ }
21716
+ },
21717
+ // ── Diary Tools ────────────────────────────────────────────
21718
+ {
21719
+ name: "recall_diary_write",
21720
+ description: `Write to your agent diary. Record observations, decisions, what you worked on, what matters.
21721
+
21722
+ Each agent gets their own persistent diary across sessions. Use this at the end of sessions
21723
+ or when you learn something important that should persist.`,
21724
+ inputSchema: {
21725
+ type: "object",
21726
+ properties: {
21727
+ agent_name: { type: "string", description: "Your name \u2014 each agent gets their own diary" },
21728
+ entry: { type: "string", description: "What happened, what you learned, what matters" },
21729
+ topic: { type: "string", default: "general", description: "Topic tag" },
21730
+ session_id: { type: "string", description: "Current session ID (for linking)" },
21731
+ project_path: { type: "string", description: "Project path (for context)" }
21732
+ },
21733
+ required: ["agent_name", "entry"]
21734
+ }
21735
+ },
21736
+ {
21737
+ name: "recall_diary_read",
21738
+ description: `Read your recent diary entries. See what past versions of yourself recorded across sessions.`,
21739
+ inputSchema: {
21740
+ type: "object",
21741
+ properties: {
21742
+ agent_name: { type: "string", description: "Agent name to read diary for" },
21743
+ last_n: { type: "number", default: 10, description: "Number of recent entries" }
21744
+ },
21745
+ required: ["agent_name"]
21746
+ }
21747
+ },
21748
+ // ── Code intelligence (codeindex companion) ──
21749
+ // Registered ONLY when the codeindex binary is on PATH: recall_code_index
21750
+ // runs the analyzer locally, and the other three are its server-side
21751
+ // browse companions — without the binary the family is dead surface.
21752
+ ...codeindexAvailable() ? [
21517
21753
  {
21518
- name: "recall_kg_timeline",
21519
- description: `Get chronological timeline of facts. Shows the story of an entity (or everything) in order.`,
21754
+ name: "recall_code_index",
21755
+ description: `Index a code repository with codeindex and sync its findings/hotspots/actions to your chat-recall server. Runs the codeindex analyzer LOCALLY (needs the repo files + git history), then ships the result. Use before recall_code_findings/actions if a repo hasn't been indexed yet.`,
21520
21756
  inputSchema: {
21521
21757
  type: "object",
21522
- properties: {
21523
- entity: { type: "string", description: "Entity to get timeline for (optional)" },
21524
- limit: { type: "number", default: 50 }
21525
- }
21758
+ properties: { path: { type: "string", description: "Repo path (default: cwd)" } }
21526
21759
  }
21527
21760
  },
21528
21761
  {
21529
- name: "recall_kg_stats",
21530
- description: "Knowledge graph overview: entities, triples, current vs expired facts, relationship types.",
21531
- inputSchema: {
21532
- type: "object",
21533
- properties: {}
21534
- }
21762
+ name: "recall_code_projects",
21763
+ description: `List code-indexed projects on your server with health score, finding count, and label (poc/production/engineering).`,
21764
+ inputSchema: { type: "object", properties: {} }
21535
21765
  },
21536
- // ── Diary Tools ────────────────────────────────────────────
21537
21766
  {
21538
- name: "recall_diary_write",
21539
- description: `Write to your agent diary. Record observations, decisions, what you worked on, what matters.
21540
-
21541
- Each agent gets their own persistent diary across sessions. Use this at the end of sessions
21542
- or when you learn something important that should persist.`,
21767
+ name: "recall_code_findings",
21768
+ description: `List code findings (security / hardcoded literals / copy-paste clones / reinvention / dead code / coupling / cycles). Each finding carries a concrete, ready-to-run agent prompt that references codeindex tools. Filter by project/severity/category.`,
21543
21769
  inputSchema: {
21544
21770
  type: "object",
21545
21771
  properties: {
21546
- agent_name: { type: "string", description: "Your name \u2014 each agent gets their own diary" },
21547
- entry: { type: "string", description: "What happened, what you learned, what matters" },
21548
- topic: { type: "string", default: "general", description: "Topic tag" },
21549
- session_id: { type: "string", description: "Current session ID (for linking)" },
21550
- project_path: { type: "string", description: "Project path (for context)" }
21551
- },
21552
- required: ["agent_name", "entry"]
21772
+ project: { type: "string", description: "Project id (omit for all)" },
21773
+ severity: { type: "string", enum: ["critical", "high", "medium", "low", "info"] },
21774
+ category: { type: "string", description: "security|literal|clone|duplication|dead_code|coupling|cycle" },
21775
+ limit: { type: "number", default: 50 },
21776
+ view: { type: "string", enum: ["findings", "summary", "hotspots", "file-sessions"], default: "findings", description: "findings = the list; summary = counts by severity/category; hotspots = churn-ranked files; file-sessions = which sessions touched `file`" },
21777
+ file: { type: "string", description: "view=file-sessions only: repo-relative path" }
21778
+ }
21553
21779
  }
21554
21780
  },
21555
21781
  {
21556
- name: "recall_diary_read",
21557
- description: `Read your recent diary entries. See what past versions of yourself recorded across sessions.`,
21782
+ name: "recall_code_actions",
21783
+ description: `The ranked, actionable plan for a codebase \u2014 prioritised tasks synthesised from the findings, each with a ready agent prompt. This is the "what should I fix next" list. Filter by project/status.`,
21558
21784
  inputSchema: {
21559
21785
  type: "object",
21560
21786
  properties: {
21561
- agent_name: { type: "string", description: "Agent name to read diary for" },
21562
- last_n: { type: "number", default: 10, description: "Number of recent entries" }
21563
- },
21564
- required: ["agent_name"]
21565
- }
21566
- },
21567
- // ── Code intelligence (codeindex companion) ──
21568
- // Registered ONLY when the codeindex binary is on PATH: recall_code_index
21569
- // runs the analyzer locally, and the other three are its server-side
21570
- // browse companions — without the binary the family is dead surface.
21571
- ...codeindexAvailable() ? [
21572
- {
21573
- name: "recall_code_index",
21574
- description: `Index a code repository with codeindex and sync its findings/hotspots/actions to your chat-recall server. Runs the codeindex analyzer LOCALLY (needs the repo files + git history), then ships the result. Use before recall_code_findings/actions if a repo hasn't been indexed yet.`,
21575
- inputSchema: {
21576
- type: "object",
21577
- properties: { path: { type: "string", description: "Repo path (default: cwd)" } }
21578
- }
21579
- },
21580
- {
21581
- name: "recall_code_projects",
21582
- description: `List code-indexed projects on your server with health score, finding count, and label (poc/production/engineering).`,
21583
- inputSchema: { type: "object", properties: {} }
21584
- },
21585
- {
21586
- name: "recall_code_findings",
21587
- description: `List code findings (security / hardcoded literals / copy-paste clones / reinvention / dead code / coupling / cycles). Each finding carries a concrete, ready-to-run agent prompt that references codeindex tools. Filter by project/severity/category.`,
21588
- inputSchema: {
21589
- type: "object",
21590
- properties: {
21591
- project: { type: "string", description: "Project id (omit for all)" },
21592
- severity: { type: "string", enum: ["critical", "high", "medium", "low", "info"] },
21593
- category: { type: "string", description: "security|literal|clone|duplication|dead_code|coupling|cycle" },
21594
- limit: { type: "number", default: 50 },
21595
- view: { type: "string", enum: ["findings", "summary", "hotspots", "file-sessions"], default: "findings", description: "findings = the list; summary = counts by severity/category; hotspots = churn-ranked files; file-sessions = which sessions touched `file`" },
21596
- file: { type: "string", description: "view=file-sessions only: repo-relative path" }
21597
- }
21598
- }
21599
- },
21600
- {
21601
- name: "recall_code_actions",
21602
- description: `The ranked, actionable plan for a codebase \u2014 prioritised tasks synthesised from the findings, each with a ready agent prompt. This is the "what should I fix next" list. Filter by project/status.`,
21603
- inputSchema: {
21604
- type: "object",
21605
- properties: {
21606
- project: { type: "string", description: "Project id (omit for all)" },
21607
- status: { type: "string", enum: ["suggested", "queued", "done", "dismissed"] },
21608
- limit: { type: "number", default: 30 }
21609
- }
21787
+ project: { type: "string", description: "Project id (omit for all)" },
21788
+ status: { type: "string", enum: ["suggested", "queued", "done", "dismissed"] },
21789
+ limit: { type: "number", default: 30 }
21610
21790
  }
21611
21791
  }
21612
- ] : [],
21613
- {
21614
- name: "recall_recommendations",
21615
- description: `Actionable recommendations, two scopes on one tool:
21792
+ }
21793
+ ] : [],
21794
+ {
21795
+ name: "recall_recommendations",
21796
+ description: `Actionable recommendations, two scopes on one tool:
21616
21797
 
21617
21798
  - scope "account" (default) \u2014 from YOUR chat-recall data: leaked secrets found by
21618
21799
  the scanner + session behaviour (unresolved/abandoned). Returns concrete
@@ -21622,21 +21803,21 @@ or when you learn something important that should persist.`,
21622
21803
  a skill, set a project label, or reset a POC db. Reasons over BOTH the code
21623
21804
  findings and how sessions in this project went (failed/abandoned, recurring
21624
21805
  corrections). Each has rationale + evidence + an apply-action.`,
21625
- inputSchema: {
21626
- type: "object",
21627
- properties: {
21628
- scope: { type: "string", enum: ["account", "project"], default: "account", description: "account = your chat-recall data; project = one code-indexed project" },
21629
- project: { type: "string", description: 'Project id (from recall_code_projects). Required when scope is "project".' }
21630
- }
21806
+ inputSchema: {
21807
+ type: "object",
21808
+ properties: {
21809
+ scope: { type: "string", enum: ["account", "project"], default: "account", description: "account = your chat-recall data; project = one code-indexed project" },
21810
+ project: { type: "string", description: 'Project id (from recall_code_projects). Required when scope is "project".' }
21631
21811
  }
21632
- },
21633
- // ── Aggregated views over the recommendation engines ──
21634
- // Registered UNGATED, unlike the recall_code_* family: they read findings
21635
- // the SERVER already holds, so a machine without the codeindex binary can
21636
- // still see them. Both degrade to account scope when no project is indexed.
21637
- {
21638
- name: "recall_claude_suggestions",
21639
- description: `Every finding that turns into an agent-instruction change, in one list \u2014 the
21812
+ }
21813
+ },
21814
+ // ── Aggregated views over the recommendation engines ──
21815
+ // Registered UNGATED, unlike the recall_code_* family: they read findings
21816
+ // the SERVER already holds, so a machine without the codeindex binary can
21817
+ // still see them. Both degrade to account scope when no project is indexed.
21818
+ {
21819
+ name: "recall_claude_suggestions",
21820
+ description: `Every finding that turns into an agent-instruction change, in one list \u2014 the
21640
21821
  "what should I tell Claude about this?" view.
21641
21822
 
21642
21823
  Merges the CLAUDE.md rules and skill installs from BOTH recommendation engines:
@@ -21648,18 +21829,18 @@ paste.
21648
21829
  This is the read side. To apply one, use the apply rail the recommendation
21649
21830
  already names (recall_recommendations documents it) \u2014 this tool never edits a
21650
21831
  CLAUDE.md itself.`,
21651
- inputSchema: {
21652
- type: "object",
21653
- properties: {
21654
- project: { type: "string", description: "Limit to one code-indexed project id (from recall_code_projects)" },
21655
- include_projects: { type: "boolean", default: true, description: "Also fan out over code-indexed projects. false = account scope only." },
21656
- kind: { type: "string", enum: [...INSTRUCTION_KINDS], description: "rule = a CLAUDE.md line; skill = a skill to install. Omit for both." }
21657
- }
21832
+ inputSchema: {
21833
+ type: "object",
21834
+ properties: {
21835
+ project: { type: "string", description: "Limit to one code-indexed project id (from recall_code_projects)" },
21836
+ include_projects: { type: "boolean", default: true, description: "Also fan out over code-indexed projects. false = account scope only." },
21837
+ kind: { type: "string", enum: [...INSTRUCTION_KINDS], description: "rule = a CLAUDE.md line; skill = a skill to install. Omit for both." }
21658
21838
  }
21659
- },
21660
- {
21661
- name: "recall_improvements",
21662
- description: `Every improvement worth doing, ranked highest-priority first \u2014 the "what should
21839
+ }
21840
+ },
21841
+ {
21842
+ name: "recall_improvements",
21843
+ description: `Every improvement worth doing, ranked highest-priority first \u2014 the "what should
21663
21844
  I fix next, across everything" view.
21664
21845
 
21665
21846
  Merges the ranked code actions (recall_code_actions) with the non-instruction
@@ -21670,273 +21851,322 @@ severity; both are mapped onto one scale so a single ordered list is honest.
21670
21851
  Set \`create_tasks: true\` to open one task per improvement on the shared team
21671
21852
  board (recall_tasks). It is off by default \u2014 this reads unless you ask it to
21672
21853
  write.`,
21673
- inputSchema: {
21674
- type: "object",
21675
- properties: {
21676
- project: { type: "string", description: "Limit to one code-indexed project id" },
21677
- min_severity: { type: "string", enum: [...SEVERITIES], default: "low", description: "Drop anything below this severity" },
21678
- limit: { type: "number", default: 30, description: "Maximum improvements to return, after ranking" },
21679
- create_tasks: { type: "boolean", default: false, description: "Create one team task per returned improvement" },
21680
- assignee: { type: "string", description: "create_tasks only: teammate user id (sub) to assign each task to" }
21681
- }
21854
+ inputSchema: {
21855
+ type: "object",
21856
+ properties: {
21857
+ project: { type: "string", description: "Limit to one code-indexed project id" },
21858
+ min_severity: { type: "string", enum: [...SEVERITIES], default: "low", description: "Drop anything below this severity" },
21859
+ limit: { type: "number", default: 30, description: "Maximum improvements to return, after ranking" },
21860
+ create_tasks: { type: "boolean", default: false, description: "Create one team task per returned improvement" },
21861
+ assignee: { type: "string", description: "create_tasks only: teammate user id (sub) to assign each task to" }
21682
21862
  }
21683
- },
21684
- // ── User-prompts / decision-record ──
21685
- {
21686
- name: "recall_user_prompts",
21687
- description: `List the human-typed prompts from a session (or recent sessions). Tool results
21863
+ }
21864
+ },
21865
+ // ── User-prompts / decision-record ──
21866
+ {
21867
+ name: "recall_user_prompts",
21868
+ description: `List the human-typed prompts from a session (or recent sessions). Tool results
21688
21869
  and system banners are stripped \u2014 you get only what the user actually wrote. Useful for "what was
21689
21870
  I asking yesterday?" or "what did I tell the agent in this session?".`,
21690
- inputSchema: {
21691
- type: "object",
21692
- properties: {
21693
- session_id: { type: "string", description: "If set, only that session's prompts" },
21694
- since_days: { type: "number", default: 7 },
21695
- limit: { type: "number", default: 50 }
21696
- }
21871
+ inputSchema: {
21872
+ type: "object",
21873
+ properties: {
21874
+ session_id: { type: "string", description: "If set, only that session's prompts" },
21875
+ since_days: { type: "number", default: 7 },
21876
+ limit: { type: "number", default: 50 }
21697
21877
  }
21698
- },
21699
- {
21700
- name: "recall_decision_record",
21701
- description: `Record an explicit decision so it shows up in wake-up context. Writes a KG triple
21878
+ }
21879
+ },
21880
+ {
21881
+ name: "recall_decision_record",
21882
+ description: `Record an explicit decision so it shows up in wake-up context. Writes a KG triple
21702
21883
  (subject \u2192 decided \u2192 decision) plus a diary entry. Use this when you and the user agree on
21703
21884
  something non-obvious that future sessions should remember.`,
21704
- inputSchema: {
21705
- type: "object",
21706
- properties: {
21707
- subject: { type: "string", description: "What the decision is about" },
21708
- decision: { type: "string", description: "The decision itself" },
21709
- reason: { type: "string", description: "Why this was decided" },
21710
- importance: { type: "number", minimum: 1, maximum: 5, default: 4 },
21711
- session_id: { type: "string" },
21712
- agent_name: { type: "string", default: "agent" }
21713
- },
21714
- required: ["subject", "decision"]
21715
- }
21716
- },
21717
- {
21718
- name: "recall_analytics_summary",
21719
- description: `Cross-tool spend & activity overview. Returns total sessions, total cost,
21885
+ inputSchema: {
21886
+ type: "object",
21887
+ properties: {
21888
+ subject: { type: "string", description: "What the decision is about" },
21889
+ decision: { type: "string", description: "The decision itself" },
21890
+ reason: { type: "string", description: "Why this was decided" },
21891
+ importance: { type: "number", minimum: 1, maximum: 5, default: 4 },
21892
+ session_id: { type: "string" },
21893
+ agent_name: { type: "string", default: "agent" }
21894
+ },
21895
+ required: ["subject", "decision"]
21896
+ }
21897
+ },
21898
+ {
21899
+ name: "recall_analytics_summary",
21900
+ description: `Cross-tool spend & activity overview. Returns total sessions, total cost,
21720
21901
  weekly delta, top tools/projects/models, and how many sessions had unknown pricing
21721
21902
  (Gemini/Ollama/custom). Same data as the web Dashboard, available to the agent so it
21722
21903
  can answer "how much have I spent this week" without you opening the UI.
21723
21904
 
21724
21905
  \`view: "patterns"\` returns behavioural patterns across sessions instead of the spend rollup.`,
21725
- inputSchema: {
21726
- type: "object",
21727
- properties: {
21728
- view: { type: "string", enum: ["summary", "patterns"], default: "summary", description: "summary = spend/activity rollup; patterns = behavioural patterns across sessions" }
21729
- }
21906
+ inputSchema: {
21907
+ type: "object",
21908
+ properties: {
21909
+ view: { type: "string", enum: ["summary", "patterns"], default: "summary", description: "summary = spend/activity rollup; patterns = behavioural patterns across sessions" }
21730
21910
  }
21731
- },
21732
- {
21733
- name: "recall_wake_up",
21734
- description: `Build startup context for an AI session: identity blurb + the top
21911
+ }
21912
+ },
21913
+ {
21914
+ name: "recall_wake_up",
21915
+ description: `Build startup context for an AI session: identity blurb + the top
21735
21916
  classifier-flagged facts (decisions/preferences/milestones at importance >=4) +
21736
21917
  current knowledge-graph facts. Replaces a manual context dump. Call this once at
21737
21918
  the start of a session to "remember who you are and what's true right now".`,
21738
- inputSchema: {
21739
- type: "object",
21740
- properties: {
21741
- max_facts: { type: "number", default: 10 },
21742
- max_kg_facts: { type: "number", default: 15 },
21743
- identity: { type: "string" },
21744
- project_filter: { type: "string", description: "Scope facts and KG entities to a project (substring match). Without it, facts are global." }
21745
- }
21919
+ inputSchema: {
21920
+ type: "object",
21921
+ properties: {
21922
+ max_facts: { type: "number", default: 10 },
21923
+ max_kg_facts: { type: "number", default: 15 },
21924
+ identity: { type: "string" },
21925
+ project_filter: { type: "string", description: "Scope facts and KG entities to a project (substring match). Without it, facts are global." }
21746
21926
  }
21747
- },
21748
- {
21749
- name: "recall_set",
21750
- description: `Persist a small key/value pair across sessions. Use this for
21927
+ }
21928
+ },
21929
+ {
21930
+ name: "recall_set",
21931
+ description: `Persist a small key/value pair across sessions. Use this for
21751
21932
  state that doesn't fit the diary (narrative) or knowledge graph (entity-relationship facts) \u2014
21752
21933
  things like "current PR url", "branch I'm working on", "user's preferred test runner".
21753
21934
  Scope namespaces keys so per-project state doesn't collide.`,
21754
- inputSchema: {
21755
- type: "object",
21756
- properties: {
21757
- key: { type: "string", description: "Key name" },
21758
- value: { type: "string", description: "Value to store. JSON-encode if structured." },
21759
- scope: { type: "string", default: "default", description: "Namespace" }
21760
- },
21761
- required: ["key", "value"]
21762
- }
21763
- },
21764
- {
21765
- name: "recall_get",
21766
- description: `Read a key/value pair previously written with recall_set \u2014 or, when \`key\` is
21935
+ inputSchema: {
21936
+ type: "object",
21937
+ properties: {
21938
+ key: { type: "string", description: "Key name" },
21939
+ value: { type: "string", description: "Value to store. JSON-encode if structured." },
21940
+ scope: { type: "string", default: "default", description: "Namespace" }
21941
+ },
21942
+ required: ["key", "value"]
21943
+ }
21944
+ },
21945
+ {
21946
+ name: "recall_get",
21947
+ description: `Read a key/value pair previously written with recall_set \u2014 or, when \`key\` is
21767
21948
  omitted, LIST the recorded keys (in one scope, or across all scopes when \`scope\` is
21768
21949
  also omitted). Use the list mode to discover what state has been recorded without
21769
21950
  remembering specific keys.`,
21770
- inputSchema: {
21771
- type: "object",
21772
- properties: {
21773
- key: { type: "string", description: "Key name. Omit to list keys instead." },
21774
- scope: { type: "string", description: 'Namespace (default "default" when reading; omit to list across all scopes)' },
21775
- limit: { type: "number", default: 50, description: "Max entries in list mode" }
21776
- }
21951
+ inputSchema: {
21952
+ type: "object",
21953
+ properties: {
21954
+ key: { type: "string", description: "Key name. Omit to list keys instead." },
21955
+ scope: { type: "string", description: 'Namespace (default "default" when reading; omit to list across all scopes)' },
21956
+ limit: { type: "number", default: 50, description: "Max entries in list mode" }
21777
21957
  }
21778
- },
21779
- // ── Security / Secret Findings Tools ─────────────────────────
21780
- {
21781
- name: "recall_security_summary",
21782
- description: `Get a security overview from the synced chat-recall server.
21958
+ }
21959
+ },
21960
+ // ── Security / Secret Findings Tools ─────────────────────────
21961
+ {
21962
+ name: "recall_security_summary",
21963
+ description: `Get a security overview from the synced chat-recall server.
21783
21964
 
21784
21965
  Returns the action-required list of distinct leaked secrets (grouped by redacted
21785
21966
  preview), plus per-detector totals and top rules. Use this when the user asks
21786
21967
  "do we have any leaked secrets?" or "security findings" or "what should I rotate".
21787
21968
 
21788
21969
  Call this first; then call recall_security_session for specific sessions.`,
21789
- inputSchema: {
21790
- type: "object",
21791
- properties: {
21792
- include_dismissed: { type: "boolean", default: false, description: "Show previously dismissed findings" },
21793
- top_k: { type: "number", default: 20, description: "Max distinct secrets to list" },
21794
- group_by: { type: "string", enum: ["detector", "rule", "project", "trend", "sessions"], default: "detector", description: "detector = action-required view; rule/project = grouped counts; trend = daily counts; sessions = one row per session with findings" },
21795
- days: { type: "number", default: 30, description: "group_by=trend only: window in days (1-365)" },
21796
- min_detectors: { type: "number", description: "group_by=sessions only: require N detectors agreeing" }
21797
- }
21970
+ inputSchema: {
21971
+ type: "object",
21972
+ properties: {
21973
+ include_dismissed: { type: "boolean", default: false, description: "Show previously dismissed findings" },
21974
+ top_k: { type: "number", default: 20, description: "Max distinct secrets to list" },
21975
+ group_by: { type: "string", enum: ["detector", "rule", "project", "trend", "sessions"], default: "detector", description: "detector = action-required view; rule/project = grouped counts; trend = daily counts; sessions = one row per session with findings" },
21976
+ days: { type: "number", default: 30, description: "group_by=trend only: window in days (1-365)" },
21977
+ min_detectors: { type: "number", description: "group_by=sessions only: require N detectors agreeing" }
21798
21978
  }
21799
- },
21800
- {
21801
- name: "recall_security_session",
21802
- description: `Get secret findings for a specific session.
21979
+ }
21980
+ },
21981
+ {
21982
+ name: "recall_security_session",
21983
+ description: `Get secret findings for a specific session.
21803
21984
 
21804
21985
  Use after recall_search or recall_security_summary to show exactly which lines
21805
21986
  in a session matched which detector.`,
21806
- inputSchema: {
21807
- type: "object",
21808
- properties: {
21809
- session_id: { type: "string", description: "Session ID from search results" }
21810
- },
21811
- required: ["session_id"]
21812
- }
21813
- },
21814
- {
21815
- name: "recall_security_dismiss",
21816
- description: `Mark a secret finding as rotated, false_positive, or dismissed.
21987
+ inputSchema: {
21988
+ type: "object",
21989
+ properties: {
21990
+ session_id: { type: "string", description: "Session ID from search results" }
21991
+ },
21992
+ required: ["session_id"]
21993
+ }
21994
+ },
21995
+ {
21996
+ name: "recall_security_dismiss",
21997
+ description: `Mark a secret finding as rotated, false_positive, or dismissed.
21817
21998
 
21818
21999
  Use when the user confirms a key was rotated or that the match is not a real
21819
22000
  secret. The dismissal syncs across devices.`,
21820
- inputSchema: {
21821
- type: "object",
21822
- properties: {
21823
- preview: { type: "string", description: "Masked secret preview from the security dashboard" },
21824
- status: { type: "string", enum: ["rotated", "false_positive", "dismissed", "undismissed"], description: "Resolution status, or `undismissed` to put the finding back on the action-required list" },
21825
- reason: { type: "string", description: "Optional note" }
21826
- },
21827
- required: ["preview", "status"]
21828
- }
21829
- },
21830
- {
21831
- name: "recall_security_rules",
21832
- description: `List tenant custom secret-detection rules, or test a regex in the sandbox.
22001
+ inputSchema: {
22002
+ type: "object",
22003
+ properties: {
22004
+ preview: { type: "string", description: "Masked secret preview from the security dashboard" },
22005
+ status: { type: "string", enum: ["rotated", "false_positive", "dismissed", "undismissed"], description: "Resolution status, or `undismissed` to put the finding back on the action-required list" },
22006
+ reason: { type: "string", description: "Optional note" }
22007
+ },
22008
+ required: ["preview", "status"]
22009
+ }
22010
+ },
22011
+ {
22012
+ name: "recall_security_rules",
22013
+ description: `List tenant custom secret-detection rules, or test a regex in the sandbox.
21833
22014
 
21834
22015
  Tenant rules are configured in the chat-recall dashboard; this tool exposes them
21835
22016
  to the agent and lets you test new regexes without persisting them.`,
21836
- inputSchema: {
21837
- type: "object",
21838
- properties: {
21839
- action: { type: "string", enum: ["list", "test"], default: "list", description: "list = existing rules; test = regex sandbox" },
21840
- name: { type: "string", description: "For test: rule name" },
21841
- regex: { type: "string", description: "For test: regex to evaluate" },
21842
- sample: { type: "string", description: "For test: text to match against" }
21843
- }
22017
+ inputSchema: {
22018
+ type: "object",
22019
+ properties: {
22020
+ action: { type: "string", enum: ["list", "test"], default: "list", description: "list = existing rules; test = regex sandbox" },
22021
+ name: { type: "string", description: "For test: rule name" },
22022
+ regex: { type: "string", description: "For test: regex to evaluate" },
22023
+ sample: { type: "string", description: "For test: text to match against" }
21844
22024
  }
21845
- },
21846
- {
21847
- name: "recall_subagent_search",
21848
- description: `Search inside SUBAGENT transcripts \u2014 the explore/compact/aside sub-tasks that run within a session and whose work never appears in the main conversation.
22025
+ }
22026
+ },
22027
+ {
22028
+ name: "recall_subagent_search",
22029
+ description: `Search inside SUBAGENT transcripts \u2014 the explore/compact/aside sub-tasks that run within a session and whose work never appears in the main conversation.
21849
22030
 
21850
22031
  Use when a session's own turns don't contain what you remember: the finding may have come from a subagent. Indexed server-side, so it works across devices.`,
21851
- inputSchema: {
21852
- type: "object",
21853
- properties: {
21854
- query: { type: "string", description: "Text to find inside subagent transcripts" },
21855
- session_id: { type: "string", description: "Restrict to one session" },
21856
- kind: { type: "string", description: "Restrict to one subagent kind" },
21857
- limit: { type: "number", default: 20 }
21858
- },
21859
- required: ["query"]
21860
- }
21861
- },
21862
- {
21863
- name: "recall_redundant_files",
21864
- description: `Before creating a file, check whether you have written one like it before. Scores the filename against every file touched in your synced history.
22032
+ inputSchema: {
22033
+ type: "object",
22034
+ properties: {
22035
+ query: { type: "string", description: "Text to find inside subagent transcripts" },
22036
+ session_id: { type: "string", description: "Restrict to one session" },
22037
+ kind: { type: "string", description: "Restrict to one subagent kind" },
22038
+ limit: { type: "number", default: 20 }
22039
+ },
22040
+ required: ["query"]
22041
+ }
22042
+ },
22043
+ {
22044
+ name: "recall_redundant_files",
22045
+ description: `Before creating a file, check whether you have written one like it before. Scores the filename against every file touched in your synced history.
21865
22046
 
21866
22047
  Answers "have I built this already?" \u2014 the reinvention check.`,
21867
- inputSchema: {
21868
- type: "object",
21869
- properties: {
21870
- filename: { type: "string", description: "Filename you are about to create" },
21871
- project: { type: "string", description: "Restrict to one project" },
21872
- limit: { type: "number", default: 20 }
21873
- },
21874
- required: ["filename"]
21875
- }
21876
- },
21877
- {
21878
- name: "recall_heal_audit",
21879
- description: `Report how many of your sessions are DAMAGED \u2014 the rendered conversation is thinner than the shrink-protected raw archive, which happens when an upstream tool truncates a transcript in place (a resume/compaction rewrite, or a script mirroring a shorter file over a longer one).
22048
+ inputSchema: {
22049
+ type: "object",
22050
+ properties: {
22051
+ filename: { type: "string", description: "Filename you are about to create" },
22052
+ project: { type: "string", description: "Restrict to one project" },
22053
+ limit: { type: "number", default: 20 }
22054
+ },
22055
+ required: ["filename"]
22056
+ }
22057
+ },
22058
+ {
22059
+ name: "recall_heal_audit",
22060
+ description: `Report how many of your sessions are DAMAGED \u2014 the rendered conversation is thinner than the shrink-protected raw archive, which happens when an upstream tool truncates a transcript in place (a resume/compaction rewrite, or a script mirroring a shorter file over a longer one).
21880
22061
 
21881
22062
  This is the authoritative "0 remaining" check. Read-only by default; apply=true heals, and healing only ever makes a conversation FULLER.`,
21882
- inputSchema: {
21883
- type: "object",
21884
- properties: {
21885
- since_hours: { type: "number", description: "Bound the scan to the last N hours" },
21886
- apply: { type: "boolean", default: false, description: "false = audit only; true = heal now" }
21887
- }
22063
+ inputSchema: {
22064
+ type: "object",
22065
+ properties: {
22066
+ since_hours: { type: "number", description: "Bound the scan to the last N hours" },
22067
+ apply: { type: "boolean", default: false, description: "false = audit only; true = heal now" }
21888
22068
  }
21889
- },
21890
- {
21891
- name: "recall_memory_item",
21892
- description: `Fetch ONE memory item, its full text, or its links \u2014 or browse a whole source type.
22069
+ }
22070
+ },
22071
+ {
22072
+ name: "recall_memory_item",
22073
+ description: `Fetch ONE memory item, its full text, or its links \u2014 or browse a whole source type.
21893
22074
 
21894
22075
  Complements recall_memory_search: search finds candidates, this reads the specific item. mode=browse lists a type (e.g. every skill, every plan) without a query.`,
21895
- inputSchema: {
21896
- type: "object",
21897
- properties: {
21898
- source_type: { type: "string", enum: ["session", "plan", "task", "claude_md", "paste", "history", "diary"] },
21899
- id: { type: "string", description: "Item id. Omit with mode=browse." },
21900
- mode: { type: "string", enum: ["item", "content", "links", "browse"], default: "item", description: "item = metadata, content = full text, links = related items, browse = list the type" },
21901
- limit: { type: "number", default: 50, description: "browse mode only" }
21902
- },
21903
- required: ["source_type"]
21904
- }
21905
- },
21906
- {
21907
- name: "recall_regenerate_summary",
21908
- description: "Force a fresh AI summary for one session \u2014 use when its stored summary is missing, stale, or plainly wrong.",
21909
- inputSchema: {
21910
- type: "object",
21911
- properties: { session_id: { type: "string" } },
21912
- required: ["session_id"]
21913
- }
21914
- },
21915
- {
21916
- name: "recall_outcome_summary",
21917
- description: 'Aggregate session outcomes (shipped / interrupted / abandoned / in_progress) over the last N days \u2014 "how much of my work actually landed?".',
21918
- inputSchema: {
21919
- type: "object",
21920
- properties: { days: { type: "number", default: 7, description: "1-90" } }
21921
- }
21922
- },
21923
- {
21924
- name: "recall_shares",
21925
- description: "Which projects are shared into your team. scope=mine for your own shares, scope=all for every share visible to you. Private by default \u2014 nothing is visible to teammates until shared.",
21926
- inputSchema: {
21927
- type: "object",
21928
- properties: { scope: { type: "string", enum: ["mine", "all"], default: "mine" } }
21929
- }
21930
- },
21931
- {
21932
- name: "recall_reclassify",
21933
- description: "Re-run the memory classifier over already-indexed chunks so classifier improvements reach old data. Idempotent \u2014 only rewrites tags that actually changed.",
21934
- inputSchema: { type: "object", properties: {} }
22076
+ inputSchema: {
22077
+ type: "object",
22078
+ properties: {
22079
+ source_type: { type: "string", enum: ["session", "plan", "task", "claude_md", "paste", "history", "diary"] },
22080
+ id: { type: "string", description: "Item id. Omit with mode=browse." },
22081
+ mode: { type: "string", enum: ["item", "content", "links", "browse"], default: "item", description: "item = metadata, content = full text, links = related items, browse = list the type" },
22082
+ limit: { type: "number", default: 50, description: "browse mode only" }
22083
+ },
22084
+ required: ["source_type"]
21935
22085
  }
21936
- ]
21937
- };
22086
+ },
22087
+ {
22088
+ name: "recall_regenerate_summary",
22089
+ description: "Force a fresh AI summary for one session \u2014 use when its stored summary is missing, stale, or plainly wrong.",
22090
+ inputSchema: {
22091
+ type: "object",
22092
+ properties: { session_id: { type: "string" } },
22093
+ required: ["session_id"]
22094
+ }
22095
+ },
22096
+ {
22097
+ name: "recall_outcome_summary",
22098
+ description: 'Aggregate session outcomes (shipped / interrupted / abandoned / in_progress) over the last N days \u2014 "how much of my work actually landed?".',
22099
+ inputSchema: {
22100
+ type: "object",
22101
+ properties: { days: { type: "number", default: 7, description: "1-90" } }
22102
+ }
22103
+ },
22104
+ {
22105
+ name: "recall_shares",
22106
+ description: "Which projects are shared into your team. scope=mine for your own shares, scope=all for every share visible to you. Private by default \u2014 nothing is visible to teammates until shared.",
22107
+ inputSchema: {
22108
+ type: "object",
22109
+ properties: { scope: { type: "string", enum: ["mine", "all"], default: "mine" } }
22110
+ }
22111
+ },
22112
+ {
22113
+ name: "recall_reclassify",
22114
+ description: "Re-run the memory classifier over already-indexed chunks so classifier improvements reach old data. Idempotent \u2014 only rewrites tags that actually changed.",
22115
+ inputSchema: { type: "object", properties: {} }
22116
+ }
22117
+ ];
22118
+ if (toolProfile() === "full") return { tools: all };
22119
+ const lean = all.filter((t) => LEAN_TOOLS.has(t.name));
22120
+ const hidden = all.length - lean.length;
22121
+ if (hidden > 0) {
22122
+ lean.push({
22123
+ name: "recall_help",
22124
+ description: `List the ${hidden} additional chat-recall tools that are not registered in this profile (analytics, knowledge-graph navigation, code intelligence, sharing, security triage, maintenance). They all still WORK if called by name. Set CHAT_RECALL_MCP_PROFILE=full to register every tool up front.`,
22125
+ inputSchema: { type: "object", properties: {} }
22126
+ });
22127
+ }
22128
+ return { tools: lean };
21938
22129
  });
22130
+ var syncStateCache = null;
22131
+ var SYNC_STATE_TTL_MS = 5 * 60 * 1e3;
22132
+ async function syncState() {
22133
+ if (syncStateCache && Date.now() - syncStateCache.at < SYNC_STATE_TTL_MS) return syncStateCache.state;
22134
+ let state = null;
22135
+ try {
22136
+ const cred = remoteCredentials();
22137
+ if (cred) {
22138
+ const res = await fetch(cred.base + "/api/billing", {
22139
+ headers: { authorization: `Bearer ${cred.token}` }
22140
+ });
22141
+ if (res.ok) {
22142
+ const b = await res.json();
22143
+ if (typeof b.entitled === "boolean") {
22144
+ state = {
22145
+ entitled: b.entitled,
22146
+ status: typeof b.status === "string" ? b.status : "unknown",
22147
+ periodEnd: typeof b.currentPeriodEnd === "number" ? b.currentPeriodEnd : null
22148
+ };
22149
+ }
22150
+ }
22151
+ }
22152
+ } catch {
22153
+ }
22154
+ syncStateCache = { at: Date.now(), state };
22155
+ return state;
22156
+ }
21939
22157
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
22158
+ const result = await dispatchTool(request);
22159
+ try {
22160
+ const banner = stalenessBanner(await syncState());
22161
+ if (!banner) return result;
22162
+ const content = result.content;
22163
+ if (!Array.isArray(content) || !content.length) return result;
22164
+ return { ...result, content: [{ type: "text", text: banner }, ...content] };
22165
+ } catch {
22166
+ return result;
22167
+ }
22168
+ });
22169
+ async function dispatchTool(request) {
21940
22170
  const { name, arguments: args } = request.params;
21941
22171
  try {
21942
22172
  if (/^recall_code_(index|projects|findings|actions)$/.test(name) && !codeindexAvailable()) {
@@ -22809,6 +23039,7 @@ ${lines.join("\n")}` }] };
22809
23039
  if (params.status) patch.status = params.status;
22810
23040
  if (params.assignee !== void 0) patch.assigneeSub = params.assignee || null;
22811
23041
  if (params.title) patch.title = params.title;
23042
+ if (params.linked_session_id !== void 0) patch.linkedSessionId = params.linked_session_id;
22812
23043
  let did = false;
22813
23044
  if (Object.keys(patch).length > 0) {
22814
23045
  await remotePatch(`/api/tasks/${encodeURIComponent(params.id)}`, patch);
@@ -22818,9 +23049,18 @@ ${lines.join("\n")}` }] };
22818
23049
  await remotePost(`/api/tasks/${encodeURIComponent(params.id)}/comments`, { body: params.comment });
22819
23050
  did = true;
22820
23051
  }
22821
- if (!did) return { content: [{ type: "text", text: "Nothing to update \u2014 pass status, assignee, title, and/or comment." }] };
23052
+ if (!did) return { content: [{ type: "text", text: "Nothing to update \u2014 pass status, assignee, title, linked_session_id, and/or comment." }] };
22822
23053
  return { content: [{ type: "text", text: `Updated task ${params.id}.` }] };
22823
23054
  }
23055
+ case "recall_task_comment": {
23056
+ const params = RecallTaskCommentSchema.parse(args);
23057
+ requireRemote();
23058
+ const { comment } = await remotePost(
23059
+ `/api/tasks/${encodeURIComponent(params.task_id)}/comments`,
23060
+ { body: params.body }
23061
+ );
23062
+ return { content: [{ type: "text", text: `Commented on task ${comment.taskId} (comment ${comment.id}).` }] };
23063
+ }
22824
23064
  // ── Knowledge Graph Handlers ─────────────────────────────────
22825
23065
  case "recall_kg_query": {
22826
23066
  const params = RecallKGQuerySchema.parse(args);
@@ -23524,6 +23764,12 @@ ${row.value}`
23524
23764
  max_facts: params.max_facts,
23525
23765
  max_kg_facts: params.max_kg_facts
23526
23766
  });
23767
+ const ot = wake.openTasks;
23768
+ if (ot && ot.total > 0) {
23769
+ lines.push("## Task board");
23770
+ lines.push(` ${ot.total} open task(s)${ot.auto ? ` \u2014 ${ot.auto} auto-filed from code findings` : ""}. List them with recall_tasks; when you start one, set it in_progress and pass your session id as linked_session_id.`);
23771
+ lines.push("");
23772
+ }
23527
23773
  if (wake.highFacts.length > 0) {
23528
23774
  lines.push("## High-importance facts");
23529
23775
  for (const c of wake.highFacts) lines.push(` [${c.type}] ${c.text}`);
@@ -23807,6 +24053,29 @@ ${JSON.stringify(r, null, 2)}
23807
24053
  if (r.error) return { content: [{ type: "text", text: `Reclassify unavailable: ${r.error}` }] };
23808
24054
  return { content: [{ type: "text", text: `${r.message ?? "Reclassify complete"} \u2014 scanned ${r.scanned ?? 0}, updated ${r.updated ?? 0}.` }] };
23809
24055
  }
24056
+ case "recall_help": {
24057
+ const lines = [
24058
+ "# chat-recall tools not registered in this profile",
24059
+ "",
24060
+ "They all still work \u2014 call them by name. To register every tool up front,",
24061
+ "set CHAT_RECALL_MCP_PROFILE=full and restart the MCP server.",
24062
+ ""
24063
+ ];
24064
+ const groups = [
24065
+ ["Aggregate views", ["recall_weekly_digest", "recall_analytics_summary", "recall_team_activity", "recall_outcome_summary"]],
24066
+ ["Task board", ["recall_task_comment"]],
24067
+ ["Knowledge graph", ["recall_kg_timeline", "recall_kg_stats", "recall_kg_invalidate"]],
24068
+ ["Session detail", ["recall_markers", "recall_subagent_search", "recall_memory_item", "recall_rename_session", "recall_regenerate_summary"]],
24069
+ ["Code intelligence", ["recall_code_index", "recall_code_projects", "recall_code_findings", "recall_code_actions"]],
24070
+ ["Recommendations", ["recall_recommendations", "recall_improvements", "recall_claude_suggestions", "recall_redundant_files"]],
24071
+ ["Security triage", ["recall_security_session", "recall_security_dismiss", "recall_security_rules"]],
24072
+ ["State + maintenance", ["recall_set", "recall_get", "recall_shares", "recall_reclassify", "recall_heal_audit"]]
24073
+ ];
24074
+ for (const [label, names] of groups) {
24075
+ lines.push(`## ${label}`, names.map((n) => `- ${n}`).join("\n"), "");
24076
+ }
24077
+ return { content: [{ type: "text", text: lines.join("\n") }] };
24078
+ }
23810
24079
  default:
23811
24080
  return { content: [{ type: "text", text: `Unknown tool: ${name}` }] };
23812
24081
  }
@@ -23818,7 +24087,7 @@ ${JSON.stringify(r, null, 2)}
23818
24087
  reportClientEvent("tool_error", { tool: name, message: msg });
23819
24088
  return { content: [{ type: "text", text: `Error: ${msg}` }] };
23820
24089
  }
23821
- });
24090
+ }
23822
24091
  function runIndexChild(force) {
23823
24092
  return new Promise((resolve2) => {
23824
24093
  let cliPath;