chat-recall 0.7.9 → 0.7.11

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/watch.js CHANGED
@@ -134,6 +134,72 @@ var init_banners = __esm({
134
134
  }
135
135
  });
136
136
 
137
+ // ../engine/dist/core/claude-prompt-origin.js
138
+ function originFromText(text) {
139
+ const head = text.trimStart();
140
+ for (const [prefix, origin] of INJECTED_PREFIXES) {
141
+ if (head.startsWith(prefix))
142
+ return origin;
143
+ }
144
+ return "human";
145
+ }
146
+ function declaredOrigin(value) {
147
+ if (!value || typeof value !== "object")
148
+ return void 0;
149
+ const kind = value.kind;
150
+ return typeof kind === "string" && kind ? kind : void 0;
151
+ }
152
+ function userRecordOrigin(obj, text) {
153
+ if (obj.isSidechain === true)
154
+ return "subagent";
155
+ const declared = declaredOrigin(obj.origin);
156
+ if (declared && declared !== "human")
157
+ return declared;
158
+ if (obj.isMeta === true)
159
+ return "meta";
160
+ if (declared === "human")
161
+ return "human";
162
+ return originFromText(text);
163
+ }
164
+ function queuedCommandPrompt(obj) {
165
+ if (obj.type !== "attachment")
166
+ return null;
167
+ const att = obj.attachment;
168
+ if (!att || att.type !== "queued_command")
169
+ return null;
170
+ const text = promptText(att.prompt);
171
+ if (!text)
172
+ return null;
173
+ if (obj.isSidechain === true)
174
+ return { text, origin: "subagent" };
175
+ const declared = declaredOrigin(att.origin);
176
+ if (declared)
177
+ return { text, origin: declared };
178
+ return { text, origin: att.humanTurn === true ? "human" : originFromText(text) };
179
+ }
180
+ function promptText(prompt) {
181
+ if (typeof prompt === "string")
182
+ return prompt;
183
+ if (!Array.isArray(prompt))
184
+ return "";
185
+ return prompt.filter((b) => !!b && typeof b === "object" && b.type === "text" && typeof b.text === "string").map((b) => b.text).join("\n");
186
+ }
187
+ function isPersonMessage(m) {
188
+ return m.role === "user" && !m.origin;
189
+ }
190
+ var INJECTED_PREFIXES;
191
+ var init_claude_prompt_origin = __esm({
192
+ "../engine/dist/core/claude-prompt-origin.js"() {
193
+ "use strict";
194
+ INJECTED_PREFIXES = [
195
+ ["<task-notification", "task-notification"],
196
+ ["<agent-message", "peer"],
197
+ ["Another Claude session sent a message:", "peer"],
198
+ ["Stop hook feedback:", "hook"]
199
+ ];
200
+ }
201
+ });
202
+
137
203
  // ../engine/dist/core/project-path-match.js
138
204
  function canonicalProjectPath(p) {
139
205
  return (p || "").trim().replace(/\\/g, "/").replace(/[-_]/g, "/").replace(/\/{2,}/g, "/").replace(/\/+$/, "").toLowerCase();
@@ -5716,7 +5782,8 @@ async function parseSessionFile(sessionPath, maxMessages = 50) {
5716
5782
  const filesToParse = hasSubagentsDir(sessionPath) ? [sessionPath, ...getSubagentPaths(sessionPath)] : [sessionPath];
5717
5783
  let userCount = 0;
5718
5784
  const seenPrompts = /* @__PURE__ */ new Set();
5719
- const recordPrompt = (raw, line, dedupe = false) => {
5785
+ const pendingQueued = /* @__PURE__ */ new Set();
5786
+ const recordPrompt = (raw, line, via = "record") => {
5720
5787
  if (userCount >= maxMessages)
5721
5788
  return;
5722
5789
  const withoutReminders = raw.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "");
@@ -5725,8 +5792,14 @@ async function parseSessionFile(sessionPath, maxMessages = 50) {
5725
5792
  const cleanedText = stripInjectedBanners(withoutReminders).trim();
5726
5793
  if (cleanedText.length < 10)
5727
5794
  return;
5728
- if (dedupe && seenPrompts.has(cleanedText))
5795
+ if (via === "enqueue") {
5796
+ if (seenPrompts.has(cleanedText))
5797
+ return;
5798
+ pendingQueued.add(cleanedText);
5799
+ } else if (pendingQueued.has(cleanedText)) {
5800
+ pendingQueued.delete(cleanedText);
5729
5801
  return;
5802
+ }
5730
5803
  seenPrompts.add(cleanedText);
5731
5804
  content.userMessages.push({ text: cleanedText, lineNumber: line, contentType: "user" });
5732
5805
  userCount++;
@@ -5811,12 +5884,16 @@ async function parseSessionFile(sessionPath, maxMessages = 50) {
5811
5884
  text = textParts.join("\n");
5812
5885
  }
5813
5886
  }
5814
- if (text)
5887
+ if (text && userRecordOrigin(obj, text) === "human")
5815
5888
  recordPrompt(text, lineNum);
5816
5889
  } else if (msgType === "queue-operation") {
5817
- if (obj.operation === "enqueue" && typeof obj.content === "string") {
5818
- recordPrompt(obj.content, lineNum, true);
5890
+ if (obj.operation === "enqueue" && typeof obj.content === "string" && originFromText(obj.content) === "human") {
5891
+ recordPrompt(obj.content, lineNum, "enqueue");
5819
5892
  }
5893
+ } else if (msgType === "attachment") {
5894
+ const queued = queuedCommandPrompt(obj);
5895
+ if (queued?.origin === "human")
5896
+ recordPrompt(queued.text, lineNum, "delivery");
5820
5897
  } else if (msgType === "assistant") {
5821
5898
  const msg = obj.message;
5822
5899
  if (!msg || typeof msg !== "object")
@@ -5920,6 +5997,7 @@ var init_session = __esm({
5920
5997
  "../engine/dist/parsers/session.js"() {
5921
5998
  "use strict";
5922
5999
  init_banners();
6000
+ init_claude_prompt_origin();
5923
6001
  init_claude2();
5924
6002
  init_tool_paths();
5925
6003
  init_project_dir_name();
@@ -6008,7 +6086,16 @@ function messagesFromRawLines(raw) {
6008
6086
  const cleaned = stripBanners(text);
6009
6087
  if (!cleaned)
6010
6088
  continue;
6011
- messages.push({ line, role: "user", content: cleaned, timestamp });
6089
+ const origin = userRecordOrigin(obj, cleaned);
6090
+ messages.push({ line, role: "user", content: cleaned, timestamp, ...origin === "human" ? {} : { origin } });
6091
+ } else if (obj.type === "attachment") {
6092
+ const queued = queuedCommandPrompt(obj);
6093
+ if (!queued)
6094
+ continue;
6095
+ const cleaned = stripBanners(queued.text);
6096
+ if (!cleaned)
6097
+ continue;
6098
+ messages.push({ line, role: "user", content: cleaned, timestamp, ...queued.origin === "human" ? {} : { origin: queued.origin } });
6012
6099
  } else if (obj.type === "assistant") {
6013
6100
  const blocks = Array.isArray(obj.message?.content) ? obj.message.content : [];
6014
6101
  const thinkingBlock = blocks.find((c) => c?.type === "thinking");
@@ -6104,6 +6191,7 @@ var init_claude = __esm({
6104
6191
  "../engine/dist/transcript/claude.js"() {
6105
6192
  "use strict";
6106
6193
  init_logger();
6194
+ init_claude_prompt_origin();
6107
6195
  log2 = createLogger("transcript-claude");
6108
6196
  INJECTED_BANNERS2 = [
6109
6197
  /MCP issues detected\. ?Run \/mcp list for status\.?/g,
@@ -7224,10 +7312,15 @@ function summarizeMarkers(marked) {
7224
7312
  }
7225
7313
  return counts;
7226
7314
  }
7227
- var PROFANITY, PROFANITY_LOOSE, APPROVAL_HEAD, NEG_HEAD, QUESTION_HEAD, DIRECTIVE_HEAD, CLARIFY_PHRASES, FRUSTRATED_PHRASES;
7315
+ function markersFromTurns(sessionId, turns) {
7316
+ const prompts = turns.filter((t) => t.kind === "user" && t.text).map((t) => ({ line: t.line, ts: t.ts, tsIso: t.tsIso, ...markPrompt(t.text) }));
7317
+ return { v: MARKERS_VERSION, sessionId, prompts, summary: summarizeMarkers(prompts) };
7318
+ }
7319
+ var PROFANITY, PROFANITY_LOOSE, APPROVAL_HEAD, NEG_HEAD, QUESTION_HEAD, DIRECTIVE_HEAD, CLARIFY_PHRASES, FRUSTRATED_PHRASES, MARKERS_VERSION;
7228
7320
  var init_session_sentiment = __esm({
7229
7321
  "../engine/dist/core/session-sentiment.js"() {
7230
7322
  "use strict";
7323
+ init_claude_prompt_origin();
7231
7324
  PROFANITY = /\b(fuck(?:ing|er|ed)?|shit|ffs|wtf|bullshit|crap|damn(?:it)?|hell)\b/i;
7232
7325
  PROFANITY_LOOSE = /\b(fu[ckj]+(?:ing|er|ed|c?k)?|fukc|fucj|sht|fk|wt[ft])\b/i;
7233
7326
  APPROVAL_HEAD = /^(?:yes|y|ok(?:ay)?|sure|alright|fine|i\s*approve|approved|go\s*(?:on|ahead)|do\s*it|continue|please\s*do|sounds?\s*good|let'?s\s*go|ship\s*it|👍|✓+)\b/i;
@@ -7236,6 +7329,7 @@ var init_session_sentiment = __esm({
7236
7329
  DIRECTIVE_HEAD = /^(?:please\s+)?(?:use|implement|build|make|add|create|fix|remove|delete|update|refactor|run|deploy|push|merge|test|check|investigate|find|search|show|list|generate|set\s+up|ensure)\b/i;
7237
7330
  CLARIFY_PHRASES = /\b(?:wdym|what do you mean|explain|clarify|what is|what's|huh\??|i don'?t understand)\b/i;
7238
7331
  FRUSTRATED_PHRASES = /\b(?:why (?:the )?(?:fuck|hell)|wtf|are you (?:kidding|serious)|stop slacking|stop making|why did you|why didn'?t you|i (?:fucking )?(?:asked|told you)|are you (?:listening|even)|come\s*on|just\s+do)\b/i;
7332
+ MARKERS_VERSION = 2;
7239
7333
  }
7240
7334
  });
7241
7335
 
@@ -8102,6 +8196,7 @@ var init_claude2 = __esm({
8102
8196
  init_first_prompt();
8103
8197
  init_tail_read();
8104
8198
  init_project_dir_name();
8199
+ init_claude_prompt_origin();
8105
8200
  init_generic_engine();
8106
8201
  UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
8107
8202
  ClaudeBackend = class {
@@ -8409,6 +8504,15 @@ var init_claude2 = __esm({
8409
8504
  }
8410
8505
  appendEventsFromText(events, raw, mtime, startLine) {
8411
8506
  let lineNum = startLine;
8507
+ const queued = /* @__PURE__ */ new Map();
8508
+ const superseded = /* @__PURE__ */ new Set();
8509
+ const pushPrompt = (e) => {
8510
+ const pending = queued.get(e.text);
8511
+ const earlier = pending?.shift();
8512
+ if (earlier)
8513
+ superseded.add(earlier);
8514
+ events.push(e);
8515
+ };
8412
8516
  {
8413
8517
  for (const line of raw.split("\n")) {
8414
8518
  lineNum++;
@@ -8424,13 +8528,26 @@ var init_claude2 = __esm({
8424
8528
  const ts = tsIso ? Date.parse(tsIso) || mtime : mtime;
8425
8529
  if (obj.type === "queue-operation") {
8426
8530
  if (obj.operation === "enqueue" && typeof obj.content === "string") {
8427
- const queued = stripReminders(obj.content);
8428
- if (queued && !queued.startsWith("<task-notification")) {
8429
- events.push({ kind: "user", ts, tsIso, line: lineNum, text: queued });
8531
+ const text = stripReminders(obj.content);
8532
+ if (text && originFromText(text) === "human") {
8533
+ const e = { kind: "user", ts, tsIso, line: lineNum, text };
8534
+ events.push(e);
8535
+ const list = queued.get(text);
8536
+ if (list)
8537
+ list.push(e);
8538
+ else
8539
+ queued.set(text, [e]);
8430
8540
  }
8431
8541
  }
8432
8542
  continue;
8433
8543
  }
8544
+ const delivered = queuedCommandPrompt(obj);
8545
+ if (delivered) {
8546
+ const text = stripReminders(delivered.text);
8547
+ if (text && delivered.origin === "human")
8548
+ pushPrompt({ kind: "user", ts, tsIso, line: lineNum, text });
8549
+ continue;
8550
+ }
8434
8551
  if (obj.type === "user") {
8435
8552
  const msg = obj.message;
8436
8553
  const content = msg?.content;
@@ -8465,8 +8582,9 @@ var init_claude2 = __esm({
8465
8582
  }
8466
8583
  }
8467
8584
  const spoken = stripReminders(text);
8468
- if (spoken)
8469
- events.push({ kind: "user", ts, tsIso, line: lineNum, text: spoken });
8585
+ if (spoken && userRecordOrigin(obj, spoken) === "human") {
8586
+ pushPrompt({ kind: "user", ts, tsIso, line: lineNum, text: spoken });
8587
+ }
8470
8588
  for (const tr of toolResults) {
8471
8589
  events.push({
8472
8590
  kind: "tool_result",
@@ -8515,6 +8633,13 @@ var init_claude2 = __esm({
8515
8633
  }
8516
8634
  }
8517
8635
  }
8636
+ if (superseded.size > 0) {
8637
+ let w = 0;
8638
+ for (const e of events)
8639
+ if (!superseded.has(e))
8640
+ events[w++] = e;
8641
+ events.length = w;
8642
+ }
8518
8643
  return lineNum;
8519
8644
  }
8520
8645
  // ── Per-session operations — all delegate to the generic engine ─
@@ -18077,7 +18202,7 @@ var init_sync_fields = __esm({
18077
18202
  import { existsSync as existsSync39, mkdirSync as mkdirSync8, openSync as openSync8, writeFileSync as writeFileSync7, readFileSync as readFileSync31, rmSync as rmSync4, statSync as statSync35, closeSync as closeSync8 } from "fs";
18078
18203
  import { dirname as dirname17, join as join40 } from "path";
18079
18204
  function acquireIndexLock(opts) {
18080
- const lockPath = lockFilePath();
18205
+ const lockPath = lockFilePath(opts.file);
18081
18206
  mkdirSync8(dirname17(lockPath), { recursive: true });
18082
18207
  if (existsSync39(lockPath)) {
18083
18208
  if (!stealStaleLock(lockPath, opts.staleAfterMs ?? 10 * 6e4)) {
@@ -18119,8 +18244,8 @@ function acquireIndexLock(opts) {
18119
18244
  }
18120
18245
  };
18121
18246
  }
18122
- function lockFilePath() {
18123
- return join40(getIndexDir(), ".compact.lock");
18247
+ function lockFilePath(file = ".compact.lock") {
18248
+ return join40(getIndexDir(), file);
18124
18249
  }
18125
18250
  function stealStaleLock(lockPath, staleAfterMs) {
18126
18251
  try {
@@ -18187,7 +18312,7 @@ var init_http = __esm({
18187
18312
  CLI_VERSION = JSON.parse(readFileSync32(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION;
18188
18313
  } catch {
18189
18314
  try {
18190
- if (true) CLI_VERSION = "0.7.9";
18315
+ if (true) CLI_VERSION = "0.7.11";
18191
18316
  } catch {
18192
18317
  }
18193
18318
  }
@@ -18232,8 +18357,7 @@ function collectDerivedRows(ref, mtime, count, maxRowBytes) {
18232
18357
  if (fullOutcome.found) data = fullOutcome.commits;
18233
18358
  } else if (kind === "markers") {
18234
18359
  const turns = extractTurnsAny(id, { maxTurns: 5e4 });
18235
- const prompts = turns.turns.filter((t) => t.kind === "user" && t.text).map((t) => ({ line: t.line, ts: t.ts, tsIso: t.tsIso, ...markPrompt(t.text) }));
18236
- data = { sessionId: id, prompts, summary: summarizeMarkers(prompts) };
18360
+ data = markersFromTurns(id, turns.turns);
18237
18361
  }
18238
18362
  } catch {
18239
18363
  data = null;
@@ -18682,7 +18806,7 @@ var init_telemetry = __esm({
18682
18806
  init_sync_client();
18683
18807
  cliVersion = () => {
18684
18808
  try {
18685
- return true ? "0.7.9" : "dev";
18809
+ return true ? "0.7.11" : "dev";
18686
18810
  } catch {
18687
18811
  return "dev";
18688
18812
  }
@@ -18897,6 +19021,8 @@ var init_client_events = __esm({
18897
19021
  // src/auto-update.ts
18898
19022
  var auto_update_exports = {};
18899
19023
  __export(auto_update_exports, {
19024
+ LOCKED_REASON: () => LOCKED_REASON,
19025
+ STOPPING_REASON: () => STOPPING_REASON,
18900
19026
  clearUpdateFailures: () => clearUpdateFailures,
18901
19027
  compareVersions: () => compareVersions,
18902
19028
  executeAutoUpdate: () => executeAutoUpdate,
@@ -18908,8 +19034,10 @@ __export(auto_update_exports, {
18908
19034
  planAutoUpdate: () => planAutoUpdate,
18909
19035
  readUpdateState: () => readUpdateState,
18910
19036
  resolveNpm: () => resolveNpm,
19037
+ restartCommand: () => restartCommand,
18911
19038
  runAutoUpdate: () => runAutoUpdate,
18912
19039
  runningPrefix: () => runningPrefix,
19040
+ stopAutoUpdates: () => stopAutoUpdates,
18913
19041
  sweepStaleStaging: () => sweepStaleStaging,
18914
19042
  tarballUrl: () => tarballUrl
18915
19043
  });
@@ -19005,6 +19133,10 @@ async function executeAutoUpdate(plan, deps) {
19005
19133
  };
19006
19134
  const tgz = join41(dir, "chat-recall.tgz");
19007
19135
  writeFileSync8(tgz, buf);
19136
+ if (deps.stopping?.()) {
19137
+ discardStaging();
19138
+ return { updated: false, reason: STOPPING_REASON };
19139
+ }
19008
19140
  try {
19009
19141
  deps.install(tgz);
19010
19142
  } catch (e) {
@@ -19019,9 +19151,11 @@ async function executeAutoUpdate(plan, deps) {
19019
19151
  reason: `install reported success but ${landed} is still on disk (wanted ${plan.to}) \u2014 the package that runs is not the one npm wrote. Reinstall by hand: npm install -g ${plan.url}`
19020
19152
  };
19021
19153
  }
19022
- try {
19023
- deps.restart(deps.platform ?? process.platform);
19024
- } catch {
19154
+ if (!deps.stopping?.()) {
19155
+ try {
19156
+ deps.restart(deps.platform ?? process.platform);
19157
+ } catch {
19158
+ }
19025
19159
  }
19026
19160
  return { updated: true, reason: `updated ${plan.from} \u2192 ${plan.to}`, from: plan.from, to: plan.to };
19027
19161
  }
@@ -19080,10 +19214,18 @@ function realInstall(tgz) {
19080
19214
  npmInstall(npm, ["install", "-g", "--prefix", join41(homedir15(), ".local"), tgz]);
19081
19215
  }
19082
19216
  }
19217
+ function acquireUpdateLock() {
19218
+ return acquireIndexLock({ kind: "auto-update", file: ".update.lock", staleAfterMs: 10 * 6e4 });
19219
+ }
19220
+ function restartCommand(platform2) {
19221
+ if (platform2 === "linux") return { cmd: "systemctl --user --no-block restart chat-recall-watch.service" };
19222
+ if (platform2 === "darwin") return { cmd: "launchctl kickstart -k gui/$(id -u)/com.chat-recall.watch", shell: "/bin/bash" };
19223
+ if (platform2 === "win32") return { cmd: "schtasks /End /TN chat-recall-watch & schtasks /Run /TN chat-recall-watch" };
19224
+ return null;
19225
+ }
19083
19226
  function realRestart(platform2) {
19084
- if (platform2 === "linux") execSync2("systemctl --user restart chat-recall-watch.service", { stdio: "ignore" });
19085
- else if (platform2 === "darwin") execSync2("launchctl kickstart -k gui/$(id -u)/com.chat-recall.watch", { stdio: "ignore", shell: "/bin/bash" });
19086
- else if (platform2 === "win32") execSync2("schtasks /End /TN chat-recall-watch & schtasks /Run /TN chat-recall-watch", { stdio: "ignore" });
19227
+ const c = restartCommand(platform2);
19228
+ if (c) execSync2(c.cmd, { stdio: "ignore", ...c.shell ? { shell: c.shell } : {} });
19087
19229
  }
19088
19230
  function updateStatePath() {
19089
19231
  const dir = process.env.CHAT_RECALL_DATA_DIR || join41(homedir15(), ".chat-recall");
@@ -19116,6 +19258,9 @@ function noteUpdateFailure(target) {
19116
19258
  function clearUpdateFailures() {
19117
19259
  writeUpdateState({});
19118
19260
  }
19261
+ function stopAutoUpdates() {
19262
+ stopRequested = true;
19263
+ }
19119
19264
  async function runAutoUpdate(base2, authHeaders, ownVersion3, deps) {
19120
19265
  let caps;
19121
19266
  try {
@@ -19142,12 +19287,28 @@ async function runAutoUpdate(base2, authHeaders, ownVersion3, deps) {
19142
19287
  if (target && hasGivenUpOn(target)) {
19143
19288
  return { updated: false, reason: `gave up installing ${plan.to} after ${GIVE_UP_AFTER} failed attempts \u2014 run \`npm install -g chat-recall\` by hand` };
19144
19289
  }
19145
- const result = await executeAutoUpdate(plan, {
19146
- download: deps?.download ?? realDownload,
19147
- install: deps?.install ?? realInstall,
19148
- restart: deps?.restart ?? realRestart,
19149
- platform: deps?.platform
19150
- });
19290
+ const stopping = deps?.stopping ?? (() => stopRequested);
19291
+ if (stopping()) return { updated: false, reason: STOPPING_REASON };
19292
+ const lock = (deps?.acquireLock ?? acquireUpdateLock)();
19293
+ if (!lock) return { updated: false, reason: LOCKED_REASON };
19294
+ let result;
19295
+ try {
19296
+ const onDisk = (deps?.onDisk ?? installedVersion)();
19297
+ if (onDisk && plan.to && compareVersions(onDisk, plan.to) >= 0) {
19298
+ return { updated: false, reason: `already current: ${onDisk} is on disk` };
19299
+ }
19300
+ result = await executeAutoUpdate(plan, {
19301
+ download: deps?.download ?? realDownload,
19302
+ install: deps?.install ?? realInstall,
19303
+ restart: deps?.restart ?? realRestart,
19304
+ platform: deps?.platform,
19305
+ verify: deps?.verify,
19306
+ stopping
19307
+ });
19308
+ } finally {
19309
+ lock.release();
19310
+ }
19311
+ if (result.reason === STOPPING_REASON) return result;
19151
19312
  if (!result.updated) {
19152
19313
  const failures = target ? noteUpdateFailure(target) : 1;
19153
19314
  if (failures <= GIVE_UP_AFTER) {
@@ -19163,14 +19324,18 @@ async function runAutoUpdate(base2, authHeaders, ownVersion3, deps) {
19163
19324
  }
19164
19325
  return result;
19165
19326
  }
19166
- var tarballUrl, GIVE_UP_AFTER, reportedStandingSkip;
19327
+ var tarballUrl, GIVE_UP_AFTER, reportedStandingSkip, STOPPING_REASON, LOCKED_REASON, stopRequested;
19167
19328
  var init_auto_update = __esm({
19168
19329
  "src/auto-update.ts"() {
19169
19330
  "use strict";
19170
19331
  init_http();
19332
+ init_index_lock();
19171
19333
  tarballUrl = (base2, version) => `${base2.replace(/\/+$/, "")}/install/${version ? `chat-recall-${version}.tgz` : "chat-recall.tgz"}`;
19172
19334
  GIVE_UP_AFTER = 3;
19173
19335
  reportedStandingSkip = /* @__PURE__ */ new Set();
19336
+ STOPPING_REASON = "not installed: this process is shutting down";
19337
+ LOCKED_REASON = "another process is installing an update";
19338
+ stopRequested = false;
19174
19339
  }
19175
19340
  });
19176
19341
 
@@ -20026,7 +20191,8 @@ async function syncToTargetInScope(cred, opts = {}) {
20026
20191
  }
20027
20192
  trace?.("conversation walk start");
20028
20193
  const walkStartedAt = Date.now();
20029
- reportWalkProgress({ done: 0, total: slice.length, startedAt: walkStartedAt, complete: false });
20194
+ const firstWalk = (ledger ?? getSyncedRows(base2)).size === 0;
20195
+ reportWalkProgress({ done: 0, total: slice.length, startedAt: walkStartedAt, complete: false, first: firstWalk });
20030
20196
  walkProgressForUpload = { done: 0, total: slice.length, complete: false };
20031
20197
  let traceN = 0;
20032
20198
  let considered = 0;
@@ -20034,7 +20200,7 @@ async function syncToTargetInScope(cred, opts = {}) {
20034
20200
  await new Promise((resolve3) => setImmediate(resolve3));
20035
20201
  considered++;
20036
20202
  const walkDone = considered >= slice.length;
20037
- reportWalkProgress({ done: considered, total: slice.length, startedAt: walkStartedAt, complete: walkDone });
20203
+ reportWalkProgress({ done: considered, total: slice.length, startedAt: walkStartedAt, complete: walkDone, first: firstWalk });
20038
20204
  walkProgressForUpload = { done: considered, total: slice.length, complete: walkDone };
20039
20205
  if (trace && ++traceN % 500 === 0) trace(`walk ${traceN}/${slice.length} \xB7 at ${ref.prefixedId}`);
20040
20206
  localSessionIds.add(ref.prefixedId);
@@ -20306,7 +20472,7 @@ async function syncToTargetInScope(cred, opts = {}) {
20306
20472
  }
20307
20473
  }
20308
20474
  if (opts.prune) await post({ prune_empty_sessions: true });
20309
- reportWalkProgress({ done: slice.length, total: slice.length, startedAt: walkStartedAt, complete: true });
20475
+ reportWalkProgress({ done: slice.length, total: slice.length, startedAt: walkStartedAt, complete: true, first: firstWalk });
20310
20476
  walkProgressForUpload = { done: slice.length, total: slice.length, complete: true };
20311
20477
  return {
20312
20478
  uploaded: convBatch.sent + appendResults.uploaded,
@@ -20437,7 +20603,7 @@ async function buildConversationSync(ref, mtime, opts = {}) {
20437
20603
  if (!transcript) return null;
20438
20604
  const textMessages = transcript.messages.filter((m) => m.role !== "summary" && m.content?.trim());
20439
20605
  if (textMessages.length === 0 && !transcript.messages.some((m) => m.toolCalls?.length)) return null;
20440
- const firstUser = textMessages.find((m) => m.role === "user");
20606
+ const firstUser = textMessages.find(isPersonMessage);
20441
20607
  if (isInternalToolPrompt(firstUser?.content)) return null;
20442
20608
  const count = { redactions: 0 };
20443
20609
  const envelope = redactDeep(
@@ -20446,7 +20612,7 @@ async function buildConversationSync(ref, mtime, opts = {}) {
20446
20612
  );
20447
20613
  let projectPath = ref.projectPath;
20448
20614
  const meta = { messageCount: textMessages.length };
20449
- if (textMessages.filter((m) => m.role === "user").length <= 1) meta.oneShot = true;
20615
+ if (textMessages.filter(isPersonMessage).length <= 1) meta.oneShot = true;
20450
20616
  try {
20451
20617
  const cwd = ref.fullPath ? readCwdFromJsonl(ref.fullPath) : "";
20452
20618
  if (cwd) projectPath = cwd;
@@ -20598,7 +20764,7 @@ async function buildConversationSync(ref, mtime, opts = {}) {
20598
20764
  envelope,
20599
20765
  raw_b64,
20600
20766
  raw_size,
20601
- first_prompt: envTexts.find((m) => m.role === "user")?.content?.slice(0, 200),
20767
+ first_prompt: envTexts.find(isPersonMessage)?.content?.slice(0, 200),
20602
20768
  // Byte offset this FULL sync is synced THROUGH (file size at build time,
20603
20769
  // or the end of the bounded tail for an oversized session). The server
20604
20770
  // stores it as the envelope's `o`; the ledger records the SAME value
@@ -20654,7 +20820,7 @@ async function buildConversationTail(ref, fromOffset, opts = {}) {
20654
20820
  bytes: 0
20655
20821
  };
20656
20822
  }
20657
- const firstUser = textMessages.find((m) => m.role === "user");
20823
+ const firstUser = textMessages.find(isPersonMessage);
20658
20824
  if (isInternalToolPrompt(firstUser?.content)) return null;
20659
20825
  const count = { redactions: 0 };
20660
20826
  const envelope = redactDeep(
@@ -20731,6 +20897,7 @@ var ownVersion, ERROR_CLASS, STANDING_FAILURE, walkProgressForUpload, lastServer
20731
20897
  var init_sync_client = __esm({
20732
20898
  "src/sync-client.ts"() {
20733
20899
  "use strict";
20900
+ init_claude_prompt_origin();
20734
20901
  init_credentials_env();
20735
20902
  init_secret_redactor();
20736
20903
  init_source_discovery();
@@ -20772,7 +20939,7 @@ var init_sync_client = __esm({
20772
20939
  ownVersion = JSON.parse(readFileSync35(join42(dir, "../package.json"), "utf-8")).version || ownVersion;
20773
20940
  } catch {
20774
20941
  try {
20775
- if (true) ownVersion = "0.7.9";
20942
+ if (true) ownVersion = "0.7.11";
20776
20943
  } catch {
20777
20944
  }
20778
20945
  }
@@ -23909,7 +24076,7 @@ try {
23909
24076
  ownVersion2 = JSON.parse(readFileSync39(join48(dir, "../package.json"), "utf-8")).version || ownVersion2;
23910
24077
  } catch {
23911
24078
  try {
23912
- if (true) ownVersion2 = "0.7.9";
24079
+ if (true) ownVersion2 = "0.7.11";
23913
24080
  } catch {
23914
24081
  }
23915
24082
  }
@@ -24294,7 +24461,7 @@ async function runCollectorMigration(opts) {
24294
24461
  init_sync_ledger();
24295
24462
  init_collector_health();
24296
24463
  import { existsSync as existsSync50, readFileSync as readFileSync45, statSync as statSync45 } from "fs";
24297
- var CLI_VERSION3 = true ? "0.7.9" : "0.0.0";
24464
+ var CLI_VERSION3 = true ? "0.7.11" : "0.0.0";
24298
24465
  var DEBOUNCE_MS = 5e3;
24299
24466
  var PLANS_DIR = claudeBackend.plansDir();
24300
24467
  var TASKS_DIR = claudeBackend.tasksDir();
@@ -24850,6 +25017,7 @@ if (CODE_INDEX_ON) setTimeout(() => {
24850
25017
  }, 15e4);
24851
25018
  function shutdown(signal) {
24852
25019
  daemonLog.info(`Received ${signal}, shutting down...`);
25020
+ stopAutoUpdates();
24853
25021
  try {
24854
25022
  flushLedger();
24855
25023
  } catch (e) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chat-recall",
3
- "version": "0.7.9",
3
+ "version": "0.7.11",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/munhq/chat-recall.git"
@@ -74,7 +74,7 @@ directly.
74
74
  Every `recall_*` tool. `packages/cli/src/skills-catalog.test.ts` fails when a
75
75
  tool is registered and never named here, so adding a tool means placing it.
76
76
 
77
- **Resume / cold start** — `recall_smart_resume` (structured resume bundle; needs a session id), `recall_recent` (list recent sessions), `recall_wake_up` (identity + high-signal facts), `recall_context` (structured dump of one session), `recall_show` (raw slice — returns 10 messages unless you raise `max_messages`), `recall_summary` (AI summary + outcome).
77
+ **Resume / cold start** — `recall_smart_resume` (structured resume bundle; needs a session id), `recall_recent` (list recent sessions), `recall_wake_up` (identity + high-signal facts), `recall_context` (structured dump of one session), `recall_show` (raw slice — `query` finds a message and its line, `around_line` reads around it; returns 10 messages unless you raise `max_messages`), `recall_summary` (AI summary + outcome).
78
78
  **Search** — `recall_search` (sessions; `include_outcome`, `like_session`), `recall_memory_search` (every memory type), `recall_memory_item` (read ONE item found by search, or browse a source type), `recall_user_prompts` (what the user actually typed), `recall_subagent_search` (inside subagent transcripts, whose work never reaches the main conversation), `recall_redundant_files` (before writing a new file, check you have not written one like it already).
79
79
  **Project** — `recall_project_context` (rich dump), `recall_weekly_digest`, `recall_analytics_summary`, `recall_outcome_summary` (how many recent sessions actually shipped), `recall_code_findings` / `recall_code_actions` / `recall_code_projects` / `recall_code_index` / `recall_recommendations`.
80
80
 
@@ -39,8 +39,10 @@ description: >-
39
39
 
40
40
  Every tool here takes a NAMED `session_id` (not a positional argument).
41
41
 
42
- - `mcp__chat-recall__recall_show` — read the session slice (`around_line`,
43
- `from_end`). Each message comes back in full, with its tool calls and
42
+ - `mcp__chat-recall__recall_show` — read the session slice. To find one
43
+ message, pass `query` with words from it: the reply gives the line of each
44
+ match. Then pass that line as `around_line`, or read the newest messages with
45
+ `from_end`. Do not guess line numbers. Each message comes back in full, with its tool calls and
44
46
  results. A body over 2000 characters shows its ends and the `expand_line`
45
47
  that returns it whole. It returns **10 messages
46
48
  by default**: raise `max_messages` before telling the user what a session
@@ -47,7 +47,9 @@ Once you have a session id:
47
47
  - `mcp__chat-recall__recall_context` — structured context (requests, decisions,
48
48
  files, tools).
49
49
  - `mcp__chat-recall__recall_show` — raw slice; `from_end: N` for the last N
50
- messages. Each message comes back in full, code and commands included, with
50
+ messages, `query` to find a message by its words and get its line,
51
+ `around_line` to read around that line. If the reply says that sync is
52
+ behind, run `recall_index` before you read the transcript file. Each message comes back in full, code and commands included, with
51
53
  its tool results. A body over 2000 characters shows its ends; pass
52
54
  `expand_line` to get it whole. It
53
55
  returns 10 messages unless you raise `max_messages`, so don't read a partial