@norman-else/dsh-claude 0.1.37 → 0.1.38

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
@@ -3352,16 +3352,23 @@ function registerClaudeProjectionRoute(ctx, sidecar, ownsSession, commandsForSes
3352
3352
  const info = (message) => {
3353
3353
  ctx.logger?.info?.(message);
3354
3354
  };
3355
- const assembleMeta = async (sessionId) => {
3355
+ /** Everything the host already knows, without touching the repository. */
3356
+ const localMeta = (sessionId) => {
3356
3357
  const owned = ownsSession(sessionId);
3357
- const repository = owned ? await repositoryForSession(sessionId) : void 0;
3358
3358
  return {
3359
3359
  owned,
3360
3360
  commands: commandsForSession(sessionId),
3361
- ...repository === void 0 ? {} : { repository },
3362
3361
  reviewComments: owned ? reviewCommentsForSession(sessionId) : []
3363
3362
  };
3364
3363
  };
3364
+ const assembleMeta = async (sessionId) => {
3365
+ const meta = localMeta(sessionId);
3366
+ const repository = meta.owned ? await repositoryForSession(sessionId) : void 0;
3367
+ return {
3368
+ ...meta,
3369
+ ...repository === void 0 ? {} : { repository }
3370
+ };
3371
+ };
3365
3372
  const streamMulti = async (res, io, sessionIds) => {
3366
3373
  info(`dsh-claude: projection stream opened for ${sessionIds.length} session(s)`);
3367
3374
  let textDeltas = 0;
@@ -3383,8 +3390,20 @@ function registerClaudeProjectionRoute(ctx, sidecar, ownsSession, commandsForSes
3383
3390
  }
3384
3391
  };
3385
3392
  const metas = /* @__PURE__ */ new Map();
3393
+ const writeMeta = (sessionId, meta) => {
3394
+ metas.set(sessionId, meta);
3395
+ writeLine({
3396
+ type: "meta",
3397
+ session: sessionId,
3398
+ owned: meta.owned,
3399
+ commands: meta.commands,
3400
+ ...meta.repository === void 0 ? {} : { repository: meta.repository },
3401
+ reviewComments: meta.reviewComments
3402
+ });
3403
+ };
3386
3404
  const writeSnapshot = async (sessionId) => {
3387
- const [projection, meta] = await Promise.all([sidecar.read(sessionId), assembleMeta(sessionId)]);
3405
+ const projection = await sidecar.read(sessionId);
3406
+ const meta = metas.get(sessionId) ?? localMeta(sessionId);
3388
3407
  metas.set(sessionId, meta);
3389
3408
  writeLine({
3390
3409
  type: "snapshot",
@@ -3392,6 +3411,9 @@ function registerClaudeProjectionRoute(ctx, sidecar, ownsSession, commandsForSes
3392
3411
  seq: sidecar.sequence(sessionId),
3393
3412
  ...envelope(projection, meta)
3394
3413
  });
3414
+ const probed = await assembleMeta(sessionId);
3415
+ if (closed || JSON.stringify(probed) === JSON.stringify(metas.get(sessionId))) return;
3416
+ writeMeta(sessionId, probed);
3395
3417
  };
3396
3418
  const unsubscribes = sessionIds.map((sessionId) => sidecar.subscribe(sessionId, (delta) => {
3397
3419
  switch (delta.kind) {
@@ -3458,15 +3480,7 @@ function registerClaudeProjectionRoute(ctx, sidecar, ownsSession, commandsForSes
3458
3480
  const next = await assembleMeta(sessionId);
3459
3481
  if (closed) return;
3460
3482
  if (JSON.stringify(next) === JSON.stringify(metas.get(sessionId))) continue;
3461
- metas.set(sessionId, next);
3462
- writeLine({
3463
- type: "meta",
3464
- session: sessionId,
3465
- owned: next.owned,
3466
- commands: next.commands,
3467
- ...next.repository === void 0 ? {} : { repository: next.repository },
3468
- reviewComments: next.reviewComments
3469
- });
3483
+ writeMeta(sessionId, next);
3470
3484
  }
3471
3485
  writeLine({ type: "ping" });
3472
3486
  })().catch(() => void 0);
@@ -4336,7 +4350,7 @@ var RepositorySetupService = class {
4336
4350
  }
4337
4351
  }
4338
4352
  if (changed) await this.#writeLeases(retained);
4339
- await this.#removeUnleasedDirectories(retained, active, now);
4353
+ await this.#removeUnleasedDirectories(retained, active, now, archiveSessions);
4340
4354
  });
4341
4355
  }
4342
4356
  /** Remove directories under the plugin's own worktree root that no lease and
@@ -4344,7 +4358,7 @@ var RepositorySetupService = class {
4344
4358
  * lease write failed, otherwise leaves a directory nothing will ever sweep.
4345
4359
  * The grace period covers the gap between `worktree add` and the lease
4346
4360
  * write, so a worktree being created right now is never taken. */
4347
- async #removeUnleasedDirectories(retained, active, now) {
4361
+ async #removeUnleasedDirectories(retained, active, now, archiveSessions) {
4348
4362
  const leased = new Set(retained.map((item) => comparablePath(item.path)));
4349
4363
  let entries;
4350
4364
  try {
@@ -4361,6 +4375,7 @@ var RepositorySetupService = class {
4361
4375
  if (!info.isDirectory()) continue;
4362
4376
  const created = info.birthtimeMs > 0 ? info.birthtimeMs : info.mtimeMs;
4363
4377
  if (Math.max(0, now - created) < this.#cleanupGraceMs) continue;
4378
+ await archiveSessions?.(path).catch(() => void 0);
4364
4379
  await rm(path, {
4365
4380
  recursive: true,
4366
4381
  force: true
@@ -5105,7 +5120,7 @@ async function streamSetup(res, service, input) {
5105
5120
  * The prefix is registered as a stream because the setup POST holds its
5106
5121
  * connection open for the whole worktree build; the short sibling paths ride
5107
5122
  * the same registration and answer with `json` before releasing it. */
5108
- function registerRepositorySetupRoute(ctx, service) {
5123
+ function registerRepositorySetupRoute(ctx, service, sweep) {
5109
5124
  registerPluginRoute(ctx, {
5110
5125
  mode: "stream",
5111
5126
  kind: "prefix",
@@ -5139,6 +5154,11 @@ function registerRepositorySetupRoute(ctx, service) {
5139
5154
  const input = await readJson$5(io);
5140
5155
  return json(res, 200, await service.cleanupMerged(string$2(input, "path"), string$2(input, "baseBranch")));
5141
5156
  }
5157
+ if (pathname === `/plugins/dsh-claude/repository/setup/sweep`) {
5158
+ if (io.method !== "POST") return json(res, 405, { error: "method not allowed" });
5159
+ sweep?.();
5160
+ return json(res, 200, { ok: true });
5161
+ }
5142
5162
  if (pathname === `/plugins/dsh-claude/repository/setup/bind`) {
5143
5163
  if (io.method !== "POST") return json(res, 405, { error: "method not allowed" });
5144
5164
  const input = await readJson$5(io);
@@ -7853,6 +7873,7 @@ async function apply(ctx, config) {
7853
7873
  reviewComments.disposeSession(agent.id);
7854
7874
  await supervisor.disposeSession(agent.id);
7855
7875
  });
7876
+ let sweepWorktrees;
7856
7877
  const injectWorkspaceRegistry = ctx.inject;
7857
7878
  injectWorkspaceRegistry(["workspaceRegistry"], (sweepCtx) => {
7858
7879
  const archiveSessions = async (worktreePath) => {
@@ -7873,9 +7894,13 @@ async function apply(ctx, config) {
7873
7894
  };
7874
7895
  sweepCtx.effect(() => {
7875
7896
  sweep();
7897
+ sweepWorktrees = sweep;
7876
7898
  const timer = setInterval(sweep, 6e4);
7877
7899
  timer.unref?.();
7878
- return () => clearInterval(timer);
7900
+ return () => {
7901
+ sweepWorktrees = void 0;
7902
+ clearInterval(timer);
7903
+ };
7879
7904
  }, "dsh-claude: worktree reconciliation");
7880
7905
  });
7881
7906
  ctx.effect(() => () => reviewComments.dispose(), "dsh-claude: review comments store");
@@ -7890,7 +7915,7 @@ async function apply(ctx, config) {
7890
7915
  defaultLimits,
7891
7916
  onUpdated: applySettingsOverrides
7892
7917
  });
7893
- registerRepositorySetupRoute(webCtx, repositorySetup);
7918
+ registerRepositorySetupRoute(webCtx, repositorySetup, () => sweepWorktrees?.());
7894
7919
  registerRepositoryStatusRoute(webCtx, repositoryStatus);
7895
7920
  registerRepositoryFileRoute(webCtx, repositoryStatus);
7896
7921
  registerJiraRoute(webCtx, new JiraService());