@norman-else/dsh-claude 0.1.44 → 0.1.46

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/lib/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { A as CLAUDE_REPOSITORY_FEEDBACK_PATH, B as DEFAULT_CLAUDE_RENDER_MODE, C as CLAUDE_PROJECTION_PATH, D as CLAUDE_PROSE_MODES, E as CLAUDE_PROMPT_REFINE_PATH, F as CLAUDE_REWIND_PATH, G as isClaudeRenderMode, H as TASK_TOOL_NAMES, I as CLAUDE_UPDATE_CHECK_PATH, L as CLAUDE_UPDATE_PATH, M as CLAUDE_REPOSITORY_SETUP_PATH, N as CLAUDE_REPOSITORY_STATUS_PATH, O as CLAUDE_RENDER_MODES, P as CLAUDE_REVIEW_COMMENT_PATH, R as CLAUDE_USAGE_PATH, S as CLAUDE_PLAN_FEEDBACK_PATH, T as CLAUDE_PROMPT_NAME_PATH, U as isClaudeAlertMode, W as isClaudeProseMode, _ as CLAUDE_CODE_PROVIDER_IDS, a as latestClaudeTasks, b as CLAUDE_GLOBAL_SETTINGS_PATH, c as normalizeTasksEvent, d as CLAUDE_ACTIVITY_EVENT, f as CLAUDE_ALERT_MODES, g as CLAUDE_CODE_PROVIDER, h as CLAUDE_CODE_PRESET_ID, i as latestClaudeSessionBinding, j as CLAUDE_REPOSITORY_FILE_PATH, k as CLAUDE_REPOSITORY_ACTION_PATH, l as redactText, m as CLAUDE_CLIENT_DIAGNOSTICS_PATH, n as currentClaudeActivityCursor, o as normalizeActivity, p as CLAUDE_ASK_PATH, r as latestClaudeContextUsage, s as normalizeContextUsage, t as boundText, u as safeDetail, v as CLAUDE_DOCTOR_PATH, w as CLAUDE_PROMPTS_PATH, x as CLAUDE_JIRA_PATH, y as CLAUDE_EDITOR_OPEN_PATH, z as DEFAULT_CLAUDE_PROSE_MODE } from "./events-oovRTmX7.mjs";
2
2
  import { a as projectClaudeCommands, i as CLAUDE_COMMANDS_SERVICE, r as dynamicPresenterDefinition, t as CLAUDE_PRESENTER_NAMES } from "./presenters-BVWj7u0a.mjs";
3
- import { a as resolveClaudeExecutable, n as ensureManagedPreset, o as runClaudeDoctor, t as ManagedPresetConflictError } from "./preset-installer-JUktnfwS.mjs";
3
+ import { a as resolveClaudeExecutable, n as ensureManagedPreset, o as runClaudeDoctor, t as ManagedPresetConflictError } from "./preset-installer-CuosP3sA.mjs";
4
4
  import z from "@deepseek-ai/schemastery";
5
5
  import { createHash, randomUUID } from "node:crypto";
6
6
  import { chmod, mkdir, opendir, readFile, readdir, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
@@ -846,7 +846,7 @@ function createPermissionBridge(approval, activeContext, userQuestion, planFeedb
846
846
  ...plan === void 0 ? {} : { text: plan }
847
847
  });
848
848
  const userDecides = plan !== void 0;
849
- silenced = userDecides && approvalPolicyOf(session.events) === SILENT_POLICY;
849
+ silenced = userDecides && approvalPolicyOf(session.snapshotEvents()) === SILENT_POLICY;
850
850
  if (silenced) session.append("approval/policy", { policy: ASKING_POLICY });
851
851
  const alreadyFullAccess = !userDecides && await active.hasFullAccess?.() === true;
852
852
  const revision = new AbortController();
@@ -1815,6 +1815,10 @@ const MAX_OUTPUT_BYTES$5 = 65536;
1815
1815
  * status probes' five seconds, and this sits in front of every turn. */
1816
1816
  const GIT_TIMEOUT_MS$4 = 3e4;
1817
1817
  const OBJECT_NAME = /^[0-9a-f]{40}$|^[0-9a-f]{64}$/u;
1818
+ /** A snapshot holds the bytes on disk, not git's line-ending translation of
1819
+ * them. Under `core.autocrlf=true` a plain `add` would store LF and the
1820
+ * restore would write CRLF, so a rewind on Windows would flip every LF file. */
1821
+ const GIT_OPTIONS = ["-c", "core.autocrlf=false"];
1818
1822
  async function collect$4(handle) {
1819
1823
  return {
1820
1824
  exitCode: (await handle.done).exitCode,
@@ -1823,7 +1827,11 @@ async function collect$4(handle) {
1823
1827
  }
1824
1828
  async function run$1(runtime, git, args, cwd, env = {}) {
1825
1829
  return collect$4(runtime.spawn({
1826
- argv: [git, ...args],
1830
+ argv: [
1831
+ git,
1832
+ ...GIT_OPTIONS,
1833
+ ...args
1834
+ ],
1827
1835
  cwd,
1828
1836
  stdio: {
1829
1837
  stdin: "ignore",
@@ -2283,7 +2291,7 @@ var ClaudeSupervisor = class {
2283
2291
  } else await this.#syncPermissionMode(entry);
2284
2292
  await throwIfUnavailable();
2285
2293
  const promptUuid = randomUUID();
2286
- const cursor = currentClaudeActivityCursor(request.agent.session.events);
2294
+ const cursor = currentClaudeActivityCursor(request.agent.session.snapshotEvents());
2287
2295
  const projection = await this.#sidecar.read(sessionId);
2288
2296
  await throwIfUnavailable();
2289
2297
  cursor.nextOrdinal = projection.activities.reduce((next, activity) => activity.turn === cursor.turn && activity.step === cursor.step ? Math.max(next, activity.ordinal + 1) : next, 0);
@@ -2436,7 +2444,7 @@ var ClaudeSupervisor = class {
2436
2444
  }
2437
2445
  }
2438
2446
  async #syncPermissionMode(entry) {
2439
- const mode = claudePermissionMode(entry.ownerAgent.session.events);
2447
+ const mode = claudePermissionMode(entry.ownerAgent.session.snapshotEvents());
2440
2448
  if (mode === entry.permissionMode) return;
2441
2449
  await this.#control(entry, entry.query.setPermissionMode(mode), "Claude Code permission mode switch");
2442
2450
  entry.permissionMode = mode;
@@ -2530,13 +2538,13 @@ var ClaudeSupervisor = class {
2530
2538
  const cwd = agent.session.header.cwd ?? process.cwd();
2531
2539
  const input = new AsyncQueue();
2532
2540
  const lifetime = new AbortController();
2533
- const projection = await this.#sidecar.importLegacy(sessionId, agent.session.events);
2541
+ const projection = await this.#sidecar.importLegacy(sessionId, agent.session.snapshotEvents());
2534
2542
  if (signalAborted(signal) || signalAborted(cancellationSignal)) throw abortFailure();
2535
2543
  const binding = projection.binding;
2536
2544
  const pendingRewind = projection.rewind?.pending;
2537
2545
  const forkAt = pendingRewind !== void 0 && "resumeAt" in pendingRewind ? pendingRewind.resumeAt : void 0;
2538
2546
  const startFresh = pendingRewind !== void 0 && "fresh" in pendingRewind;
2539
- const permissionMode = claudePermissionMode(agent.session.events);
2547
+ const permissionMode = claudePermissionMode(agent.session.snapshotEvents());
2540
2548
  const entry = {
2541
2549
  sessionId,
2542
2550
  ownerAgent: agent,
@@ -2788,7 +2796,7 @@ var ClaudeSupervisor = class {
2788
2796
  case "unknown":
2789
2797
  await this.#appendActivity(active, {
2790
2798
  kind: message.kind === "status" ? "status" : "warning",
2791
- phase: "updated",
2799
+ phase: "completed",
2792
2800
  title: message.title,
2793
2801
  ..."summary" in message ? { summary: message.summary } : {},
2794
2802
  ..."detail" in message ? { detail: message.detail } : {}
@@ -4236,6 +4244,7 @@ function envelope(projection, meta) {
4236
4244
  ...projection.contextUsage === void 0 ? {} : { contextUsage: projection.contextUsage },
4237
4245
  ...projection.tasks === void 0 ? {} : { tasks: projection.tasks },
4238
4246
  ...meta.repository === void 0 ? {} : { repository: meta.repository },
4247
+ ...meta.repositories === void 0 ? {} : { repositories: meta.repositories },
4239
4248
  reviewComments: meta.reviewComments,
4240
4249
  ...projection.rewind === void 0 ? {} : { rewind: { ranges: projection.rewind.ranges } }
4241
4250
  };
@@ -4251,7 +4260,7 @@ function envelope(projection, meta) {
4251
4260
  * session spent it in proportion to how many Claude sessions existed — which
4252
4261
  * is what left the settings panel unable to get a connection at all. One
4253
4262
  * carrier is one connection, whatever the session count. */
4254
- function registerClaudeProjectionRoute(ctx, sidecar, ownsSession, commandsForSession = () => [], repositoryForSession = async () => void 0, reviewCommentsForSession = () => []) {
4263
+ function registerClaudeProjectionRoute(ctx, sidecar, ownsSession, commandsForSession = () => [], repositoryForSession = async () => void 0, reviewCommentsForSession = () => [], extraRepositoriesForSession = async () => []) {
4255
4264
  const info = (message) => {
4256
4265
  ctx.logger?.info?.(message);
4257
4266
  };
@@ -4264,12 +4273,14 @@ function registerClaudeProjectionRoute(ctx, sidecar, ownsSession, commandsForSes
4264
4273
  reviewComments: owned ? reviewCommentsForSession(sessionId) : []
4265
4274
  };
4266
4275
  };
4267
- const assembleMeta = async (sessionId) => {
4276
+ const assembleMeta = async (sessionId, activities) => {
4268
4277
  const meta = localMeta(sessionId);
4269
- const repository = meta.owned ? await repositoryForSession(sessionId) : void 0;
4278
+ if (!meta.owned) return meta;
4279
+ const [repository, repositories] = await Promise.all([repositoryForSession(sessionId), extraRepositoriesForSession(sessionId, activities ?? (await sidecar.read(sessionId)).activities)]);
4270
4280
  return {
4271
4281
  ...meta,
4272
- ...repository === void 0 ? {} : { repository }
4282
+ ...repository === void 0 ? {} : { repository },
4283
+ ...repositories.length === 0 ? {} : { repositories }
4273
4284
  };
4274
4285
  };
4275
4286
  const streamMulti = async (res, io, sessionIds) => {
@@ -4301,6 +4312,7 @@ function registerClaudeProjectionRoute(ctx, sidecar, ownsSession, commandsForSes
4301
4312
  owned: meta.owned,
4302
4313
  commands: meta.commands,
4303
4314
  ...meta.repository === void 0 ? {} : { repository: meta.repository },
4315
+ ...meta.repositories === void 0 ? {} : { repositories: meta.repositories },
4304
4316
  reviewComments: meta.reviewComments
4305
4317
  });
4306
4318
  };
@@ -4314,7 +4326,7 @@ function registerClaudeProjectionRoute(ctx, sidecar, ownsSession, commandsForSes
4314
4326
  seq: sidecar.sequence(sessionId),
4315
4327
  ...envelope(projection, meta)
4316
4328
  });
4317
- const probed = await assembleMeta(sessionId);
4329
+ const probed = await assembleMeta(sessionId, projection.activities);
4318
4330
  if (closed || JSON.stringify(probed) === JSON.stringify(metas.get(sessionId))) return;
4319
4331
  writeMeta(sessionId, probed);
4320
4332
  };
@@ -4427,7 +4439,8 @@ function registerClaudeProjectionRoute(ctx, sidecar, ownsSession, commandsForSes
4427
4439
  if (target.kind === "multi") return await streamMulti(res, io, target.sessionIds);
4428
4440
  info(`dsh-claude: projection poll for ${target.sessionId.slice(0, 64)}`);
4429
4441
  try {
4430
- const body = envelope(await sidecar.read(target.sessionId), await assembleMeta(target.sessionId));
4442
+ const projection = await sidecar.read(target.sessionId);
4443
+ const body = envelope(projection, await assembleMeta(target.sessionId, projection.activities));
4431
4444
  res.writeHead(200, {
4432
4445
  "content-type": "application/json; charset=utf-8",
4433
4446
  "cache-control": "no-store",
@@ -4727,6 +4740,7 @@ var RepositoryStatusService = class {
4727
4740
  #lastReady = /* @__PURE__ */ new Map();
4728
4741
  #gitExecutable;
4729
4742
  #ghExecutable;
4743
+ #roots = /* @__PURE__ */ new Map();
4730
4744
  constructor(runtime, cacheTtlMs = CACHE_TTL_MS) {
4731
4745
  this.#runtime = runtime;
4732
4746
  this.#cacheTtlMs = cacheTtlMs;
@@ -4749,7 +4763,27 @@ var RepositoryStatusService = class {
4749
4763
  this.#cache.delete(cwd);
4750
4764
  this.#lastReady.delete(cwd);
4751
4765
  }
4766
+ /** The repository holding `directory`, or undefined outside any. A root
4767
+ * does not move, so a settled answer is kept for the service's lifetime;
4768
+ * a probe that could not run is not an answer and is asked again. */
4769
+ rootOf(directory) {
4770
+ const known = this.#roots.get(directory);
4771
+ if (known !== void 0) return known;
4772
+ const value = (async () => {
4773
+ const top = await run(this.#runtime, await this.#git(), [
4774
+ "rev-parse",
4775
+ "--path-format=absolute",
4776
+ "--show-toplevel"
4777
+ ], directory, GIT_TIMEOUT_MS$3);
4778
+ return top.exitCode === 0 ? resolve(top.stdout.trim()) : void 0;
4779
+ })().catch(() => {
4780
+ this.#roots.delete(directory);
4781
+ });
4782
+ this.#roots.set(directory, value);
4783
+ return value;
4784
+ }
4752
4785
  dispose() {
4786
+ this.#roots.clear();
4753
4787
  this.#cache.clear();
4754
4788
  this.#lastReady.clear();
4755
4789
  }
@@ -5833,7 +5867,8 @@ var RepositoryActionService = class {
5833
5867
  const merged = await this.#run(gh, [
5834
5868
  "pr",
5835
5869
  "merge",
5836
- `--${method}`
5870
+ `--${method}`,
5871
+ ...request.admin === true ? ["--admin"] : []
5837
5872
  ], before.root, REMOTE_TIMEOUT_MS);
5838
5873
  if (merged.exitCode !== 0 || merged.lossy) {
5839
5874
  const reason = merged.stderr.split(/\r?\n/u).map((line) => line.trim()).filter((line) => line.length > 0).at(-1);
@@ -6274,7 +6309,7 @@ async function streamSetup(res, service, input) {
6274
6309
  * The prefix is registered as a stream because the setup POST holds its
6275
6310
  * connection open for the whole worktree build; the short sibling paths ride
6276
6311
  * the same registration and answer with `json` before releasing it. */
6277
- function registerRepositorySetupRoute(ctx, service, sweep) {
6312
+ function registerRepositorySetupRoute(ctx, service, sweep, cleaned) {
6278
6313
  registerPluginRoute(ctx, {
6279
6314
  mode: "stream",
6280
6315
  kind: "prefix",
@@ -6306,7 +6341,10 @@ function registerRepositorySetupRoute(ctx, service, sweep) {
6306
6341
  if (pathname === `/plugins/dsh-claude/repository/setup/cleanup`) {
6307
6342
  if (io.method !== "POST") return json(res, 405, { error: "method not allowed" });
6308
6343
  const input = await readJson$6(io);
6309
- return json(res, 200, await service.cleanupMerged(string$2(input, "path"), string$2(input, "baseBranch")));
6344
+ const path = string$2(input, "path");
6345
+ const result = await service.cleanupMerged(path, string$2(input, "baseBranch"));
6346
+ cleaned?.(path);
6347
+ return json(res, 200, result);
6310
6348
  }
6311
6349
  if (pathname === `/plugins/dsh-claude/repository/setup/sweep`) {
6312
6350
  if (io.method !== "POST") return json(res, 405, { error: "method not allowed" });
@@ -6335,6 +6373,7 @@ function registerRepositorySetupRoute(ctx, service, sweep) {
6335
6373
  //#region src/repository-action-routes.ts
6336
6374
  const MAX_BODY_BYTES$6 = 16384;
6337
6375
  const MAX_SESSION_ID_CHARS$6 = 1024;
6376
+ const MAX_ROOT_CHARS = 4096;
6338
6377
  const ACTIONS = /* @__PURE__ */ new Set([
6339
6378
  "commit",
6340
6379
  "commit-push",
@@ -6373,6 +6412,10 @@ function sessionId$1(url) {
6373
6412
  if (value === null || value.length === 0 || value.length > MAX_SESSION_ID_CHARS$6) throw new RepositoryActionError("invalid-session", "The session is invalid.");
6374
6413
  return value;
6375
6414
  }
6415
+ function requestedRoot(url) {
6416
+ const value = url.searchParams.get("root");
6417
+ return value === null || value.length === 0 || value.length > MAX_ROOT_CHARS ? void 0 : value;
6418
+ }
6376
6419
  function string$1(input, key) {
6377
6420
  const value = input[key];
6378
6421
  if (typeof value !== "string") throw new RepositoryActionError("invalid-request", `The ${key} field is required.`);
@@ -6401,6 +6444,9 @@ function actionRequest(input) {
6401
6444
  ...input.push === void 0 ? {} : typeof input.push === "boolean" ? { push: input.push } : (() => {
6402
6445
  throw new RepositoryActionError("invalid-request", "The push field must be a boolean.");
6403
6446
  })(),
6447
+ ...input.admin === void 0 ? {} : typeof input.admin === "boolean" ? { admin: input.admin } : (() => {
6448
+ throw new RepositoryActionError("invalid-request", "The admin field must be a boolean.");
6449
+ })(),
6404
6450
  ...input.mergeMethod === void 0 ? {} : input.mergeMethod === "merge" || input.mergeMethod === "squash" || input.mergeMethod === "rebase" ? { mergeMethod: input.mergeMethod } : (() => {
6405
6451
  throw new RepositoryActionError("invalid-request", "The mergeMethod field is invalid.");
6406
6452
  })()
@@ -6420,7 +6466,7 @@ function registerRepositoryActionRoute(ctx, service, cwdForSession) {
6420
6466
  handler: async (io) => {
6421
6467
  const url = io.url;
6422
6468
  try {
6423
- const cwd = cwdForSession(sessionId$1(url));
6469
+ const cwd = cwdForSession(sessionId$1(url), requestedRoot(url));
6424
6470
  if (cwd === void 0) throw new RepositoryActionError("session-unavailable", "The Claude session is unavailable.");
6425
6471
  if (url.pathname === `/plugins/dsh-claude/repository/action/preview`) {
6426
6472
  if (io.method !== "GET") return {
@@ -6680,10 +6726,13 @@ const PROMPT_NAME = /^[\p{L}\p{N}][\p{L}\p{M}\p{N} ._()\[\]-]{0,127}$/u;
6680
6726
  function claudePromptsDir() {
6681
6727
  return join(homedir(), ".claude", "prompts");
6682
6728
  }
6683
- /** `~/.claude/prompts/x.md` rather than the absolute path it expands to. */
6729
+ /** `~/.claude/prompts/x.md` rather than the absolute path it expands to.
6730
+ * Windows joins with backslashes; the display form is the same on every OS. */
6684
6731
  function displayPath(file) {
6685
6732
  const home = homedir();
6686
- return file.startsWith(`${home}/`) ? `~${file.slice(home.length)}` : file;
6733
+ const separator = file.charAt(home.length);
6734
+ if (!file.startsWith(home) || separator !== "/" && separator !== "\\") return file;
6735
+ return `~${file.slice(home.length).replaceAll("\\", "/")}`;
6687
6736
  }
6688
6737
  /** The menu's second row: the first non-empty line, collapsed and bounded. */
6689
6738
  function summarize(body) {
@@ -7456,7 +7505,7 @@ function registerPullRequestFeedbackRoute(ctx, service, cwdForSession) {
7456
7505
  const reads = io.method === "GET";
7457
7506
  const writes = io.method === "POST";
7458
7507
  try {
7459
- const cwd = cwdForSession(sessionId(url));
7508
+ const cwd = cwdForSession(sessionId(url), url.searchParams.get("root") ?? void 0);
7460
7509
  if (cwd === void 0) throw new PullRequestFeedbackError("session-unavailable", "The Claude session is unavailable.");
7461
7510
  if (url.pathname === `/plugins/dsh-claude/repository/feedback/comments`) {
7462
7511
  if (!reads) return {
@@ -8595,6 +8644,60 @@ function registerClaudeRewindRoute(ctx, sidecar, access) {
8595
8644
  });
8596
8645
  }
8597
8646
  //#endregion
8647
+ //#region src/touched-repositories.ts
8648
+ /** Which other repositories a session has written into.
8649
+ *
8650
+ * A session is pinned to one checkout, but Claude writes wherever it is
8651
+ * pointed: a monorepo sibling, a dependency checked out next door. Those
8652
+ * edits never showed up anywhere -- no diff, no pull request -- because every
8653
+ * repository read keyed off the session's own cwd. The file tools' arguments
8654
+ * are already on the activity log, so the extra roots are derived from there
8655
+ * rather than tracked as new state. */
8656
+ const FILE_TOOLS = /* @__PURE__ */ new Set([
8657
+ "Edit",
8658
+ "MultiEdit",
8659
+ "Write",
8660
+ "NotebookEdit"
8661
+ ]);
8662
+ /** The activity detail is a redacted JSON string that may be cut short, so
8663
+ * this matches the one key rather than parsing the document. */
8664
+ const PATH_KEY = /"(?:file_path|notebook_path)"\s*:\s*"((?:[^"\\]|\\.)*)"/u;
8665
+ /** Absolute paths Claude wrote through its file tools, first-seen order. */
8666
+ function touchedFilePaths(activities) {
8667
+ const paths = /* @__PURE__ */ new Set();
8668
+ for (const activity of activities) {
8669
+ if (activity.kind !== "tool-call" && activity.kind !== "subagent" || activity.toolName === void 0 || !FILE_TOOLS.has(activity.toolName) || activity.detail === void 0) continue;
8670
+ const escaped = PATH_KEY.exec(activity.detail)?.[1];
8671
+ if (escaped === void 0) continue;
8672
+ try {
8673
+ const path = JSON.parse(`"${escaped}"`);
8674
+ if (isAbsolute(path)) paths.add(path);
8675
+ } catch {}
8676
+ }
8677
+ return [...paths];
8678
+ }
8679
+ /** Repository roots behind the touched paths, minus the session's own, in
8680
+ * first-seen order. `rootOf` answers undefined outside any repository. */
8681
+ async function touchedRepositoryRoots(paths, sessionRoot, rootOf, max) {
8682
+ const roots = [];
8683
+ const directories = new Set(paths.map((path) => dirname(path)));
8684
+ for (const directory of directories) {
8685
+ const root = await rootOf(directory);
8686
+ if (root === void 0 || root === sessionRoot || roots.includes(root)) continue;
8687
+ roots.push(root);
8688
+ if (roots.length >= max) break;
8689
+ }
8690
+ return roots;
8691
+ }
8692
+ /** Whether a linked checkout still has anything to show. A cleaned-up
8693
+ * worktree is gone, and a checkout cleaned up in place is back on base with
8694
+ * nothing pending -- either way its bar comes down. A clean checkout with an
8695
+ * open or merged pull request, or unpushed work, stays. */
8696
+ function linkedRepositoryShown(status) {
8697
+ if (status.status !== "ready") return false;
8698
+ return status.dirty === true || status.upstream === false || (status.ahead ?? 0) > 0 || status.pullRequest !== void 0;
8699
+ }
8700
+ //#endregion
8598
8701
  //#region src/update-routes.ts
8599
8702
  const PLUGIN_PACKAGE_NAME = "@norman-else/dsh-claude";
8600
8703
  const MAX_MANIFEST_BYTES = 262144;
@@ -9303,6 +9406,8 @@ const CLAUDE_SCOPE_UNAVAILABLE_MESSAGE = "agent command scope unavailable (prese
9303
9406
  const CATALOG_RETRY_MS = 5e3;
9304
9407
  const SCOPE_RETRY_MS = 500;
9305
9408
  const MAX_CATALOG_RETRIES = 3;
9409
+ /** Bounded: each extra checkout costs a git chain and a `gh pr view` per sweep. */
9410
+ const MAX_EXTRA_REPOSITORIES = 8;
9306
9411
  const MAX_SCOPE_RETRIES = 24;
9307
9412
  function mountClaudeMetadata(ctx, supervisor, agent, model, sidecar, publishCommands = () => {}, resolveCommands = () => ctx.agentPresets.serviceFor(agent, CLAUDE_COMMANDS_SERVICE)) {
9308
9413
  if (ctx.agentPresets.composedPreset(agent.ctx) !== "claude") return void 0;
@@ -9553,15 +9658,28 @@ async function apply(ctx, config) {
9553
9658
  supervisor.limitsChanged();
9554
9659
  }
9555
9660
  });
9556
- registerRepositorySetupRoute(webCtx, repositorySetup, () => sweepWorktrees?.());
9661
+ registerRepositorySetupRoute(webCtx, repositorySetup, () => sweepWorktrees?.(), (path) => repositoryStatus.invalidate(path));
9557
9662
  registerRepositoryStatusRoute(webCtx, repositoryStatus);
9558
9663
  registerRepositoryFileRoute(webCtx, repositoryStatus);
9559
9664
  registerJiraRoute(webCtx, new JiraService());
9560
9665
  const repositoryActions = new RepositoryActionService(webCtx.subprocess, supervisorConfig.executablePath, (cwd) => repositoryStatus.invalidate(cwd));
9561
- const cwdForClaudeSession = (sessionId) => {
9666
+ /** Roots the latest projection probe vouched for, per session: the only
9667
+ * checkouts a route may act on besides the session's own. */
9668
+ const extraRoots = /* @__PURE__ */ new Map();
9669
+ const cwdForClaudeSession = (sessionId, root) => {
9562
9670
  const agent = webCtx.agents.get(sessionId);
9563
9671
  if (agent === void 0 || webCtx.agentPresets.composedPreset(agent.ctx) !== "claude") return void 0;
9564
- return agent.session.header.cwd;
9672
+ if (root === void 0) return agent.session.header.cwd;
9673
+ return extraRoots.get(sessionId)?.includes(root) === true ? root : void 0;
9674
+ };
9675
+ const extraRepositoriesForClaudeSession = async (sessionId, activities) => {
9676
+ const cwd = cwdForClaudeSession(sessionId);
9677
+ if (cwd === void 0) return [];
9678
+ const own = await repositoryStatus.rootOf(cwd);
9679
+ const roots = await touchedRepositoryRoots(touchedFilePaths(activities), own ?? cwd, (directory) => repositoryStatus.rootOf(directory), MAX_EXTRA_REPOSITORIES);
9680
+ const statuses = (await Promise.all(roots.map((root) => repositoryStatus.inspect(root)))).filter(linkedRepositoryShown);
9681
+ extraRoots.set(sessionId, statuses.map((status) => status.root ?? status.cwd));
9682
+ return statuses;
9565
9683
  };
9566
9684
  registerRepositoryActionRoute(webCtx, repositoryActions, cwdForClaudeSession);
9567
9685
  registerEditorOpenRoute(webCtx, new EditorOpenService(webCtx.subprocess), cwdForClaudeSession);
@@ -9586,7 +9704,7 @@ async function apply(ctx, config) {
9586
9704
  registerClaudeRewindRoute(webCtx, sidecar, {
9587
9705
  eventsFor: (sessionId) => {
9588
9706
  const agent = webCtx.agents.get(sessionId);
9589
- return agent === void 0 || webCtx.agentPresets.composedPreset(agent.ctx) !== "claude" ? void 0 : agent.session.events;
9707
+ return agent === void 0 || webCtx.agentPresets.composedPreset(agent.ctx) !== "claude" ? void 0 : agent.session.snapshotEvents();
9590
9708
  },
9591
9709
  busy: (sessionId) => supervisor.snapshots().some((item) => item.sessionId === sessionId && (item.state === "running" || item.state === "interrupting")),
9592
9710
  reset: (sessionId) => supervisor.disposeSession(sessionId),
@@ -9601,7 +9719,7 @@ async function apply(ctx, config) {
9601
9719
  if (agent === void 0 || webCtx.agentPresets.composedPreset(agent.ctx) !== "claude") return void 0;
9602
9720
  const cwd = agent.session.header.cwd;
9603
9721
  return cwd === void 0 ? void 0 : repositoryStatus.inspect(cwd);
9604
- }, (sessionId) => reviewComments.list(sessionId));
9722
+ }, (sessionId) => reviewComments.list(sessionId), extraRepositoriesForClaudeSession);
9605
9723
  });
9606
9724
  }
9607
9725
  //#endregion