chat-recall 0.6.8 → 0.6.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +966 -909
- package/dist/cli.js.map +1 -1
- package/dist/mcp-relay.js +1 -1
- package/dist/mcp.js +1043 -775
- package/dist/scan-worker.js +325 -25
- package/dist/scan-worker.js.map +1 -1
- package/dist/sync-client.d.ts +13 -0
- package/dist/sync-client.d.ts.map +1 -1
- package/dist/sync-client.js +38 -4
- package/dist/sync-client.js.map +1 -1
- package/dist/watch.js +895 -856
- package/package.json +1 -1
package/dist/mcp.js
CHANGED
|
@@ -4593,6 +4593,28 @@ function resolveProjectId(inputPath) {
|
|
|
4593
4593
|
resolveCache.set(realPath, result);
|
|
4594
4594
|
return result;
|
|
4595
4595
|
}
|
|
4596
|
+
function resolveWorkspaceId(inputPath) {
|
|
4597
|
+
if (!inputPath || !isAbsolute(inputPath))
|
|
4598
|
+
return null;
|
|
4599
|
+
const realPath = safeRealpath(inputPath);
|
|
4600
|
+
const cached = wsIdCache.get(realPath);
|
|
4601
|
+
if (cached !== void 0)
|
|
4602
|
+
return cached;
|
|
4603
|
+
const result = doResolveWorkspace(realPath);
|
|
4604
|
+
wsIdCache.set(realPath, result);
|
|
4605
|
+
return result;
|
|
4606
|
+
}
|
|
4607
|
+
function doResolveWorkspace(realPath) {
|
|
4608
|
+
const cfg = loadProjectsConfig();
|
|
4609
|
+
const declaredWorkspaces = (cfg.projects || []).filter((p) => p.workspace).sort((a, b) => b.root.length - a.root.length);
|
|
4610
|
+
for (const proj of declaredWorkspaces) {
|
|
4611
|
+
if (isInside(realPath, safeRealpath(proj.root)))
|
|
4612
|
+
return proj.id;
|
|
4613
|
+
}
|
|
4614
|
+
const anchor = findGitToplevel(realPath) ?? realPath;
|
|
4615
|
+
const ws = resolveAutoWorkspace(anchor, cfg.autoWorkspaceMinRepos ?? DEFAULT_AUTO_WORKSPACE_MIN_REPOS);
|
|
4616
|
+
return ws ? ws.id : null;
|
|
4617
|
+
}
|
|
4596
4618
|
function doResolve(realPath) {
|
|
4597
4619
|
const cfg = loadProjectsConfig();
|
|
4598
4620
|
if (matchesIgnore(realPath, cfg)) {
|
|
@@ -4869,7 +4891,7 @@ function globToRegex(glob) {
|
|
|
4869
4891
|
function escapeRe(s) {
|
|
4870
4892
|
return s.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
4871
4893
|
}
|
|
4872
|
-
var DEFAULT_AUTO_WORKSPACE_MIN_REPOS, BOT_WORKTREE_MARKERS, resolveCache, gitCache, workspaceCache, cachedConfig, GIT_TIMEOUT_MS, AUTO_WORKSPACE_MAX_DEPTH;
|
|
4894
|
+
var DEFAULT_AUTO_WORKSPACE_MIN_REPOS, BOT_WORKTREE_MARKERS, resolveCache, gitCache, workspaceCache, wsIdCache, cachedConfig, GIT_TIMEOUT_MS, AUTO_WORKSPACE_MAX_DEPTH;
|
|
4873
4895
|
var init_project_resolver = __esm({
|
|
4874
4896
|
"../engine/dist/core/project-resolver.js"() {
|
|
4875
4897
|
"use strict";
|
|
@@ -4879,6 +4901,7 @@ var init_project_resolver = __esm({
|
|
|
4879
4901
|
resolveCache = /* @__PURE__ */ new Map();
|
|
4880
4902
|
gitCache = /* @__PURE__ */ new Map();
|
|
4881
4903
|
workspaceCache = /* @__PURE__ */ new Map();
|
|
4904
|
+
wsIdCache = /* @__PURE__ */ new Map();
|
|
4882
4905
|
cachedConfig = null;
|
|
4883
4906
|
GIT_TIMEOUT_MS = 1e4;
|
|
4884
4907
|
AUTO_WORKSPACE_MAX_DEPTH = 2;
|
|
@@ -5568,591 +5591,262 @@ var init_settings = __esm({
|
|
|
5568
5591
|
}
|
|
5569
5592
|
});
|
|
5570
5593
|
|
|
5571
|
-
// ../engine/dist/core/
|
|
5572
|
-
import {
|
|
5573
|
-
|
|
5574
|
-
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
} catch {
|
|
5581
|
-
return trimmed;
|
|
5582
|
-
}
|
|
5594
|
+
// ../engine/dist/core/secret-redactor.js
|
|
5595
|
+
import { statSync as statSync5 } from "fs";
|
|
5596
|
+
function secretOf(rule, m) {
|
|
5597
|
+
const raw = rule.secret != null ? m[rule.secret] : m[0];
|
|
5598
|
+
if (raw == null)
|
|
5599
|
+
return null;
|
|
5600
|
+
if (rule.where && !rule.where(raw))
|
|
5601
|
+
return null;
|
|
5602
|
+
return raw;
|
|
5583
5603
|
}
|
|
5584
|
-
function
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
const
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
for (const e of readdirSync3(parent, { withFileTypes: true })) {
|
|
5608
|
-
if (!e.isDirectory())
|
|
5609
|
-
continue;
|
|
5610
|
-
if (e.name === leaf || !e.name.startsWith(leaf + "-"))
|
|
5611
|
-
continue;
|
|
5612
|
-
if (e.name === ".claude-code")
|
|
5613
|
-
continue;
|
|
5614
|
-
add(join5(parent, e.name), marker);
|
|
5604
|
+
function looksLikeSecretToken(t) {
|
|
5605
|
+
if (t.length < 32)
|
|
5606
|
+
return false;
|
|
5607
|
+
if (/^[a-f0-9]+$/i.test(t))
|
|
5608
|
+
return true;
|
|
5609
|
+
return /[A-Z]/.test(t) && /[a-z]/.test(t) && /[0-9]/.test(t);
|
|
5610
|
+
}
|
|
5611
|
+
function findContextualSecrets(text) {
|
|
5612
|
+
if (!text)
|
|
5613
|
+
return [];
|
|
5614
|
+
const ranges = [];
|
|
5615
|
+
let m;
|
|
5616
|
+
SECRET_CONTEXT_RE.lastIndex = 0;
|
|
5617
|
+
while ((m = SECRET_CONTEXT_RE.exec(text)) !== null) {
|
|
5618
|
+
const winStart = m.index + m[0].length;
|
|
5619
|
+
const window = text.slice(winStart, winStart + 80);
|
|
5620
|
+
HIGH_ENTROPY_TOKEN_RE.lastIndex = 0;
|
|
5621
|
+
let tm;
|
|
5622
|
+
while ((tm = HIGH_ENTROPY_TOKEN_RE.exec(window)) !== null) {
|
|
5623
|
+
if (!looksLikeSecretToken(tm[0]))
|
|
5624
|
+
continue;
|
|
5625
|
+
if (/^[a-f0-9]+$/i.test(tm[0]) && /\b(?:commit|sha-?\d*|hash|digest|checksum|uuid|guid|etag|blob|ref)\b/i.test(window.slice(0, tm.index))) {
|
|
5626
|
+
continue;
|
|
5615
5627
|
}
|
|
5616
|
-
|
|
5628
|
+
ranges.push({ start: winStart + tm.index, end: winStart + tm.index + tm[0].length });
|
|
5629
|
+
break;
|
|
5630
|
+
}
|
|
5631
|
+
}
|
|
5632
|
+
ranges.sort((a, b) => a.start - b.start);
|
|
5633
|
+
const out = [];
|
|
5634
|
+
for (const r of ranges) {
|
|
5635
|
+
const last = out[out.length - 1];
|
|
5636
|
+
if (last && r.start < last.end) {
|
|
5637
|
+
last.end = Math.max(last.end, r.end);
|
|
5638
|
+
continue;
|
|
5617
5639
|
}
|
|
5640
|
+
out.push({ ...r });
|
|
5618
5641
|
}
|
|
5619
5642
|
return out;
|
|
5620
5643
|
}
|
|
5621
|
-
function
|
|
5622
|
-
|
|
5644
|
+
function isEnvRedactionRequested() {
|
|
5645
|
+
const v = process.env.CHAT_RECALL_REDACT_INDEX;
|
|
5646
|
+
if (v === void 0)
|
|
5647
|
+
return null;
|
|
5648
|
+
return v === "1" || v === "true" || v === "yes";
|
|
5649
|
+
}
|
|
5650
|
+
function settingsView() {
|
|
5651
|
+
let mtimeMs = -1;
|
|
5623
5652
|
try {
|
|
5624
|
-
|
|
5653
|
+
mtimeMs = statSync5(settingsFilePath()).mtimeMs;
|
|
5625
5654
|
} catch {
|
|
5626
|
-
return { seeded: [] };
|
|
5627
|
-
}
|
|
5628
|
-
if (s.sources.homesGrandfathered)
|
|
5629
|
-
return { seeded: [] };
|
|
5630
|
-
const legacy = legacyAutoSyncedHomes();
|
|
5631
|
-
const approved = new Set((s.sources.approvedHomes ?? []).map(normalizeHomePath));
|
|
5632
|
-
const seeded = [];
|
|
5633
|
-
for (const p of legacy) {
|
|
5634
|
-
if (!approved.has(p)) {
|
|
5635
|
-
approved.add(p);
|
|
5636
|
-
seeded.push(p);
|
|
5637
|
-
}
|
|
5638
5655
|
}
|
|
5639
|
-
|
|
5640
|
-
|
|
5656
|
+
if (cache && cache.mtimeMs === mtimeMs)
|
|
5657
|
+
return cache;
|
|
5658
|
+
let enabled = false;
|
|
5659
|
+
const extra = [];
|
|
5641
5660
|
try {
|
|
5642
|
-
|
|
5661
|
+
const s = loadSettings();
|
|
5662
|
+
enabled = !!s.privacy?.redactIndex;
|
|
5663
|
+
for (const r of s.privacy?.redactionRules ?? []) {
|
|
5664
|
+
try {
|
|
5665
|
+
extra.push({ label: r.label, pattern: new RegExp(r.pattern, "g") });
|
|
5666
|
+
} catch {
|
|
5667
|
+
}
|
|
5668
|
+
}
|
|
5643
5669
|
} catch {
|
|
5644
|
-
return { seeded: [] };
|
|
5645
5670
|
}
|
|
5646
|
-
|
|
5671
|
+
cache = { mtimeMs, enabled, rules: [...DEFAULT_REDACTION_RULES, ...extra] };
|
|
5672
|
+
return cache;
|
|
5647
5673
|
}
|
|
5648
|
-
function
|
|
5649
|
-
const
|
|
5650
|
-
|
|
5651
|
-
|
|
5674
|
+
function compileServerRule(spec) {
|
|
5675
|
+
const name = spec.name || "(unnamed)";
|
|
5676
|
+
if (!spec.regex)
|
|
5677
|
+
return { name, reason: "empty pattern" };
|
|
5678
|
+
if (spec.regex.length > MAX_PATTERN_LEN)
|
|
5679
|
+
return { name, reason: `pattern longer than ${MAX_PATTERN_LEN} chars` };
|
|
5680
|
+
let re;
|
|
5652
5681
|
try {
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
|
|
5682
|
+
const flags = "g" + (spec.flags || "").replace(/[^ims]/g, "");
|
|
5683
|
+
re = new RegExp(spec.regex, flags);
|
|
5684
|
+
} catch (e) {
|
|
5685
|
+
return { name, reason: `invalid regex: ${e instanceof Error ? e.message : e}` };
|
|
5656
5686
|
}
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
dirOf(opencodeDb)
|
|
5665
|
-
];
|
|
5666
|
-
return candidates2.map(normalizeHomePath);
|
|
5687
|
+
if (re.test(""))
|
|
5688
|
+
return { name, reason: "matches the empty string (would redact everything)" };
|
|
5689
|
+
re.lastIndex = 0;
|
|
5690
|
+
if (re.test(CANARY))
|
|
5691
|
+
return { name, reason: "matches benign text \u2014 too broad" };
|
|
5692
|
+
re.lastIndex = 0;
|
|
5693
|
+
return { label: `${spec.source === "pack" ? "pack" : "tenant"}:${name}`, pattern: re };
|
|
5667
5694
|
}
|
|
5668
|
-
function
|
|
5669
|
-
const
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
5695
|
+
function installServerRulePack(pack) {
|
|
5696
|
+
const rejected = [];
|
|
5697
|
+
const rules = [];
|
|
5698
|
+
for (const spec of pack.rules || []) {
|
|
5699
|
+
if (rules.length >= MAX_PACK_RULES) {
|
|
5700
|
+
rejected.push({ name: spec.name || "(unnamed)", reason: `pack exceeds ${MAX_PACK_RULES} rules` });
|
|
5701
|
+
continue;
|
|
5702
|
+
}
|
|
5703
|
+
const out = compileServerRule(spec);
|
|
5704
|
+
if ("reason" in out)
|
|
5705
|
+
rejected.push(out);
|
|
5706
|
+
else
|
|
5707
|
+
rules.push(out);
|
|
5677
5708
|
}
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
return "approved";
|
|
5682
|
-
if ((s.extraClaudeHomes ?? []).map(normalizeHomePath).includes(p))
|
|
5683
|
-
return "approved";
|
|
5684
|
-
return "pending";
|
|
5685
|
-
}
|
|
5686
|
-
function isHomeSynced(path) {
|
|
5687
|
-
const d = homeDecision(path);
|
|
5688
|
-
return d === "primary" || d === "approved";
|
|
5709
|
+
const version2 = pack.version || "";
|
|
5710
|
+
serverPack = { version: version2, rules };
|
|
5711
|
+
return { version: version2, accepted: rules.length, rejected };
|
|
5689
5712
|
}
|
|
5690
|
-
function
|
|
5691
|
-
|
|
5692
|
-
let s;
|
|
5693
|
-
try {
|
|
5694
|
-
s = loadSettings();
|
|
5695
|
-
} catch {
|
|
5696
|
-
return false;
|
|
5697
|
-
}
|
|
5698
|
-
const approved = new Set((s.sources.approvedHomes ?? []).map(normalizeHomePath));
|
|
5699
|
-
const declined = new Set((s.sources.declinedHomes ?? []).map(normalizeHomePath));
|
|
5700
|
-
approved.delete(p);
|
|
5701
|
-
declined.delete(p);
|
|
5702
|
-
(decision === "approved" ? approved : declined).add(p);
|
|
5703
|
-
s.sources.approvedHomes = [...approved];
|
|
5704
|
-
s.sources.declinedHomes = [...declined];
|
|
5705
|
-
try {
|
|
5706
|
-
saveSettings(s);
|
|
5707
|
-
} catch {
|
|
5708
|
-
return false;
|
|
5709
|
-
}
|
|
5710
|
-
return true;
|
|
5713
|
+
function serverRulePackVersion() {
|
|
5714
|
+
return serverPack ? serverPack.version || "unversioned" : null;
|
|
5711
5715
|
}
|
|
5712
|
-
function
|
|
5713
|
-
|
|
5716
|
+
function activeRules() {
|
|
5717
|
+
const base = settingsView().rules;
|
|
5718
|
+
return serverPack && serverPack.rules.length > 0 ? [...base, ...serverPack.rules] : base;
|
|
5714
5719
|
}
|
|
5715
|
-
function
|
|
5716
|
-
|
|
5720
|
+
function isRedactionEnabled() {
|
|
5721
|
+
const env = isEnvRedactionRequested();
|
|
5722
|
+
if (env !== null)
|
|
5723
|
+
return env;
|
|
5724
|
+
return settingsView().enabled;
|
|
5717
5725
|
}
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
|
|
5726
|
-
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
const dirs = [];
|
|
5737
|
-
const addHome = (d) => {
|
|
5738
|
-
const p = join6(d, "projects");
|
|
5739
|
-
if (existsSync5(p) && !dirs.includes(p))
|
|
5740
|
-
dirs.push(p);
|
|
5741
|
-
};
|
|
5742
|
-
addHome(claudeHomeDir());
|
|
5743
|
-
const homeOverridden = !!(process.env.CHAT_RECALL_CLAUDE_HOME || sources().claudeHome);
|
|
5744
|
-
if (!homeOverridden) {
|
|
5745
|
-
try {
|
|
5746
|
-
for (const entry of readdirSync4(home, { withFileTypes: true })) {
|
|
5747
|
-
if (entry.isDirectory() && entry.name.startsWith(".claude-") && entry.name !== ".claude-code") {
|
|
5748
|
-
addHome(join6(home, entry.name));
|
|
5749
|
-
}
|
|
5750
|
-
}
|
|
5751
|
-
} catch {
|
|
5752
|
-
}
|
|
5726
|
+
function redactSecrets(text, opts = {}) {
|
|
5727
|
+
if (!opts.force && !isRedactionEnabled())
|
|
5728
|
+
return text;
|
|
5729
|
+
if (!text)
|
|
5730
|
+
return text;
|
|
5731
|
+
const rules = opts.rules || activeRules();
|
|
5732
|
+
let out = text;
|
|
5733
|
+
for (const r of rules) {
|
|
5734
|
+
out = out.replace(r.pattern, (...args) => {
|
|
5735
|
+
const whole = args[0];
|
|
5736
|
+
const m = args.slice(0, args.length);
|
|
5737
|
+
const secret = secretOf(r, m);
|
|
5738
|
+
if (secret == null)
|
|
5739
|
+
return whole;
|
|
5740
|
+
if (opts.count)
|
|
5741
|
+
opts.count.redactions++;
|
|
5742
|
+
return whole.slice(0, whole.length - secret.length) + `[REDACTED:${r.label}]`;
|
|
5743
|
+
});
|
|
5753
5744
|
}
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5745
|
+
const ctx = findContextualSecrets(out);
|
|
5746
|
+
for (let i = ctx.length - 1; i >= 0; i--) {
|
|
5747
|
+
const { start, end } = ctx[i];
|
|
5748
|
+
if (opts.count)
|
|
5749
|
+
opts.count.redactions++;
|
|
5750
|
+
out = out.slice(0, start) + "[REDACTED:secret-context]" + out.slice(end);
|
|
5760
5751
|
}
|
|
5761
|
-
|
|
5762
|
-
dirs.push(join6(claudeHomeDir(), "projects"));
|
|
5763
|
-
return applyDecisions(dirs, opts.includeExcluded);
|
|
5764
|
-
}
|
|
5765
|
-
function _setSourceExclusionFilter(fn) {
|
|
5766
|
-
_sourceExclusionFilter = fn;
|
|
5767
|
-
}
|
|
5768
|
-
function geminiHomeDir() {
|
|
5769
|
-
return process.env.CHAT_RECALL_GEMINI_HOME || sources().geminiHome || join6(homedir3(), ".gemini");
|
|
5770
|
-
}
|
|
5771
|
-
function opencodeDbPath() {
|
|
5772
|
-
return process.env.CHAT_RECALL_OPENCODE_DB || sources().opencodeDbPath || join6(homedir3(), ".local", "share", "opencode", "opencode.db");
|
|
5773
|
-
}
|
|
5774
|
-
function codexHomeDir() {
|
|
5775
|
-
return process.env.CHAT_RECALL_CODEX_HOME || sources().codexHome || join6(homedir3(), ".codex");
|
|
5776
|
-
}
|
|
5777
|
-
function agyHomeDir() {
|
|
5778
|
-
return process.env.CHAT_RECALL_AGY_HOME || sources().agyHome || join6(homedir3(), ".gemini", "antigravity-cli");
|
|
5752
|
+
return out;
|
|
5779
5753
|
}
|
|
5780
|
-
function
|
|
5781
|
-
|
|
5754
|
+
function maskSecret(s) {
|
|
5755
|
+
if (!s)
|
|
5756
|
+
return "";
|
|
5757
|
+
if (s.length <= 4)
|
|
5758
|
+
return "*".repeat(s.length);
|
|
5759
|
+
return "*".repeat(Math.min(s.length - 4, 60)) + s.slice(-4);
|
|
5782
5760
|
}
|
|
5783
|
-
function
|
|
5784
|
-
|
|
5761
|
+
function globalRegexFor(pattern) {
|
|
5762
|
+
const flags = pattern.flags.includes("g") ? pattern.flags : pattern.flags + "g";
|
|
5763
|
+
const key2 = pattern.source + "\0" + flags;
|
|
5764
|
+
let re = globalRegexCache.get(key2);
|
|
5765
|
+
if (!re) {
|
|
5766
|
+
re = new RegExp(pattern.source, flags);
|
|
5767
|
+
globalRegexCache.set(key2, re);
|
|
5768
|
+
}
|
|
5769
|
+
return re;
|
|
5785
5770
|
}
|
|
5786
|
-
function
|
|
5787
|
-
const
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
|
|
5798
|
-
for (const entry of readdirSync4(parent, { withFileTypes: true })) {
|
|
5799
|
-
if (!entry.isDirectory())
|
|
5800
|
-
continue;
|
|
5801
|
-
if (entry.name === leaf || !entry.name.startsWith(leaf + "-"))
|
|
5802
|
-
continue;
|
|
5803
|
-
add(join6(parent, entry.name));
|
|
5804
|
-
}
|
|
5805
|
-
} catch {
|
|
5771
|
+
function makeLineResolver(text) {
|
|
5772
|
+
const starts = [];
|
|
5773
|
+
for (let i = text.indexOf("\n"); i !== -1; i = text.indexOf("\n", i + 1))
|
|
5774
|
+
starts.push(i);
|
|
5775
|
+
return (index) => {
|
|
5776
|
+
let lo = 0, hi = starts.length;
|
|
5777
|
+
while (lo < hi) {
|
|
5778
|
+
const mid = lo + hi >> 1;
|
|
5779
|
+
if (starts[mid] < index)
|
|
5780
|
+
lo = mid + 1;
|
|
5781
|
+
else
|
|
5782
|
+
hi = mid;
|
|
5806
5783
|
}
|
|
5807
|
-
|
|
5808
|
-
|
|
5784
|
+
return lo + 1;
|
|
5785
|
+
};
|
|
5809
5786
|
}
|
|
5810
|
-
function
|
|
5811
|
-
if (
|
|
5812
|
-
return
|
|
5813
|
-
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
|
|
5820
|
-
|
|
5787
|
+
function scanTextForFindings(text, opts = {}) {
|
|
5788
|
+
if (!text)
|
|
5789
|
+
return [];
|
|
5790
|
+
const rules = opts.rules || activeRules();
|
|
5791
|
+
const findings = [];
|
|
5792
|
+
const seenSpans = /* @__PURE__ */ new Set();
|
|
5793
|
+
const lineAt = makeLineResolver(text);
|
|
5794
|
+
for (const r of rules) {
|
|
5795
|
+
const re = globalRegexFor(r.pattern);
|
|
5796
|
+
re.lastIndex = 0;
|
|
5797
|
+
let m;
|
|
5798
|
+
while ((m = re.exec(text)) !== null) {
|
|
5799
|
+
const secret = secretOf(r, m);
|
|
5800
|
+
if (secret == null) {
|
|
5801
|
+
if (re.lastIndex === m.index)
|
|
5802
|
+
re.lastIndex++;
|
|
5803
|
+
continue;
|
|
5804
|
+
}
|
|
5805
|
+
const at = m.index + (m[0].length - secret.length);
|
|
5806
|
+
const span = `${at}:${secret.length}`;
|
|
5807
|
+
if (!seenSpans.has(span)) {
|
|
5808
|
+
seenSpans.add(span);
|
|
5809
|
+
findings.push({ rule: r.label, line: lineAt(at), preview: maskSecret(secret) });
|
|
5810
|
+
}
|
|
5811
|
+
if (re.lastIndex === m.index)
|
|
5812
|
+
re.lastIndex++;
|
|
5821
5813
|
}
|
|
5822
5814
|
}
|
|
5823
|
-
|
|
5824
|
-
const
|
|
5825
|
-
|
|
5826
|
-
kept = notExcluded;
|
|
5815
|
+
for (const { start, end } of findContextualSecrets(text)) {
|
|
5816
|
+
const line = lineAt(start);
|
|
5817
|
+
findings.push({ rule: "secret-context", line, preview: maskSecret(text.slice(start, end)) });
|
|
5827
5818
|
}
|
|
5828
|
-
return
|
|
5829
|
-
}
|
|
5830
|
-
function applyExclusions(roots, includeExcluded) {
|
|
5831
|
-
return applyDecisions(roots, includeExcluded);
|
|
5832
|
-
}
|
|
5833
|
-
function codexSessionDirs(opts = {}) {
|
|
5834
|
-
const overridden = !!(process.env.CHAT_RECALL_CODEX_HOME || sources().codexHome);
|
|
5835
|
-
return applyExclusions(siblingHomes(codexHomeDir(), overridden, "sessions"), opts.includeExcluded);
|
|
5836
|
-
}
|
|
5837
|
-
function agyBrainDirs(opts = {}) {
|
|
5838
|
-
const overridden = !!(process.env.CHAT_RECALL_AGY_HOME || sources().agyHome);
|
|
5839
|
-
return applyExclusions(siblingHomes(agyHomeDir(), overridden, "brain"), opts.includeExcluded);
|
|
5840
|
-
}
|
|
5841
|
-
function cursorChatDirs(opts = {}) {
|
|
5842
|
-
const overridden = !!(process.env.CHAT_RECALL_CURSOR_HOME || sources().cursorHome);
|
|
5843
|
-
return applyExclusions(siblingHomes(cursorHomeDir(), overridden, "chats"), opts.includeExcluded);
|
|
5844
|
-
}
|
|
5845
|
-
function cursorIdeWorkspaceDirs(opts = {}) {
|
|
5846
|
-
const overridden = !!(process.env.CHAT_RECALL_CURSOR_IDE_HOME || sources().cursorIdeHome);
|
|
5847
|
-
return applyExclusions(siblingHomes(cursorIdeHomeDir(), overridden, join6("User", "workspaceStorage")), opts.includeExcluded);
|
|
5819
|
+
return findings;
|
|
5848
5820
|
}
|
|
5849
|
-
function
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
const p = join6(r, "state.vscdb");
|
|
5855
|
-
if (existsSync5(p) && !dbs.includes(p))
|
|
5856
|
-
dbs.push(p);
|
|
5821
|
+
function privacyView() {
|
|
5822
|
+
let mtimeMs = -1;
|
|
5823
|
+
try {
|
|
5824
|
+
mtimeMs = statSync5(settingsFilePath()).mtimeMs;
|
|
5825
|
+
} catch {
|
|
5857
5826
|
}
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
|
|
5861
|
-
|
|
5862
|
-
|
|
5863
|
-
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
for (const d of dirs) {
|
|
5867
|
-
const p = join6(d, dbFile);
|
|
5868
|
-
if (existsSync5(p) && !dbs.includes(p))
|
|
5869
|
-
dbs.push(p);
|
|
5827
|
+
if (privacyCache && privacyCache.mtimeMs === mtimeMs)
|
|
5828
|
+
return privacyCache;
|
|
5829
|
+
let redactToolOutputs = false, redactFilePaths = false;
|
|
5830
|
+
try {
|
|
5831
|
+
const s = loadSettings();
|
|
5832
|
+
redactToolOutputs = !!s.privacy?.redactToolOutputs;
|
|
5833
|
+
redactFilePaths = !!s.privacy?.redactFilePaths;
|
|
5834
|
+
} catch {
|
|
5870
5835
|
}
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
return applyExclusions(dbs, opts.includeExcluded);
|
|
5836
|
+
privacyCache = { mtimeMs, redactToolOutputs, redactFilePaths };
|
|
5837
|
+
return privacyCache;
|
|
5874
5838
|
}
|
|
5875
|
-
function
|
|
5876
|
-
|
|
5877
|
-
const primary = claudeHomeDir();
|
|
5878
|
-
return Array.from(new Set(extras)).filter((p) => p && p !== primary);
|
|
5839
|
+
function isToolOutputRedactionEnabled() {
|
|
5840
|
+
return privacyView().redactToolOutputs;
|
|
5879
5841
|
}
|
|
5880
|
-
function
|
|
5881
|
-
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
if (existsSync5(extra) && !homes.includes(extra))
|
|
5885
|
-
homes.push(extra);
|
|
5886
|
-
}
|
|
5887
|
-
return homes;
|
|
5842
|
+
function redactToolResultBody(toolName, body) {
|
|
5843
|
+
if (!body)
|
|
5844
|
+
return body;
|
|
5845
|
+
return `[redacted: ${toolName} output, ${body.length} chars]`;
|
|
5888
5846
|
}
|
|
5889
|
-
var
|
|
5890
|
-
var
|
|
5891
|
-
"../engine/dist/core/
|
|
5892
|
-
"use strict";
|
|
5893
|
-
init_home_approval();
|
|
5894
|
-
init_settings();
|
|
5895
|
-
sources = loadSourceSettings;
|
|
5896
|
-
_sourceExclusionFilter = null;
|
|
5897
|
-
}
|
|
5898
|
-
});
|
|
5899
|
-
|
|
5900
|
-
// ../engine/dist/core/secret-redactor.js
|
|
5901
|
-
import { statSync as statSync5 } from "fs";
|
|
5902
|
-
function secretOf(rule, m) {
|
|
5903
|
-
const raw = rule.secret != null ? m[rule.secret] : m[0];
|
|
5904
|
-
if (raw == null)
|
|
5905
|
-
return null;
|
|
5906
|
-
if (rule.where && !rule.where(raw))
|
|
5907
|
-
return null;
|
|
5908
|
-
return raw;
|
|
5909
|
-
}
|
|
5910
|
-
function looksLikeSecretToken(t) {
|
|
5911
|
-
if (t.length < 32)
|
|
5912
|
-
return false;
|
|
5913
|
-
if (/^[a-f0-9]+$/i.test(t))
|
|
5914
|
-
return true;
|
|
5915
|
-
return /[A-Z]/.test(t) && /[a-z]/.test(t) && /[0-9]/.test(t);
|
|
5916
|
-
}
|
|
5917
|
-
function findContextualSecrets(text) {
|
|
5918
|
-
if (!text)
|
|
5919
|
-
return [];
|
|
5920
|
-
const ranges = [];
|
|
5921
|
-
let m;
|
|
5922
|
-
SECRET_CONTEXT_RE.lastIndex = 0;
|
|
5923
|
-
while ((m = SECRET_CONTEXT_RE.exec(text)) !== null) {
|
|
5924
|
-
const winStart = m.index + m[0].length;
|
|
5925
|
-
const window = text.slice(winStart, winStart + 80);
|
|
5926
|
-
HIGH_ENTROPY_TOKEN_RE.lastIndex = 0;
|
|
5927
|
-
let tm;
|
|
5928
|
-
while ((tm = HIGH_ENTROPY_TOKEN_RE.exec(window)) !== null) {
|
|
5929
|
-
if (!looksLikeSecretToken(tm[0]))
|
|
5930
|
-
continue;
|
|
5931
|
-
if (/^[a-f0-9]+$/i.test(tm[0]) && /\b(?:commit|sha-?\d*|hash|digest|checksum|uuid|guid|etag|blob|ref)\b/i.test(window.slice(0, tm.index))) {
|
|
5932
|
-
continue;
|
|
5933
|
-
}
|
|
5934
|
-
ranges.push({ start: winStart + tm.index, end: winStart + tm.index + tm[0].length });
|
|
5935
|
-
break;
|
|
5936
|
-
}
|
|
5937
|
-
}
|
|
5938
|
-
ranges.sort((a, b) => a.start - b.start);
|
|
5939
|
-
const out = [];
|
|
5940
|
-
for (const r of ranges) {
|
|
5941
|
-
const last = out[out.length - 1];
|
|
5942
|
-
if (last && r.start < last.end) {
|
|
5943
|
-
last.end = Math.max(last.end, r.end);
|
|
5944
|
-
continue;
|
|
5945
|
-
}
|
|
5946
|
-
out.push({ ...r });
|
|
5947
|
-
}
|
|
5948
|
-
return out;
|
|
5949
|
-
}
|
|
5950
|
-
function isEnvRedactionRequested() {
|
|
5951
|
-
const v = process.env.CHAT_RECALL_REDACT_INDEX;
|
|
5952
|
-
if (v === void 0)
|
|
5953
|
-
return null;
|
|
5954
|
-
return v === "1" || v === "true" || v === "yes";
|
|
5955
|
-
}
|
|
5956
|
-
function settingsView() {
|
|
5957
|
-
let mtimeMs = -1;
|
|
5958
|
-
try {
|
|
5959
|
-
mtimeMs = statSync5(settingsFilePath()).mtimeMs;
|
|
5960
|
-
} catch {
|
|
5961
|
-
}
|
|
5962
|
-
if (cache && cache.mtimeMs === mtimeMs)
|
|
5963
|
-
return cache;
|
|
5964
|
-
let enabled = false;
|
|
5965
|
-
const extra = [];
|
|
5966
|
-
try {
|
|
5967
|
-
const s = loadSettings();
|
|
5968
|
-
enabled = !!s.privacy?.redactIndex;
|
|
5969
|
-
for (const r of s.privacy?.redactionRules ?? []) {
|
|
5970
|
-
try {
|
|
5971
|
-
extra.push({ label: r.label, pattern: new RegExp(r.pattern, "g") });
|
|
5972
|
-
} catch {
|
|
5973
|
-
}
|
|
5974
|
-
}
|
|
5975
|
-
} catch {
|
|
5976
|
-
}
|
|
5977
|
-
cache = { mtimeMs, enabled, rules: [...DEFAULT_REDACTION_RULES, ...extra] };
|
|
5978
|
-
return cache;
|
|
5979
|
-
}
|
|
5980
|
-
function compileServerRule(spec) {
|
|
5981
|
-
const name = spec.name || "(unnamed)";
|
|
5982
|
-
if (!spec.regex)
|
|
5983
|
-
return { name, reason: "empty pattern" };
|
|
5984
|
-
if (spec.regex.length > MAX_PATTERN_LEN)
|
|
5985
|
-
return { name, reason: `pattern longer than ${MAX_PATTERN_LEN} chars` };
|
|
5986
|
-
let re;
|
|
5987
|
-
try {
|
|
5988
|
-
const flags = "g" + (spec.flags || "").replace(/[^ims]/g, "");
|
|
5989
|
-
re = new RegExp(spec.regex, flags);
|
|
5990
|
-
} catch (e) {
|
|
5991
|
-
return { name, reason: `invalid regex: ${e instanceof Error ? e.message : e}` };
|
|
5992
|
-
}
|
|
5993
|
-
if (re.test(""))
|
|
5994
|
-
return { name, reason: "matches the empty string (would redact everything)" };
|
|
5995
|
-
re.lastIndex = 0;
|
|
5996
|
-
if (re.test(CANARY))
|
|
5997
|
-
return { name, reason: "matches benign text \u2014 too broad" };
|
|
5998
|
-
re.lastIndex = 0;
|
|
5999
|
-
return { label: `${spec.source === "pack" ? "pack" : "tenant"}:${name}`, pattern: re };
|
|
6000
|
-
}
|
|
6001
|
-
function installServerRulePack(pack) {
|
|
6002
|
-
const rejected = [];
|
|
6003
|
-
const rules = [];
|
|
6004
|
-
for (const spec of pack.rules || []) {
|
|
6005
|
-
if (rules.length >= MAX_PACK_RULES) {
|
|
6006
|
-
rejected.push({ name: spec.name || "(unnamed)", reason: `pack exceeds ${MAX_PACK_RULES} rules` });
|
|
6007
|
-
continue;
|
|
6008
|
-
}
|
|
6009
|
-
const out = compileServerRule(spec);
|
|
6010
|
-
if ("reason" in out)
|
|
6011
|
-
rejected.push(out);
|
|
6012
|
-
else
|
|
6013
|
-
rules.push(out);
|
|
6014
|
-
}
|
|
6015
|
-
const version2 = pack.version || "";
|
|
6016
|
-
serverPack = { version: version2, rules };
|
|
6017
|
-
return { version: version2, accepted: rules.length, rejected };
|
|
6018
|
-
}
|
|
6019
|
-
function serverRulePackVersion() {
|
|
6020
|
-
return serverPack ? serverPack.version || "unversioned" : null;
|
|
6021
|
-
}
|
|
6022
|
-
function activeRules() {
|
|
6023
|
-
const base = settingsView().rules;
|
|
6024
|
-
return serverPack && serverPack.rules.length > 0 ? [...base, ...serverPack.rules] : base;
|
|
6025
|
-
}
|
|
6026
|
-
function isRedactionEnabled() {
|
|
6027
|
-
const env = isEnvRedactionRequested();
|
|
6028
|
-
if (env !== null)
|
|
6029
|
-
return env;
|
|
6030
|
-
return settingsView().enabled;
|
|
6031
|
-
}
|
|
6032
|
-
function redactSecrets(text, opts = {}) {
|
|
6033
|
-
if (!opts.force && !isRedactionEnabled())
|
|
6034
|
-
return text;
|
|
6035
|
-
if (!text)
|
|
6036
|
-
return text;
|
|
6037
|
-
const rules = opts.rules || activeRules();
|
|
6038
|
-
let out = text;
|
|
6039
|
-
for (const r of rules) {
|
|
6040
|
-
out = out.replace(r.pattern, (...args) => {
|
|
6041
|
-
const whole = args[0];
|
|
6042
|
-
const m = args.slice(0, args.length);
|
|
6043
|
-
const secret = secretOf(r, m);
|
|
6044
|
-
if (secret == null)
|
|
6045
|
-
return whole;
|
|
6046
|
-
if (opts.count)
|
|
6047
|
-
opts.count.redactions++;
|
|
6048
|
-
return whole.slice(0, whole.length - secret.length) + `[REDACTED:${r.label}]`;
|
|
6049
|
-
});
|
|
6050
|
-
}
|
|
6051
|
-
const ctx = findContextualSecrets(out);
|
|
6052
|
-
for (let i = ctx.length - 1; i >= 0; i--) {
|
|
6053
|
-
const { start, end } = ctx[i];
|
|
6054
|
-
if (opts.count)
|
|
6055
|
-
opts.count.redactions++;
|
|
6056
|
-
out = out.slice(0, start) + "[REDACTED:secret-context]" + out.slice(end);
|
|
6057
|
-
}
|
|
6058
|
-
return out;
|
|
6059
|
-
}
|
|
6060
|
-
function maskSecret(s) {
|
|
6061
|
-
if (!s)
|
|
6062
|
-
return "";
|
|
6063
|
-
if (s.length <= 4)
|
|
6064
|
-
return "*".repeat(s.length);
|
|
6065
|
-
return "*".repeat(Math.min(s.length - 4, 60)) + s.slice(-4);
|
|
6066
|
-
}
|
|
6067
|
-
function globalRegexFor(pattern) {
|
|
6068
|
-
const flags = pattern.flags.includes("g") ? pattern.flags : pattern.flags + "g";
|
|
6069
|
-
const key2 = pattern.source + "\0" + flags;
|
|
6070
|
-
let re = globalRegexCache.get(key2);
|
|
6071
|
-
if (!re) {
|
|
6072
|
-
re = new RegExp(pattern.source, flags);
|
|
6073
|
-
globalRegexCache.set(key2, re);
|
|
6074
|
-
}
|
|
6075
|
-
return re;
|
|
6076
|
-
}
|
|
6077
|
-
function makeLineResolver(text) {
|
|
6078
|
-
const starts = [];
|
|
6079
|
-
for (let i = text.indexOf("\n"); i !== -1; i = text.indexOf("\n", i + 1))
|
|
6080
|
-
starts.push(i);
|
|
6081
|
-
return (index) => {
|
|
6082
|
-
let lo = 0, hi = starts.length;
|
|
6083
|
-
while (lo < hi) {
|
|
6084
|
-
const mid = lo + hi >> 1;
|
|
6085
|
-
if (starts[mid] < index)
|
|
6086
|
-
lo = mid + 1;
|
|
6087
|
-
else
|
|
6088
|
-
hi = mid;
|
|
6089
|
-
}
|
|
6090
|
-
return lo + 1;
|
|
6091
|
-
};
|
|
6092
|
-
}
|
|
6093
|
-
function scanTextForFindings(text, opts = {}) {
|
|
6094
|
-
if (!text)
|
|
6095
|
-
return [];
|
|
6096
|
-
const rules = opts.rules || activeRules();
|
|
6097
|
-
const findings = [];
|
|
6098
|
-
const seenSpans = /* @__PURE__ */ new Set();
|
|
6099
|
-
const lineAt = makeLineResolver(text);
|
|
6100
|
-
for (const r of rules) {
|
|
6101
|
-
const re = globalRegexFor(r.pattern);
|
|
6102
|
-
re.lastIndex = 0;
|
|
6103
|
-
let m;
|
|
6104
|
-
while ((m = re.exec(text)) !== null) {
|
|
6105
|
-
const secret = secretOf(r, m);
|
|
6106
|
-
if (secret == null) {
|
|
6107
|
-
if (re.lastIndex === m.index)
|
|
6108
|
-
re.lastIndex++;
|
|
6109
|
-
continue;
|
|
6110
|
-
}
|
|
6111
|
-
const at = m.index + (m[0].length - secret.length);
|
|
6112
|
-
const span = `${at}:${secret.length}`;
|
|
6113
|
-
if (!seenSpans.has(span)) {
|
|
6114
|
-
seenSpans.add(span);
|
|
6115
|
-
findings.push({ rule: r.label, line: lineAt(at), preview: maskSecret(secret) });
|
|
6116
|
-
}
|
|
6117
|
-
if (re.lastIndex === m.index)
|
|
6118
|
-
re.lastIndex++;
|
|
6119
|
-
}
|
|
6120
|
-
}
|
|
6121
|
-
for (const { start, end } of findContextualSecrets(text)) {
|
|
6122
|
-
const line = lineAt(start);
|
|
6123
|
-
findings.push({ rule: "secret-context", line, preview: maskSecret(text.slice(start, end)) });
|
|
6124
|
-
}
|
|
6125
|
-
return findings;
|
|
6126
|
-
}
|
|
6127
|
-
function privacyView() {
|
|
6128
|
-
let mtimeMs = -1;
|
|
6129
|
-
try {
|
|
6130
|
-
mtimeMs = statSync5(settingsFilePath()).mtimeMs;
|
|
6131
|
-
} catch {
|
|
6132
|
-
}
|
|
6133
|
-
if (privacyCache && privacyCache.mtimeMs === mtimeMs)
|
|
6134
|
-
return privacyCache;
|
|
6135
|
-
let redactToolOutputs = false, redactFilePaths = false;
|
|
6136
|
-
try {
|
|
6137
|
-
const s = loadSettings();
|
|
6138
|
-
redactToolOutputs = !!s.privacy?.redactToolOutputs;
|
|
6139
|
-
redactFilePaths = !!s.privacy?.redactFilePaths;
|
|
6140
|
-
} catch {
|
|
6141
|
-
}
|
|
6142
|
-
privacyCache = { mtimeMs, redactToolOutputs, redactFilePaths };
|
|
6143
|
-
return privacyCache;
|
|
6144
|
-
}
|
|
6145
|
-
function isToolOutputRedactionEnabled() {
|
|
6146
|
-
return privacyView().redactToolOutputs;
|
|
6147
|
-
}
|
|
6148
|
-
function redactToolResultBody(toolName, body) {
|
|
6149
|
-
if (!body)
|
|
6150
|
-
return body;
|
|
6151
|
-
return `[redacted: ${toolName} output, ${body.length} chars]`;
|
|
6152
|
-
}
|
|
6153
|
-
var DEFAULT_REDACTION_RULES, SECRET_CONTEXT_RE, HIGH_ENTROPY_TOKEN_RE, cache, CANARY, MAX_PACK_RULES, MAX_PATTERN_LEN, serverPack, globalRegexCache, privacyCache;
|
|
6154
|
-
var init_secret_redactor = __esm({
|
|
6155
|
-
"../engine/dist/core/secret-redactor.js"() {
|
|
5847
|
+
var DEFAULT_REDACTION_RULES, SECRET_CONTEXT_RE, HIGH_ENTROPY_TOKEN_RE, cache, CANARY, MAX_PACK_RULES, MAX_PATTERN_LEN, serverPack, globalRegexCache, privacyCache;
|
|
5848
|
+
var init_secret_redactor = __esm({
|
|
5849
|
+
"../engine/dist/core/secret-redactor.js"() {
|
|
6156
5850
|
"use strict";
|
|
6157
5851
|
init_settings();
|
|
6158
5852
|
DEFAULT_REDACTION_RULES = [
|
|
@@ -6302,164 +5996,493 @@ var init_secret_redactor = __esm({
|
|
|
6302
5996
|
globalRegexCache = /* @__PURE__ */ new Map();
|
|
6303
5997
|
privacyCache = null;
|
|
6304
5998
|
}
|
|
6305
|
-
});
|
|
6306
|
-
|
|
6307
|
-
// ../engine/dist/parsers/chunker.js
|
|
6308
|
-
function stripInjectedBanners(text) {
|
|
6309
|
-
let result = text;
|
|
6310
|
-
for (const re of INJECTED_BANNERS)
|
|
6311
|
-
result = result.replace(re, " ");
|
|
6312
|
-
return result;
|
|
5999
|
+
});
|
|
6000
|
+
|
|
6001
|
+
// ../engine/dist/parsers/chunker.js
|
|
6002
|
+
function stripInjectedBanners(text) {
|
|
6003
|
+
let result = text;
|
|
6004
|
+
for (const re of INJECTED_BANNERS)
|
|
6005
|
+
result = result.replace(re, " ");
|
|
6006
|
+
return result;
|
|
6007
|
+
}
|
|
6008
|
+
function cleanText(text) {
|
|
6009
|
+
let result = text.replace(/<system-reminder>.*?<\/system-reminder>/gs, "");
|
|
6010
|
+
result = stripInjectedBanners(result);
|
|
6011
|
+
result = result.replace(/\x1b\[[0-9;]*m/g, "");
|
|
6012
|
+
result = result.replace(/\s+/g, " ");
|
|
6013
|
+
return result.trim();
|
|
6014
|
+
}
|
|
6015
|
+
function windowText(text, maxChars, maxWindows = 12) {
|
|
6016
|
+
const clean = cleanText(text);
|
|
6017
|
+
if (clean.length <= maxChars)
|
|
6018
|
+
return clean ? [clean] : [];
|
|
6019
|
+
const out = [];
|
|
6020
|
+
const overlap = 80;
|
|
6021
|
+
const step = Math.max(1, maxChars - overlap);
|
|
6022
|
+
for (let i = 0; i < clean.length && out.length < maxWindows; i += step) {
|
|
6023
|
+
out.push(clean.slice(i, i + maxChars));
|
|
6024
|
+
}
|
|
6025
|
+
return out;
|
|
6026
|
+
}
|
|
6027
|
+
function chunkSession(entry, content, maxChunks = 80, maxTokensPerChunk = 500) {
|
|
6028
|
+
const chunks = [];
|
|
6029
|
+
const maxChars = maxTokensPerChunk * 4;
|
|
6030
|
+
const baseData = {
|
|
6031
|
+
sessionId: entry.sessionId,
|
|
6032
|
+
projectPath: entry.projectPath,
|
|
6033
|
+
created: entry.created,
|
|
6034
|
+
modified: entry.modified,
|
|
6035
|
+
mtime: entry.fileMtime,
|
|
6036
|
+
firstPrompt: entry.firstPrompt || content.firstPrompt,
|
|
6037
|
+
sourceFile: content.sessionPath
|
|
6038
|
+
};
|
|
6039
|
+
for (let i = 0; i < Math.min(2, content.summaries.length); i++) {
|
|
6040
|
+
const summary = content.summaries[i];
|
|
6041
|
+
if (!summary?.trim())
|
|
6042
|
+
continue;
|
|
6043
|
+
const chunkText = cleanText(summary.slice(0, maxChars));
|
|
6044
|
+
if (chunkText.length > 8e3) {
|
|
6045
|
+
chunks.push({
|
|
6046
|
+
chunkId: `${entry.sessionId}_summary_${i}`,
|
|
6047
|
+
chunkType: "summary",
|
|
6048
|
+
text: chunkText.slice(0, 8e3),
|
|
6049
|
+
sourceLine: 0,
|
|
6050
|
+
...baseData
|
|
6051
|
+
});
|
|
6052
|
+
} else {
|
|
6053
|
+
chunks.push({
|
|
6054
|
+
chunkId: `${entry.sessionId}_summary_${i}`,
|
|
6055
|
+
chunkType: "summary",
|
|
6056
|
+
text: chunkText,
|
|
6057
|
+
sourceLine: 0,
|
|
6058
|
+
...baseData
|
|
6059
|
+
});
|
|
6060
|
+
}
|
|
6061
|
+
}
|
|
6062
|
+
if (chunks.length >= maxChunks) {
|
|
6063
|
+
return chunks.slice(0, maxChunks);
|
|
6064
|
+
}
|
|
6065
|
+
const firstPrompt = entry.firstPrompt || content.firstPrompt;
|
|
6066
|
+
if (firstPrompt?.trim()) {
|
|
6067
|
+
const firstLine = content.userMessages[0]?.lineNumber || 0;
|
|
6068
|
+
chunks.push({
|
|
6069
|
+
chunkId: `${entry.sessionId}_first_prompt`,
|
|
6070
|
+
chunkType: "first_prompt",
|
|
6071
|
+
text: cleanText(firstPrompt.slice(0, maxChars)),
|
|
6072
|
+
sourceLine: firstLine,
|
|
6073
|
+
...baseData
|
|
6074
|
+
});
|
|
6075
|
+
}
|
|
6076
|
+
if (chunks.length >= maxChunks) {
|
|
6077
|
+
return chunks.slice(0, maxChunks);
|
|
6078
|
+
}
|
|
6079
|
+
const userStart = firstPrompt ? 1 : 0;
|
|
6080
|
+
for (let i = userStart; i < content.userMessages.length; i++) {
|
|
6081
|
+
if (chunks.length >= maxChunks)
|
|
6082
|
+
break;
|
|
6083
|
+
const msg = content.userMessages[i];
|
|
6084
|
+
if (!msg.text.trim())
|
|
6085
|
+
continue;
|
|
6086
|
+
const windows = windowText(msg.text, maxChars);
|
|
6087
|
+
for (let w = 0; w < windows.length; w++) {
|
|
6088
|
+
if (chunks.length >= maxChunks)
|
|
6089
|
+
break;
|
|
6090
|
+
chunks.push({
|
|
6091
|
+
chunkId: `${entry.sessionId}_user_${i}_${w}`,
|
|
6092
|
+
chunkType: "user_context",
|
|
6093
|
+
text: windows[w],
|
|
6094
|
+
sourceLine: msg.lineNumber,
|
|
6095
|
+
...baseData
|
|
6096
|
+
});
|
|
6097
|
+
}
|
|
6098
|
+
}
|
|
6099
|
+
if (chunks.length >= maxChunks) {
|
|
6100
|
+
return chunks.slice(0, maxChunks);
|
|
6101
|
+
}
|
|
6102
|
+
for (let i = 0; i < content.assistantMessages.length; i++) {
|
|
6103
|
+
if (chunks.length >= maxChunks)
|
|
6104
|
+
break;
|
|
6105
|
+
const msg = content.assistantMessages[i];
|
|
6106
|
+
if (!msg.text.trim() || msg.text.length <= 50)
|
|
6107
|
+
continue;
|
|
6108
|
+
const windows = windowText(msg.text, maxChars);
|
|
6109
|
+
for (let w = 0; w < windows.length; w++) {
|
|
6110
|
+
if (chunks.length >= maxChunks)
|
|
6111
|
+
break;
|
|
6112
|
+
chunks.push({
|
|
6113
|
+
chunkId: `${entry.sessionId}_assistant_${i}_${w}`,
|
|
6114
|
+
chunkType: "assistant",
|
|
6115
|
+
text: windows[w],
|
|
6116
|
+
sourceLine: msg.lineNumber,
|
|
6117
|
+
...baseData
|
|
6118
|
+
});
|
|
6119
|
+
}
|
|
6120
|
+
}
|
|
6121
|
+
if (chunks.length >= maxChunks) {
|
|
6122
|
+
return chunks.slice(0, maxChunks);
|
|
6123
|
+
}
|
|
6124
|
+
if (content.toolResults.length > 0) {
|
|
6125
|
+
const webSearches = content.toolResults.filter((r) => r.contentType === "web_search");
|
|
6126
|
+
const otherResults = content.toolResults.filter((r) => r.contentType !== "web_search");
|
|
6127
|
+
const resultsToProcess = [...webSearches.slice(0, 5), ...otherResults.slice(0, 10)];
|
|
6128
|
+
const stripBodies = isToolOutputRedactionEnabled();
|
|
6129
|
+
for (let i = 0; i < resultsToProcess.length; i++) {
|
|
6130
|
+
if (chunks.length >= maxChunks)
|
|
6131
|
+
break;
|
|
6132
|
+
const result = resultsToProcess[i];
|
|
6133
|
+
if (result.text.trim()) {
|
|
6134
|
+
const body = stripBodies && result.contentType !== "web_search" ? redactToolResultBody(result.contentType, result.text) : result.text.slice(0, maxChars);
|
|
6135
|
+
chunks.push({
|
|
6136
|
+
chunkId: `${entry.sessionId}_${result.contentType}_${i}`,
|
|
6137
|
+
chunkType: result.contentType,
|
|
6138
|
+
text: cleanText(body),
|
|
6139
|
+
sourceLine: result.lineNumber,
|
|
6140
|
+
...baseData
|
|
6141
|
+
});
|
|
6142
|
+
}
|
|
6143
|
+
}
|
|
6144
|
+
}
|
|
6145
|
+
return chunks.slice(0, maxChunks);
|
|
6146
|
+
}
|
|
6147
|
+
var INJECTED_BANNERS;
|
|
6148
|
+
var init_chunker = __esm({
|
|
6149
|
+
"../engine/dist/parsers/chunker.js"() {
|
|
6150
|
+
"use strict";
|
|
6151
|
+
init_secret_redactor();
|
|
6152
|
+
INJECTED_BANNERS = [
|
|
6153
|
+
/MCP issues detected\. ?Run \/mcp list for status\.?/g,
|
|
6154
|
+
/Context low[^\n]*Run \/compact[^\n]*/g,
|
|
6155
|
+
/API Error:[^\n]{0,120}/g
|
|
6156
|
+
];
|
|
6157
|
+
}
|
|
6158
|
+
});
|
|
6159
|
+
|
|
6160
|
+
// ../engine/dist/core/home-approval.js
|
|
6161
|
+
import { existsSync as existsSync4, readdirSync as readdirSync3, realpathSync as realpathSync2 } from "fs";
|
|
6162
|
+
import { homedir as homedir2 } from "os";
|
|
6163
|
+
import { join as join5, dirname as dirname5 } from "path";
|
|
6164
|
+
function normalizeHomePath(p) {
|
|
6165
|
+
const expanded = p.startsWith("~") ? join5(homedir2(), p.slice(1)) : p;
|
|
6166
|
+
const trimmed = expanded.replace(/[/\\]+$/, "");
|
|
6167
|
+
try {
|
|
6168
|
+
return realpathSync2(trimmed);
|
|
6169
|
+
} catch {
|
|
6170
|
+
return trimmed;
|
|
6171
|
+
}
|
|
6172
|
+
}
|
|
6173
|
+
function legacyAutoSyncedHomes() {
|
|
6174
|
+
const home = homedir2();
|
|
6175
|
+
const out = [];
|
|
6176
|
+
const add = (dir, marker) => {
|
|
6177
|
+
if (!existsSync4(join5(dir, marker)))
|
|
6178
|
+
return;
|
|
6179
|
+
const n = normalizeHomePath(dir);
|
|
6180
|
+
if (!out.includes(n))
|
|
6181
|
+
out.push(n);
|
|
6182
|
+
};
|
|
6183
|
+
const bases = [
|
|
6184
|
+
{ base: join5(home, ".claude"), marker: "projects" },
|
|
6185
|
+
{ base: join5(home, ".gemini"), marker: "tmp" },
|
|
6186
|
+
{ base: join5(home, ".codex"), marker: "sessions" },
|
|
6187
|
+
{ base: join5(home, ".gemini", "antigravity-cli"), marker: "brain" },
|
|
6188
|
+
{ base: join5(home, ".local", "share", "opencode"), marker: "opencode.db" },
|
|
6189
|
+
{ base: join5(home, ".cursor"), marker: "chats" }
|
|
6190
|
+
];
|
|
6191
|
+
for (const { base, marker } of bases) {
|
|
6192
|
+
add(base, marker);
|
|
6193
|
+
const parent = dirname5(base);
|
|
6194
|
+
const leaf = base.slice(parent.length + 1);
|
|
6195
|
+
try {
|
|
6196
|
+
for (const e of readdirSync3(parent, { withFileTypes: true })) {
|
|
6197
|
+
if (!e.isDirectory())
|
|
6198
|
+
continue;
|
|
6199
|
+
if (e.name === leaf || !e.name.startsWith(leaf + "-"))
|
|
6200
|
+
continue;
|
|
6201
|
+
if (e.name === ".claude-code")
|
|
6202
|
+
continue;
|
|
6203
|
+
add(join5(parent, e.name), marker);
|
|
6204
|
+
}
|
|
6205
|
+
} catch {
|
|
6206
|
+
}
|
|
6207
|
+
}
|
|
6208
|
+
return out;
|
|
6313
6209
|
}
|
|
6314
|
-
function
|
|
6315
|
-
let
|
|
6316
|
-
|
|
6317
|
-
|
|
6318
|
-
|
|
6319
|
-
|
|
6210
|
+
function grandfatherLegacyHomes() {
|
|
6211
|
+
let s;
|
|
6212
|
+
try {
|
|
6213
|
+
s = loadSettings();
|
|
6214
|
+
} catch {
|
|
6215
|
+
return { seeded: [] };
|
|
6216
|
+
}
|
|
6217
|
+
if (s.sources.homesGrandfathered)
|
|
6218
|
+
return { seeded: [] };
|
|
6219
|
+
const legacy = legacyAutoSyncedHomes();
|
|
6220
|
+
const approved = new Set((s.sources.approvedHomes ?? []).map(normalizeHomePath));
|
|
6221
|
+
const seeded = [];
|
|
6222
|
+
for (const p of legacy) {
|
|
6223
|
+
if (!approved.has(p)) {
|
|
6224
|
+
approved.add(p);
|
|
6225
|
+
seeded.push(p);
|
|
6226
|
+
}
|
|
6227
|
+
}
|
|
6228
|
+
s.sources.approvedHomes = [...approved];
|
|
6229
|
+
s.sources.homesGrandfathered = true;
|
|
6230
|
+
try {
|
|
6231
|
+
saveSettings(s);
|
|
6232
|
+
} catch {
|
|
6233
|
+
return { seeded: [] };
|
|
6234
|
+
}
|
|
6235
|
+
return { seeded };
|
|
6320
6236
|
}
|
|
6321
|
-
function
|
|
6322
|
-
const
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
out.push(clean.slice(i, i + maxChars));
|
|
6237
|
+
function primaryHomes() {
|
|
6238
|
+
const home = homedir2();
|
|
6239
|
+
const env = process.env;
|
|
6240
|
+
let s;
|
|
6241
|
+
try {
|
|
6242
|
+
s = loadSettings().sources;
|
|
6243
|
+
} catch {
|
|
6244
|
+
s = void 0;
|
|
6330
6245
|
}
|
|
6331
|
-
|
|
6246
|
+
const opencodeDb = env.CHAT_RECALL_OPENCODE_DB || s?.opencodeDbPath || join5(home, ".local", "share", "opencode", "opencode.db");
|
|
6247
|
+
const candidates2 = [
|
|
6248
|
+
env.CHAT_RECALL_CLAUDE_HOME || s?.claudeHome || join5(home, ".claude"),
|
|
6249
|
+
env.CHAT_RECALL_GEMINI_HOME || s?.geminiHome || join5(home, ".gemini"),
|
|
6250
|
+
env.CHAT_RECALL_CODEX_HOME || s?.codexHome || join5(home, ".codex"),
|
|
6251
|
+
env.CHAT_RECALL_AGY_HOME || s?.agyHome || join5(home, ".gemini", "antigravity-cli"),
|
|
6252
|
+
env.CHAT_RECALL_CURSOR_HOME || s?.cursorHome || join5(home, ".cursor"),
|
|
6253
|
+
dirOf(opencodeDb)
|
|
6254
|
+
];
|
|
6255
|
+
return candidates2.map(normalizeHomePath);
|
|
6332
6256
|
}
|
|
6333
|
-
function
|
|
6334
|
-
const
|
|
6335
|
-
|
|
6336
|
-
|
|
6337
|
-
|
|
6338
|
-
|
|
6339
|
-
|
|
6340
|
-
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
|
|
6257
|
+
function homeDecision(path) {
|
|
6258
|
+
const p = normalizeHomePath(path);
|
|
6259
|
+
if (primaryHomes().includes(p))
|
|
6260
|
+
return "primary";
|
|
6261
|
+
let s;
|
|
6262
|
+
try {
|
|
6263
|
+
s = loadSettings().sources;
|
|
6264
|
+
} catch {
|
|
6265
|
+
return "pending";
|
|
6266
|
+
}
|
|
6267
|
+
if ((s.declinedHomes ?? []).map(normalizeHomePath).includes(p))
|
|
6268
|
+
return "declined";
|
|
6269
|
+
if ((s.approvedHomes ?? []).map(normalizeHomePath).includes(p))
|
|
6270
|
+
return "approved";
|
|
6271
|
+
if ((s.extraClaudeHomes ?? []).map(normalizeHomePath).includes(p))
|
|
6272
|
+
return "approved";
|
|
6273
|
+
return "pending";
|
|
6274
|
+
}
|
|
6275
|
+
function isHomeSynced(path) {
|
|
6276
|
+
const d = homeDecision(path);
|
|
6277
|
+
return d === "primary" || d === "approved";
|
|
6278
|
+
}
|
|
6279
|
+
function setDecision(path, decision) {
|
|
6280
|
+
const p = normalizeHomePath(path);
|
|
6281
|
+
let s;
|
|
6282
|
+
try {
|
|
6283
|
+
s = loadSettings();
|
|
6284
|
+
} catch {
|
|
6285
|
+
return false;
|
|
6286
|
+
}
|
|
6287
|
+
const approved = new Set((s.sources.approvedHomes ?? []).map(normalizeHomePath));
|
|
6288
|
+
const declined = new Set((s.sources.declinedHomes ?? []).map(normalizeHomePath));
|
|
6289
|
+
approved.delete(p);
|
|
6290
|
+
declined.delete(p);
|
|
6291
|
+
(decision === "approved" ? approved : declined).add(p);
|
|
6292
|
+
s.sources.approvedHomes = [...approved];
|
|
6293
|
+
s.sources.declinedHomes = [...declined];
|
|
6294
|
+
try {
|
|
6295
|
+
saveSettings(s);
|
|
6296
|
+
} catch {
|
|
6297
|
+
return false;
|
|
6298
|
+
}
|
|
6299
|
+
return true;
|
|
6300
|
+
}
|
|
6301
|
+
function approveHome(path) {
|
|
6302
|
+
return setDecision(path, "approved");
|
|
6303
|
+
}
|
|
6304
|
+
function declineHome(path) {
|
|
6305
|
+
return setDecision(path, "declined");
|
|
6306
|
+
}
|
|
6307
|
+
var dirOf;
|
|
6308
|
+
var init_home_approval = __esm({
|
|
6309
|
+
"../engine/dist/core/home-approval.js"() {
|
|
6310
|
+
"use strict";
|
|
6311
|
+
init_settings();
|
|
6312
|
+
dirOf = (filePath) => dirname5(filePath);
|
|
6313
|
+
}
|
|
6314
|
+
});
|
|
6315
|
+
|
|
6316
|
+
// ../engine/dist/core/tool-paths.js
|
|
6317
|
+
import { existsSync as existsSync5, readdirSync as readdirSync4 } from "fs";
|
|
6318
|
+
import { homedir as homedir3 } from "os";
|
|
6319
|
+
import { join as join6, dirname as dirname6, basename as basename2 } from "path";
|
|
6320
|
+
function claudeHomeDir() {
|
|
6321
|
+
return process.env.CHAT_RECALL_CLAUDE_HOME || sources().claudeHome || join6(homedir3(), ".claude");
|
|
6322
|
+
}
|
|
6323
|
+
function claudeProjectDirs(opts = {}) {
|
|
6324
|
+
const home = homedir3();
|
|
6325
|
+
const dirs = [];
|
|
6326
|
+
const addHome = (d) => {
|
|
6327
|
+
const p = join6(d, "projects");
|
|
6328
|
+
if (existsSync5(p) && !dirs.includes(p))
|
|
6329
|
+
dirs.push(p);
|
|
6344
6330
|
};
|
|
6345
|
-
|
|
6346
|
-
|
|
6347
|
-
|
|
6348
|
-
|
|
6349
|
-
|
|
6350
|
-
|
|
6351
|
-
|
|
6352
|
-
|
|
6353
|
-
|
|
6354
|
-
|
|
6355
|
-
sourceLine: 0,
|
|
6356
|
-
...baseData
|
|
6357
|
-
});
|
|
6358
|
-
} else {
|
|
6359
|
-
chunks.push({
|
|
6360
|
-
chunkId: `${entry.sessionId}_summary_${i}`,
|
|
6361
|
-
chunkType: "summary",
|
|
6362
|
-
text: chunkText,
|
|
6363
|
-
sourceLine: 0,
|
|
6364
|
-
...baseData
|
|
6365
|
-
});
|
|
6331
|
+
addHome(claudeHomeDir());
|
|
6332
|
+
const homeOverridden = !!(process.env.CHAT_RECALL_CLAUDE_HOME || sources().claudeHome);
|
|
6333
|
+
if (!homeOverridden) {
|
|
6334
|
+
try {
|
|
6335
|
+
for (const entry of readdirSync4(home, { withFileTypes: true })) {
|
|
6336
|
+
if (entry.isDirectory() && entry.name.startsWith(".claude-") && entry.name !== ".claude-code") {
|
|
6337
|
+
addHome(join6(home, entry.name));
|
|
6338
|
+
}
|
|
6339
|
+
}
|
|
6340
|
+
} catch {
|
|
6366
6341
|
}
|
|
6367
6342
|
}
|
|
6368
|
-
if (
|
|
6369
|
-
|
|
6370
|
-
|
|
6371
|
-
|
|
6372
|
-
|
|
6373
|
-
|
|
6374
|
-
chunks.push({
|
|
6375
|
-
chunkId: `${entry.sessionId}_first_prompt`,
|
|
6376
|
-
chunkType: "first_prompt",
|
|
6377
|
-
text: cleanText(firstPrompt.slice(0, maxChars)),
|
|
6378
|
-
sourceLine: firstLine,
|
|
6379
|
-
...baseData
|
|
6380
|
-
});
|
|
6343
|
+
if (process.env.CLAUDE_DIRS) {
|
|
6344
|
+
for (const d of process.env.CLAUDE_DIRS.split(",")) {
|
|
6345
|
+
const t = d.trim();
|
|
6346
|
+
if (t)
|
|
6347
|
+
addHome(t.startsWith("~/") ? join6(home, t.slice(2)) : t);
|
|
6348
|
+
}
|
|
6381
6349
|
}
|
|
6382
|
-
if (
|
|
6383
|
-
|
|
6350
|
+
if (dirs.length === 0)
|
|
6351
|
+
dirs.push(join6(claudeHomeDir(), "projects"));
|
|
6352
|
+
return applyDecisions(dirs, opts.includeExcluded);
|
|
6353
|
+
}
|
|
6354
|
+
function _setSourceExclusionFilter(fn) {
|
|
6355
|
+
_sourceExclusionFilter = fn;
|
|
6356
|
+
}
|
|
6357
|
+
function geminiHomeDir() {
|
|
6358
|
+
return process.env.CHAT_RECALL_GEMINI_HOME || sources().geminiHome || join6(homedir3(), ".gemini");
|
|
6359
|
+
}
|
|
6360
|
+
function opencodeDbPath() {
|
|
6361
|
+
return process.env.CHAT_RECALL_OPENCODE_DB || sources().opencodeDbPath || join6(homedir3(), ".local", "share", "opencode", "opencode.db");
|
|
6362
|
+
}
|
|
6363
|
+
function codexHomeDir() {
|
|
6364
|
+
return process.env.CHAT_RECALL_CODEX_HOME || sources().codexHome || join6(homedir3(), ".codex");
|
|
6365
|
+
}
|
|
6366
|
+
function agyHomeDir() {
|
|
6367
|
+
return process.env.CHAT_RECALL_AGY_HOME || sources().agyHome || join6(homedir3(), ".gemini", "antigravity-cli");
|
|
6368
|
+
}
|
|
6369
|
+
function cursorHomeDir() {
|
|
6370
|
+
return process.env.CHAT_RECALL_CURSOR_HOME || sources().cursorHome || join6(homedir3(), ".cursor");
|
|
6371
|
+
}
|
|
6372
|
+
function cursorIdeHomeDir() {
|
|
6373
|
+
return process.env.CHAT_RECALL_CURSOR_IDE_HOME || sources().cursorIdeHome || (process.platform === "darwin" ? join6(homedir3(), "Library", "Application Support", "Cursor") : join6(homedir3(), ".config", "Cursor"));
|
|
6374
|
+
}
|
|
6375
|
+
function siblingHomes(base, overridden, suffix) {
|
|
6376
|
+
const roots = [];
|
|
6377
|
+
const add = (home) => {
|
|
6378
|
+
const root = suffix ? join6(home, suffix) : home;
|
|
6379
|
+
if (existsSync5(root) && !roots.includes(root))
|
|
6380
|
+
roots.push(root);
|
|
6381
|
+
};
|
|
6382
|
+
add(base);
|
|
6383
|
+
if (!overridden) {
|
|
6384
|
+
const parent = dirname6(base);
|
|
6385
|
+
const leaf = basename2(base);
|
|
6386
|
+
try {
|
|
6387
|
+
for (const entry of readdirSync4(parent, { withFileTypes: true })) {
|
|
6388
|
+
if (!entry.isDirectory())
|
|
6389
|
+
continue;
|
|
6390
|
+
if (entry.name === leaf || !entry.name.startsWith(leaf + "-"))
|
|
6391
|
+
continue;
|
|
6392
|
+
add(join6(parent, entry.name));
|
|
6393
|
+
}
|
|
6394
|
+
} catch {
|
|
6395
|
+
}
|
|
6384
6396
|
}
|
|
6385
|
-
|
|
6386
|
-
|
|
6387
|
-
|
|
6388
|
-
|
|
6389
|
-
|
|
6390
|
-
|
|
6391
|
-
|
|
6392
|
-
|
|
6393
|
-
|
|
6394
|
-
|
|
6395
|
-
|
|
6396
|
-
|
|
6397
|
-
|
|
6398
|
-
chunkType: "user_context",
|
|
6399
|
-
text: windows[w],
|
|
6400
|
-
sourceLine: msg.lineNumber,
|
|
6401
|
-
...baseData
|
|
6402
|
-
});
|
|
6397
|
+
return roots;
|
|
6398
|
+
}
|
|
6399
|
+
function applyDecisions(roots, includeExcluded) {
|
|
6400
|
+
if (roots.length === 0)
|
|
6401
|
+
return roots;
|
|
6402
|
+
let kept = roots;
|
|
6403
|
+
if (!includeExcluded) {
|
|
6404
|
+
try {
|
|
6405
|
+
grandfatherLegacyHomes();
|
|
6406
|
+
const approved = kept.filter((r) => isHomeSynced(dirname6(r)));
|
|
6407
|
+
if (approved.length > 0)
|
|
6408
|
+
kept = approved;
|
|
6409
|
+
} catch {
|
|
6403
6410
|
}
|
|
6404
6411
|
}
|
|
6405
|
-
if (
|
|
6406
|
-
|
|
6412
|
+
if (!includeExcluded && _sourceExclusionFilter) {
|
|
6413
|
+
const notExcluded = kept.filter((r) => !_sourceExclusionFilter(r));
|
|
6414
|
+
if (notExcluded.length > 0)
|
|
6415
|
+
kept = notExcluded;
|
|
6407
6416
|
}
|
|
6408
|
-
|
|
6409
|
-
|
|
6410
|
-
|
|
6411
|
-
|
|
6412
|
-
|
|
6413
|
-
|
|
6414
|
-
|
|
6415
|
-
|
|
6416
|
-
|
|
6417
|
-
|
|
6418
|
-
|
|
6419
|
-
|
|
6420
|
-
|
|
6421
|
-
|
|
6422
|
-
|
|
6423
|
-
|
|
6424
|
-
|
|
6425
|
-
|
|
6417
|
+
return kept;
|
|
6418
|
+
}
|
|
6419
|
+
function applyExclusions(roots, includeExcluded) {
|
|
6420
|
+
return applyDecisions(roots, includeExcluded);
|
|
6421
|
+
}
|
|
6422
|
+
function codexSessionDirs(opts = {}) {
|
|
6423
|
+
const overridden = !!(process.env.CHAT_RECALL_CODEX_HOME || sources().codexHome);
|
|
6424
|
+
return applyExclusions(siblingHomes(codexHomeDir(), overridden, "sessions"), opts.includeExcluded);
|
|
6425
|
+
}
|
|
6426
|
+
function agyBrainDirs(opts = {}) {
|
|
6427
|
+
const overridden = !!(process.env.CHAT_RECALL_AGY_HOME || sources().agyHome);
|
|
6428
|
+
return applyExclusions(siblingHomes(agyHomeDir(), overridden, "brain"), opts.includeExcluded);
|
|
6429
|
+
}
|
|
6430
|
+
function cursorChatDirs(opts = {}) {
|
|
6431
|
+
const overridden = !!(process.env.CHAT_RECALL_CURSOR_HOME || sources().cursorHome);
|
|
6432
|
+
return applyExclusions(siblingHomes(cursorHomeDir(), overridden, "chats"), opts.includeExcluded);
|
|
6433
|
+
}
|
|
6434
|
+
function cursorIdeWorkspaceDirs(opts = {}) {
|
|
6435
|
+
const overridden = !!(process.env.CHAT_RECALL_CURSOR_IDE_HOME || sources().cursorIdeHome);
|
|
6436
|
+
return applyExclusions(siblingHomes(cursorIdeHomeDir(), overridden, join6("User", "workspaceStorage")), opts.includeExcluded);
|
|
6437
|
+
}
|
|
6438
|
+
function cursorIdeGlobalDbs(opts = {}) {
|
|
6439
|
+
const overridden = !!(process.env.CHAT_RECALL_CURSOR_IDE_HOME || sources().cursorIdeHome);
|
|
6440
|
+
const roots = applyExclusions(siblingHomes(cursorIdeHomeDir(), overridden, join6("User", "globalStorage")), opts.includeExcluded);
|
|
6441
|
+
const dbs = [];
|
|
6442
|
+
for (const r of roots) {
|
|
6443
|
+
const p = join6(r, "state.vscdb");
|
|
6444
|
+
if (existsSync5(p) && !dbs.includes(p))
|
|
6445
|
+
dbs.push(p);
|
|
6426
6446
|
}
|
|
6427
|
-
|
|
6428
|
-
|
|
6447
|
+
return dbs;
|
|
6448
|
+
}
|
|
6449
|
+
function opencodeDbPaths(opts = {}) {
|
|
6450
|
+
const primary = opencodeDbPath();
|
|
6451
|
+
const overridden = !!(process.env.CHAT_RECALL_OPENCODE_DB || sources().opencodeDbPath);
|
|
6452
|
+
const dbFile = basename2(primary);
|
|
6453
|
+
const dirs = siblingHomes(dirname6(primary), overridden, "");
|
|
6454
|
+
const dbs = [];
|
|
6455
|
+
for (const d of dirs) {
|
|
6456
|
+
const p = join6(d, dbFile);
|
|
6457
|
+
if (existsSync5(p) && !dbs.includes(p))
|
|
6458
|
+
dbs.push(p);
|
|
6429
6459
|
}
|
|
6430
|
-
if (
|
|
6431
|
-
|
|
6432
|
-
|
|
6433
|
-
|
|
6434
|
-
|
|
6435
|
-
|
|
6436
|
-
|
|
6437
|
-
|
|
6438
|
-
|
|
6439
|
-
|
|
6440
|
-
|
|
6441
|
-
|
|
6442
|
-
|
|
6443
|
-
|
|
6444
|
-
|
|
6445
|
-
sourceLine: result.lineNumber,
|
|
6446
|
-
...baseData
|
|
6447
|
-
});
|
|
6448
|
-
}
|
|
6449
|
-
}
|
|
6460
|
+
if (dbs.length === 0)
|
|
6461
|
+
dbs.push(primary);
|
|
6462
|
+
return applyExclusions(dbs, opts.includeExcluded);
|
|
6463
|
+
}
|
|
6464
|
+
function extraClaudeHomeDirs() {
|
|
6465
|
+
const extras = sources().extraClaudeHomes ?? [];
|
|
6466
|
+
const primary = claudeHomeDir();
|
|
6467
|
+
return Array.from(new Set(extras)).filter((p) => p && p !== primary);
|
|
6468
|
+
}
|
|
6469
|
+
function claudeHomeDirs() {
|
|
6470
|
+
const overridden = !!(process.env.CHAT_RECALL_CLAUDE_HOME || sources().claudeHome);
|
|
6471
|
+
const homes = siblingHomes(claudeHomeDir(), overridden, "").filter((h) => basename2(h) !== ".claude-code");
|
|
6472
|
+
for (const extra of extraClaudeHomeDirs()) {
|
|
6473
|
+
if (existsSync5(extra) && !homes.includes(extra))
|
|
6474
|
+
homes.push(extra);
|
|
6450
6475
|
}
|
|
6451
|
-
return
|
|
6476
|
+
return homes;
|
|
6452
6477
|
}
|
|
6453
|
-
var
|
|
6454
|
-
var
|
|
6455
|
-
"../engine/dist/
|
|
6478
|
+
var sources, _sourceExclusionFilter;
|
|
6479
|
+
var init_tool_paths = __esm({
|
|
6480
|
+
"../engine/dist/core/tool-paths.js"() {
|
|
6456
6481
|
"use strict";
|
|
6457
|
-
|
|
6458
|
-
|
|
6459
|
-
|
|
6460
|
-
|
|
6461
|
-
/API Error:[^\n]{0,120}/g
|
|
6462
|
-
];
|
|
6482
|
+
init_home_approval();
|
|
6483
|
+
init_settings();
|
|
6484
|
+
sources = loadSourceSettings;
|
|
6485
|
+
_sourceExclusionFilter = null;
|
|
6463
6486
|
}
|
|
6464
6487
|
});
|
|
6465
6488
|
|
|
@@ -8114,13 +8137,26 @@ function resolveSessionContentGroups(sessionId) {
|
|
|
8114
8137
|
}
|
|
8115
8138
|
function withSessionReadCache(fn) {
|
|
8116
8139
|
readScopeDepth++;
|
|
8140
|
+
if (readScopeDepth === 1)
|
|
8141
|
+
derivedScope = /* @__PURE__ */ new Map();
|
|
8117
8142
|
try {
|
|
8118
8143
|
return fn();
|
|
8119
8144
|
} finally {
|
|
8120
|
-
if (--readScopeDepth === 0)
|
|
8145
|
+
if (--readScopeDepth === 0) {
|
|
8121
8146
|
readScope = null;
|
|
8147
|
+
derivedScope = null;
|
|
8148
|
+
}
|
|
8122
8149
|
}
|
|
8123
8150
|
}
|
|
8151
|
+
function memoInSessionScope(key2, compute) {
|
|
8152
|
+
if (!derivedScope)
|
|
8153
|
+
return compute();
|
|
8154
|
+
if (derivedScope.has(key2))
|
|
8155
|
+
return derivedScope.get(key2);
|
|
8156
|
+
const value = compute();
|
|
8157
|
+
derivedScope.set(key2, value);
|
|
8158
|
+
return value;
|
|
8159
|
+
}
|
|
8124
8160
|
function readSessionGroupText(group) {
|
|
8125
8161
|
let key2 = "";
|
|
8126
8162
|
if (readScopeDepth > 0) {
|
|
@@ -8232,7 +8268,7 @@ function findCodexSessionFileIn(root, sessionId) {
|
|
|
8232
8268
|
}
|
|
8233
8269
|
return scanDir(root);
|
|
8234
8270
|
}
|
|
8235
|
-
var scanScopeDepth, indexCache, readScope, readScopeDepth, geminiCache, MAX_EVENT_LINE_BYTES;
|
|
8271
|
+
var scanScopeDepth, indexCache, readScope, readScopeDepth, derivedScope, geminiCache, MAX_EVENT_LINE_BYTES;
|
|
8236
8272
|
var init_live_session_scan = __esm({
|
|
8237
8273
|
"../engine/dist/core/live-session-scan.js"() {
|
|
8238
8274
|
"use strict";
|
|
@@ -8246,6 +8282,7 @@ var init_live_session_scan = __esm({
|
|
|
8246
8282
|
indexCache = null;
|
|
8247
8283
|
readScope = null;
|
|
8248
8284
|
readScopeDepth = 0;
|
|
8285
|
+
derivedScope = null;
|
|
8249
8286
|
geminiCache = null;
|
|
8250
8287
|
MAX_EVENT_LINE_BYTES = 8 * 1024 * 1024;
|
|
8251
8288
|
}
|
|
@@ -8523,6 +8560,28 @@ var init_session_git = __esm({
|
|
|
8523
8560
|
}
|
|
8524
8561
|
});
|
|
8525
8562
|
|
|
8563
|
+
// ../engine/dist/core/session-multi-tool.js
|
|
8564
|
+
function extractTurnsAny(sessionId, opts = {}) {
|
|
8565
|
+
const backend = getBackendForId(sessionId);
|
|
8566
|
+
if (!backend)
|
|
8567
|
+
return { sessionId, found: false, turns: [], startMs: 0, endMs: 0 };
|
|
8568
|
+
return memoInSessionScope(`turns:${sessionId}:${opts.maxTurns ?? ""}:${opts.assistantMax ?? ""}`, () => backend.extractTurns(sessionId, opts));
|
|
8569
|
+
}
|
|
8570
|
+
function replaySessionAny(sessionId) {
|
|
8571
|
+
const backend = getBackendForId(sessionId);
|
|
8572
|
+
if (!backend)
|
|
8573
|
+
return { sessionId, found: false, projectPath: "", files: [], totalLinesAdded: 0, totalLinesRemoved: 0 };
|
|
8574
|
+
return memoInSessionScope(`replay:${sessionId}`, () => backend.replay(sessionId));
|
|
8575
|
+
}
|
|
8576
|
+
var init_session_multi_tool = __esm({
|
|
8577
|
+
"../engine/dist/core/session-multi-tool.js"() {
|
|
8578
|
+
"use strict";
|
|
8579
|
+
init_tool_backend();
|
|
8580
|
+
init_live_session_scan();
|
|
8581
|
+
init_backends();
|
|
8582
|
+
}
|
|
8583
|
+
});
|
|
8584
|
+
|
|
8526
8585
|
// ../engine/dist/core/session-outcome.js
|
|
8527
8586
|
function extractDecisionsFromTurn(turn) {
|
|
8528
8587
|
if (turn.kind !== "assistant_text" || !turn.text)
|
|
@@ -8619,7 +8678,7 @@ function computeOutcome(sessionId, opts = {}) {
|
|
|
8619
8678
|
};
|
|
8620
8679
|
if (!backend)
|
|
8621
8680
|
return notFound;
|
|
8622
|
-
const turnsRes =
|
|
8681
|
+
const turnsRes = extractTurnsAny(sessionId, { assistantMax: 2e3 });
|
|
8623
8682
|
if (!turnsRes.found)
|
|
8624
8683
|
return notFound;
|
|
8625
8684
|
const decisions = [];
|
|
@@ -8658,7 +8717,7 @@ function computeOutcome(sessionId, opts = {}) {
|
|
|
8658
8717
|
totalLinesAdded = fast.totalLinesAdded;
|
|
8659
8718
|
totalLinesRemoved = fast.totalLinesRemoved;
|
|
8660
8719
|
} else {
|
|
8661
|
-
const replay =
|
|
8720
|
+
const replay = replaySessionAny(sessionId);
|
|
8662
8721
|
filesChanged = replay.files.map((f) => f.file);
|
|
8663
8722
|
totalLinesAdded = replay.totalLinesAdded;
|
|
8664
8723
|
totalLinesRemoved = replay.totalLinesRemoved;
|
|
@@ -8697,6 +8756,7 @@ var init_session_outcome = __esm({
|
|
|
8697
8756
|
init_session_sentiment();
|
|
8698
8757
|
init_session_git();
|
|
8699
8758
|
init_tool_backend();
|
|
8759
|
+
init_session_multi_tool();
|
|
8700
8760
|
init_backends();
|
|
8701
8761
|
DECISION_PATTERNS = [
|
|
8702
8762
|
/\b(?:I'?ll|I will|I'?m going to|Going to|Let me|We'?ll|We will|We should|Let'?s|I'?m about to|I'?m going to)\b[^\n.!?]*[.!?]/i,
|
|
@@ -9166,6 +9226,7 @@ var UUID_RE, ClaudeBackend, claudeBackend;
|
|
|
9166
9226
|
var init_claude2 = __esm({
|
|
9167
9227
|
"../engine/dist/core/backends/claude.js"() {
|
|
9168
9228
|
"use strict";
|
|
9229
|
+
init_live_session_scan();
|
|
9169
9230
|
init_tool_paths();
|
|
9170
9231
|
init_live_session_scan();
|
|
9171
9232
|
init_session_outcome();
|
|
@@ -9428,6 +9489,9 @@ var init_claude2 = __esm({
|
|
|
9428
9489
|
* splits show up alongside the parent.
|
|
9429
9490
|
*/
|
|
9430
9491
|
readEvents(rawId) {
|
|
9492
|
+
return memoInSessionScope(`events:claude:${rawId}`, () => this.#readEventsUncached(rawId));
|
|
9493
|
+
}
|
|
9494
|
+
#readEventsUncached(rawId) {
|
|
9431
9495
|
const groups = resolveSessionContentGroups(rawId);
|
|
9432
9496
|
if (groups.length === 0)
|
|
9433
9497
|
return [];
|
|
@@ -15237,15 +15301,15 @@ function extractEntities(text, context = {}) {
|
|
|
15237
15301
|
seen.add(key2);
|
|
15238
15302
|
triples.push({ subject: s, predicate: p, object: o, confidence: conf, validFrom: context.validFrom });
|
|
15239
15303
|
};
|
|
15240
|
-
const
|
|
15304
|
+
const projectName2 = context.projectPath ? context.projectPath.split("/").filter(Boolean).pop() || "" : "";
|
|
15241
15305
|
for (const { pattern, countPattern, contextPattern, name, category } of TOOL_PATTERNS) {
|
|
15242
15306
|
if (!pattern.test(text))
|
|
15243
15307
|
continue;
|
|
15244
15308
|
countPattern.lastIndex = 0;
|
|
15245
15309
|
const mentions = (text.match(countPattern) || []).length;
|
|
15246
15310
|
const contextual = contextPattern.test(text);
|
|
15247
|
-
if (
|
|
15248
|
-
addTriple(
|
|
15311
|
+
if (projectName2 && (mentions >= 2 || contextual)) {
|
|
15312
|
+
addTriple(projectName2, "uses", canonTool(name), Math.min(0.9, 0.5 + 0.1 * Math.min(mentions, 4) + (contextual ? 0.1 : 0)));
|
|
15249
15313
|
}
|
|
15250
15314
|
void category;
|
|
15251
15315
|
}
|
|
@@ -15258,13 +15322,13 @@ function extractEntities(text, context = {}) {
|
|
|
15258
15322
|
if (rejected) {
|
|
15259
15323
|
if (chosen && looksLikeStrictTool(chosen) && looksLikeStrictTool(rejected)) {
|
|
15260
15324
|
const c = canonTool(chosen), r = canonTool(rejected);
|
|
15261
|
-
if (
|
|
15262
|
-
addTriple(
|
|
15263
|
-
addTriple(
|
|
15325
|
+
if (projectName2)
|
|
15326
|
+
addTriple(projectName2, "chose", c, DECISION_GUESS_CONFIDENCE);
|
|
15327
|
+
addTriple(projectName2 || "project", "rejected", r, DECISION_GUESS_CONFIDENCE);
|
|
15264
15328
|
addTriple(c, "chosen_over", r, DECISION_GUESS_CONFIDENCE);
|
|
15265
15329
|
}
|
|
15266
|
-
} else if (chosen && looksLikeDecisionObject(chosen) &&
|
|
15267
|
-
addTriple(
|
|
15330
|
+
} else if (chosen && looksLikeDecisionObject(chosen) && projectName2) {
|
|
15331
|
+
addTriple(projectName2, "chose", canonTool(chosen), DECISION_GUESS_CONFIDENCE);
|
|
15268
15332
|
}
|
|
15269
15333
|
}
|
|
15270
15334
|
}
|
|
@@ -15294,8 +15358,8 @@ function extractEntities(text, context = {}) {
|
|
|
15294
15358
|
if (match[0].startsWith("@") && (after === "/" || after === "." || after === "("))
|
|
15295
15359
|
continue;
|
|
15296
15360
|
addTriple(name, "is_a", "person", 0.6);
|
|
15297
|
-
if (
|
|
15298
|
-
addTriple(name, "works_on",
|
|
15361
|
+
if (projectName2) {
|
|
15362
|
+
addTriple(name, "works_on", projectName2, 0.5);
|
|
15299
15363
|
}
|
|
15300
15364
|
}
|
|
15301
15365
|
}
|
|
@@ -16331,13 +16395,13 @@ var init_plan_source = __esm({
|
|
|
16331
16395
|
});
|
|
16332
16396
|
}
|
|
16333
16397
|
if (item.projectPath) {
|
|
16334
|
-
const
|
|
16335
|
-
if (
|
|
16398
|
+
const projectName2 = basename13(item.projectPath);
|
|
16399
|
+
if (projectName2) {
|
|
16336
16400
|
links.push({
|
|
16337
16401
|
sourceType: "plan",
|
|
16338
16402
|
sourceId: planName,
|
|
16339
16403
|
targetType: "claude_md",
|
|
16340
|
-
targetId:
|
|
16404
|
+
targetId: projectName2,
|
|
16341
16405
|
linkType: "plan_for_project",
|
|
16342
16406
|
confidence: sessionId ? 1 : 0.8
|
|
16343
16407
|
});
|
|
@@ -16837,15 +16901,15 @@ var init_claude_md_source = __esm({
|
|
|
16837
16901
|
try {
|
|
16838
16902
|
const stat3 = statSync26(filePath);
|
|
16839
16903
|
const projectDir = dirname16(filePath);
|
|
16840
|
-
const
|
|
16904
|
+
const projectName2 = basename14(projectDir);
|
|
16841
16905
|
const projectPath = projectDir;
|
|
16842
16906
|
const content = readFileSync21(filePath, "utf-8");
|
|
16843
|
-
const firstLine = content.split("\n").find((l) => l.trim() && !l.startsWith("#"))?.trim()?.slice(0, 200) || `${filename} for ${
|
|
16844
|
-
const id = tool === "claude" ?
|
|
16907
|
+
const firstLine = content.split("\n").find((l) => l.trim() && !l.startsWith("#"))?.trim()?.slice(0, 200) || `${filename} for ${projectName2}`;
|
|
16908
|
+
const id = tool === "claude" ? projectName2 : `${projectName2}_${tool}`;
|
|
16845
16909
|
return {
|
|
16846
16910
|
id,
|
|
16847
16911
|
sourceType: "claude_md",
|
|
16848
|
-
title: `${filename} - ${
|
|
16912
|
+
title: `${filename} - ${projectName2}`,
|
|
16849
16913
|
projectPath,
|
|
16850
16914
|
filePath,
|
|
16851
16915
|
mtime: stat3.mtimeMs,
|
|
@@ -19728,7 +19792,7 @@ var init_http = __esm({
|
|
|
19728
19792
|
CLI_VERSION = JSON.parse(readFileSync35(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION;
|
|
19729
19793
|
} catch {
|
|
19730
19794
|
try {
|
|
19731
|
-
if (true) CLI_VERSION = "0.6.
|
|
19795
|
+
if (true) CLI_VERSION = "0.6.9";
|
|
19732
19796
|
} catch {
|
|
19733
19797
|
}
|
|
19734
19798
|
}
|
|
@@ -19753,27 +19817,6 @@ var init_redact_deep = __esm({
|
|
|
19753
19817
|
}
|
|
19754
19818
|
});
|
|
19755
19819
|
|
|
19756
|
-
// ../engine/dist/core/session-multi-tool.js
|
|
19757
|
-
function extractTurnsAny(sessionId, opts = {}) {
|
|
19758
|
-
const backend = getBackendForId(sessionId);
|
|
19759
|
-
if (!backend)
|
|
19760
|
-
return { sessionId, found: false, turns: [], startMs: 0, endMs: 0 };
|
|
19761
|
-
return backend.extractTurns(sessionId, opts);
|
|
19762
|
-
}
|
|
19763
|
-
function replaySessionAny(sessionId) {
|
|
19764
|
-
const backend = getBackendForId(sessionId);
|
|
19765
|
-
if (!backend)
|
|
19766
|
-
return { sessionId, found: false, projectPath: "", files: [], totalLinesAdded: 0, totalLinesRemoved: 0 };
|
|
19767
|
-
return backend.replay(sessionId);
|
|
19768
|
-
}
|
|
19769
|
-
var init_session_multi_tool = __esm({
|
|
19770
|
-
"../engine/dist/core/session-multi-tool.js"() {
|
|
19771
|
-
"use strict";
|
|
19772
|
-
init_tool_backend();
|
|
19773
|
-
init_backends();
|
|
19774
|
-
}
|
|
19775
|
-
});
|
|
19776
|
-
|
|
19777
19820
|
// src/derived.ts
|
|
19778
19821
|
function collectDerivedRows(ref, mtime, count, maxRowBytes) {
|
|
19779
19822
|
const id = ref.prefixedId;
|
|
@@ -20248,7 +20291,7 @@ var init_telemetry = __esm({
|
|
|
20248
20291
|
init_sync_client();
|
|
20249
20292
|
cliVersion2 = () => {
|
|
20250
20293
|
try {
|
|
20251
|
-
return true ? "0.6.
|
|
20294
|
+
return true ? "0.6.9" : "dev";
|
|
20252
20295
|
} catch {
|
|
20253
20296
|
return "dev";
|
|
20254
20297
|
}
|
|
@@ -20652,6 +20695,7 @@ var sync_client_exports = {};
|
|
|
20652
20695
|
__export(sync_client_exports, {
|
|
20653
20696
|
BATCH_SCAN_MAX_BYTES: () => BATCH_SCAN_MAX_BYTES,
|
|
20654
20697
|
FULL_BUILD_MAX_BYTES: () => FULL_BUILD_MAX_BYTES,
|
|
20698
|
+
_acceptedUploads: () => _acceptedUploads,
|
|
20655
20699
|
_fetchTenantSyncConfigForTests: () => _fetchTenantSyncConfigForTests,
|
|
20656
20700
|
_resetCapsCacheForTests: () => _resetCapsCacheForTests,
|
|
20657
20701
|
_resetTenantSecurityConfigCache: () => _resetTenantSecurityConfigCache,
|
|
@@ -20915,6 +20959,9 @@ function removeCredentials(serverUrl) {
|
|
|
20915
20959
|
writeFileSync10(credPath(), JSON.stringify({ targets: kept }, null, 2));
|
|
20916
20960
|
return true;
|
|
20917
20961
|
}
|
|
20962
|
+
function _acceptedUploads(url) {
|
|
20963
|
+
return acceptedUploads.get(url) ?? 0;
|
|
20964
|
+
}
|
|
20918
20965
|
async function syncSessions(opts = {}) {
|
|
20919
20966
|
const targets = loadAllCredentials();
|
|
20920
20967
|
if (targets.length === 0) throw new Error("Not logged in \u2014 run `chat-recall login <server-url> --token <token>`");
|
|
@@ -20938,10 +20985,11 @@ async function syncSessions(opts = {}) {
|
|
|
20938
20985
|
};
|
|
20939
20986
|
continue;
|
|
20940
20987
|
}
|
|
20988
|
+
acceptedUploads.set(cred.serverUrl, 0);
|
|
20941
20989
|
try {
|
|
20942
20990
|
const r = await syncToTarget(cred, opts);
|
|
20943
20991
|
noteTargetSuccess(cred.serverUrl);
|
|
20944
|
-
perTarget[cred.serverUrl] = { ok: true };
|
|
20992
|
+
perTarget[cred.serverUrl] = { ok: true, accepted: acceptedUploads.get(cred.serverUrl) ?? 0 };
|
|
20945
20993
|
agg = agg ? {
|
|
20946
20994
|
uploaded: agg.uploaded + r.uploaded,
|
|
20947
20995
|
skipped: agg.skipped + r.skipped,
|
|
@@ -20958,7 +21006,7 @@ async function syncSessions(opts = {}) {
|
|
|
20958
21006
|
} catch (e) {
|
|
20959
21007
|
const msg = e instanceof Error ? e.message : String(e);
|
|
20960
21008
|
errors.push(`${cred.serverUrl}: ${msg}`);
|
|
20961
|
-
perTarget[cred.serverUrl] = { ok: false, error: msg };
|
|
21009
|
+
perTarget[cred.serverUrl] = { ok: false, error: msg, accepted: acceptedUploads.get(cred.serverUrl) ?? 0 };
|
|
20962
21010
|
const next = noteTargetFailure(cred.serverUrl, msg);
|
|
20963
21011
|
const notice = breakerNotice(cred.serverUrl, next);
|
|
20964
21012
|
if (notice) console.error(`[sync] ${notice}`);
|
|
@@ -21224,7 +21272,7 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
21224
21272
|
return [];
|
|
21225
21273
|
}
|
|
21226
21274
|
});
|
|
21227
|
-
const MAX_BATCH_BYTES = 4 * 1024 * 1024;
|
|
21275
|
+
const MAX_BATCH_BYTES = Number(process.env.CHAT_RECALL_MAX_BATCH_BYTES) || (isLocalHost(cred.serverUrl) ? 4 * 1024 * 1024 : 1024 * 1024);
|
|
21228
21276
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
21229
21277
|
const explicitThrottle = typeof opts.throttleMs === "number" && opts.throttleMs > 0;
|
|
21230
21278
|
const throttleMs = explicitThrottle ? opts.throttleMs : 0;
|
|
@@ -21259,6 +21307,7 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
21259
21307
|
signal: ac.signal
|
|
21260
21308
|
});
|
|
21261
21309
|
if (res.ok) {
|
|
21310
|
+
acceptedUploads.set(cred.serverUrl, (acceptedUploads.get(cred.serverUrl) ?? 0) + 1);
|
|
21262
21311
|
const body2 = await res.json().catch(() => ({}));
|
|
21263
21312
|
if (typeof body2.telemetry === "boolean") {
|
|
21264
21313
|
setTelemetryEligible(base, body2.telemetry);
|
|
@@ -21309,7 +21358,8 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
21309
21358
|
if (attempt >= RETRY_DELAYS_MS.length) throw new Error(`sync failed after ${attempt + 1} attempts: HTTP ${res.status} ${text}`);
|
|
21310
21359
|
const retryAfterS = Number(res.headers.get("retry-after"));
|
|
21311
21360
|
const waitMs = res.status === 429 && retryAfterS > 0 ? retryAfterS * 1e3 : RETRY_DELAYS_MS[attempt];
|
|
21312
|
-
|
|
21361
|
+
const why = res.status === 524 ? ` \u2014 the proxy cut the request at its time limit; the batch is too big for one request` : "";
|
|
21362
|
+
console.error(`[sync] HTTP ${res.status} from ${base}${why} \u2014 backing off ${Math.round(waitMs / 1e3)}s (attempt ${attempt + 1})`);
|
|
21313
21363
|
await sleep(waitMs);
|
|
21314
21364
|
continue;
|
|
21315
21365
|
} catch (err) {
|
|
@@ -22168,7 +22218,7 @@ async function syncIncremental(opts = {}) {
|
|
|
22168
22218
|
lock.release();
|
|
22169
22219
|
}
|
|
22170
22220
|
}
|
|
22171
|
-
var ownVersion, ERROR_CLASS, STANDING_FAILURE, walkProgressForUpload, lastServerPackSpec, credPath, _securityConfigCache, SECURITY_CONFIG_TTL_MS, _syncConfigCache, EMPTY_SYNC_CONFIG, _fetchTenantSyncConfigForTests, SYNC_RULES_REFUSAL, hashPath, MAX_DERIVED_ROW_BYTES, isLocalHost, CAPS_TTL_MS, capsOk, advertisedIngestConc, advertisedGzipBody, FULL_BUILD_MAX_BYTES, FULL_BUILD_TAIL_BYTES, BATCH_SCAN_MAX_BYTES;
|
|
22221
|
+
var ownVersion, ERROR_CLASS, STANDING_FAILURE, walkProgressForUpload, lastServerPackSpec, credPath, _securityConfigCache, SECURITY_CONFIG_TTL_MS, _syncConfigCache, EMPTY_SYNC_CONFIG, _fetchTenantSyncConfigForTests, SYNC_RULES_REFUSAL, hashPath, acceptedUploads, MAX_DERIVED_ROW_BYTES, isLocalHost, CAPS_TTL_MS, capsOk, advertisedIngestConc, advertisedGzipBody, FULL_BUILD_MAX_BYTES, FULL_BUILD_TAIL_BYTES, BATCH_SCAN_MAX_BYTES;
|
|
22172
22222
|
var init_sync_client = __esm({
|
|
22173
22223
|
"src/sync-client.ts"() {
|
|
22174
22224
|
"use strict";
|
|
@@ -22213,7 +22263,7 @@ var init_sync_client = __esm({
|
|
|
22213
22263
|
ownVersion = JSON.parse(readFileSync37(join48(dir, "../package.json"), "utf-8")).version || ownVersion;
|
|
22214
22264
|
} catch {
|
|
22215
22265
|
try {
|
|
22216
|
-
if (true) ownVersion = "0.6.
|
|
22266
|
+
if (true) ownVersion = "0.6.9";
|
|
22217
22267
|
} catch {
|
|
22218
22268
|
}
|
|
22219
22269
|
}
|
|
@@ -22233,6 +22283,7 @@ var init_sync_client = __esm({
|
|
|
22233
22283
|
_fetchTenantSyncConfigForTests = fetchTenantSyncConfig;
|
|
22234
22284
|
SYNC_RULES_REFUSAL = /* @__PURE__ */ Symbol.for("chat-recall.syncRulesRefusal");
|
|
22235
22285
|
hashPath = (p) => p ? "p_" + createHash11("sha256").update(p).digest("hex").slice(0, 12) : "";
|
|
22286
|
+
acceptedUploads = /* @__PURE__ */ new Map();
|
|
22236
22287
|
MAX_DERIVED_ROW_BYTES = 2 * 1024 * 1024;
|
|
22237
22288
|
isLocalHost = isPrivateHost;
|
|
22238
22289
|
CAPS_TTL_MS = 60 * 6e4;
|
|
@@ -23065,6 +23116,128 @@ function resumeCommandFor(sessionId, tool) {
|
|
|
23065
23116
|
// ../engine/dist/mcp/tools.js
|
|
23066
23117
|
init_project_resolver();
|
|
23067
23118
|
|
|
23119
|
+
// ../engine/dist/mcp/resume-digest.js
|
|
23120
|
+
function splitSummary(summary) {
|
|
23121
|
+
const out = {};
|
|
23122
|
+
if (!summary)
|
|
23123
|
+
return out;
|
|
23124
|
+
const heads = [
|
|
23125
|
+
["request", /\*\*Request:?\*\*/i],
|
|
23126
|
+
["plan", /\*\*Plan:?\*\*/i],
|
|
23127
|
+
["done", /\*\*What was done:?\*\*/i],
|
|
23128
|
+
["remaining", /\*\*Remaining\s*\/?\s*Not done:?\*\*/i]
|
|
23129
|
+
];
|
|
23130
|
+
const marks = [];
|
|
23131
|
+
for (const [key2, re] of heads) {
|
|
23132
|
+
const m = re.exec(summary);
|
|
23133
|
+
if (m)
|
|
23134
|
+
marks.push({ key: key2, start: m.index + m[0].length, end: m.index });
|
|
23135
|
+
}
|
|
23136
|
+
marks.sort((a, b) => a.start - b.start);
|
|
23137
|
+
for (let i = 0; i < marks.length; i++) {
|
|
23138
|
+
const stop = i + 1 < marks.length ? marks[i + 1].end : summary.length;
|
|
23139
|
+
const body = summary.slice(marks[i].start, stop).trim();
|
|
23140
|
+
if (body)
|
|
23141
|
+
out[marks[i].key] = body;
|
|
23142
|
+
}
|
|
23143
|
+
return out;
|
|
23144
|
+
}
|
|
23145
|
+
function isLabelOnly(text) {
|
|
23146
|
+
const bare = text.replace(/\*\*/g, "").trim();
|
|
23147
|
+
return bare.endsWith(":");
|
|
23148
|
+
}
|
|
23149
|
+
function bulletLines(section, max, chars = 160) {
|
|
23150
|
+
if (!section)
|
|
23151
|
+
return [];
|
|
23152
|
+
const out = [];
|
|
23153
|
+
for (const raw of section.split("\n")) {
|
|
23154
|
+
const line = raw.trim();
|
|
23155
|
+
if (!line.startsWith("-") && !line.startsWith("*"))
|
|
23156
|
+
continue;
|
|
23157
|
+
const text = line.replace(/^[-*]\s*/, "").replace(/\s+/g, " ").trim();
|
|
23158
|
+
if (!text || isLabelOnly(text))
|
|
23159
|
+
continue;
|
|
23160
|
+
out.push(text.length > chars ? `${text.slice(0, chars)}\u2026` : text);
|
|
23161
|
+
if (out.length >= max)
|
|
23162
|
+
break;
|
|
23163
|
+
}
|
|
23164
|
+
return out;
|
|
23165
|
+
}
|
|
23166
|
+
function statusEmoji(status) {
|
|
23167
|
+
if (status === "shipped")
|
|
23168
|
+
return "\u{1F6A2}";
|
|
23169
|
+
if (status === "interrupted")
|
|
23170
|
+
return "\u23F8";
|
|
23171
|
+
if (status === "abandoned")
|
|
23172
|
+
return "\u{1FAA6}";
|
|
23173
|
+
if (status === "in_progress")
|
|
23174
|
+
return "\u{1F7E1}";
|
|
23175
|
+
return "\u2754";
|
|
23176
|
+
}
|
|
23177
|
+
function projectName(projectPath) {
|
|
23178
|
+
if (!projectPath)
|
|
23179
|
+
return "";
|
|
23180
|
+
return projectPath.split("/").pop() || projectPath;
|
|
23181
|
+
}
|
|
23182
|
+
function digestTitle(row, max = 90) {
|
|
23183
|
+
const t = row.userTitle?.trim() || row.toolTitle?.trim() || row.firstPrompt || "(no prompt)";
|
|
23184
|
+
const flat = t.replace(/\s+/g, " ").trim();
|
|
23185
|
+
return flat.length > max ? flat.slice(0, max) : flat;
|
|
23186
|
+
}
|
|
23187
|
+
function formatDigest(input, summaryChars = 400) {
|
|
23188
|
+
const { row, outcome, related, resumeCmd } = input;
|
|
23189
|
+
const lines = [];
|
|
23190
|
+
lines.push(`### ${digestTitle(row)}`);
|
|
23191
|
+
const when = (row.modified || "").slice(0, 16).replace("T", " ");
|
|
23192
|
+
lines.push(`**Project:** ${projectName(row.projectPath) || "(unknown)"} \xB7 **Tool:** ${row.tool || "claude"}` + (when ? ` \xB7 **Modified:** ${when}` : "") + ` \xB7 \`${row.sessionId}\``);
|
|
23193
|
+
if (outcome) {
|
|
23194
|
+
const edits = outcome.fileCount > 0 ? ` \xB7 ${outcome.fileCount} file(s) +${outcome.totalLinesAdded}/\u2212${outcome.totalLinesRemoved}` : "";
|
|
23195
|
+
const commits = outcome.commits && outcome.commits.totalCommits > 0 ? ` \xB7 ${outcome.commits.totalCommits} commit(s)` : "";
|
|
23196
|
+
lines.push(`${statusEmoji(outcome.status)} **${outcome.status}** \u2014 ${outcome.reason}${edits}${commits}`);
|
|
23197
|
+
}
|
|
23198
|
+
const sections = splitSummary(row.summary || "");
|
|
23199
|
+
const did = bulletLines(sections.done, 4);
|
|
23200
|
+
const left = bulletLines(sections.remaining, 4);
|
|
23201
|
+
if (did.length) {
|
|
23202
|
+
lines.push("**Did:**");
|
|
23203
|
+
for (const d of did)
|
|
23204
|
+
lines.push(`- ${d}`);
|
|
23205
|
+
}
|
|
23206
|
+
if (left.length) {
|
|
23207
|
+
lines.push("**Still open:**");
|
|
23208
|
+
for (const l of left)
|
|
23209
|
+
lines.push(`- ${l}`);
|
|
23210
|
+
}
|
|
23211
|
+
if (!did.length && !left.length) {
|
|
23212
|
+
const gist = (row.summary || "").replace(/\s+/g, " ").trim();
|
|
23213
|
+
if (gist)
|
|
23214
|
+
lines.push(gist.length > summaryChars ? `${gist.slice(0, summaryChars)}\u2026` : gist);
|
|
23215
|
+
}
|
|
23216
|
+
if (outcome?.decisions?.length) {
|
|
23217
|
+
lines.push("**Decisions:**");
|
|
23218
|
+
for (const d of outcome.decisions.slice(0, 3))
|
|
23219
|
+
lines.push(`- ${d.text.slice(0, 160)}`);
|
|
23220
|
+
}
|
|
23221
|
+
const tasks = (related?.links ?? []).filter((l) => l.sourceType === "task");
|
|
23222
|
+
if (tasks.length > 0) {
|
|
23223
|
+
lines.push("**Task lists:**");
|
|
23224
|
+
for (const t of tasks.slice(0, 5))
|
|
23225
|
+
lines.push(`- ${t.title}`);
|
|
23226
|
+
}
|
|
23227
|
+
if (resumeCmd)
|
|
23228
|
+
lines.push(`**Resume:** \`${resumeCmd}\``);
|
|
23229
|
+
lines.push("");
|
|
23230
|
+
return lines;
|
|
23231
|
+
}
|
|
23232
|
+
function crossProjectNote(headProject, priorRows, hasProjectFilter) {
|
|
23233
|
+
if (hasProjectFilter)
|
|
23234
|
+
return null;
|
|
23235
|
+
const names = new Set([headProject, ...priorRows.map((r) => projectName(r.projectPath))].filter(Boolean));
|
|
23236
|
+
if (names.size < 2)
|
|
23237
|
+
return null;
|
|
23238
|
+
return `_These sessions span ${names.size} projects (${[...names].join(", ")}). Pass \`project_filter\` to scope the resume to one._`;
|
|
23239
|
+
}
|
|
23240
|
+
|
|
23068
23241
|
// ../engine/dist/core/decision-areas.js
|
|
23069
23242
|
var DECISION_AREAS = [
|
|
23070
23243
|
"auth",
|
|
@@ -23169,10 +23342,11 @@ function canonArea(raw) {
|
|
|
23169
23342
|
function isKnownArea(key2) {
|
|
23170
23343
|
return !!key2 && AREA_SET.has(key2);
|
|
23171
23344
|
}
|
|
23172
|
-
|
|
23173
|
-
|
|
23174
|
-
|
|
23175
|
-
|
|
23345
|
+
function parseDecisionSubject(subject) {
|
|
23346
|
+
const i = subject.lastIndexOf(":");
|
|
23347
|
+
if (i <= 0 || i === subject.length - 1)
|
|
23348
|
+
return null;
|
|
23349
|
+
return { project: subject.slice(0, i), area: subject.slice(i + 1) };
|
|
23176
23350
|
}
|
|
23177
23351
|
|
|
23178
23352
|
// ../engine/dist/mcp/tools.js
|
|
@@ -23845,7 +24019,15 @@ var RecallSmartResumeSchema = z.object({
|
|
|
23845
24019
|
// agent had to call recall_recent first, and a bare call failed with a zod
|
|
23846
24020
|
// error about a missing field rather than doing the obvious thing.
|
|
23847
24021
|
session_id: z.string().optional().describe("Session to resume. Omit for the most recent one."),
|
|
23848
|
-
project_filter: z.string().optional().describe("When session_id is omitted, resume the latest session from this project (path or name substring).")
|
|
24022
|
+
project_filter: z.string().optional().describe("When session_id is omitted, resume the latest session from this project (path or name substring)."),
|
|
24023
|
+
// "Continue" rarely means one session. Work spans an evening across three of
|
|
24024
|
+
// them, and the newest alone reports a fragment: the tail of a task whose
|
|
24025
|
+
// decision was taken two sessions back. A bare call also takes the globally
|
|
24026
|
+
// newest session, so when the last thing on the machine belonged to another
|
|
24027
|
+
// project, the single-session answer was confidently about the wrong work
|
|
24028
|
+
// with nothing on screen to show it. The trailing digests carry their own
|
|
24029
|
+
// project, which makes that visible instead of silent.
|
|
24030
|
+
depth: z.number().int().min(1).max(5).default(3).describe("How many recent sessions to cover: the newest in full, the rest as digests. Ignored when session_id is set.")
|
|
23849
24031
|
});
|
|
23850
24032
|
var RecallSubagentSearchSchema = z.object({
|
|
23851
24033
|
query: z.string().describe("Text to find inside subagent transcripts (explore/compact/aside sub-tasks)"),
|
|
@@ -24020,6 +24202,30 @@ var RecallUserPromptsSchema = z.object({
|
|
|
24020
24202
|
limit: z.number().optional().default(50).describe("Maximum prompts to return"),
|
|
24021
24203
|
with_markers: z.boolean().optional().default(true).describe("Tag each prompt with sentiment / corrective markers (interrupt, frustrated, correction, approval, \u2026). Set false to revert to legacy text-only output.")
|
|
24022
24204
|
});
|
|
24205
|
+
function resolveRecordScope(params) {
|
|
24206
|
+
const fromSubject = parseDecisionSubject(params.subject);
|
|
24207
|
+
const subjectArea = fromSubject && isKnownArea(canonArea(fromSubject.area) ?? "") ? canonArea(fromSubject.area) : null;
|
|
24208
|
+
const area = canonArea(params.area) ?? subjectArea;
|
|
24209
|
+
const subjectScope = subjectArea ? fromSubject.project : null;
|
|
24210
|
+
const subjectProject = subjectScope && subjectScope !== "*" && !subjectScope.startsWith("ws:") ? subjectScope : void 0;
|
|
24211
|
+
const subjectWorkspace = subjectScope?.startsWith("ws:") ? subjectScope.slice(3) : void 0;
|
|
24212
|
+
return {
|
|
24213
|
+
area,
|
|
24214
|
+
project: params.project ?? subjectProject,
|
|
24215
|
+
workspace: params.workspace ?? subjectWorkspace ?? (params.cwdWorkspace || void 0),
|
|
24216
|
+
// A subject that named `*` asked for the account and still gets it. The
|
|
24217
|
+
// response says which scope it landed on either way.
|
|
24218
|
+
scope: params.scope ?? (subjectScope === "*" ? "account" : void 0)
|
|
24219
|
+
};
|
|
24220
|
+
}
|
|
24221
|
+
function describeWriteScope(subject) {
|
|
24222
|
+
const key2 = parseDecisionSubject(subject)?.project ?? "*";
|
|
24223
|
+
if (key2 === "*")
|
|
24224
|
+
return "account-wide \u2014 every project with no decision of its own inherits this";
|
|
24225
|
+
if (key2.startsWith("ws:"))
|
|
24226
|
+
return `the ${key2.slice(3)} folder group \u2014 every repository in it inherits this`;
|
|
24227
|
+
return `${key2} only`;
|
|
24228
|
+
}
|
|
24023
24229
|
var RecallDecisionRecordSchema = z.object({
|
|
24024
24230
|
subject: z.string().describe('What the decision is about (e.g., "chat-recall", "auth strategy")'),
|
|
24025
24231
|
decision: z.string().describe('The decision itself in plain words (e.g., "use Postgres full-text search as the default backend")'),
|
|
@@ -24028,7 +24234,9 @@ var RecallDecisionRecordSchema = z.object({
|
|
|
24028
24234
|
// "auth", "authentication" and "auth setup" are three separate facts and a
|
|
24029
24235
|
// later decision never closes an earlier one — see core/decision-areas.ts.
|
|
24030
24236
|
area: z.string().optional().describe(`The area this decides, so a later decision about the SAME area supersedes it. Use one of: ${DECISION_AREAS.join(", ")} \u2014 or any short slug for an area not listed. Without it the decision is recorded but nothing it contradicts is closed.`),
|
|
24031
|
-
project: z.string().optional().describe(
|
|
24237
|
+
project: z.string().optional().describe('Project this decision applies to \u2014 one repository, e.g. "example-app". The narrowest scope, and the one to prefer when the decision came out of one codebase.'),
|
|
24238
|
+
workspace: z.string().optional().describe('Folder group this decision applies to, e.g. "personal" for everything under code/personal. Use it for a decision that binds a group of repositories but not the whole account. Resolved from the current directory when omitted.'),
|
|
24239
|
+
scope: z.enum(["project", "workspace", "account"]).optional().describe('Which tier to record at. Omit and the narrowest scope you named is used: project, else workspace, else account. Pass "account" ONLY for something true of every project you own \u2014 an account decision is inherited by every project that has no opinion of its own.'),
|
|
24032
24240
|
importance: z.number().min(1).max(5).optional().default(4).describe("1\u20135; the classifier surfaces 4+ in wake-up context"),
|
|
24033
24241
|
session_id: z.string().optional().describe("Session this decision was made in (for traceability)"),
|
|
24034
24242
|
agent_name: z.string().optional().default("agent").describe("Who recorded the decision (for diary linkage)")
|
|
@@ -24036,6 +24244,7 @@ var RecallDecisionRecordSchema = z.object({
|
|
|
24036
24244
|
var RecallDecisionsSchema = z.object({
|
|
24037
24245
|
area: z.string().optional().describe("Only this area"),
|
|
24038
24246
|
project: z.string().optional().describe("Resolve for this project"),
|
|
24247
|
+
workspace: z.string().optional().describe('Folder group to resolve through, e.g. "personal". Resolved from the current directory when omitted.'),
|
|
24039
24248
|
include_candidates: z.boolean().optional().default(false)
|
|
24040
24249
|
});
|
|
24041
24250
|
var RecallAnalyticsSummarySchema = z.object({
|
|
@@ -24608,25 +24817,32 @@ with recall_show (pass the plan id).`,
|
|
|
24608
24817
|
},
|
|
24609
24818
|
{
|
|
24610
24819
|
name: "recall_smart_resume",
|
|
24611
|
-
description: `Get structured resume context for
|
|
24820
|
+
description: `Get structured resume context for recent work \u2014 by default the last 3 sessions.
|
|
24612
24821
|
|
|
24613
|
-
Call it with NO arguments to resume
|
|
24614
|
-
|
|
24615
|
-
|
|
24822
|
+
Call it with NO arguments to resume. That is the common case: "continue", "pick
|
|
24823
|
+
up where we left off". Pass session_id to resume one specific session, or
|
|
24824
|
+
project_filter to scope to one project.
|
|
24616
24825
|
|
|
24617
|
-
Returns:
|
|
24826
|
+
Returns, for the newest session:
|
|
24618
24827
|
- What was done (completed work, decisions made)
|
|
24619
24828
|
- What's pending (unfinished tasks, TODOs mentioned)
|
|
24620
24829
|
- Files modified with change summary
|
|
24621
24830
|
- Token/cost budget used
|
|
24622
24831
|
- Resume command
|
|
24623
24832
|
|
|
24833
|
+
Then a digest of each earlier session: its project, outcome, summary, the
|
|
24834
|
+
decisions it took and the task lists it left open. Set depth to change how many.
|
|
24835
|
+
|
|
24836
|
+
A bare call takes the globally newest sessions, which may belong to different
|
|
24837
|
+
projects \u2014 the output says so when they do. Pass project_filter to scope it.
|
|
24838
|
+
|
|
24624
24839
|
Use this instead of recall_context for a more actionable summary when resuming work.`,
|
|
24625
24840
|
inputSchema: {
|
|
24626
24841
|
type: "object",
|
|
24627
24842
|
properties: {
|
|
24628
24843
|
session_id: { type: "string", description: "Session to resume. OMIT to resume the most recent session." },
|
|
24629
|
-
project_filter: { type: "string", description: "With session_id omitted, resume the latest
|
|
24844
|
+
project_filter: { type: "string", description: "With session_id omitted, resume the latest sessions from this project." },
|
|
24845
|
+
depth: { type: "number", default: 3, minimum: 1, maximum: 5, description: "How many recent sessions to cover: the newest in full, the rest as digests. Ignored when session_id is set." }
|
|
24630
24846
|
}
|
|
24631
24847
|
}
|
|
24632
24848
|
},
|
|
@@ -25987,7 +26203,7 @@ Sessions already uploaded from that path stay until they are deleted; use recall
|
|
|
25987
26203
|
return { content: [{ type: "text", text: soft.message || (soft.status === 404 ? `Session not found: ${params.session_id}` : `Summary not synced yet for ${params.session_id}.`) }] };
|
|
25988
26204
|
}
|
|
25989
26205
|
const outcome = soft.data;
|
|
25990
|
-
const
|
|
26206
|
+
const statusEmoji2 = outcome.status === "shipped" ? "\u{1F6A2}" : outcome.status === "interrupted" ? "\u23F8" : outcome.status === "abandoned" ? "\u{1FAA6}" : outcome.status === "in_progress" ? "\u{1F7E1}" : "\u2754";
|
|
25991
26207
|
if (!params.rich) {
|
|
25992
26208
|
return { content: [{ type: "text", text: [
|
|
25993
26209
|
`# \u{1F4CB} Summary`,
|
|
@@ -25995,7 +26211,7 @@ Sessions already uploaded from that path stay until they are deleted; use recall
|
|
|
25995
26211
|
`**Session:** ${params.session_id.substring(0, 8)}...`,
|
|
25996
26212
|
aiSummary ? `
|
|
25997
26213
|
${aiSummary}
|
|
25998
|
-
` : `**Status:** ${
|
|
26214
|
+
` : `**Status:** ${statusEmoji2} ${outcome.status} \u2014 ${outcome.reason}`,
|
|
25999
26215
|
"",
|
|
26000
26216
|
"---",
|
|
26001
26217
|
"",
|
|
@@ -26009,7 +26225,7 @@ ${aiSummary}
|
|
|
26009
26225
|
lines.push(aiSummary);
|
|
26010
26226
|
lines.push("");
|
|
26011
26227
|
}
|
|
26012
|
-
lines.push(`**Status:** ${
|
|
26228
|
+
lines.push(`**Status:** ${statusEmoji2} ${outcome.status} \u2014 ${outcome.reason}`);
|
|
26013
26229
|
if (outcome.startMs && outcome.endMs) {
|
|
26014
26230
|
const mins = Math.round((outcome.endMs - outcome.startMs) / 6e4);
|
|
26015
26231
|
lines.push(`**Window:** ${new Date(outcome.startMs).toISOString().slice(0, 16).replace("T", " ")} \u2192 ${new Date(outcome.endMs).toISOString().slice(0, 16).replace("T", " ")} (${mins} min)`);
|
|
@@ -26241,13 +26457,15 @@ _Edits stayed local or window was off \u2014 try increasing buffer_minutes._` }]
|
|
|
26241
26457
|
case "recall_smart_resume": {
|
|
26242
26458
|
const params = RecallSmartResumeSchema.parse(args);
|
|
26243
26459
|
requireRemote();
|
|
26460
|
+
let priorRows = [];
|
|
26244
26461
|
let sid = params.session_id;
|
|
26245
26462
|
if (!sid) {
|
|
26246
|
-
const qs = new URLSearchParams({ limit:
|
|
26463
|
+
const qs = new URLSearchParams({ limit: String(params.depth) });
|
|
26247
26464
|
if (params.project_filter)
|
|
26248
26465
|
qs.set("project", params.project_filter);
|
|
26249
26466
|
const recent = await remoteGet(`/api/conversations/recent?${qs.toString()}`);
|
|
26250
26467
|
sid = recent.sessions?.[0]?.sessionId;
|
|
26468
|
+
priorRows = (recent.sessions ?? []).slice(1);
|
|
26251
26469
|
if (!sid) {
|
|
26252
26470
|
return { content: [{ type: "text", text: params.project_filter ? `No sessions found for project \`${params.project_filter}\` \u2014 nothing to resume.` : "No sessions have synced yet, so there is nothing to resume. Run `chat-recall index` first." }] };
|
|
26253
26471
|
}
|
|
@@ -26371,6 +26589,29 @@ _Edits stayed local or window was off \u2014 try increasing buffer_minutes._` }]
|
|
|
26371
26589
|
if (rc)
|
|
26372
26590
|
lines.push(`**Resume:** \`${rc}\``);
|
|
26373
26591
|
}
|
|
26592
|
+
if (priorRows.length > 0) {
|
|
26593
|
+
const digests = await Promise.all(priorRows.map(async (row) => {
|
|
26594
|
+
const renc = encodeURIComponent(row.sessionId);
|
|
26595
|
+
const [o, r] = await Promise.all([
|
|
26596
|
+
remoteGetSoft(`/api/conversations/${renc}/outcome`),
|
|
26597
|
+
remoteGetSoft(`/api/conversations/${renc}/related`)
|
|
26598
|
+
]);
|
|
26599
|
+
return {
|
|
26600
|
+
row,
|
|
26601
|
+
outcome: o.data,
|
|
26602
|
+
related: r.data,
|
|
26603
|
+
resumeCmd: resumeCommandFor(row.sessionId, row.tool)
|
|
26604
|
+
};
|
|
26605
|
+
}));
|
|
26606
|
+
lines.push("");
|
|
26607
|
+
lines.push(`## Before that (${digests.length} earlier session${digests.length === 1 ? "" : "s"})`);
|
|
26608
|
+
lines.push("");
|
|
26609
|
+
for (const d of digests)
|
|
26610
|
+
lines.push(...formatDigest(d));
|
|
26611
|
+
const note = crossProjectNote(projName, priorRows, !!params.project_filter);
|
|
26612
|
+
if (note)
|
|
26613
|
+
lines.push(note);
|
|
26614
|
+
}
|
|
26374
26615
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
26375
26616
|
}
|
|
26376
26617
|
case "recall_project_context": {
|
|
@@ -27232,24 +27473,40 @@ ${JSON.stringify(r, null, 2)}
|
|
|
27232
27473
|
const wal = getWAL();
|
|
27233
27474
|
wal.log("decision_record", { subject: params.subject, importance: params.importance });
|
|
27234
27475
|
const confidence = Math.min(1, params.importance / 5);
|
|
27235
|
-
const area =
|
|
27236
|
-
|
|
27237
|
-
|
|
27238
|
-
subject: kgSubject,
|
|
27239
|
-
predicate: "decided",
|
|
27240
|
-
object: params.decision,
|
|
27241
|
-
confidence,
|
|
27242
|
-
source_session: params.session_id
|
|
27476
|
+
const { area, project, workspace, scope } = resolveRecordScope({
|
|
27477
|
+
...params,
|
|
27478
|
+
cwdWorkspace: resolveWorkspaceId(process.cwd())?.slice(3) ?? null
|
|
27243
27479
|
});
|
|
27244
|
-
|
|
27480
|
+
let kgSubject = params.subject;
|
|
27481
|
+
if (area) {
|
|
27482
|
+
const rec = await remotePost("/api/decisions", {
|
|
27483
|
+
area,
|
|
27484
|
+
value: params.decision,
|
|
27485
|
+
reason: params.reason,
|
|
27486
|
+
project,
|
|
27487
|
+
workspace,
|
|
27488
|
+
scope,
|
|
27489
|
+
session_id: params.session_id
|
|
27490
|
+
});
|
|
27491
|
+
kgSubject = rec.subject;
|
|
27492
|
+
} else {
|
|
27245
27493
|
await remotePost("/api/kg/add", {
|
|
27246
27494
|
subject: kgSubject,
|
|
27247
|
-
predicate: "
|
|
27248
|
-
object: params.
|
|
27495
|
+
predicate: "decided",
|
|
27496
|
+
object: params.decision,
|
|
27249
27497
|
confidence,
|
|
27250
|
-
source_session: params.session_id
|
|
27251
|
-
supersede: false
|
|
27498
|
+
source_session: params.session_id
|
|
27252
27499
|
});
|
|
27500
|
+
if (params.reason) {
|
|
27501
|
+
await remotePost("/api/kg/add", {
|
|
27502
|
+
subject: kgSubject,
|
|
27503
|
+
predicate: "because",
|
|
27504
|
+
object: params.reason,
|
|
27505
|
+
confidence,
|
|
27506
|
+
source_session: params.session_id,
|
|
27507
|
+
supersede: false
|
|
27508
|
+
});
|
|
27509
|
+
}
|
|
27253
27510
|
}
|
|
27254
27511
|
const diaryText = params.reason ? `Decided: ${params.decision}
|
|
27255
27512
|
Subject: ${params.subject}
|
|
@@ -27268,8 +27525,9 @@ Subject: ${params.subject}`;
|
|
|
27268
27525
|
"Decision recorded.",
|
|
27269
27526
|
`- KG: ${kgSubject} \u2192 decided \u2192 ${params.decision}`,
|
|
27270
27527
|
area ? `- Area: ${area}${isKnownArea(area) ? "" : " (not a standard area \u2014 kept as a slug)"} \xB7 a later decision on this area supersedes this one` : "- No area given, so nothing this contradicts was closed. Pass `area` to make reversals work.",
|
|
27528
|
+
area ? `- Scope: ${describeWriteScope(kgSubject)}` : "",
|
|
27271
27529
|
`- Diary entry: ${diary.id} (importance ${params.importance})`
|
|
27272
|
-
].join("\n")
|
|
27530
|
+
].filter(Boolean).join("\n")
|
|
27273
27531
|
}]
|
|
27274
27532
|
};
|
|
27275
27533
|
}
|
|
@@ -27279,27 +27537,37 @@ Subject: ${params.subject}`;
|
|
|
27279
27537
|
requireRemote();
|
|
27280
27538
|
const r = await remoteGetQS("/api/decisions", {
|
|
27281
27539
|
project: p.project,
|
|
27540
|
+
// Resolved HERE: the folder group comes from the checkout, and the
|
|
27541
|
+
// checkout is on this machine.
|
|
27542
|
+
workspace: p.workspace ?? resolveWorkspaceId(process.cwd())?.slice(3),
|
|
27282
27543
|
include_candidates: p.include_candidates ? "1" : "0"
|
|
27283
27544
|
});
|
|
27284
27545
|
const rows = p.area ? r.decisions.filter((d) => d.area === canonArea(p.area)) : r.decisions;
|
|
27285
27546
|
const lines = [];
|
|
27286
27547
|
lines.push(r.project ? `Decisions for ${r.project}` : "Decisions \u2014 account-wide");
|
|
27548
|
+
if (r.project && r.workspace) {
|
|
27549
|
+
lines.push(`Resolved through the ${r.workspace.replace(/^ws:/, "")} folder group, then the account.`);
|
|
27550
|
+
}
|
|
27287
27551
|
if (rows.length === 0) {
|
|
27288
27552
|
lines.push("", p.area ? `Nothing decided for "${p.area}". You are free to choose \u2014 record it afterwards with recall_decision_record.` : "Nothing decided yet.");
|
|
27289
27553
|
} else {
|
|
27290
27554
|
lines.push("");
|
|
27291
27555
|
for (const d of rows) {
|
|
27556
|
+
const from = d.scope === "workspace" ? `the ${d.scope_key.replace(/^ws:/, "")} folder group` : d.scope === "account" ? "the account" : d.scope_key;
|
|
27292
27557
|
const tags = [
|
|
27293
|
-
d.override ?
|
|
27294
|
-
d.inherited ?
|
|
27558
|
+
d.override ? `OVERRIDES a broader decision` : "",
|
|
27559
|
+
d.inherited ? `inherited from ${from}` : "",
|
|
27295
27560
|
d.advisory ? "advisory \u2014 a personal preference, not binding" : ""
|
|
27296
27561
|
].filter(Boolean).join(" \xB7 ");
|
|
27297
27562
|
lines.push(`${d.area}: ${d.value}${d.since ? ` (since ${d.since})` : ""}${tags ? ` [${tags}]` : ""}`);
|
|
27298
27563
|
if (d.why)
|
|
27299
27564
|
lines.push(` why: ${d.why}`);
|
|
27565
|
+
if (d.by)
|
|
27566
|
+
lines.push(` decided by ${d.by}`);
|
|
27300
27567
|
const replaced = d.history.filter((h) => !h.current);
|
|
27301
|
-
for (const h of replaced)
|
|
27302
|
-
lines.push(` replaced: ${h.value} (ended ${h.to})`);
|
|
27568
|
+
for (const h of replaced) {
|
|
27569
|
+
lines.push(` replaced: ${h.value} (ended ${h.to}${h.by ? `, by ${h.by}` : ""})`);
|
|
27570
|
+
}
|
|
27303
27571
|
if (d.source_session)
|
|
27304
27572
|
lines.push(` from session ${d.source_session}`);
|
|
27305
27573
|
}
|
|
@@ -28061,7 +28329,7 @@ function installCrashGuards() {
|
|
|
28061
28329
|
reportClientEvent2("mcp_crash", { message: err.stack || err.message });
|
|
28062
28330
|
});
|
|
28063
28331
|
}
|
|
28064
|
-
var RELAY_VERSION = true ? "0.6.
|
|
28332
|
+
var RELAY_VERSION = true ? "0.6.9" : "0.0.0";
|
|
28065
28333
|
function daemonIdleSecs() {
|
|
28066
28334
|
const raw = process.env.CHAT_RECALL_DAEMON_IDLE_SECS;
|
|
28067
28335
|
if (raw === void 0) return 900;
|