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/cli.js CHANGED
@@ -4568,6 +4568,72 @@ var init_banners = __esm({
4568
4568
  }
4569
4569
  });
4570
4570
 
4571
+ // ../engine/dist/core/claude-prompt-origin.js
4572
+ function originFromText(text) {
4573
+ const head = text.trimStart();
4574
+ for (const [prefix, origin] of INJECTED_PREFIXES) {
4575
+ if (head.startsWith(prefix))
4576
+ return origin;
4577
+ }
4578
+ return "human";
4579
+ }
4580
+ function declaredOrigin(value) {
4581
+ if (!value || typeof value !== "object")
4582
+ return void 0;
4583
+ const kind = value.kind;
4584
+ return typeof kind === "string" && kind ? kind : void 0;
4585
+ }
4586
+ function userRecordOrigin(obj, text) {
4587
+ if (obj.isSidechain === true)
4588
+ return "subagent";
4589
+ const declared = declaredOrigin(obj.origin);
4590
+ if (declared && declared !== "human")
4591
+ return declared;
4592
+ if (obj.isMeta === true)
4593
+ return "meta";
4594
+ if (declared === "human")
4595
+ return "human";
4596
+ return originFromText(text);
4597
+ }
4598
+ function queuedCommandPrompt(obj) {
4599
+ if (obj.type !== "attachment")
4600
+ return null;
4601
+ const att = obj.attachment;
4602
+ if (!att || att.type !== "queued_command")
4603
+ return null;
4604
+ const text = promptText(att.prompt);
4605
+ if (!text)
4606
+ return null;
4607
+ if (obj.isSidechain === true)
4608
+ return { text, origin: "subagent" };
4609
+ const declared = declaredOrigin(att.origin);
4610
+ if (declared)
4611
+ return { text, origin: declared };
4612
+ return { text, origin: att.humanTurn === true ? "human" : originFromText(text) };
4613
+ }
4614
+ function promptText(prompt) {
4615
+ if (typeof prompt === "string")
4616
+ return prompt;
4617
+ if (!Array.isArray(prompt))
4618
+ return "";
4619
+ return prompt.filter((b) => !!b && typeof b === "object" && b.type === "text" && typeof b.text === "string").map((b) => b.text).join("\n");
4620
+ }
4621
+ function isPersonMessage(m) {
4622
+ return m.role === "user" && !m.origin;
4623
+ }
4624
+ var INJECTED_PREFIXES;
4625
+ var init_claude_prompt_origin = __esm({
4626
+ "../engine/dist/core/claude-prompt-origin.js"() {
4627
+ "use strict";
4628
+ INJECTED_PREFIXES = [
4629
+ ["<task-notification", "task-notification"],
4630
+ ["<agent-message", "peer"],
4631
+ ["Another Claude session sent a message:", "peer"],
4632
+ ["Stop hook feedback:", "hook"]
4633
+ ];
4634
+ }
4635
+ });
4636
+
4571
4637
  // ../engine/dist/core/project-path-match.js
4572
4638
  function canonicalProjectPath(p) {
4573
4639
  return (p || "").trim().replace(/\\/g, "/").replace(/[-_]/g, "/").replace(/\/{2,}/g, "/").replace(/\/+$/, "").toLowerCase();
@@ -5809,7 +5875,8 @@ async function parseSessionFile(sessionPath, maxMessages = 50) {
5809
5875
  const filesToParse = hasSubagentsDir(sessionPath) ? [sessionPath, ...getSubagentPaths(sessionPath)] : [sessionPath];
5810
5876
  let userCount = 0;
5811
5877
  const seenPrompts = /* @__PURE__ */ new Set();
5812
- const recordPrompt = (raw, line, dedupe = false) => {
5878
+ const pendingQueued = /* @__PURE__ */ new Set();
5879
+ const recordPrompt = (raw, line, via = "record") => {
5813
5880
  if (userCount >= maxMessages)
5814
5881
  return;
5815
5882
  const withoutReminders = raw.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "");
@@ -5818,8 +5885,14 @@ async function parseSessionFile(sessionPath, maxMessages = 50) {
5818
5885
  const cleanedText = stripInjectedBanners(withoutReminders).trim();
5819
5886
  if (cleanedText.length < 10)
5820
5887
  return;
5821
- if (dedupe && seenPrompts.has(cleanedText))
5888
+ if (via === "enqueue") {
5889
+ if (seenPrompts.has(cleanedText))
5890
+ return;
5891
+ pendingQueued.add(cleanedText);
5892
+ } else if (pendingQueued.has(cleanedText)) {
5893
+ pendingQueued.delete(cleanedText);
5822
5894
  return;
5895
+ }
5823
5896
  seenPrompts.add(cleanedText);
5824
5897
  content.userMessages.push({ text: cleanedText, lineNumber: line, contentType: "user" });
5825
5898
  userCount++;
@@ -5904,12 +5977,16 @@ async function parseSessionFile(sessionPath, maxMessages = 50) {
5904
5977
  text = textParts.join("\n");
5905
5978
  }
5906
5979
  }
5907
- if (text)
5980
+ if (text && userRecordOrigin(obj, text) === "human")
5908
5981
  recordPrompt(text, lineNum);
5909
5982
  } else if (msgType === "queue-operation") {
5910
- if (obj.operation === "enqueue" && typeof obj.content === "string") {
5911
- recordPrompt(obj.content, lineNum, true);
5983
+ if (obj.operation === "enqueue" && typeof obj.content === "string" && originFromText(obj.content) === "human") {
5984
+ recordPrompt(obj.content, lineNum, "enqueue");
5912
5985
  }
5986
+ } else if (msgType === "attachment") {
5987
+ const queued = queuedCommandPrompt(obj);
5988
+ if (queued?.origin === "human")
5989
+ recordPrompt(queued.text, lineNum, "delivery");
5913
5990
  } else if (msgType === "assistant") {
5914
5991
  const msg = obj.message;
5915
5992
  if (!msg || typeof msg !== "object")
@@ -6013,6 +6090,7 @@ var init_session = __esm({
6013
6090
  "../engine/dist/parsers/session.js"() {
6014
6091
  "use strict";
6015
6092
  init_banners();
6093
+ init_claude_prompt_origin();
6016
6094
  init_claude2();
6017
6095
  init_tool_paths();
6018
6096
  init_project_dir_name();
@@ -6177,7 +6255,16 @@ function messagesFromRawLines(raw) {
6177
6255
  const cleaned = stripBanners(text);
6178
6256
  if (!cleaned)
6179
6257
  continue;
6180
- messages.push({ line, role: "user", content: cleaned, timestamp });
6258
+ const origin = userRecordOrigin(obj, cleaned);
6259
+ messages.push({ line, role: "user", content: cleaned, timestamp, ...origin === "human" ? {} : { origin } });
6260
+ } else if (obj.type === "attachment") {
6261
+ const queued = queuedCommandPrompt(obj);
6262
+ if (!queued)
6263
+ continue;
6264
+ const cleaned = stripBanners(queued.text);
6265
+ if (!cleaned)
6266
+ continue;
6267
+ messages.push({ line, role: "user", content: cleaned, timestamp, ...queued.origin === "human" ? {} : { origin: queued.origin } });
6181
6268
  } else if (obj.type === "assistant") {
6182
6269
  const blocks = Array.isArray(obj.message?.content) ? obj.message.content : [];
6183
6270
  const thinkingBlock = blocks.find((c) => c?.type === "thinking");
@@ -6273,6 +6360,7 @@ var init_claude = __esm({
6273
6360
  "../engine/dist/transcript/claude.js"() {
6274
6361
  "use strict";
6275
6362
  init_logger();
6363
+ init_claude_prompt_origin();
6276
6364
  log2 = createLogger("transcript-claude");
6277
6365
  INJECTED_BANNERS2 = [
6278
6366
  /MCP issues detected\. ?Run \/mcp list for status\.?/g,
@@ -6885,10 +6973,15 @@ function summarizeMarkers(marked) {
6885
6973
  }
6886
6974
  return counts;
6887
6975
  }
6888
- var PROFANITY, PROFANITY_LOOSE, APPROVAL_HEAD, NEG_HEAD, QUESTION_HEAD, DIRECTIVE_HEAD, CLARIFY_PHRASES, FRUSTRATED_PHRASES;
6976
+ function markersFromTurns(sessionId, turns) {
6977
+ const prompts = turns.filter((t) => t.kind === "user" && t.text).map((t) => ({ line: t.line, ts: t.ts, tsIso: t.tsIso, ...markPrompt(t.text) }));
6978
+ return { v: MARKERS_VERSION, sessionId, prompts, summary: summarizeMarkers(prompts) };
6979
+ }
6980
+ var PROFANITY, PROFANITY_LOOSE, APPROVAL_HEAD, NEG_HEAD, QUESTION_HEAD, DIRECTIVE_HEAD, CLARIFY_PHRASES, FRUSTRATED_PHRASES, MARKERS_VERSION;
6889
6981
  var init_session_sentiment = __esm({
6890
6982
  "../engine/dist/core/session-sentiment.js"() {
6891
6983
  "use strict";
6984
+ init_claude_prompt_origin();
6892
6985
  PROFANITY = /\b(fuck(?:ing|er|ed)?|shit|ffs|wtf|bullshit|crap|damn(?:it)?|hell)\b/i;
6893
6986
  PROFANITY_LOOSE = /\b(fu[ckj]+(?:ing|er|ed|c?k)?|fukc|fucj|sht|fk|wt[ft])\b/i;
6894
6987
  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;
@@ -6897,6 +6990,7 @@ var init_session_sentiment = __esm({
6897
6990
  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;
6898
6991
  CLARIFY_PHRASES = /\b(?:wdym|what do you mean|explain|clarify|what is|what's|huh\??|i don'?t understand)\b/i;
6899
6992
  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;
6993
+ MARKERS_VERSION = 2;
6900
6994
  }
6901
6995
  });
6902
6996
 
@@ -11365,6 +11459,7 @@ var init_claude2 = __esm({
11365
11459
  init_first_prompt();
11366
11460
  init_tail_read();
11367
11461
  init_project_dir_name();
11462
+ init_claude_prompt_origin();
11368
11463
  init_generic_engine();
11369
11464
  UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
11370
11465
  ClaudeBackend = class {
@@ -11672,6 +11767,15 @@ var init_claude2 = __esm({
11672
11767
  }
11673
11768
  appendEventsFromText(events, raw, mtime, startLine) {
11674
11769
  let lineNum = startLine;
11770
+ const queued = /* @__PURE__ */ new Map();
11771
+ const superseded = /* @__PURE__ */ new Set();
11772
+ const pushPrompt = (e) => {
11773
+ const pending = queued.get(e.text);
11774
+ const earlier = pending?.shift();
11775
+ if (earlier)
11776
+ superseded.add(earlier);
11777
+ events.push(e);
11778
+ };
11675
11779
  {
11676
11780
  for (const line of raw.split("\n")) {
11677
11781
  lineNum++;
@@ -11687,13 +11791,26 @@ var init_claude2 = __esm({
11687
11791
  const ts = tsIso ? Date.parse(tsIso) || mtime : mtime;
11688
11792
  if (obj.type === "queue-operation") {
11689
11793
  if (obj.operation === "enqueue" && typeof obj.content === "string") {
11690
- const queued = stripReminders(obj.content);
11691
- if (queued && !queued.startsWith("<task-notification")) {
11692
- events.push({ kind: "user", ts, tsIso, line: lineNum, text: queued });
11794
+ const text = stripReminders(obj.content);
11795
+ if (text && originFromText(text) === "human") {
11796
+ const e = { kind: "user", ts, tsIso, line: lineNum, text };
11797
+ events.push(e);
11798
+ const list = queued.get(text);
11799
+ if (list)
11800
+ list.push(e);
11801
+ else
11802
+ queued.set(text, [e]);
11693
11803
  }
11694
11804
  }
11695
11805
  continue;
11696
11806
  }
11807
+ const delivered = queuedCommandPrompt(obj);
11808
+ if (delivered) {
11809
+ const text = stripReminders(delivered.text);
11810
+ if (text && delivered.origin === "human")
11811
+ pushPrompt({ kind: "user", ts, tsIso, line: lineNum, text });
11812
+ continue;
11813
+ }
11697
11814
  if (obj.type === "user") {
11698
11815
  const msg = obj.message;
11699
11816
  const content = msg?.content;
@@ -11728,8 +11845,9 @@ var init_claude2 = __esm({
11728
11845
  }
11729
11846
  }
11730
11847
  const spoken = stripReminders(text);
11731
- if (spoken)
11732
- events.push({ kind: "user", ts, tsIso, line: lineNum, text: spoken });
11848
+ if (spoken && userRecordOrigin(obj, spoken) === "human") {
11849
+ pushPrompt({ kind: "user", ts, tsIso, line: lineNum, text: spoken });
11850
+ }
11733
11851
  for (const tr of toolResults) {
11734
11852
  events.push({
11735
11853
  kind: "tool_result",
@@ -11778,6 +11896,13 @@ var init_claude2 = __esm({
11778
11896
  }
11779
11897
  }
11780
11898
  }
11899
+ if (superseded.size > 0) {
11900
+ let w = 0;
11901
+ for (const e of events)
11902
+ if (!superseded.has(e))
11903
+ events[w++] = e;
11904
+ events.length = w;
11905
+ }
11781
11906
  return lineNum;
11782
11907
  }
11783
11908
  // ── Per-session operations — all delegate to the generic engine ─
@@ -18521,7 +18646,7 @@ var init_sync_fields = __esm({
18521
18646
  import { existsSync as existsSync39, mkdirSync as mkdirSync7, openSync as openSync8, writeFileSync as writeFileSync6, readFileSync as readFileSync30, rmSync as rmSync4, statSync as statSync35, closeSync as closeSync8 } from "fs";
18522
18647
  import { dirname as dirname16, join as join39 } from "path";
18523
18648
  function acquireIndexLock(opts) {
18524
- const lockPath = lockFilePath();
18649
+ const lockPath = lockFilePath(opts.file);
18525
18650
  mkdirSync7(dirname16(lockPath), { recursive: true });
18526
18651
  if (existsSync39(lockPath)) {
18527
18652
  if (!stealStaleLock(lockPath, opts.staleAfterMs ?? 10 * 6e4)) {
@@ -18563,8 +18688,8 @@ function acquireIndexLock(opts) {
18563
18688
  }
18564
18689
  };
18565
18690
  }
18566
- function lockFilePath() {
18567
- return join39(getIndexDir(), ".compact.lock");
18691
+ function lockFilePath(file = ".compact.lock") {
18692
+ return join39(getIndexDir(), file);
18568
18693
  }
18569
18694
  function stealStaleLock(lockPath, staleAfterMs) {
18570
18695
  try {
@@ -18640,7 +18765,7 @@ var init_http = __esm({
18640
18765
  CLI_VERSION = JSON.parse(readFileSync31(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION;
18641
18766
  } catch {
18642
18767
  try {
18643
- if (true) CLI_VERSION = "0.7.9";
18768
+ if (true) CLI_VERSION = "0.7.11";
18644
18769
  } catch {
18645
18770
  }
18646
18771
  }
@@ -18685,8 +18810,7 @@ function collectDerivedRows(ref, mtime, count, maxRowBytes) {
18685
18810
  if (fullOutcome.found) data = fullOutcome.commits;
18686
18811
  } else if (kind === "markers") {
18687
18812
  const turns = extractTurnsAny(id, { maxTurns: 5e4 });
18688
- 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) }));
18689
- data = { sessionId: id, prompts, summary: summarizeMarkers(prompts) };
18813
+ data = markersFromTurns(id, turns.turns);
18690
18814
  }
18691
18815
  } catch {
18692
18816
  data = null;
@@ -19139,7 +19263,7 @@ var init_telemetry = __esm({
19139
19263
  init_sync_client();
19140
19264
  cliVersion2 = () => {
19141
19265
  try {
19142
- return true ? "0.7.9" : "dev";
19266
+ return true ? "0.7.11" : "dev";
19143
19267
  } catch {
19144
19268
  return "dev";
19145
19269
  }
@@ -19157,6 +19281,7 @@ __export(collector_health_exports, {
19157
19281
  applySyncOutcome: () => applySyncOutcome,
19158
19282
  collectorHealthPath: () => collectorHealthPath,
19159
19283
  crashesAtBoot: () => crashesAtBoot,
19284
+ firstSyncInProgress: () => firstSyncInProgress,
19160
19285
  judgeHealth: () => judgeHealth,
19161
19286
  markCleanExit: () => markCleanExit,
19162
19287
  progressLine: () => progressLine,
@@ -19189,7 +19314,11 @@ function progressLine(h) {
19189
19314
  if (!p || p.complete || p.total <= 0)
19190
19315
  return null;
19191
19316
  const pct = Math.min(99, Math.floor(100 * p.done / p.total));
19192
- return `first sync in progress \u2014 ${p.done.toLocaleString()} of ${p.total.toLocaleString()} sessions (${pct}%)`;
19317
+ return `${p.first ? "first sync" : "sync"} in progress \u2014 ${p.done.toLocaleString()} of ${p.total.toLocaleString()} sessions (${pct}%)`;
19318
+ }
19319
+ function firstSyncInProgress(h) {
19320
+ const p = h?.progress;
19321
+ return !!p && p.first === true && !p.complete && p.total > 0;
19193
19322
  }
19194
19323
  function collectorHealthPath() {
19195
19324
  return healthPath();
@@ -19502,6 +19631,8 @@ var init_client_events = __esm({
19502
19631
  // src/auto-update.ts
19503
19632
  var auto_update_exports = {};
19504
19633
  __export(auto_update_exports, {
19634
+ LOCKED_REASON: () => LOCKED_REASON,
19635
+ STOPPING_REASON: () => STOPPING_REASON,
19505
19636
  clearUpdateFailures: () => clearUpdateFailures,
19506
19637
  compareVersions: () => compareVersions,
19507
19638
  executeAutoUpdate: () => executeAutoUpdate,
@@ -19513,8 +19644,10 @@ __export(auto_update_exports, {
19513
19644
  planAutoUpdate: () => planAutoUpdate,
19514
19645
  readUpdateState: () => readUpdateState,
19515
19646
  resolveNpm: () => resolveNpm,
19647
+ restartCommand: () => restartCommand,
19516
19648
  runAutoUpdate: () => runAutoUpdate,
19517
19649
  runningPrefix: () => runningPrefix,
19650
+ stopAutoUpdates: () => stopAutoUpdates,
19518
19651
  sweepStaleStaging: () => sweepStaleStaging,
19519
19652
  tarballUrl: () => tarballUrl
19520
19653
  });
@@ -19610,6 +19743,10 @@ async function executeAutoUpdate(plan, deps) {
19610
19743
  };
19611
19744
  const tgz = join41(dir, "chat-recall.tgz");
19612
19745
  writeFileSync8(tgz, buf);
19746
+ if (deps.stopping?.()) {
19747
+ discardStaging();
19748
+ return { updated: false, reason: STOPPING_REASON };
19749
+ }
19613
19750
  try {
19614
19751
  deps.install(tgz);
19615
19752
  } catch (e) {
@@ -19624,9 +19761,11 @@ async function executeAutoUpdate(plan, deps) {
19624
19761
  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}`
19625
19762
  };
19626
19763
  }
19627
- try {
19628
- deps.restart(deps.platform ?? process.platform);
19629
- } catch {
19764
+ if (!deps.stopping?.()) {
19765
+ try {
19766
+ deps.restart(deps.platform ?? process.platform);
19767
+ } catch {
19768
+ }
19630
19769
  }
19631
19770
  return { updated: true, reason: `updated ${plan.from} \u2192 ${plan.to}`, from: plan.from, to: plan.to };
19632
19771
  }
@@ -19685,10 +19824,18 @@ function realInstall(tgz) {
19685
19824
  npmInstall(npm, ["install", "-g", "--prefix", join41(homedir15(), ".local"), tgz]);
19686
19825
  }
19687
19826
  }
19827
+ function acquireUpdateLock() {
19828
+ return acquireIndexLock({ kind: "auto-update", file: ".update.lock", staleAfterMs: 10 * 6e4 });
19829
+ }
19830
+ function restartCommand(platform2) {
19831
+ if (platform2 === "linux") return { cmd: "systemctl --user --no-block restart chat-recall-watch.service" };
19832
+ if (platform2 === "darwin") return { cmd: "launchctl kickstart -k gui/$(id -u)/com.chat-recall.watch", shell: "/bin/bash" };
19833
+ if (platform2 === "win32") return { cmd: "schtasks /End /TN chat-recall-watch & schtasks /Run /TN chat-recall-watch" };
19834
+ return null;
19835
+ }
19688
19836
  function realRestart(platform2) {
19689
- if (platform2 === "linux") execSync2("systemctl --user restart chat-recall-watch.service", { stdio: "ignore" });
19690
- else if (platform2 === "darwin") execSync2("launchctl kickstart -k gui/$(id -u)/com.chat-recall.watch", { stdio: "ignore", shell: "/bin/bash" });
19691
- else if (platform2 === "win32") execSync2("schtasks /End /TN chat-recall-watch & schtasks /Run /TN chat-recall-watch", { stdio: "ignore" });
19837
+ const c = restartCommand(platform2);
19838
+ if (c) execSync2(c.cmd, { stdio: "ignore", ...c.shell ? { shell: c.shell } : {} });
19692
19839
  }
19693
19840
  function updateStatePath() {
19694
19841
  const dir = process.env.CHAT_RECALL_DATA_DIR || join41(homedir15(), ".chat-recall");
@@ -19721,6 +19868,9 @@ function noteUpdateFailure(target) {
19721
19868
  function clearUpdateFailures() {
19722
19869
  writeUpdateState({});
19723
19870
  }
19871
+ function stopAutoUpdates() {
19872
+ stopRequested = true;
19873
+ }
19724
19874
  async function runAutoUpdate(base3, authHeaders, ownVersion3, deps) {
19725
19875
  let caps;
19726
19876
  try {
@@ -19747,12 +19897,28 @@ async function runAutoUpdate(base3, authHeaders, ownVersion3, deps) {
19747
19897
  if (target && hasGivenUpOn(target)) {
19748
19898
  return { updated: false, reason: `gave up installing ${plan.to} after ${GIVE_UP_AFTER} failed attempts \u2014 run \`npm install -g chat-recall\` by hand` };
19749
19899
  }
19750
- const result = await executeAutoUpdate(plan, {
19751
- download: deps?.download ?? realDownload,
19752
- install: deps?.install ?? realInstall,
19753
- restart: deps?.restart ?? realRestart,
19754
- platform: deps?.platform
19755
- });
19900
+ const stopping = deps?.stopping ?? (() => stopRequested);
19901
+ if (stopping()) return { updated: false, reason: STOPPING_REASON };
19902
+ const lock = (deps?.acquireLock ?? acquireUpdateLock)();
19903
+ if (!lock) return { updated: false, reason: LOCKED_REASON };
19904
+ let result;
19905
+ try {
19906
+ const onDisk = (deps?.onDisk ?? installedVersion)();
19907
+ if (onDisk && plan.to && compareVersions(onDisk, plan.to) >= 0) {
19908
+ return { updated: false, reason: `already current: ${onDisk} is on disk` };
19909
+ }
19910
+ result = await executeAutoUpdate(plan, {
19911
+ download: deps?.download ?? realDownload,
19912
+ install: deps?.install ?? realInstall,
19913
+ restart: deps?.restart ?? realRestart,
19914
+ platform: deps?.platform,
19915
+ verify: deps?.verify,
19916
+ stopping
19917
+ });
19918
+ } finally {
19919
+ lock.release();
19920
+ }
19921
+ if (result.reason === STOPPING_REASON) return result;
19756
19922
  if (!result.updated) {
19757
19923
  const failures = target ? noteUpdateFailure(target) : 1;
19758
19924
  if (failures <= GIVE_UP_AFTER) {
@@ -19768,14 +19934,18 @@ async function runAutoUpdate(base3, authHeaders, ownVersion3, deps) {
19768
19934
  }
19769
19935
  return result;
19770
19936
  }
19771
- var tarballUrl, GIVE_UP_AFTER, reportedStandingSkip;
19937
+ var tarballUrl, GIVE_UP_AFTER, reportedStandingSkip, STOPPING_REASON, LOCKED_REASON, stopRequested;
19772
19938
  var init_auto_update = __esm({
19773
19939
  "src/auto-update.ts"() {
19774
19940
  "use strict";
19775
19941
  init_http();
19942
+ init_index_lock();
19776
19943
  tarballUrl = (base3, version2) => `${base3.replace(/\/+$/, "")}/install/${version2 ? `chat-recall-${version2}.tgz` : "chat-recall.tgz"}`;
19777
19944
  GIVE_UP_AFTER = 3;
19778
19945
  reportedStandingSkip = /* @__PURE__ */ new Set();
19946
+ STOPPING_REASON = "not installed: this process is shutting down";
19947
+ LOCKED_REASON = "another process is installing an update";
19948
+ stopRequested = false;
19779
19949
  }
19780
19950
  });
19781
19951
 
@@ -20726,7 +20896,8 @@ async function syncToTargetInScope(cred, opts = {}) {
20726
20896
  }
20727
20897
  trace?.("conversation walk start");
20728
20898
  const walkStartedAt = Date.now();
20729
- reportWalkProgress({ done: 0, total: slice.length, startedAt: walkStartedAt, complete: false });
20899
+ const firstWalk = (ledger ?? getSyncedRows(base3)).size === 0;
20900
+ reportWalkProgress({ done: 0, total: slice.length, startedAt: walkStartedAt, complete: false, first: firstWalk });
20730
20901
  walkProgressForUpload = { done: 0, total: slice.length, complete: false };
20731
20902
  let traceN = 0;
20732
20903
  let considered = 0;
@@ -20734,7 +20905,7 @@ async function syncToTargetInScope(cred, opts = {}) {
20734
20905
  await new Promise((resolve3) => setImmediate(resolve3));
20735
20906
  considered++;
20736
20907
  const walkDone = considered >= slice.length;
20737
- reportWalkProgress({ done: considered, total: slice.length, startedAt: walkStartedAt, complete: walkDone });
20908
+ reportWalkProgress({ done: considered, total: slice.length, startedAt: walkStartedAt, complete: walkDone, first: firstWalk });
20738
20909
  walkProgressForUpload = { done: considered, total: slice.length, complete: walkDone };
20739
20910
  if (trace && ++traceN % 500 === 0) trace(`walk ${traceN}/${slice.length} \xB7 at ${ref.prefixedId}`);
20740
20911
  localSessionIds.add(ref.prefixedId);
@@ -21006,7 +21177,7 @@ async function syncToTargetInScope(cred, opts = {}) {
21006
21177
  }
21007
21178
  }
21008
21179
  if (opts.prune) await post({ prune_empty_sessions: true });
21009
- reportWalkProgress({ done: slice.length, total: slice.length, startedAt: walkStartedAt, complete: true });
21180
+ reportWalkProgress({ done: slice.length, total: slice.length, startedAt: walkStartedAt, complete: true, first: firstWalk });
21010
21181
  walkProgressForUpload = { done: slice.length, total: slice.length, complete: true };
21011
21182
  return {
21012
21183
  uploaded: convBatch.sent + appendResults.uploaded,
@@ -21137,7 +21308,7 @@ async function buildConversationSync(ref, mtime, opts = {}) {
21137
21308
  if (!transcript) return null;
21138
21309
  const textMessages = transcript.messages.filter((m) => m.role !== "summary" && m.content?.trim());
21139
21310
  if (textMessages.length === 0 && !transcript.messages.some((m) => m.toolCalls?.length)) return null;
21140
- const firstUser = textMessages.find((m) => m.role === "user");
21311
+ const firstUser = textMessages.find(isPersonMessage);
21141
21312
  if (isInternalToolPrompt(firstUser?.content)) return null;
21142
21313
  const count = { redactions: 0 };
21143
21314
  const envelope = redactDeep(
@@ -21146,7 +21317,7 @@ async function buildConversationSync(ref, mtime, opts = {}) {
21146
21317
  );
21147
21318
  let projectPath = ref.projectPath;
21148
21319
  const meta = { messageCount: textMessages.length };
21149
- if (textMessages.filter((m) => m.role === "user").length <= 1) meta.oneShot = true;
21320
+ if (textMessages.filter(isPersonMessage).length <= 1) meta.oneShot = true;
21150
21321
  try {
21151
21322
  const cwd = ref.fullPath ? readCwdFromJsonl(ref.fullPath) : "";
21152
21323
  if (cwd) projectPath = cwd;
@@ -21298,7 +21469,7 @@ async function buildConversationSync(ref, mtime, opts = {}) {
21298
21469
  envelope,
21299
21470
  raw_b64,
21300
21471
  raw_size,
21301
- first_prompt: envTexts.find((m) => m.role === "user")?.content?.slice(0, 200),
21472
+ first_prompt: envTexts.find(isPersonMessage)?.content?.slice(0, 200),
21302
21473
  // Byte offset this FULL sync is synced THROUGH (file size at build time,
21303
21474
  // or the end of the bounded tail for an oversized session). The server
21304
21475
  // stores it as the envelope's `o`; the ledger records the SAME value
@@ -21354,7 +21525,7 @@ async function buildConversationTail(ref, fromOffset, opts = {}) {
21354
21525
  bytes: 0
21355
21526
  };
21356
21527
  }
21357
- const firstUser = textMessages.find((m) => m.role === "user");
21528
+ const firstUser = textMessages.find(isPersonMessage);
21358
21529
  if (isInternalToolPrompt(firstUser?.content)) return null;
21359
21530
  const count = { redactions: 0 };
21360
21531
  const envelope = redactDeep(
@@ -21431,6 +21602,7 @@ var ownVersion, ERROR_CLASS, STANDING_FAILURE, walkProgressForUpload, lastServer
21431
21602
  var init_sync_client = __esm({
21432
21603
  "src/sync-client.ts"() {
21433
21604
  "use strict";
21605
+ init_claude_prompt_origin();
21434
21606
  init_credentials_env();
21435
21607
  init_secret_redactor();
21436
21608
  init_source_discovery();
@@ -21472,7 +21644,7 @@ var init_sync_client = __esm({
21472
21644
  ownVersion = JSON.parse(readFileSync35(join42(dir, "../package.json"), "utf-8")).version || ownVersion;
21473
21645
  } catch {
21474
21646
  try {
21475
- if (true) ownVersion = "0.7.9";
21647
+ if (true) ownVersion = "0.7.11";
21476
21648
  } catch {
21477
21649
  }
21478
21650
  }
@@ -23676,7 +23848,7 @@ function extractCorrections(messages, cap = 5) {
23676
23848
  const seen = /* @__PURE__ */ new Set();
23677
23849
  let userTurns = 0;
23678
23850
  for (const msg of messages) {
23679
- if (msg.role !== "user") continue;
23851
+ if (!isPersonMessage(msg)) continue;
23680
23852
  userTurns++;
23681
23853
  if (userTurns === 1) continue;
23682
23854
  if (isNoiseUserMessage(msg.content)) continue;
@@ -23782,6 +23954,7 @@ var CORRECTION_STARTS, CORRECTION_CONTAINS, TERMINAL_STATUSES;
23782
23954
  var init_escalate = __esm({
23783
23955
  "src/escalate.ts"() {
23784
23956
  "use strict";
23957
+ init_claude_prompt_origin();
23785
23958
  CORRECTION_STARTS = [
23786
23959
  /^no+\b[,.! ]/i,
23787
23960
  /^(nope|wrong|incorrect|not quite|not right)\b/i,
@@ -25633,7 +25806,7 @@ var init_intent_drain = __esm({
25633
25806
  ownVersion2 = JSON.parse(readFileSync45(join56(dir, "../package.json"), "utf-8")).version || ownVersion2;
25634
25807
  } catch {
25635
25808
  try {
25636
- if (true) ownVersion2 = "0.7.9";
25809
+ if (true) ownVersion2 = "0.7.11";
25637
25810
  } catch {
25638
25811
  }
25639
25812
  }
@@ -27650,17 +27823,18 @@ var init_bulk_delete = __esm({
27650
27823
  });
27651
27824
 
27652
27825
  // ../engine/dist/core/resume-command.js
27826
+ var SESSION_ID_PREFIXES = {
27827
+ codex: "codex_",
27828
+ opencode: "opencode_",
27829
+ agy: "agy_",
27830
+ cursor: "cursor_",
27831
+ gemini: "gemini_"
27832
+ };
27653
27833
  function toolOfSessionId(sessionId) {
27654
- if (sessionId.startsWith("codex_"))
27655
- return "codex";
27656
- if (sessionId.startsWith("opencode_"))
27657
- return "opencode";
27658
- if (sessionId.startsWith("agy_"))
27659
- return "agy";
27660
- if (sessionId.startsWith("cursor_"))
27661
- return "cursor";
27662
- if (sessionId.startsWith("gemini_"))
27663
- return "gemini";
27834
+ for (const [tool, prefix] of Object.entries(SESSION_ID_PREFIXES)) {
27835
+ if (sessionId.startsWith(prefix))
27836
+ return tool;
27837
+ }
27664
27838
  return "claude";
27665
27839
  }
27666
27840
  function resumeCommandFor(sessionId, tool) {
@@ -27771,7 +27945,7 @@ function renderShowMessages(messages, opts = {}) {
27771
27945
  const calls = msg.toolCalls ?? [];
27772
27946
  if (!text.trim() && !thinking.trim() && !calls.length && msg.thinking !== void 0)
27773
27947
  continue;
27774
- out.push(`**${msg.role}** (line ${msg.line})`);
27948
+ out.push(msg.origin ? `**${msg.role}** (line ${msg.line}, written by the harness: ${msg.origin})` : `**${msg.role}** (line ${msg.line})`);
27775
27949
  if (thinking.trim())
27776
27950
  out.push(`_(thinking)_
27777
27951
  ${thinking}`);
@@ -27785,6 +27959,13 @@ ${thinking}`);
27785
27959
  }
27786
27960
  return out;
27787
27961
  }
27962
+ function nearestMessageLines(messages, line, count = 4) {
27963
+ return [...new Set(messages.map((m) => m.line))].sort((a, b) => Math.abs(a - line) - Math.abs(b - line) || a - b).slice(0, count).sort((a, b) => a - b);
27964
+ }
27965
+ function noMessageAtLine(messages, line, sessionId) {
27966
+ const near = nearestMessageLines(messages, line);
27967
+ return `No message at line ${line} in ${sessionId}. The nearest messages are at lines ${near.join(", ")}.`;
27968
+ }
27788
27969
 
27789
27970
  // src/cli.ts
27790
27971
  init_paths();
@@ -28541,7 +28722,7 @@ program.command("search <query>").description("Search your server for relevant s
28541
28722
  (c) => c.chunkType !== "summary" && c.chunkType !== "first_prompt"
28542
28723
  );
28543
28724
  if (chunk) {
28544
- const label = chunk.chunkType === "assistant" ? "Discussed" : chunk.chunkType === "user_context" ? "Asked about" : chunk.chunkType === "tool_result" ? "Tool result" : "Context";
28725
+ const label = chunk.chunkType === "assistant" ? "Discussed" : chunk.chunkType === "user_context" ? "Asked about" : chunk.chunkType === "tool_result" ? "Tool result" : chunk.chunkType === "harness" ? "Harness message" : "Context";
28545
28726
  let text = chunk.text.replace(/\n/g, " ").trim();
28546
28727
  if (text.length > 150) text = text.slice(0, 150) + "...";
28547
28728
  console.log(` ${chalk2.magenta(label + ":")} ${text}`);
@@ -28607,8 +28788,7 @@ program.command("show <session_id>").description("Show conversation content from
28607
28788
  const line = parseInt(options.line, 10);
28608
28789
  const hit = messagesList.find((m) => m.line === line);
28609
28790
  if (!hit) {
28610
- const lines = messagesList.map((m) => m.line);
28611
- console.log(chalk2.yellow(`No message at line ${options.line}. Its lines run from ${Math.min(...lines)} to ${Math.max(...lines)}.`));
28791
+ console.log(chalk2.yellow(noMessageAtLine(messagesList, line, sessionId)));
28612
28792
  process.exit(1);
28613
28793
  }
28614
28794
  console.log(renderShowMessages([hit], { full: true }).join("\n"));