@staff0rd/assist 0.776.0 → 0.777.0

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/index.js CHANGED
@@ -6,7 +6,7 @@ import { Command } from "commander";
6
6
  // package.json
7
7
  var package_default = {
8
8
  name: "@staff0rd/assist",
9
- version: "0.776.0",
9
+ version: "0.777.0",
10
10
  type: "module",
11
11
  main: "dist/index.js",
12
12
  bin: {
@@ -1936,6 +1936,7 @@ var daemonPaths = {
1936
1936
  socket: process.platform === "win32" ? perUserWindowsPipe() : join9(DAEMON_DIR, "daemon.sock"),
1937
1937
  log: join9(DAEMON_DIR, "daemon.log"),
1938
1938
  pid: join9(DAEMON_DIR, "daemon.pid"),
1939
+ autoUpdatePaused: join9(DAEMON_DIR, "auto-update-paused"),
1939
1940
  spawnLock: join9(DAEMON_DIR, "spawn.lock"),
1940
1941
  hooksSettings: join9(DAEMON_DIR, "hooks-settings.json")
1941
1942
  };
@@ -11055,7 +11056,7 @@ var defaultDeps = {
11055
11056
  httpsPort: tailnetHttpsPort
11056
11057
  };
11057
11058
  async function ensureTailscaleServe(port, deps2 = {}) {
11058
- const { enabled, status: status2, serveStatus, serve, sleep: sleep3, httpsPort } = {
11059
+ const { enabled, status: status2, serveStatus, serve, sleep: sleep2, httpsPort } = {
11059
11060
  ...defaultDeps,
11060
11061
  ...deps2
11061
11062
  };
@@ -11070,7 +11071,7 @@ async function ensureTailscaleServe(port, deps2 = {}) {
11070
11071
  return `serving ${plan2.target} at ${plan2.url}`;
11071
11072
  } catch (error) {
11072
11073
  if (attempt >= SERVE_WRITE_ATTEMPTS) throw error;
11073
- await sleep3(attempt * 500 + Math.random() * 500);
11074
+ await sleep2(attempt * 500 + Math.random() * 500);
11074
11075
  }
11075
11076
  }
11076
11077
  }
@@ -17221,6 +17222,33 @@ async function updates(_req, res) {
17221
17222
  respondJson(res, 200, status2);
17222
17223
  }
17223
17224
 
17225
+ // src/commands/sessions/shared/AutoUpdateAction.ts
17226
+ var AUTO_UPDATE_ACTIONS = ["pause", "resume", "check"];
17227
+
17228
+ // src/commands/sessions/web/updates/updatesControl.ts
17229
+ async function updatesControl(req, res) {
17230
+ const url = new URL(req.url ?? "/", "http://localhost");
17231
+ const action = url.searchParams.get("action");
17232
+ if (!AUTO_UPDATE_ACTIONS.includes(action)) {
17233
+ respondJson(res, 400, { error: "Invalid action" });
17234
+ return;
17235
+ }
17236
+ console.log(`auto-update ${action} ${linkedRequester(req)}`);
17237
+ const reply2 = await requestDaemonReply(
17238
+ { type: "auto-update-control", action },
17239
+ "auto-update-control"
17240
+ );
17241
+ if (!reply2) {
17242
+ respondJson(res, 503, { error: "Daemon not reachable" });
17243
+ return;
17244
+ }
17245
+ if (!reply2.ok) {
17246
+ respondJson(res, 409, { error: reply2.error ?? `${action} failed` });
17247
+ return;
17248
+ }
17249
+ respondJson(res, 200, { ok: true });
17250
+ }
17251
+
17224
17252
  // src/commands/sessions/web/readRequestBuffer.ts
17225
17253
  async function readRequestBuffer(req, limit) {
17226
17254
  const chunks = [];
@@ -17494,7 +17522,8 @@ var routes2 = {
17494
17522
  "POST /api/config/set": setConfig,
17495
17523
  "POST /api/config/unset": unsetConfig,
17496
17524
  "GET /api/review/synthesis": getReviewSynthesis,
17497
- "GET /api/updates": updates
17525
+ "GET /api/updates": updates,
17526
+ "POST /api/updates/control": updatesControl
17498
17527
  };
17499
17528
  var handleRequest = createFallbackHandler(
17500
17529
  routes2,
@@ -40010,15 +40039,20 @@ function gitFailureReason(error) {
40010
40039
  return message3.trim() || "git failed without reporting a reason";
40011
40040
  }
40012
40041
 
40013
- // src/commands/watch/simulatedDivergencePath.ts
40042
+ // src/commands/watch/gitMarkerPath.ts
40014
40043
  import { resolve as resolve25 } from "path";
40015
- function simulatedDivergencePath(cwd) {
40044
+ function gitMarkerPath(name, cwd) {
40016
40045
  return resolve25(
40017
40046
  cwd ?? process.cwd(),
40018
- runGit3(["rev-parse", "--git-path", "assist-simulate-divergence"], cwd)
40047
+ runGit3(["rev-parse", "--git-path", name], cwd)
40019
40048
  );
40020
40049
  }
40021
40050
 
40051
+ // src/commands/watch/simulatedDivergencePath.ts
40052
+ function simulatedDivergencePath(cwd) {
40053
+ return gitMarkerPath("assist-simulate-divergence", cwd);
40054
+ }
40055
+
40022
40056
  // src/commands/watch/simulateDivergence.ts
40023
40057
  function simulateDivergence() {
40024
40058
  try {
@@ -40526,11 +40560,10 @@ ${buildWatchReport(from)}`);
40526
40560
  process.exit(0);
40527
40561
  }
40528
40562
 
40529
- // src/commands/watch/consumeSimulatedDivergence.ts
40563
+ // src/commands/watch/consumeMarker.ts
40530
40564
  import { existsSync as existsSync81, rmSync as rmSync8 } from "fs";
40531
- function consumeSimulatedDivergence(cwd) {
40565
+ function consumeMarker(path106) {
40532
40566
  try {
40533
- const path106 = simulatedDivergencePath(cwd);
40534
40567
  if (!existsSync81(path106)) return false;
40535
40568
  rmSync8(path106);
40536
40569
  return true;
@@ -40539,6 +40572,15 @@ function consumeSimulatedDivergence(cwd) {
40539
40572
  }
40540
40573
  }
40541
40574
 
40575
+ // src/commands/watch/consumeSimulatedDivergence.ts
40576
+ function consumeSimulatedDivergence(cwd) {
40577
+ try {
40578
+ return consumeMarker(simulatedDivergencePath(cwd));
40579
+ } catch {
40580
+ return false;
40581
+ }
40582
+ }
40583
+
40542
40584
  // src/commands/watch/fetchQuietly.ts
40543
40585
  import { execFileSync as execFileSync21 } from "child_process";
40544
40586
  var MIN_FETCH_TIMEOUT_MS = 6e4;
@@ -40573,26 +40615,33 @@ function readMovement(cwd) {
40573
40615
  }
40574
40616
 
40575
40617
  // src/commands/watch/pollForMovement.ts
40618
+ var CONTROL_POLL_MS = 1e3;
40576
40619
  function pollForMovement(options2) {
40577
- const { upstream, intervalMs, timeoutMs, timeout, cwd } = options2;
40620
+ const { upstream, intervalMs, timeoutMs, timeout, cwd, control } = options2;
40578
40621
  return new Promise((resolve28) => {
40579
40622
  let settled = false;
40580
40623
  const finish2 = (outcome) => {
40581
40624
  if (settled) return;
40582
40625
  settled = true;
40583
40626
  clearInterval(ticker);
40627
+ clearInterval(controller);
40584
40628
  clearTimeout(deadline);
40585
40629
  process.off("SIGINT", onInterrupt);
40586
40630
  resolve28(outcome);
40587
40631
  };
40588
40632
  const onInterrupt = () => finish2({ kind: "interrupted" });
40589
- const ticker = setInterval(() => {
40633
+ const check2 = () => {
40590
40634
  if (consumeSimulatedDivergence(cwd))
40591
40635
  return finish2({ kind: "simulated-divergence", upstream });
40592
40636
  fetchQuietly(cwd, intervalMs);
40593
40637
  const found = readMovement(cwd);
40594
40638
  if (found) finish2({ kind: "moved", upstream, ...found });
40595
- }, intervalMs);
40639
+ };
40640
+ const ticker = setInterval(check2, intervalMs);
40641
+ const controller = control && setInterval(() => {
40642
+ if (consumeMarker(control.stop)) return finish2({ kind: "interrupted" });
40643
+ if (consumeMarker(control.check)) check2();
40644
+ }, CONTROL_POLL_MS);
40596
40645
  const deadline = timeoutMs === void 0 ? void 0 : setTimeout(
40597
40646
  () => finish2({ kind: "timeout", upstream, timeout }),
40598
40647
  timeoutMs
@@ -40601,6 +40650,18 @@ function pollForMovement(options2) {
40601
40650
  });
40602
40651
  }
40603
40652
 
40653
+ // src/commands/watch/watchControlPaths.ts
40654
+ function watchControlPaths(cwd) {
40655
+ try {
40656
+ return {
40657
+ check: gitMarkerPath("assist-watch-check", cwd),
40658
+ stop: gitMarkerPath("assist-watch-stop", cwd)
40659
+ };
40660
+ } catch {
40661
+ return void 0;
40662
+ }
40663
+ }
40664
+
40604
40665
  // src/commands/watch/waitForUpstream.ts
40605
40666
  function waitForUpstream(options2) {
40606
40667
  const { intervalMs, timeoutMs, timeout, cwd, onStart } = options2;
@@ -40616,10 +40677,20 @@ function waitForUpstream(options2) {
40616
40677
  onStart?.(upstream);
40617
40678
  if (consumeSimulatedDivergence(cwd))
40618
40679
  return Promise.resolve({ kind: "simulated-divergence", upstream });
40680
+ const control = watchControlPaths(cwd);
40619
40681
  fetchQuietly(cwd, intervalMs);
40682
+ if (control && consumeMarker(control.stop))
40683
+ return Promise.resolve({ kind: "interrupted" });
40620
40684
  const moved = readMovement(cwd);
40621
40685
  if (moved) return Promise.resolve({ kind: "moved", upstream, ...moved });
40622
- return pollForMovement({ upstream, intervalMs, timeoutMs, timeout, cwd });
40686
+ return pollForMovement({
40687
+ upstream,
40688
+ intervalMs,
40689
+ timeoutMs,
40690
+ timeout,
40691
+ cwd,
40692
+ control
40693
+ });
40623
40694
  }
40624
40695
 
40625
40696
  // src/commands/watch/watchWait.ts
@@ -42136,6 +42207,39 @@ function createRunSession(id, { runName, runArgs, cwd, meta }) {
42136
42207
  };
42137
42208
  }
42138
42209
 
42210
+ // src/commands/sessions/daemon/autoUpdate/autoUpdateControl.ts
42211
+ import { existsSync as existsSync86, rmSync as rmSync9, writeFileSync as writeFileSync54 } from "fs";
42212
+ var paused;
42213
+ var wake;
42214
+ function persist2(value) {
42215
+ try {
42216
+ if (value) writeFileSync54(daemonPaths.autoUpdatePaused, "");
42217
+ else rmSync9(daemonPaths.autoUpdatePaused, { force: true });
42218
+ } catch {
42219
+ }
42220
+ }
42221
+ var autoUpdateControl = {
42222
+ paused: () => {
42223
+ paused ??= existsSync86(daemonPaths.autoUpdatePaused);
42224
+ return paused;
42225
+ },
42226
+ setPaused: (value) => {
42227
+ paused = value;
42228
+ persist2(value);
42229
+ wake?.();
42230
+ },
42231
+ sleep: (ms) => new Promise((resolve28) => {
42232
+ const done2 = () => {
42233
+ clearTimeout(timer);
42234
+ if (wake === done2) wake = void 0;
42235
+ resolve28();
42236
+ };
42237
+ const timer = setTimeout(done2, ms);
42238
+ wake = done2;
42239
+ }),
42240
+ wake: () => wake?.()
42241
+ };
42242
+
42139
42243
  // src/commands/sessions/daemon/autoUpdate/autoUpdateEnabled.ts
42140
42244
  function autoUpdateEnabled(installDir) {
42141
42245
  try {
@@ -42166,6 +42270,14 @@ var autoUpdateState = {
42166
42270
  }
42167
42271
  };
42168
42272
 
42273
+ // src/commands/sessions/daemon/autoUpdate/backOff.ts
42274
+ var RETRY_AFTER_FAILURE_MS = 5 * 60 * 1e3;
42275
+ async function backOff(reason4, deps2) {
42276
+ deps2.enter("retrying", { reason: reason4 });
42277
+ deps2.note(`${reason4}; retrying in ${RETRY_AFTER_FAILURE_MS / 6e4}m`);
42278
+ await deps2.sleep(RETRY_AFTER_FAILURE_MS);
42279
+ }
42280
+
42169
42281
  // src/commands/sessions/daemon/autoUpdate/holdForEscalation.ts
42170
42282
  var ESCALATION_POLL_MS = 5e3;
42171
42283
  async function holdForEscalation(sessionId, deps2) {
@@ -42186,50 +42298,47 @@ async function runLapCapturingTail(deps2) {
42186
42298
  return { end, tail };
42187
42299
  }
42188
42300
 
42189
- // src/commands/sessions/daemon/autoUpdate/runAutoUpdateLoop.ts
42301
+ // src/commands/sessions/daemon/autoUpdate/settleLap.ts
42190
42302
  var DIVERGED_EXIT_CODE = 3;
42191
- var RETRY_AFTER_FAILURE_MS = 5 * 60 * 1e3;
42303
+ async function settleLap(lap, { end, tail }, deps2) {
42304
+ const how = end.signal ? `killed by ${end.signal}` : `exited ${end.code}`;
42305
+ if (end.code === DIVERGED_EXIT_CODE) {
42306
+ const held = deps2.escalate(tail);
42307
+ deps2.note(`lap ${lap} ${how} (divergence); escalated to session ${held}`);
42308
+ return held;
42309
+ }
42310
+ if (deps2.paused()) deps2.note(`lap ${lap} ${how}; paused`);
42311
+ else if (decideLap(end, false).kind === "relaunch")
42312
+ deps2.note(`lap ${lap} ${how}; relaunching`);
42313
+ else await backOff(`lap ${lap} ${how}`, deps2);
42314
+ return void 0;
42315
+ }
42316
+
42317
+ // src/commands/sessions/daemon/autoUpdate/runAutoUpdateLoop.ts
42318
+ var PAUSE_POLL_MS = 60 * 1e3;
42192
42319
  async function runAutoUpdateLoop(deps2, maxLaps = Number.POSITIVE_INFINITY) {
42193
42320
  let held = deps2.liveEscalation();
42194
42321
  for (let lap = 1; lap <= maxLaps; lap++) {
42195
42322
  if (held) await holdForEscalation(held, deps2);
42196
42323
  held = void 0;
42324
+ while (deps2.paused()) await deps2.sleep(PAUSE_POLL_MS);
42197
42325
  deps2.enter("waiting");
42198
42326
  deps2.note(
42199
42327
  `lap ${lap} at ${(/* @__PURE__ */ new Date()).toLocaleString()}: assist ${WATCH_LAP_ARGS.join(" ")}`
42200
42328
  );
42201
- let result;
42202
42329
  try {
42203
- result = await runLapCapturingTail(deps2);
42330
+ held = await settleLap(lap, await runLapCapturingTail(deps2), deps2);
42204
42331
  } catch (error) {
42205
42332
  await backOff(
42206
42333
  `lap ${lap} could not start (${error instanceof Error ? error.message : String(error)})`,
42207
42334
  deps2
42208
42335
  );
42209
- continue;
42210
42336
  }
42211
- const { end, tail } = result;
42212
- const how = end.signal ? `killed by ${end.signal}` : `exited ${end.code}`;
42213
- if (end.code === DIVERGED_EXIT_CODE) {
42214
- held = deps2.escalate(tail);
42215
- deps2.note(`lap ${lap} ${how} (divergence); escalated to session ${held}`);
42216
- continue;
42217
- }
42218
- if (decideLap(end, false).kind === "relaunch") {
42219
- deps2.note(`lap ${lap} ${how}; relaunching`);
42220
- continue;
42221
- }
42222
- await backOff(`lap ${lap} ${how}`, deps2);
42223
42337
  }
42224
42338
  }
42225
- async function backOff(reason4, deps2) {
42226
- deps2.enter("retrying", { reason: reason4 });
42227
- deps2.note(`${reason4}; retrying in ${RETRY_AFTER_FAILURE_MS / 6e4}m`);
42228
- await deps2.sleep(RETRY_AFTER_FAILURE_MS);
42229
- }
42230
42339
 
42231
42340
  // src/commands/sessions/daemon/autoUpdate/trimWatcherLog.ts
42232
- import { readFileSync as readFileSync65, writeFileSync as writeFileSync54 } from "fs";
42341
+ import { readFileSync as readFileSync65, writeFileSync as writeFileSync55 } from "fs";
42233
42342
  var KEEP_TAIL = 256 * 1024;
42234
42343
  var MAX_LOG_SIZE = 4 * 1024 * 1024;
42235
42344
  function trimWatcherLog(cwd) {
@@ -42241,20 +42350,10 @@ function trimWatcherLog(cwd) {
42241
42350
  return;
42242
42351
  }
42243
42352
  if (log2.length <= MAX_LOG_SIZE) return;
42244
- writeFileSync54(path106, log2.slice(-KEEP_TAIL));
42353
+ writeFileSync55(path106, log2.slice(-KEEP_TAIL));
42245
42354
  daemonLog(`auto-update: log ${path106} trimmed to its last ${KEEP_TAIL} chars`);
42246
42355
  }
42247
42356
 
42248
- // src/commands/sessions/daemon/autoUpdate/loopDeps.ts
42249
- import { setTimeout as sleep2 } from "timers/promises";
42250
-
42251
- // src/commands/sessions/daemon/watcherNote.ts
42252
- function watcherNote(text17) {
42253
- return `\r
42254
- \x1B[2m\u2500\u2500 ${text17} \u2500\u2500\x1B[0m\r
42255
- `;
42256
- }
42257
-
42258
42357
  // src/commands/sessions/daemon/worktree/appendWatcherLog.ts
42259
42358
  import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync34 } from "fs";
42260
42359
  import { dirname as dirname42 } from "path";
@@ -42424,9 +42523,30 @@ function lineRecorder(onLine) {
42424
42523
  };
42425
42524
  }
42426
42525
 
42526
+ // src/commands/sessions/daemon/watcherNote.ts
42527
+ function watcherNote(text17) {
42528
+ return `\r
42529
+ \x1B[2m\u2500\u2500 ${text17} \u2500\u2500\x1B[0m\r
42530
+ `;
42531
+ }
42532
+
42533
+ // src/commands/sessions/daemon/autoUpdate/noteAutoUpdate.ts
42534
+ function noteAutoUpdate(installDir, text17) {
42535
+ appendWatcherLog(installDir, watcherNote(text17));
42536
+ daemonLog(`auto-update: ${text17}`);
42537
+ }
42538
+
42427
42539
  // src/commands/sessions/daemon/autoUpdate/runUpdateLap.ts
42428
42540
  import { spawn as spawn11 } from "child_process";
42541
+ import { rmSync as rmSync10 } from "fs";
42542
+ function clearLapSignals(cwd) {
42543
+ const control = watchControlPaths(cwd);
42544
+ if (!control) return;
42545
+ for (const path106 of [control.check, control.stop])
42546
+ rmSync10(path106, { force: true });
42547
+ }
42429
42548
  function runUpdateLap(cwd, onOutput) {
42549
+ clearLapSignals(cwd);
42430
42550
  return new Promise((resolve28, reject) => {
42431
42551
  const { ASSIST_ACTIVITY_ID: _activity, ...env } = process.env;
42432
42552
  const child = spawn11(
@@ -42466,10 +42586,7 @@ function loopDeps(installDir, ctx) {
42466
42586
  appendWatcherLog(installDir, text17);
42467
42587
  logLine(text17);
42468
42588
  },
42469
- note: (text17) => {
42470
- appendWatcherLog(installDir, watcherNote(text17));
42471
- daemonLog(`auto-update: ${text17}`);
42472
- },
42589
+ note: (text17) => noteAutoUpdate(installDir, text17),
42473
42590
  enter: autoUpdateState.enter,
42474
42591
  liveEscalation: () => liveEscalationIn(ctx().sessions, clone)?.id,
42475
42592
  escalate: (output) => escalateDivergence(ctx(), installDir, output),
@@ -42477,7 +42594,8 @@ function loopDeps(installDir, ctx) {
42477
42594
  const status2 = ctx().sessions.get(sessionId)?.status;
42478
42595
  return status2 !== void 0 && status2 !== "done" && status2 !== "error";
42479
42596
  },
42480
- sleep: (ms) => sleep2(ms)
42597
+ paused: autoUpdateControl.paused,
42598
+ sleep: autoUpdateControl.sleep
42481
42599
  };
42482
42600
  }
42483
42601
 
@@ -42498,7 +42616,9 @@ function startAutoUpdate(ctx) {
42498
42616
  return;
42499
42617
  }
42500
42618
  trimWatcherLog(installDir);
42501
- daemonLog(`auto-update: pulling and building ${installDir} as origin moves`);
42619
+ daemonLog(
42620
+ autoUpdateControl.paused() ? `auto-update: paused for ${installDir}; resume it from the Updates page` : `auto-update: pulling and building ${installDir} as origin moves`
42621
+ );
42502
42622
  void runAutoUpdateLoop(loopDeps(installDir, ctx)).catch(
42503
42623
  (error) => daemonLog(
42504
42624
  `auto-update: loop stopped: ${error instanceof Error ? error.message : String(error)}`
@@ -42527,11 +42647,11 @@ function setStatus2(session, newStatus) {
42527
42647
  }
42528
42648
 
42529
42649
  // src/commands/sessions/daemon/worktree/reapWorktree.ts
42530
- import { existsSync as existsSync87 } from "fs";
42650
+ import { existsSync as existsSync88 } from "fs";
42531
42651
  import { basename as basename30 } from "path";
42532
42652
 
42533
42653
  // src/commands/sessions/daemon/worktree/deleteStrandedTree.ts
42534
- import { existsSync as existsSync86 } from "fs";
42654
+ import { existsSync as existsSync87 } from "fs";
42535
42655
  import { join as join103 } from "path";
42536
42656
 
42537
42657
  // src/commands/sessions/daemon/worktree/deleteTreeDirectly.ts
@@ -42601,7 +42721,7 @@ async function deleteStrandedTree(clone, worktreePath, cause) {
42601
42721
  );
42602
42722
  }
42603
42723
  function strandedReason(worktreePath, cause) {
42604
- if (!existsSync86(join103(worktreePath, ".git")))
42724
+ if (!existsSync87(join103(worktreePath, ".git")))
42605
42725
  return "its .git link is already gone";
42606
42726
  if (/not a working tree|not a git repository/i.test(reason2(cause)))
42607
42727
  return "git no longer recognises it as a working tree";
@@ -42653,7 +42773,7 @@ function reason3(error) {
42653
42773
 
42654
42774
  // src/commands/sessions/daemon/worktree/reapWorktree.ts
42655
42775
  async function reapWorktree(worktreePath, force = false) {
42656
- if (!existsSync87(worktreePath)) {
42776
+ if (!existsSync88(worktreePath)) {
42657
42777
  forgetWorktree(worktreePath);
42658
42778
  daemonLog(
42659
42779
  `worktree ${worktreePath} already gone; its record was forgotten`
@@ -42678,7 +42798,7 @@ async function reapWorktree(worktreePath, force = false) {
42678
42798
  }
42679
42799
  function owningClone(worktreePath) {
42680
42800
  const recorded = worktreeAttributionIncludingReaped(worktreePath)?.clone;
42681
- if (recorded && existsSync87(recorded)) return recorded;
42801
+ if (recorded && existsSync88(recorded)) return recorded;
42682
42802
  const detected = mainWorktree(worktreePath);
42683
42803
  if (detected) return detected;
42684
42804
  daemonLog(
@@ -42881,12 +43001,12 @@ async function closeBlockReason(sessionId, tree) {
42881
43001
  }
42882
43002
 
42883
43003
  // src/commands/sessions/daemon/worktree/watchGitState.ts
42884
- import { existsSync as existsSync88, watch } from "fs";
43004
+ import { existsSync as existsSync89, watch } from "fs";
42885
43005
  var DEBOUNCE_MS = 500;
42886
43006
  var POLL_MS2 = 3e4;
42887
43007
  function watchGitState(cwd, onChange) {
42888
43008
  const common = gitCommonDir(cwd);
42889
- if (!common || !existsSync88(common)) return void 0;
43009
+ if (!common || !existsSync89(common)) return void 0;
42890
43010
  const watchers = [
42891
43011
  watchGitDir(common, onChange),
42892
43012
  pollGitState(cwd, onChange)
@@ -43433,10 +43553,10 @@ function emitSessionOutput(session, clients, data) {
43433
43553
  }
43434
43554
 
43435
43555
  // src/commands/sessions/daemon/exitReason.ts
43436
- import { existsSync as existsSync89 } from "fs";
43556
+ import { existsSync as existsSync90 } from "fs";
43437
43557
  import { resolve as resolve27 } from "path";
43438
43558
  function exitDetail(session) {
43439
- if (session.cwd && !existsSync89(session.cwd))
43559
+ if (session.cwd && !existsSync90(session.cwd))
43440
43560
  return `working directory ${session.cwd} no longer exists`;
43441
43561
  return missingRunConfigCwd(session);
43442
43562
  }
@@ -43450,7 +43570,7 @@ function missingRunConfigCwd(session) {
43450
43570
  const config = resolveRunConfig(session.runName, dir);
43451
43571
  if (!config?.cwd) return void 0;
43452
43572
  const configured = resolve27(runConfigBaseDirFrom(dir), config.cwd);
43453
- if (existsSync89(configured)) return void 0;
43573
+ if (existsSync90(configured)) return void 0;
43454
43574
  return `run config "${config.name}": cwd ${configured} does not exist`;
43455
43575
  }
43456
43576
 
@@ -43498,7 +43618,7 @@ function handleRefusedRun(session, exitCode, onStatusChange) {
43498
43618
  }
43499
43619
 
43500
43620
  // src/commands/sessions/daemon/watchActivity.ts
43501
- import { existsSync as existsSync90, mkdirSync as mkdirSync35, watch as watch2 } from "fs";
43621
+ import { existsSync as existsSync91, mkdirSync as mkdirSync35, watch as watch2 } from "fs";
43502
43622
  import { dirname as dirname43 } from "path";
43503
43623
 
43504
43624
  // src/commands/sessions/daemon/applyActivityToSession.ts
@@ -43584,7 +43704,7 @@ function watchActivity(session, notify2, onClaudeSessionId) {
43584
43704
  if (timer) clearTimeout(timer);
43585
43705
  timer = setTimeout(read3, DEBOUNCE_MS2);
43586
43706
  });
43587
- if (existsSync90(path106)) read3();
43707
+ if (existsSync91(path106)) read3();
43588
43708
  }
43589
43709
  function refreshActivity(session) {
43590
43710
  if (session.commandType !== "assist" || !session.cwd) return;
@@ -45575,7 +45695,7 @@ function rearmStoppedSessions(sessions, notify2) {
45575
45695
  }
45576
45696
 
45577
45697
  // src/commands/sessions/daemon/worktree/reconcileWorktreesOnRestore.ts
45578
- import { existsSync as existsSync93 } from "fs";
45698
+ import { existsSync as existsSync94 } from "fs";
45579
45699
  import { basename as basename32 } from "path";
45580
45700
 
45581
45701
  // src/commands/sessions/daemon/worktree/accountedTrees.ts
@@ -45596,9 +45716,9 @@ function accountedTrees(sessions) {
45596
45716
  }
45597
45717
 
45598
45718
  // src/commands/sessions/daemon/worktree/reclaimVanishedWorktrees.ts
45599
- import { existsSync as existsSync92 } from "fs";
45719
+ import { existsSync as existsSync93 } from "fs";
45600
45720
  async function reclaimVanishedWorktrees(clone, paths) {
45601
- if (!existsSync92(clone)) {
45721
+ if (!existsSync93(clone)) {
45602
45722
  for (const { path: path106 } of paths) forgetWorktree(path106);
45603
45723
  daemonLog(
45604
45724
  `clone ${clone} is gone; forgot ${paths.length} worktree record(s) it owned`
@@ -45764,7 +45884,7 @@ async function recoverOrphanedWorktrees(sessions, spawnWith, notify2) {
45764
45884
  );
45765
45885
  continue;
45766
45886
  }
45767
- if (!existsSync93(path106)) {
45887
+ if (!existsSync94(path106)) {
45768
45888
  logVanishedTree(sessions, path106);
45769
45889
  vanished.set(clone, [
45770
45890
  ...vanished.get(clone) ?? [],
@@ -46809,7 +46929,7 @@ function spawnAssistInTree(ctx, assistArgs, cwd, meta, context) {
46809
46929
  }
46810
46930
 
46811
46931
  // src/commands/sessions/daemon/worktree/resumeInTree.ts
46812
- import { existsSync as existsSync96 } from "fs";
46932
+ import { existsSync as existsSync97 } from "fs";
46813
46933
 
46814
46934
  // src/commands/sessions/daemon/resumeSession.ts
46815
46935
  function resumeSession(id, sessionId, cwd, name, holdPty, harness) {
@@ -46840,10 +46960,10 @@ function resumeSession(id, sessionId, cwd, name, holdPty, harness) {
46840
46960
  }
46841
46961
 
46842
46962
  // src/commands/sessions/daemon/worktree/resumeInReplacementTree.ts
46843
- import { existsSync as existsSync95 } from "fs";
46963
+ import { existsSync as existsSync96 } from "fs";
46844
46964
 
46845
46965
  // src/commands/sessions/daemon/worktree/carryTranscriptToTree.ts
46846
- import { copyFileSync as copyFileSync5, existsSync as existsSync94, mkdirSync as mkdirSync37 } from "fs";
46966
+ import { copyFileSync as copyFileSync5, existsSync as existsSync95, mkdirSync as mkdirSync37 } from "fs";
46847
46967
  import { join as join105 } from "path";
46848
46968
  function carryTranscriptToTree(claudeSessionId, fromCwd, toCwd) {
46849
46969
  const dir = projectDirForCwd(toCwd);
@@ -46854,7 +46974,7 @@ function carryTranscriptToTree(claudeSessionId, fromCwd, toCwd) {
46854
46974
  return;
46855
46975
  }
46856
46976
  const dest = join105(dir, `${claudeSessionId}.jsonl`);
46857
- if (existsSync94(dest)) {
46977
+ if (existsSync95(dest)) {
46858
46978
  daemonLog(`transcript ${claudeSessionId} already present in ${dir}`);
46859
46979
  return;
46860
46980
  }
@@ -46905,7 +47025,7 @@ function resumeInReplacementTree(ctx, claudeSessionId, missingCwd, name, harness
46905
47025
  }
46906
47026
  function cloneForReapedTree(missingCwd) {
46907
47027
  const clone = worktreeAttributionIncludingReaped(missingCwd)?.clone;
46908
- if (!clone || !existsSync95(clone))
47028
+ if (!clone || !existsSync96(clone))
46909
47029
  throw new Error(
46910
47030
  `working directory no longer exists and no clone is recorded to re-allocate from: ${missingCwd}`
46911
47031
  );
@@ -46914,7 +47034,7 @@ function cloneForReapedTree(missingCwd) {
46914
47034
 
46915
47035
  // src/commands/sessions/daemon/worktree/resumeInTree.ts
46916
47036
  function resumeInTree(ctx, sessionId, cwd, name, harness) {
46917
- if (!existsSync96(cwd))
47037
+ if (!existsSync97(cwd))
46918
47038
  return resumeInReplacementTree(ctx, sessionId, cwd, name, harness);
46919
47039
  const id = ctx.spawnWith(
46920
47040
  (sid) => resumeSession(sid, sessionId, cwd, name, void 0, harness)
@@ -47107,10 +47227,60 @@ function autoUpdateReply() {
47107
47227
  type: "auto-update",
47108
47228
  version: ASSIST_VERSION,
47109
47229
  startCommit: autoUpdateState.startCommit(),
47110
- loop: autoUpdateState.loop()
47230
+ loop: { ...autoUpdateState.loop(), paused: autoUpdateControl.paused() }
47111
47231
  };
47112
47232
  }
47113
47233
 
47234
+ // src/commands/sessions/daemon/autoUpdate/applyAutoUpdateAction.ts
47235
+ import { rmSync as rmSync11, writeFileSync as writeFileSync56 } from "fs";
47236
+ function signalLap(installDir, marker2) {
47237
+ const control = watchControlPaths(installDir);
47238
+ if (control) writeFileSync56(control[marker2], "");
47239
+ }
47240
+ function applyAutoUpdateAction(action) {
47241
+ const installDir = getInstallDir();
47242
+ const loop2 = autoUpdateState.loop();
47243
+ if (action === "resume") {
47244
+ if (!autoUpdateControl.paused()) return void 0;
47245
+ const control = watchControlPaths(installDir);
47246
+ if (control) rmSync11(control.stop, { force: true });
47247
+ autoUpdateControl.setPaused(false);
47248
+ noteAutoUpdate(installDir, "resumed from Updates");
47249
+ return void 0;
47250
+ }
47251
+ if (loop2.phase === "off")
47252
+ return `auto-update is off (${loop2.reason ?? "not running"})`;
47253
+ if (action === "pause") {
47254
+ if (autoUpdateControl.paused()) return void 0;
47255
+ autoUpdateControl.setPaused(true);
47256
+ signalLap(installDir, "stop");
47257
+ noteAutoUpdate(installDir, "paused from Updates");
47258
+ return void 0;
47259
+ }
47260
+ if (autoUpdateControl.paused())
47261
+ return "auto-update is paused; resume it first";
47262
+ signalLap(installDir, "check");
47263
+ autoUpdateControl.wake();
47264
+ noteAutoUpdate(installDir, "check requested from Updates");
47265
+ return void 0;
47266
+ }
47267
+
47268
+ // src/commands/sessions/daemon/autoUpdate/handleAutoUpdateControl.ts
47269
+ function tryAction(action) {
47270
+ if (!AUTO_UPDATE_ACTIONS.includes(action))
47271
+ return `unknown auto-update action ${String(action)}`;
47272
+ try {
47273
+ return applyAutoUpdateAction(action);
47274
+ } catch (error) {
47275
+ return error instanceof Error ? error.message : String(error);
47276
+ }
47277
+ }
47278
+ var handleAutoUpdateControl = (client, _m, d) => {
47279
+ const error = tryAction(d.action);
47280
+ daemonLog(`auto-update-control ${String(d.action)}: ${error ?? "ok"}`);
47281
+ sendTo(client, { type: "auto-update-control", ok: !error, error });
47282
+ };
47283
+
47114
47284
  // src/commands/sessions/daemon/handleHelloRequest.ts
47115
47285
  var handleHelloRequest = (client, m, d) => {
47116
47286
  sendTo(client, buildHello());
@@ -47637,6 +47807,7 @@ var messageHandlers = {
47637
47807
  output: handleOutputRequest,
47638
47808
  nodes: (client, m) => m.links.sendNodes(client),
47639
47809
  "auto-update": (client) => sendTo(client, autoUpdateReply()),
47810
+ "auto-update-control": handleAutoUpdateControl,
47640
47811
  "reload-links": (client, m) => {
47641
47812
  daemonLog("reload-links received");
47642
47813
  m.links.reload();
@@ -47753,7 +47924,7 @@ function handleConnection(socket, manager) {
47753
47924
  }
47754
47925
 
47755
47926
  // src/commands/sessions/daemon/onListening.ts
47756
- import { unlinkSync as unlinkSync22, writeFileSync as writeFileSync55 } from "fs";
47927
+ import { unlinkSync as unlinkSync22, writeFileSync as writeFileSync57 } from "fs";
47757
47928
 
47758
47929
  // src/commands/sessions/daemon/startPidFileWatchdog.ts
47759
47930
  import { readFileSync as readFileSync66 } from "fs";
@@ -47775,7 +47946,7 @@ function ownsPidFile() {
47775
47946
 
47776
47947
  // src/commands/sessions/daemon/onListening.ts
47777
47948
  function onListening(manager, checkAutoExit) {
47778
- writeFileSync55(daemonPaths.pid, String(process.pid));
47949
+ writeFileSync57(daemonPaths.pid, String(process.pid));
47779
47950
  startPidFileWatchdog(() => {
47780
47951
  daemonLog("lost daemon.pid ownership; shutting down sessions and exiting");
47781
47952
  void manager.flushActiveMs().finally(() => {
@@ -48161,14 +48332,14 @@ async function linkStatus(name, query = queryNodes) {
48161
48332
  return (await query())?.links.find((l) => l.name === name);
48162
48333
  }
48163
48334
  async function awaitLinkReturn(name, options2, deps2 = {}) {
48164
- const { query = queryNodes, sleep: sleep3 = wait, now = Date.now } = deps2;
48335
+ const { query = queryNodes, sleep: sleep2 = wait, now = Date.now } = deps2;
48165
48336
  const deadline = now() + options2.timeoutMs;
48166
48337
  let sawDown = options2.sawDown;
48167
48338
  while (now() < deadline) {
48168
48339
  const link3 = await linkStatus(name, query);
48169
48340
  if (link3?.state !== "connected") sawDown = true;
48170
48341
  else if (sawDown) return link3;
48171
- await sleep3(POLL_MS4);
48342
+ await sleep2(POLL_MS4);
48172
48343
  }
48173
48344
  return void 0;
48174
48345
  }