threadnote 0.7.7 → 0.7.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.
@@ -28477,25 +28477,25 @@ var Protocol = class {
28477
28477
  });
28478
28478
  }
28479
28479
  _resetTimeout(messageId) {
28480
- const info = this._timeoutInfo.get(messageId);
28481
- if (!info)
28480
+ const info2 = this._timeoutInfo.get(messageId);
28481
+ if (!info2)
28482
28482
  return false;
28483
- const totalElapsed = Date.now() - info.startTime;
28484
- if (info.maxTotalTimeout && totalElapsed >= info.maxTotalTimeout) {
28483
+ const totalElapsed = Date.now() - info2.startTime;
28484
+ if (info2.maxTotalTimeout && totalElapsed >= info2.maxTotalTimeout) {
28485
28485
  this._timeoutInfo.delete(messageId);
28486
28486
  throw McpError.fromError(ErrorCode.RequestTimeout, "Maximum total timeout exceeded", {
28487
- maxTotalTimeout: info.maxTotalTimeout,
28487
+ maxTotalTimeout: info2.maxTotalTimeout,
28488
28488
  totalElapsed
28489
28489
  });
28490
28490
  }
28491
- clearTimeout(info.timeoutId);
28492
- info.timeoutId = setTimeout(info.onTimeout, info.timeout);
28491
+ clearTimeout(info2.timeoutId);
28492
+ info2.timeoutId = setTimeout(info2.onTimeout, info2.timeout);
28493
28493
  return true;
28494
28494
  }
28495
28495
  _cleanupTimeout(messageId) {
28496
- const info = this._timeoutInfo.get(messageId);
28497
- if (info) {
28498
- clearTimeout(info.timeoutId);
28496
+ const info2 = this._timeoutInfo.get(messageId);
28497
+ if (info2) {
28498
+ clearTimeout(info2.timeoutId);
28499
28499
  this._timeoutInfo.delete(messageId);
28500
28500
  }
28501
28501
  }
@@ -28540,8 +28540,8 @@ var Protocol = class {
28540
28540
  this._progressHandlers.clear();
28541
28541
  this._taskProgressTokens.clear();
28542
28542
  this._pendingDebouncedNotifications.clear();
28543
- for (const info of this._timeoutInfo.values()) {
28544
- clearTimeout(info.timeoutId);
28543
+ for (const info2 of this._timeoutInfo.values()) {
28544
+ clearTimeout(info2.timeoutId);
28545
28545
  }
28546
28546
  this._timeoutInfo.clear();
28547
28547
  for (const controller of this._requestHandlerAbortControllers.values()) {
@@ -30036,8 +30036,8 @@ function validateToolName(name) {
30036
30036
  function issueToolNameWarning(name, warnings) {
30037
30037
  if (warnings.length > 0) {
30038
30038
  console.warn(`Tool name validation warning for "${name}":`);
30039
- for (const warning of warnings) {
30040
- console.warn(` - ${warning}`);
30039
+ for (const warning2 of warnings) {
30040
+ console.warn(` - ${warning2}`);
30041
30041
  }
30042
30042
  console.warn("Tool registration will proceed, but this may cause compatibility issues.");
30043
30043
  console.warn("Consider updating the tool name to conform to the MCP tool naming standard.");
@@ -33591,6 +33591,39 @@ var import_node_crypto = require("node:crypto");
33591
33591
  var import_promises = require("node:fs/promises");
33592
33592
  var import_node_os = require("node:os");
33593
33593
  var import_node_path = require("node:path");
33594
+
33595
+ // src/cli_ui.ts
33596
+ var import_node_process2 = require("node:process");
33597
+ var ANSI = {
33598
+ blue: "\x1B[34m",
33599
+ bold: "\x1B[1m",
33600
+ cyan: "\x1B[36m",
33601
+ dim: "\x1B[2m",
33602
+ green: "\x1B[32m",
33603
+ red: "\x1B[31m",
33604
+ reset: "\x1B[0m",
33605
+ yellow: "\x1B[33m"
33606
+ };
33607
+ function color(name, text) {
33608
+ if (!shouldUseColor()) {
33609
+ return text;
33610
+ }
33611
+ return `${ANSI[name]}${text}${ANSI.reset}`;
33612
+ }
33613
+ function command(text) {
33614
+ return color("cyan", text);
33615
+ }
33616
+ function info(text) {
33617
+ return color("cyan", text);
33618
+ }
33619
+ function warning(text) {
33620
+ return color("yellow", text);
33621
+ }
33622
+ function shouldUseColor() {
33623
+ return import_node_process2.stdout.isTTY === true && process.env.NO_COLOR === void 0 && process.env.CI === void 0 && process.env.TERM !== "dumb";
33624
+ }
33625
+
33626
+ // src/utils.ts
33594
33627
  function isJsonObject(value) {
33595
33628
  return typeof value === "object" && value !== null && !Array.isArray(value);
33596
33629
  }
@@ -33609,11 +33642,11 @@ function redactText(content) {
33609
33642
  ).replace(/Bearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [REDACTED]").replace(/sk-[A-Za-z0-9_-]{16,}/g, "sk-[REDACTED]").replace(/gh[pousr]_[A-Za-z0-9_]{16,}/g, "gh_[REDACTED]");
33610
33643
  }
33611
33644
  async function requiredOpenVikingCli() {
33612
- const command = await findExecutable(["ov", "openviking"]);
33613
- if (!command) {
33645
+ const command2 = await findExecutable(["ov", "openviking"]);
33646
+ if (!command2) {
33614
33647
  throw new Error("Neither ov nor openviking was found in PATH. Run threadnote install first.");
33615
33648
  }
33616
- return command;
33649
+ return command2;
33617
33650
  }
33618
33651
  async function openVikingCliForMode(dryRun) {
33619
33652
  if (dryRun) {
@@ -33621,16 +33654,16 @@ async function openVikingCliForMode(dryRun) {
33621
33654
  }
33622
33655
  return requiredOpenVikingCli();
33623
33656
  }
33624
- async function requiredExecutable(command) {
33625
- const executable = await findExecutable([command]);
33657
+ async function requiredExecutable(command2) {
33658
+ const executable = await findExecutable([command2]);
33626
33659
  if (!executable) {
33627
- throw new Error(`${command} was not found in PATH.`);
33660
+ throw new Error(`${command2} was not found in PATH.`);
33628
33661
  }
33629
33662
  return executable;
33630
33663
  }
33631
33664
  async function findExecutable(commands) {
33632
- for (const command of commands) {
33633
- const result = await runCommand("which", [command], { allowFailure: true });
33665
+ for (const command2 of commands) {
33666
+ const result = await runCommand("which", [command2], { allowFailure: true });
33634
33667
  if (result.exitCode === 0 && result.stdout.trim()) {
33635
33668
  return result.stdout.trim();
33636
33669
  }
@@ -33639,7 +33672,8 @@ async function findExecutable(commands) {
33639
33672
  }
33640
33673
  async function maybeRun(dryRun, executable, args, options = {}) {
33641
33674
  const cwdSuffix = options.cwd ? ` (cwd: ${options.cwd})` : "";
33642
- console.log(`${dryRun ? "Would run" : "Running"}: ${formatShellCommand(executable, args)}${cwdSuffix}`);
33675
+ const label = dryRun ? warning("Would run") : info("Running");
33676
+ console.log(`${label}: ${command(formatShellCommand(executable, args))}${cwdSuffix}`);
33643
33677
  if (dryRun) {
33644
33678
  return void 0;
33645
33679
  }
@@ -33657,33 +33691,111 @@ async function runCommand(executable, args, options = {}) {
33657
33691
  const child = (0, import_node_child_process.spawn)(executable, args, { cwd: options.cwd });
33658
33692
  const stdoutChunks = [];
33659
33693
  const stderrChunks = [];
33694
+ const maxOutputBytes = options.maxOutputBytes ?? defaultCommandMaxOutputBytes();
33695
+ let stdoutBytes = 0;
33696
+ let stderrBytes = 0;
33697
+ let finished = false;
33698
+ let failureResult;
33699
+ let killTimer;
33700
+ let sentTerminationSignal = false;
33701
+ const timeoutMs = options.timeoutMs ?? defaultCommandTimeoutMs();
33702
+ const finish = (result) => {
33703
+ if (finished) {
33704
+ return;
33705
+ }
33706
+ finished = true;
33707
+ if (killTimer) {
33708
+ clearTimeout(killTimer);
33709
+ }
33710
+ if (result.exitCode !== 0 && options.allowFailure !== true) {
33711
+ rejectPromise(new Error(`${formatShellCommand(executable, args)} failed: ${result.stderr || result.stdout}`));
33712
+ return;
33713
+ }
33714
+ resolvePromise(result);
33715
+ };
33716
+ const failAndKill = (message) => {
33717
+ if (failureResult) {
33718
+ return;
33719
+ }
33720
+ failureResult = { exitCode: 124, stderr: message, stdout: stdoutChunks.join("") };
33721
+ if (!sentTerminationSignal) {
33722
+ sentTerminationSignal = true;
33723
+ child.kill("SIGTERM");
33724
+ }
33725
+ setTimeout(() => {
33726
+ if (!finished) {
33727
+ child.kill("SIGKILL");
33728
+ }
33729
+ }, 1e3).unref?.();
33730
+ };
33731
+ if (timeoutMs > 0) {
33732
+ killTimer = setTimeout(() => {
33733
+ failAndKill(`${formatShellCommand(executable, args)} timed out after ${timeoutMs}ms`);
33734
+ }, timeoutMs);
33735
+ killTimer.unref?.();
33736
+ }
33660
33737
  child.stdout.on("data", (chunk) => {
33661
- stdoutChunks.push(String(chunk));
33738
+ if (failureResult) {
33739
+ return;
33740
+ }
33741
+ const text = String(chunk);
33742
+ stdoutBytes += Buffer.byteLength(text);
33743
+ if (stdoutBytes + stderrBytes > maxOutputBytes) {
33744
+ failAndKill(`${formatShellCommand(executable, args)} exceeded output limit of ${maxOutputBytes} bytes`);
33745
+ return;
33746
+ }
33747
+ stdoutChunks.push(text);
33662
33748
  });
33663
33749
  child.stderr.on("data", (chunk) => {
33664
- stderrChunks.push(String(chunk));
33750
+ if (failureResult) {
33751
+ return;
33752
+ }
33753
+ const text = String(chunk);
33754
+ stderrBytes += Buffer.byteLength(text);
33755
+ if (stdoutBytes + stderrBytes > maxOutputBytes) {
33756
+ failAndKill(`${formatShellCommand(executable, args)} exceeded output limit of ${maxOutputBytes} bytes`);
33757
+ return;
33758
+ }
33759
+ stderrChunks.push(text);
33665
33760
  });
33666
33761
  child.on("error", (err) => {
33762
+ if (finished) {
33763
+ return;
33764
+ }
33765
+ if (killTimer) {
33766
+ clearTimeout(killTimer);
33767
+ }
33667
33768
  if (options.allowFailure === true) {
33668
- resolvePromise({ exitCode: 127, stderr: errorMessage(err), stdout: "" });
33769
+ finish({ exitCode: 127, stderr: errorMessage(err), stdout: "" });
33669
33770
  } else {
33771
+ finished = true;
33670
33772
  rejectPromise(err);
33671
33773
  }
33672
33774
  });
33673
33775
  child.on("close", (code) => {
33674
- const result = {
33776
+ const result = failureResult ?? {
33675
33777
  exitCode: code ?? 1,
33676
33778
  stderr: stderrChunks.join(""),
33677
33779
  stdout: stdoutChunks.join("")
33678
33780
  };
33679
- if (result.exitCode !== 0 && options.allowFailure !== true) {
33680
- rejectPromise(new Error(`${formatShellCommand(executable, args)} failed: ${result.stderr || result.stdout}`));
33681
- return;
33682
- }
33683
- resolvePromise(result);
33781
+ finish(result);
33684
33782
  });
33685
33783
  });
33686
33784
  }
33785
+ function defaultCommandTimeoutMs() {
33786
+ return positiveIntegerFromEnv("THREADNOTE_COMMAND_TIMEOUT_MS") ?? 10 * 60 * 1e3;
33787
+ }
33788
+ function defaultCommandMaxOutputBytes() {
33789
+ return positiveIntegerFromEnv("THREADNOTE_COMMAND_MAX_OUTPUT_BYTES") ?? 5 * 1024 * 1024;
33790
+ }
33791
+ function positiveIntegerFromEnv(name) {
33792
+ const value = process.env[name];
33793
+ if (value === void 0) {
33794
+ return void 0;
33795
+ }
33796
+ const parsed = Number.parseInt(value, 10);
33797
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
33798
+ }
33687
33799
  async function gitValue(args, cwd = getInvocationCwd()) {
33688
33800
  const result = await runCommand("git", args, { allowFailure: true, cwd });
33689
33801
  if (result.exitCode !== 0) {
@@ -34445,9 +34557,9 @@ async function syncSharedReposBeforeAgentRead(config2) {
34445
34557
  const remainingBehind = new Set(state.behindTeams);
34446
34558
  for (const team of syncTeams) {
34447
34559
  try {
34448
- const warning = await runShareSyncQuiet(config2, state, { team });
34449
- if (warning) {
34450
- warnings.push(warning);
34560
+ const warning2 = await runShareSyncQuiet(config2, state, { team });
34561
+ if (warning2) {
34562
+ warnings.push(warning2);
34451
34563
  } else {
34452
34564
  remainingBehind.delete(team);
34453
34565
  syncedTeams.push(team);
@@ -34529,8 +34641,8 @@ async function refreshShareUpdateState(config2, options) {
34529
34641
  state,
34530
34642
  async () => refreshShareUpdateStateLocked(config2, state, options)
34531
34643
  );
34532
- for (const warning of warnings) {
34533
- console.error(warning);
34644
+ for (const warning2 of warnings) {
34645
+ console.error(warning2);
34534
34646
  }
34535
34647
  }
34536
34648
  async function refreshShareUpdateStateLocked(config2, state, options) {
@@ -34637,6 +34749,58 @@ async function runShareSyncQuiet(config2, state, options) {
34637
34749
  }
34638
34750
  return void 0;
34639
34751
  }
34752
+ async function publishShareGitChange(worktree, relativePath, commitMessage, options = {}) {
34753
+ const dryRun = options.dryRun === true;
34754
+ const push = options.push !== false;
34755
+ const verb = options.verb ?? "add";
34756
+ const git = await requiredExecutable("git");
34757
+ const messages = [];
34758
+ const stageArgs = verb === "rm" ? ["-C", worktree, "rm", relativePath] : ["-C", worktree, "add", "--", relativePath];
34759
+ const stageResult = await runGitCommand(dryRun, git, stageArgs, `git ${verb} failed`);
34760
+ if (stageResult) {
34761
+ messages.push(`git ${verb}: ${stageResult.stdout.trim() || "ok"}`);
34762
+ }
34763
+ if (dryRun) {
34764
+ console.log(`Would run: ${formatShellCommand(git, ["-C", worktree, "commit", "-m", commitMessage])}`);
34765
+ } else {
34766
+ const commitResult = await runCommand(git, ["-C", worktree, "commit", "-m", commitMessage], { allowFailure: true });
34767
+ if (commitResult.exitCode !== 0) {
34768
+ const detail = commitResult.stdout.trim() || commitResult.stderr.trim();
34769
+ if (/nothing to commit|no changes added/i.test(detail)) {
34770
+ messages.push("git commit: nothing to commit (file already in tree)");
34771
+ } else {
34772
+ throw new Error(`git commit failed: ${detail || "unknown error"}`);
34773
+ }
34774
+ } else {
34775
+ messages.push(`git commit: ${commitResult.stdout.trim().split("\n").slice(0, 2).join(" ")}`);
34776
+ }
34777
+ }
34778
+ if (!push) {
34779
+ messages.push("git push skipped (push=false)");
34780
+ return messages;
34781
+ }
34782
+ const pushResult = await runGitCommand(
34783
+ dryRun,
34784
+ git,
34785
+ ["-C", worktree, "push", DEFAULT_GIT_REMOTE_NAME],
34786
+ "git push failed"
34787
+ );
34788
+ if (pushResult) {
34789
+ messages.push(`git push: ${pushResult.stdout.trim() || pushResult.stderr.trim() || "ok"}`);
34790
+ }
34791
+ return messages;
34792
+ }
34793
+ async function runGitCommand(dryRun, git, args, failureLabel) {
34794
+ if (dryRun) {
34795
+ console.log(`Would run: ${formatShellCommand(git, args)}`);
34796
+ return void 0;
34797
+ }
34798
+ const result = await runCommand(git, args, { allowFailure: true });
34799
+ if (result.exitCode !== 0) {
34800
+ throw new Error(`${failureLabel}: ${result.stderr.trim() || result.stdout.trim() || "unknown error"}`);
34801
+ }
34802
+ return result;
34803
+ }
34640
34804
  function normalizeTeamName(input) {
34641
34805
  const candidate = (input ?? "default").trim();
34642
34806
  if (!candidate) {
@@ -34917,61 +35081,20 @@ async function waitForOvQueue(ov, config2, options = {}) {
34917
35081
  console.error(result.stderr.trim());
34918
35082
  }
34919
35083
  }
34920
- function isTransientOvFailure(stderr, stdout) {
35084
+ function isTransientOvFailure(stderr, stdout2) {
34921
35085
  const output = `${stderr}
34922
- ${stdout}`.toLowerCase();
35086
+ ${stdout2}`.toLowerCase();
34923
35087
  return output.includes("resource is busy") || output.includes("resource is being processed") || output.includes("network error") || output.includes("error sending request") || output.includes("http request failed") || output.includes("connection refused") || output.includes("connection reset") || output.includes("timed out");
34924
35088
  }
34925
- function isResourceBusyFailure(stderr, stdout) {
35089
+ function isResourceBusyFailure(stderr, stdout2) {
34926
35090
  const output = `${stderr}
34927
- ${stdout}`.toLowerCase();
35091
+ ${stdout2}`.toLowerCase();
34928
35092
  return output.includes("resource is busy") || output.includes("resource is being processed");
34929
35093
  }
34930
35094
  async function ingestSingleFile(ov, config2, uri, filePath, initialMode, options = {}) {
34931
35095
  const content = await (0, import_promises3.readFile)(filePath, "utf8");
34932
35096
  await writeMemoryFile(config2, ov, uri, content, initialMode, false, options);
34933
35097
  }
34934
- async function removeWithRollback(config2, ov, sourceUri, rollbackUri, worktree, dryRun, label) {
34935
- try {
34936
- await removeMemoryUri(config2, ov, sourceUri, dryRun);
34937
- } catch (sourceErr) {
34938
- if (dryRun) {
34939
- throw sourceErr;
34940
- }
34941
- console.error(
34942
- `Source removal failed during ${label}; rolling back ${rollbackUri} so the system is back to the pre-${label} state.`
34943
- );
34944
- try {
34945
- await removeMemoryUri(config2, ov, rollbackUri, false);
34946
- } catch (rollbackErr) {
34947
- console.error(
34948
- `Rollback of ${rollbackUri} also failed. Manual cleanup needed via: threadnote forget ${rollbackUri}
34949
- Rollback error: ${rollbackErr instanceof Error ? rollbackErr.message : String(rollbackErr)}`
34950
- );
34951
- }
34952
- await bestEffortRemoveWorktreeFile(rollbackUri, worktree, label);
34953
- throw sourceErr;
34954
- }
34955
- }
34956
- async function bestEffortRemoveWorktreeFile(rollbackUri, worktree, label) {
34957
- if (label !== "publish") {
34958
- return;
34959
- }
34960
- const prefix = "viking://";
34961
- if (!rollbackUri.startsWith(prefix)) {
34962
- return;
34963
- }
34964
- const parts = rollbackUri.slice(prefix.length).split("/");
34965
- const sharedIndex = parts.indexOf("shared");
34966
- if (sharedIndex === -1 || sharedIndex + 2 >= parts.length) {
34967
- return;
34968
- }
34969
- const relative2 = parts.slice(sharedIndex + 2).join("/");
34970
- if (!relative2) {
34971
- return;
34972
- }
34973
- await (0, import_promises3.rm)((0, import_node_path3.join)(worktree, relative2), { force: true });
34974
- }
34975
35098
  async function removeMemoryUri(config2, ov, uri, dryRun, options = {}) {
34976
35099
  const args = withIdentity(config2, ["rm", uri]);
34977
35100
  if (dryRun) {
@@ -35133,7 +35256,7 @@ async function main() {
35133
35256
  "During feature work, update durable feature knowledge when valuable implementation details, decisions, interfaces, or gotchas change.",
35134
35257
  "When updating the same active issue, pass project/topic or replaceUri to remember_context so duplicate durable memories or handoffs do not accumulate.",
35135
35258
  "Use compact_context with dryRun=true for scoped memory hygiene when recall surfaces overlapping active memories.",
35136
- "To share a durable memory with teammates, call `share_publish` with its viking:// URI. share_publish is destructive: it scrubs for secrets, moves the memory into the shared subtree, removes the personal copy, and pushes a git commit. Do not publish handoffs, preferences, or anything carrying machine-local paths or in-flight task context.",
35259
+ "To share a durable memory with teammates, call `share_publish` with its viking:// URI. share_publish scrubs for secrets, writes and pushes the shared copy first, then removes the personal copy after the push succeeds. Do not publish handoffs, preferences, or anything carrying machine-local paths or in-flight task context.",
35137
35260
  "Do not store secrets, customer data, raw production logs, or credentials."
35138
35261
  ].join("\n")
35139
35262
  }
@@ -35317,7 +35440,7 @@ function registerTools(server, config2) {
35317
35440
  "share_publish",
35318
35441
  {
35319
35442
  annotations: { readOnlyHint: false, destructiveHint: true },
35320
- description: "Publish a personal memory into a team's shared memories git repo. Reads the memory, strips local-only provenance frontmatter (supersedes:, archived_from:), refuses publish if it matches secret patterns, copies it into the shared subtree, removes the personal original, and commits/pushes. Default team is used unless team is provided. Pass preview=true to return the would-be-published bytes without writing or committing.",
35443
+ description: "Publish a personal memory into a team's shared memories git repo. Reads the memory, strips local-only provenance frontmatter (supersedes:, archived_from:), refuses publish if it matches secret patterns, writes and pushes the shared copy first, then removes the personal original. Default team is used unless team is provided. Pass preview=true to return the would-be-published bytes without writing or committing.",
35321
35444
  inputSchema: {
35322
35445
  message: external_exports.string().optional().describe('Commit message override; defaults to "share: publish <path>"'),
35323
35446
  preview: external_exports.boolean().optional().describe(
@@ -35428,8 +35551,8 @@ ${exactMatches}`);
35428
35551
  if (syncedTeams.length > 0) {
35429
35552
  sections.push(`Auto-synced shared memories: ${syncedTeams.join(", ")}`);
35430
35553
  }
35431
- for (const warning of syncWarnings) {
35432
- sections.push(`Auto-sync warning: ${warning}`);
35554
+ for (const warning2 of syncWarnings) {
35555
+ sections.push(`Auto-sync warning: ${warning2}`);
35433
35556
  }
35434
35557
  if (sections.length <= 1) {
35435
35558
  return semanticResult;
@@ -35527,7 +35650,7 @@ function registerReadTool(server, config2, name, description) {
35527
35650
  }
35528
35651
  const syncMessages = [
35529
35652
  syncedTeams.length > 0 ? `Auto-synced shared memories: ${syncedTeams.join(", ")}` : void 0,
35530
- ...syncWarnings.map((warning) => `Auto-sync warning: ${warning}`)
35653
+ ...syncWarnings.map((warning2) => `Auto-sync warning: ${warning2}`)
35531
35654
  ].filter((part) => part !== void 0);
35532
35655
  return {
35533
35656
  ...result,
@@ -35715,27 +35838,8 @@ function registerCompactTool(server, config2) {
35715
35838
  const ov = await requiredOpenVikingCli2();
35716
35839
  const appliedMessages = [];
35717
35840
  for (const action of plan.keepUpdates) {
35718
- const result = await runOpenVikingWriteWithRetry(
35719
- ov,
35720
- config2,
35721
- action.uri,
35722
- withIdentity2(config2, [
35723
- "write",
35724
- action.uri,
35725
- "--content",
35726
- action.content,
35727
- "--mode",
35728
- "replace",
35729
- "--wait",
35730
- "--timeout",
35731
- "120"
35732
- ])
35733
- );
35841
+ await writeMemoryFile(config2, ov, action.uri, action.content, "replace", false, { quiet: true });
35734
35842
  appliedMessages.push(`Updated kept memory: ${action.uri}`);
35735
- const output = [result.stdout.trim(), result.stderr.trim()].filter(Boolean).join("\n");
35736
- if (output) {
35737
- appliedMessages.push(output);
35738
- }
35739
35843
  }
35740
35844
  for (const action of plan.archives) {
35741
35845
  const archiveResult = await archiveMemoryForCompact(config2, ov, action);
@@ -35908,22 +36012,7 @@ async function writeDurableMemory(config2, params) {
35908
36012
  const finalMetadata = isInPlaceUpdate ? { ...params.metadata, supersedes: void 0 } : candidateMetadata;
35909
36013
  const memory = isInPlaceUpdate ? formatMemoryDocument2("MEMORY", finalMetadata, params.bodyText) : candidateMemory;
35910
36014
  const writeMode = await memoryWriteMode(ov, config2, memoryUri, finalMetadata);
35911
- const result = await runOpenVikingWriteWithRetry(
35912
- ov,
35913
- config2,
35914
- memoryUri,
35915
- withIdentity2(config2, [
35916
- "write",
35917
- memoryUri,
35918
- "--content",
35919
- memory,
35920
- "--mode",
35921
- writeMode,
35922
- "--wait",
35923
- "--timeout",
35924
- "120"
35925
- ])
35926
- );
36015
+ await writeMemoryFile(config2, ov, memoryUri, memory, writeMode, false, { quiet: true });
35927
36016
  const messages = [`Stored memory: ${memoryUri}`];
35928
36017
  if (params.replaceUri && !isInPlaceUpdate) {
35929
36018
  const removedReplacedMemory = await removeVikingResourceWithRetry(ov, config2, params.replaceUri);
@@ -35933,8 +36022,7 @@ async function writeDurableMemory(config2, params) {
35933
36022
  } else if (isInPlaceUpdate) {
35934
36023
  messages.push(`Updated existing memory in place: ${memoryUri}`);
35935
36024
  }
35936
- const text = [result.stdout.trim(), result.stderr.trim()].filter(Boolean).join("\n");
35937
- return { content: [{ type: "text", text: [...messages, text].filter(Boolean).join("\n") }] };
36025
+ return { content: [{ type: "text", text: messages.join("\n") }] };
35938
36026
  } catch (err) {
35939
36027
  return { content: [{ type: "text", text: errorMessage(err) }], isError: true };
35940
36028
  }
@@ -35969,27 +36057,10 @@ async function writeSharedMemoryReplacement(config2, ov, params, targetUri) {
35969
36057
  messages.push(`Redacted ${redaction.count}\xD7 ${redaction.name} before shared update.`);
35970
36058
  }
35971
36059
  messages.push(
35972
- ...await gitPublishWorkflow(resolved.config.worktree, relativePath, `share: update ${relativePath}`, true)
36060
+ ...await publishShareGitChange(resolved.config.worktree, relativePath, `share: update ${relativePath}`)
35973
36061
  );
35974
36062
  return { content: [{ type: "text", text: messages.join("\n") }] };
35975
36063
  }
35976
- async function runOpenVikingWriteWithRetry(ov, config2, memoryUri, args) {
35977
- for (let attempt = 0; attempt < 4; attempt += 1) {
35978
- const result = await runCommand(ov, args, { allowFailure: true });
35979
- if (result.exitCode === 0) {
35980
- return result;
35981
- }
35982
- if (await vikingResourceExists2(ov, config2, memoryUri)) {
35983
- await runCommand(ov, withIdentity2(config2, ["wait", "--timeout", "120"]), { allowFailure: true });
35984
- return { exitCode: 0, stdout: "OpenViking accepted the memory and indexing wait completed.", stderr: "" };
35985
- }
35986
- if (!isResourceBusy(result.stderr, result.stdout) || attempt === 3) {
35987
- throw new Error(`${ov} ${args.join(" ")} failed: ${result.stderr || result.stdout}`);
35988
- }
35989
- await sleep(1e3 * (attempt + 1));
35990
- }
35991
- throw new Error(`${ov} ${args.join(" ")} failed.`);
35992
- }
35993
36064
  async function vikingResourceExists2(ov, config2, uri) {
35994
36065
  const stat2 = await runCommand(ov, withIdentity2(config2, ["stat", uri]), { allowFailure: true });
35995
36066
  return stat2.exitCode === 0;
@@ -36011,9 +36082,9 @@ async function removeVikingResourceWithRetry(ov, config2, uri) {
36011
36082
  }
36012
36083
  return false;
36013
36084
  }
36014
- function isResourceBusy(stderr, stdout) {
36085
+ function isResourceBusy(stderr, stdout2) {
36015
36086
  const output = `${stderr}
36016
- ${stdout}`.toLowerCase();
36087
+ ${stdout2}`.toLowerCase();
36017
36088
  return output.includes("resource is busy") || output.includes("resource is being processed");
36018
36089
  }
36019
36090
  async function ensureMemoryDirectory(ov, config2, directoryUri) {
@@ -36217,17 +36288,27 @@ async function runSharePublishTool(config2, sourceUri, options) {
36217
36288
  }
36218
36289
  await ensureSharedDirectoryChain(config2, ov, targetUri, false);
36219
36290
  await writeMemoryFile(config2, ov, targetUri, content, "create", false);
36291
+ const messages = [`Published ${sourceUri} -> ${targetUri}`];
36292
+ for (const redaction of scrub.redactions) {
36293
+ messages.push(`Redacted ${redaction.count}\xD7 ${redaction.name} before publish.`);
36294
+ }
36295
+ const relativePath = vikingUriToWorktreeRelative(config2, targetUri, resolved.name);
36296
+ const commitMessage = options.message ?? `share: publish ${relativePath}`;
36297
+ const gitMessages = await publishShareGitChange(resolved.config.worktree, relativePath, commitMessage, {
36298
+ push: options.push
36299
+ });
36220
36300
  try {
36221
- await removeWithRollback(config2, ov, sourceUri, targetUri, resolved.config.worktree, false, "publish");
36301
+ await removeMemoryUri(config2, ov, sourceUri, false, { quiet: true });
36222
36302
  } catch (sourceErr) {
36223
36303
  return {
36224
36304
  content: [
36225
36305
  {
36226
36306
  type: "text",
36227
36307
  text: [
36228
- `Refused to leave a half-published state: could not remove ${sourceUri}.`,
36229
- `Rolled back ${targetUri} so the system is back to the pre-publish state.`,
36230
- `Retry the publish once OpenViking's queue settles.`,
36308
+ ...messages,
36309
+ ...gitMessages,
36310
+ `Could not remove the personal source after publish: ${sourceUri}.`,
36311
+ `Retry cleanup later with: threadnote forget ${sourceUri}`,
36231
36312
  sourceErr instanceof Error ? sourceErr.message : String(sourceErr)
36232
36313
  ].join("\n")
36233
36314
  }
@@ -36235,18 +36316,6 @@ async function runSharePublishTool(config2, sourceUri, options) {
36235
36316
  isError: true
36236
36317
  };
36237
36318
  }
36238
- const messages = [`Published ${sourceUri} -> ${targetUri}`];
36239
- for (const redaction of scrub.redactions) {
36240
- messages.push(`Redacted ${redaction.count}\xD7 ${redaction.name} before publish.`);
36241
- }
36242
- const relativePath = vikingUriToWorktreeRelative(config2, targetUri, resolved.name);
36243
- const commitMessage = options.message ?? `share: publish ${relativePath}`;
36244
- const gitMessages = await gitPublishWorkflow(
36245
- resolved.config.worktree,
36246
- relativePath,
36247
- commitMessage,
36248
- options.push !== false
36249
- );
36250
36319
  return {
36251
36320
  content: [{ type: "text", text: [...messages, ...gitMessages].join("\n") }],
36252
36321
  isError: false
@@ -36255,47 +36324,15 @@ async function runSharePublishTool(config2, sourceUri, options) {
36255
36324
  return { content: [{ type: "text", text: errorMessage(err) }], isError: true };
36256
36325
  }
36257
36326
  }
36258
- async function gitPublishWorkflow(worktree, relativePath, commitMessage, push) {
36259
- const messages = [];
36260
- const add = await runCommand("git", ["-C", worktree, "add", relativePath], { allowFailure: true });
36261
- if (add.exitCode !== 0) {
36262
- messages.push(`git add failed: ${add.stderr.trim() || add.stdout.trim()}`);
36263
- return messages;
36264
- }
36265
- const commit = await runCommand("git", ["-C", worktree, "commit", "-m", commitMessage], { allowFailure: true });
36266
- if (commit.exitCode !== 0) {
36267
- const detail = commit.stdout.trim() || commit.stderr.trim();
36268
- if (/nothing to commit|no changes added/i.test(detail)) {
36269
- messages.push("git commit: nothing to commit (file already in tree)");
36270
- } else {
36271
- messages.push(`git commit failed: ${detail}`);
36272
- return messages;
36273
- }
36274
- } else {
36275
- messages.push(`git commit: ${commit.stdout.trim().split("\n").slice(0, 2).join(" ")}`);
36276
- }
36277
- if (!push) {
36278
- messages.push("git push skipped (push=false)");
36279
- return messages;
36280
- }
36281
- const pushResult = await runCommand("git", ["-C", worktree, "push", DEFAULT_GIT_REMOTE_NAME], { allowFailure: true });
36282
- const pushDetail = pushResult.stdout.trim() || pushResult.stderr.trim();
36283
- if (pushResult.exitCode !== 0) {
36284
- messages.push(`git push failed: ${pushDetail}`);
36285
- } else {
36286
- messages.push(`git push: ${pushDetail || "ok"}`);
36287
- }
36288
- return messages;
36289
- }
36290
36327
  function withIdentity2(config2, args) {
36291
36328
  return [...args, "--account", config2.account, "--user", config2.user, "--agent-id", config2.agentId];
36292
36329
  }
36293
36330
  async function requiredOpenVikingCli2() {
36294
- const command = process.env.THREADNOTE_OV ?? await findExecutable(["ov", "openviking"]) ?? await firstExistingPath([(0, import_node_path4.join)((0, import_node_os3.homedir)(), ".local", "bin", "ov"), (0, import_node_path4.join)((0, import_node_os3.homedir)(), ".local", "bin", "openviking")]);
36295
- if (!command) {
36331
+ const command2 = process.env.THREADNOTE_OV ?? await findExecutable(["ov", "openviking"]) ?? await firstExistingPath([(0, import_node_path4.join)((0, import_node_os3.homedir)(), ".local", "bin", "ov"), (0, import_node_path4.join)((0, import_node_os3.homedir)(), ".local", "bin", "openviking")]);
36332
+ if (!command2) {
36296
36333
  throw new Error("Neither ov nor openviking was found. Run threadnote install first.");
36297
36334
  }
36298
- return command;
36335
+ return command2;
36299
36336
  }
36300
36337
  async function firstExistingPath(paths) {
36301
36338
  for (const path of paths) {