chat-recall 0.5.3 → 0.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +121 -40
- package/dist/cli.js.map +1 -1
- package/dist/mcp-diagnostics.d.ts +45 -0
- package/dist/mcp-diagnostics.d.ts.map +1 -0
- package/dist/mcp-diagnostics.js +96 -0
- package/dist/mcp-diagnostics.js.map +1 -0
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +946 -677
- package/dist/mcp.js.map +1 -1
- package/dist/sync-client.d.ts.map +1 -1
- package/dist/sync-client.js +18 -0
- package/dist/sync-client.js.map +1 -1
- package/dist/watch.js +120 -41
- package/package.json +1 -1
- package/skills/chat-recall/SKILL.md +3 -2
package/dist/watch.js
CHANGED
|
@@ -6141,6 +6141,24 @@ async function parseSessionFile(sessionPath, maxMessages = 50) {
|
|
|
6141
6141
|
}
|
|
6142
6142
|
const filesToParse = hasSubagentsDir(sessionPath) ? [sessionPath, ...getSubagentPaths(sessionPath)] : [sessionPath];
|
|
6143
6143
|
let userCount = 0;
|
|
6144
|
+
const seenPrompts = /* @__PURE__ */ new Set();
|
|
6145
|
+
const recordPrompt = (raw, line, dedupe = false) => {
|
|
6146
|
+
if (userCount >= maxMessages)
|
|
6147
|
+
return;
|
|
6148
|
+
const withoutReminders = raw.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "");
|
|
6149
|
+
if (!withoutReminders.trim() || withoutReminders.trim().length <= 10)
|
|
6150
|
+
return;
|
|
6151
|
+
const cleanedText = stripInjectedBanners(withoutReminders).trim();
|
|
6152
|
+
if (cleanedText.length < 10)
|
|
6153
|
+
return;
|
|
6154
|
+
if (dedupe && seenPrompts.has(cleanedText))
|
|
6155
|
+
return;
|
|
6156
|
+
seenPrompts.add(cleanedText);
|
|
6157
|
+
content.userMessages.push({ text: cleanedText, lineNumber: line, contentType: "user" });
|
|
6158
|
+
userCount++;
|
|
6159
|
+
if (!content.firstPrompt)
|
|
6160
|
+
content.firstPrompt = cleanedText.slice(0, 1e3);
|
|
6161
|
+
};
|
|
6144
6162
|
let assistantCount = 0;
|
|
6145
6163
|
let toolCount = 0;
|
|
6146
6164
|
let lineNum = 0;
|
|
@@ -6219,19 +6237,11 @@ async function parseSessionFile(sessionPath, maxMessages = 50) {
|
|
|
6219
6237
|
text = textParts.join("\n");
|
|
6220
6238
|
}
|
|
6221
6239
|
}
|
|
6222
|
-
if (text
|
|
6223
|
-
|
|
6224
|
-
|
|
6225
|
-
|
|
6226
|
-
content
|
|
6227
|
-
text: cleanedText,
|
|
6228
|
-
lineNumber: lineNum,
|
|
6229
|
-
contentType: "user"
|
|
6230
|
-
});
|
|
6231
|
-
userCount++;
|
|
6232
|
-
if (!content.firstPrompt) {
|
|
6233
|
-
content.firstPrompt = cleanedText.slice(0, 1e3);
|
|
6234
|
-
}
|
|
6240
|
+
if (text)
|
|
6241
|
+
recordPrompt(text, lineNum);
|
|
6242
|
+
} else if (msgType === "queue-operation") {
|
|
6243
|
+
if (obj.operation === "enqueue" && typeof obj.content === "string") {
|
|
6244
|
+
recordPrompt(obj.content, lineNum, true);
|
|
6235
6245
|
}
|
|
6236
6246
|
} else if (msgType === "assistant") {
|
|
6237
6247
|
const msg = obj.message;
|
|
@@ -8566,6 +8576,9 @@ var init_generic_engine = __esm({
|
|
|
8566
8576
|
// ../engine/dist/core/backends/claude.js
|
|
8567
8577
|
import { existsSync as existsSync10, readdirSync as readdirSync6, readFileSync as readFileSync6, statSync as statSync8, openSync as openSync4, readSync as readSync4, closeSync as closeSync4 } from "fs";
|
|
8568
8578
|
import { join as join10, basename as basename8 } from "path";
|
|
8579
|
+
function stripReminders(text) {
|
|
8580
|
+
return text.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "").trim();
|
|
8581
|
+
}
|
|
8569
8582
|
function parseExitCode(text) {
|
|
8570
8583
|
const m = text.match(/(?:^|\n)\s*(?:exit\s+code|returncode|exit\s+status|exit)\s*[:=]\s*(\d+)\b/i);
|
|
8571
8584
|
return m ? Number(m[1]) : void 0;
|
|
@@ -8874,6 +8887,15 @@ var init_claude2 = __esm({
|
|
|
8874
8887
|
}
|
|
8875
8888
|
const tsIso = typeof obj.timestamp === "string" ? obj.timestamp : void 0;
|
|
8876
8889
|
const ts2 = tsIso ? Date.parse(tsIso) || mtime : mtime;
|
|
8890
|
+
if (obj.type === "queue-operation") {
|
|
8891
|
+
if (obj.operation === "enqueue" && typeof obj.content === "string") {
|
|
8892
|
+
const queued = stripReminders(obj.content);
|
|
8893
|
+
if (queued && !queued.startsWith("<task-notification")) {
|
|
8894
|
+
events.push({ kind: "user", ts: ts2, tsIso, line: lineNum, text: queued });
|
|
8895
|
+
}
|
|
8896
|
+
}
|
|
8897
|
+
continue;
|
|
8898
|
+
}
|
|
8877
8899
|
if (obj.type === "user") {
|
|
8878
8900
|
const msg = obj.message;
|
|
8879
8901
|
const content = msg?.content;
|
|
@@ -8907,9 +8929,9 @@ var init_claude2 = __esm({
|
|
|
8907
8929
|
}
|
|
8908
8930
|
}
|
|
8909
8931
|
}
|
|
8910
|
-
|
|
8911
|
-
|
|
8912
|
-
|
|
8932
|
+
const spoken = stripReminders(text);
|
|
8933
|
+
if (spoken)
|
|
8934
|
+
events.push({ kind: "user", ts: ts2, tsIso, line: lineNum, text: spoken });
|
|
8913
8935
|
for (const tr of toolResults) {
|
|
8914
8936
|
events.push({
|
|
8915
8937
|
kind: "tool_result",
|
|
@@ -10524,23 +10546,24 @@ var init_codex2 = __esm({
|
|
|
10524
10546
|
return null;
|
|
10525
10547
|
}
|
|
10526
10548
|
/**
|
|
10527
|
-
|
|
10528
|
-
|
|
10529
|
-
|
|
10530
|
-
|
|
10531
|
-
|
|
10532
|
-
|
|
10533
|
-
|
|
10534
|
-
|
|
10535
|
-
|
|
10536
|
-
|
|
10537
|
-
|
|
10538
|
-
|
|
10539
|
-
|
|
10540
|
-
|
|
10541
|
-
|
|
10542
|
-
|
|
10543
|
-
|
|
10549
|
+
* Stream a Codex JSONL rollout into canonical events. Codex separates:
|
|
10550
|
+
* - event_msg + payload.type='user_message' → user
|
|
10551
|
+
* - response_item + payload.type='message' → user OR assistant_text,
|
|
10552
|
+
* by payload.role
|
|
10553
|
+
* - response_item + payload.type='function_call' → tool_use
|
|
10554
|
+
* - response_item + payload.type='function_call_output' → tool_result
|
|
10555
|
+
*
|
|
10556
|
+
* Filters Codex's wrapper-bearing user messages (environment_context etc.).
|
|
10557
|
+
*
|
|
10558
|
+
* **Subagent fan-out:** Codex orchestrates work via `spawn_agent` calls
|
|
10559
|
+
* that produce sibling rollout files in the same YYYY/MM/DD directory.
|
|
10560
|
+
* Sub-agent rollouts carry `payload.source.subagent.thread_spawn` linking
|
|
10561
|
+
* back to the parent. The actual file edits often happen inside those
|
|
10562
|
+
* subagent rollouts — without folding their events into the parent's
|
|
10563
|
+
* stream, replay/scan would see the parent as edit-free. We append every
|
|
10564
|
+
* subagent rollout's events here so the generic engine sees one unified
|
|
10565
|
+
* stream per user-facing session.
|
|
10566
|
+
*/
|
|
10544
10567
|
readEvents(rawId) {
|
|
10545
10568
|
const located = findCodexSessionFile(this.toRawId(rawId));
|
|
10546
10569
|
if (!located)
|
|
@@ -10610,6 +10633,11 @@ var init_codex2 = __esm({
|
|
|
10610
10633
|
if (text && !isCodexInjectedWrapper2(text)) {
|
|
10611
10634
|
events.push({ kind: "user", ts: ts2, tsIso, line: lineNum, text });
|
|
10612
10635
|
}
|
|
10636
|
+
} else if (obj.type === "response_item" && payload.type === "message" && payload.role === "user") {
|
|
10637
|
+
const text = flattenContent(payload.content).trim();
|
|
10638
|
+
if (text && !isCodexInjectedWrapper2(text)) {
|
|
10639
|
+
events.push({ kind: "user", ts: ts2, tsIso, line: lineNum, text });
|
|
10640
|
+
}
|
|
10613
10641
|
} else if (obj.type === "response_item" && payload.type === "message" && payload.role === "assistant") {
|
|
10614
10642
|
const text = flattenContent(payload.content).trim();
|
|
10615
10643
|
if (text)
|
|
@@ -17111,7 +17139,9 @@ var init_extractor_version = __esm({
|
|
|
17111
17139
|
"use strict";
|
|
17112
17140
|
BASE_EXTRACTOR_VERSION = 2;
|
|
17113
17141
|
TOOL_EXTRACTOR_BUMP = {
|
|
17114
|
-
agy: 2
|
|
17142
|
+
agy: 2,
|
|
17143
|
+
claude: 1,
|
|
17144
|
+
codex: 1
|
|
17115
17145
|
};
|
|
17116
17146
|
EXTRACTOR_VERSION = BASE_EXTRACTOR_VERSION;
|
|
17117
17147
|
}
|
|
@@ -17429,7 +17459,7 @@ var init_http = __esm({
|
|
|
17429
17459
|
CLI_VERSION = JSON.parse(readFileSync31(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION;
|
|
17430
17460
|
} catch {
|
|
17431
17461
|
try {
|
|
17432
|
-
if (true) CLI_VERSION = "0.5.
|
|
17462
|
+
if (true) CLI_VERSION = "0.5.5";
|
|
17433
17463
|
} catch {
|
|
17434
17464
|
}
|
|
17435
17465
|
}
|
|
@@ -17996,6 +18026,17 @@ async function syncToTarget(cred, opts = {}) {
|
|
|
17996
18026
|
if (res.status === 401 || res.status === 403) {
|
|
17997
18027
|
throw new Error(`server rejected this machine's device token (HTTP ${res.status} \u2014 revoked?). Reconnect with: chat-recall login ${base2}`);
|
|
17998
18028
|
}
|
|
18029
|
+
if (res.status === 402) {
|
|
18030
|
+
let detail = text;
|
|
18031
|
+
try {
|
|
18032
|
+
const body2 = JSON.parse(text);
|
|
18033
|
+
detail = body2.error ?? text;
|
|
18034
|
+
if (body2.resetsAt) detail += ` (resets ${new Date(body2.resetsAt).toISOString().slice(0, 10)})`;
|
|
18035
|
+
if (body2.upgradeUrl) detail += ` \u2014 ${body2.upgradeUrl}`;
|
|
18036
|
+
} catch {
|
|
18037
|
+
}
|
|
18038
|
+
throw new Error(`sync paused by the server: ${detail}`);
|
|
18039
|
+
}
|
|
17999
18040
|
if (!retryable) throw new Error(`sync failed: HTTP ${res.status} ${text}`);
|
|
18000
18041
|
if (attempt >= RETRY_DELAYS_MS.length) throw new Error(`sync failed after ${attempt + 1} attempts: HTTP ${res.status} ${text}`);
|
|
18001
18042
|
const retryAfterS = Number(res.headers.get("retry-after"));
|
|
@@ -18880,7 +18921,7 @@ var init_sync_client = __esm({
|
|
|
18880
18921
|
ownVersion = JSON.parse(readFileSync33(join40(dir, "../package.json"), "utf-8")).version || ownVersion;
|
|
18881
18922
|
} catch {
|
|
18882
18923
|
try {
|
|
18883
|
-
if (true) ownVersion = "0.5.
|
|
18924
|
+
if (true) ownVersion = "0.5.5";
|
|
18884
18925
|
} catch {
|
|
18885
18926
|
}
|
|
18886
18927
|
}
|
|
@@ -19863,6 +19904,14 @@ async function collectCode(opts) {
|
|
|
19863
19904
|
agentPrompt: manifestPrompt(v.violation_type ?? "", file, v.line ?? null, v.description ?? "")
|
|
19864
19905
|
});
|
|
19865
19906
|
}
|
|
19907
|
+
{
|
|
19908
|
+
const before = findings.length;
|
|
19909
|
+
const own = findings.filter((f) => !f.file || !isGenerated(f.file));
|
|
19910
|
+
if (own.length !== before) {
|
|
19911
|
+
findings.splice(0, before, ...own);
|
|
19912
|
+
log6(`dropped ${before - own.length} finding(s) in vendored/generated files`);
|
|
19913
|
+
}
|
|
19914
|
+
}
|
|
19866
19915
|
const godSet = new Set((coup?.god_modules ?? []).map((m) => rel(m.file)));
|
|
19867
19916
|
const cloneSet = /* @__PURE__ */ new Set();
|
|
19868
19917
|
for (const g of clones?.groups ?? [])
|
|
@@ -19948,7 +19997,9 @@ async function collectCode(opts) {
|
|
|
19948
19997
|
}
|
|
19949
19998
|
for (const g of clones?.groups ?? []) {
|
|
19950
19999
|
if ((g.count ?? 0) >= 3 || (g.lines ?? 0) >= 12) {
|
|
19951
|
-
const fns = (g.functions ?? []).filter((x) => x && typeof x === "object");
|
|
20000
|
+
const fns = (g.functions ?? []).filter((x) => x && typeof x === "object").filter((x) => !isGenerated(rel(x.file)));
|
|
20001
|
+
if (fns.length < 2)
|
|
20002
|
+
continue;
|
|
19952
20003
|
const names = [...new Set(fns.map((x) => x.name))];
|
|
19953
20004
|
const locs = fns.map((x) => rel(x.file)).slice(0, 5);
|
|
19954
20005
|
pushAction((g.lines ?? 0) * (g.count ?? 0) >= 150 ? 1 : 2, "duplication", `${names.join(", ")} copy-pasted ${g.count}\xD7 (${g.lines} lines each)`, "Extract one shared implementation and replace the copies.", locs.map((f) => ({ file: f })), clonePrompt(names, g.lines ?? 0, g.count ?? 0, locs));
|
|
@@ -19967,10 +20018,14 @@ async function collectCode(opts) {
|
|
|
19967
20018
|
}
|
|
19968
20019
|
for (const m of (coup?.god_modules ?? []).slice(0, 5)) {
|
|
19969
20020
|
const file = rel(m.file);
|
|
20021
|
+
if (isGenerated(file))
|
|
20022
|
+
continue;
|
|
19970
20023
|
pushAction(2, "modularity", `God module ${file} (fan-in ${m.fan_in}, fan-out ${m.fan_out})`, "Split into cohesive sub-modules; introduce interfaces to cut fan-out.", [{ file }], couplingPrompt(file, m.fan_in ?? 0, m.fan_out ?? 0, Math.round((m.instability ?? 0) * 100) / 100));
|
|
19971
20024
|
}
|
|
19972
20025
|
for (const c of (cyc?.cycles ?? []).slice(0, 5)) {
|
|
19973
|
-
const chain = (c.files ?? []).map(rel);
|
|
20026
|
+
const chain = (c.files ?? []).map(rel).filter((f) => !isGenerated(f));
|
|
20027
|
+
if (chain.length < 2)
|
|
20028
|
+
continue;
|
|
19974
20029
|
pushAction(2, "architecture", `Circular dependency (${chain.length} files)`, "Break the cycle: extract the shared type/interface into a separate module.", chain.slice(0, 4).map((f) => ({ file: f })), cyclePrompt(chain.slice(0, 6)));
|
|
19975
20030
|
}
|
|
19976
20031
|
for (const h of hotspotsAll.slice(0, 5)) {
|
|
@@ -20109,7 +20164,31 @@ var init_collector = __esm({
|
|
|
20109
20164
|
"migration_parity",
|
|
20110
20165
|
"manifest_compliance"
|
|
20111
20166
|
];
|
|
20112
|
-
GENERATED = [
|
|
20167
|
+
GENERATED = [
|
|
20168
|
+
"generated/",
|
|
20169
|
+
"__generated__/",
|
|
20170
|
+
".pb.go",
|
|
20171
|
+
".gen.",
|
|
20172
|
+
"_pb2.py",
|
|
20173
|
+
"/gen/",
|
|
20174
|
+
".g.dart",
|
|
20175
|
+
"/node_modules/",
|
|
20176
|
+
"/vendor/",
|
|
20177
|
+
"/third_party/",
|
|
20178
|
+
"/site-packages/",
|
|
20179
|
+
".lock",
|
|
20180
|
+
"/dist/",
|
|
20181
|
+
"/build/",
|
|
20182
|
+
"/out/",
|
|
20183
|
+
"/target/",
|
|
20184
|
+
"/.next/",
|
|
20185
|
+
"/coverage/",
|
|
20186
|
+
".min.js",
|
|
20187
|
+
".min.css",
|
|
20188
|
+
".bundle.js",
|
|
20189
|
+
"-bundle.js",
|
|
20190
|
+
".map"
|
|
20191
|
+
];
|
|
20113
20192
|
CF_RE = /\b(if|for|while|switch|case|catch|elif|when|loop|&&|\|\||\?)\b|\?/g;
|
|
20114
20193
|
DEPTH = 2;
|
|
20115
20194
|
MCP_OUT_MAX_BYTES = 64 * 1024 * 1024;
|
|
@@ -20959,7 +21038,7 @@ try {
|
|
|
20959
21038
|
ownVersion2 = JSON.parse(readFileSync37(join44(dir, "../package.json"), "utf-8")).version || ownVersion2;
|
|
20960
21039
|
} catch {
|
|
20961
21040
|
try {
|
|
20962
|
-
if (true) ownVersion2 = "0.5.
|
|
21041
|
+
if (true) ownVersion2 = "0.5.5";
|
|
20963
21042
|
} catch {
|
|
20964
21043
|
}
|
|
20965
21044
|
}
|
|
@@ -21134,7 +21213,7 @@ async function runCollectorMigration(opts) {
|
|
|
21134
21213
|
|
|
21135
21214
|
// auto-indexer/indexer.ts
|
|
21136
21215
|
import { existsSync as existsSync44, readFileSync as readFileSync40 } from "fs";
|
|
21137
|
-
var CLI_VERSION3 = true ? "0.5.
|
|
21216
|
+
var CLI_VERSION3 = true ? "0.5.5" : "0.0.0";
|
|
21138
21217
|
var DEBOUNCE_MS = 5e3;
|
|
21139
21218
|
function ts() {
|
|
21140
21219
|
return (/* @__PURE__ */ new Date()).toISOString();
|
package/package.json
CHANGED
|
@@ -31,6 +31,7 @@ read except these, which change stored memory or state — treat each as an edit
|
|
|
31
31
|
and apply the test in `chat-recall-memory` before calling one:
|
|
32
32
|
`recall_kg_add`, `recall_kg_invalidate`, `recall_decision_record`,
|
|
33
33
|
`recall_diary_write`, `recall_set`, `recall_task_create`, `recall_task_update`,
|
|
34
|
+
`recall_task_comment`,
|
|
34
35
|
`recall_security_dismiss`, `recall_rename_session`, `recall_regenerate_summary`,
|
|
35
36
|
`recall_reclassify`, `recall_index`, `recall_code_index`.
|
|
36
37
|
|
|
@@ -65,9 +66,9 @@ tool is registered and never named here, so adding a tool means placing it.
|
|
|
65
66
|
**Findings, ranked** — `recall_claude_suggestions` (every finding that becomes an agent-instruction change — the CLAUDE.md rules and skill installs — merged across account scope and every indexed project) and `recall_improvements` (everything else, most urgent first; `create_tasks: true` opens one team task per item). They split the same engines, so an item never appears in both. Neither needs the codeindex binary: without it you still get the account-level half.
|
|
66
67
|
**What changed** — `recall_edits_timeline` (cross-tool edits), `recall_diff` (per-session diffs), `recall_commits` (did it actually land), `recall_markers` (where a session went sideways).
|
|
67
68
|
**Durable memory / KG** — `recall_kg_query` / `recall_kg_add` / `recall_kg_invalidate` / `recall_kg_timeline` / `recall_kg_stats`, `recall_decision_record`, `recall_diary_write` / `recall_diary_read`, `recall_set` / `recall_get`.
|
|
68
|
-
**Team** — `recall_tasks` (the shared task board), `recall_task_create`, `recall_task_update` (status, assignee,
|
|
69
|
+
**Team** — `recall_tasks` (the shared task board), `recall_task_create`, `recall_task_update` (status, assignee, linked session; task ids look like `t_…`; when you start work on a task, set it `in_progress` and pass your session id as `linked_session_id` so the board can verify the work shipped), `recall_task_comment` (leave a progress note on a card), `recall_team_activity` (per-teammate × per-project rollup), `recall_shares` (which projects are shared — private by default, nothing is visible to teammates until shared).
|
|
69
70
|
**Security** — `recall_security_summary` (leaked secrets that still need action — start here for "did I paste a key somewhere"), `recall_security_session` (findings for one session), `recall_security_dismiss` (mark rotated / false positive — do this only when the user confirms which), `recall_security_rules` (tenant detection rules; also tests a regex).
|
|
70
|
-
**Health / maintenance** — `recall_status` (is the index alive, what is synced), `recall_index` (sync now), `recall_heal_audit` (sessions whose rendered text is thinner than the raw archive, i.e. truncated upstream), `recall_regenerate_summary` (stored summary is stale or wrong), `recall_reclassify` (re-run the classifier over old chunks), `recall_rename_session`.
|
|
71
|
+
**Health / maintenance** — `recall_status` (is the index alive, what is synced), `recall_index` (sync now), `recall_help` (names the tools the lean profile leaves unlisted — they all still work by name), `recall_heal_audit` (sessions whose rendered text is thinner than the raw archive, i.e. truncated upstream), `recall_regenerate_summary` (stored summary is stale or wrong), `recall_reclassify` (re-run the classifier over old chunks), `recall_rename_session`.
|
|
71
72
|
|
|
72
73
|
## If you're unsure where to start
|
|
73
74
|
|