@staff0rd/assist 0.758.0 → 0.759.1

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.758.0",
9
+ version: "0.759.1",
10
10
  type: "module",
11
11
  main: "dist/index.js",
12
12
  bin: {
@@ -13058,7 +13058,7 @@ var sessionsConfigHelp = [
13058
13058
  {
13059
13059
  key: "worktree.watcher",
13060
13060
  setter: "assist config set worktree.watcher true -g --repo",
13061
- note: "a backlog run also starts one starred claude session in the clone running /watch, so the built version stays current while the run works in its worktree; requires worktree.enabled, a /watch command in the repo and an auto-build run entry (default off)"
13061
+ note: "a backlog run also starts one starred console session in the clone running assist watch loop (no claude, relaunched after a daemon restart), so the built version stays current while the run works in its worktree; requires worktree.enabled and an auto-build run entry (default off)"
13062
13062
  },
13063
13063
  {
13064
13064
  key: "worktree.trunk",
@@ -41342,6 +41342,7 @@ function dismissSession(sessions, id) {
41342
41342
  s.activityWatcher?.close();
41343
41343
  s.transcriptWatcher?.close();
41344
41344
  s.gitWatcher?.close();
41345
+ s.closeGrace?.cancel();
41345
41346
  removeActivity(s.id);
41346
41347
  if (s.activity?.itemId != null) releaseLock(s.activity.itemId);
41347
41348
  sessions.delete(id);
@@ -41414,6 +41415,28 @@ function processCwd(link3) {
41414
41415
  }
41415
41416
  }
41416
41417
 
41418
+ // src/commands/sessions/daemon/worktree/awaitTreeVacated.ts
41419
+ var POLL_MS = 500;
41420
+ var GRACE_MS = 5e3;
41421
+ function awaitTreeVacated(session, treePath) {
41422
+ return new Promise((resolve26) => {
41423
+ let ticks = GRACE_MS / POLL_MS;
41424
+ const settle = (outcome) => {
41425
+ clearTimeout(timer);
41426
+ session.closeGrace = void 0;
41427
+ resolve26(outcome);
41428
+ };
41429
+ const tick = () => {
41430
+ if (liveProcessesInTree(treePath).length === 0) settle("vacated");
41431
+ else if (--ticks <= 0) settle("occupied");
41432
+ else timer = setTimeout(tick, POLL_MS);
41433
+ };
41434
+ let timer = setTimeout(tick, POLL_MS);
41435
+ session.closeGrace?.cancel();
41436
+ session.closeGrace = { cancel: () => settle("cancelled") };
41437
+ });
41438
+ }
41439
+
41417
41440
  // src/commands/sessions/daemon/worktree/liveOccupantReason.ts
41418
41441
  function liveOccupantReason(treePath) {
41419
41442
  stopInstall(treePath);
@@ -41425,20 +41448,21 @@ function liveOccupantReason(treePath) {
41425
41448
  // src/commands/sessions/daemon/worktree/closeBlockReason.ts
41426
41449
  async function closeBlockReason(sessionId, tree) {
41427
41450
  const durability = await checkDurability(tree.path);
41428
- if (!durability.durable) return durability.reason;
41451
+ if (!durability.durable) return { reason: durability.reason, live: false };
41429
41452
  if (durability.gone) {
41430
41453
  daemonLog(
41431
41454
  `session ${sessionId} closing: worktree ${tree.path} is gone from disk \u2014 released with nothing to land, not landed work`
41432
41455
  );
41433
41456
  return void 0;
41434
41457
  }
41435
- return tree.removable ? liveOccupantReason(tree.path) : void 0;
41458
+ const occupied = tree.removable ? liveOccupantReason(tree.path) : void 0;
41459
+ return occupied ? { reason: occupied, live: true } : void 0;
41436
41460
  }
41437
41461
 
41438
41462
  // src/commands/sessions/daemon/worktree/watchGitState.ts
41439
41463
  import { existsSync as existsSync87, watch } from "fs";
41440
41464
  var DEBOUNCE_MS = 500;
41441
- var POLL_MS = 3e4;
41465
+ var POLL_MS2 = 3e4;
41442
41466
  function watchGitState(cwd, onChange) {
41443
41467
  const common = gitCommonDir(cwd);
41444
41468
  if (!common || !existsSync87(common)) return void 0;
@@ -41469,22 +41493,59 @@ function watchGitDir(common, onChange) {
41469
41493
  }
41470
41494
  }
41471
41495
  function pollGitState(cwd, onChange) {
41472
- const timer = setInterval(onChange, POLL_MS);
41496
+ const timer = setInterval(onChange, POLL_MS2);
41473
41497
  timer.unref?.();
41474
- daemonLog(`git-state polled every ${POLL_MS / 1e3}s for ${cwd}`);
41498
+ daemonLog(`git-state polled every ${POLL_MS2 / 1e3}s for ${cwd}`);
41475
41499
  return { close: () => clearInterval(timer) };
41476
41500
  }
41477
41501
 
41502
+ // src/commands/sessions/daemon/worktree/holdStopped.ts
41503
+ function holdStopped(session, tree, reason4, recheck, notify2) {
41504
+ if (session.undurable?.reason !== reason4)
41505
+ daemonLog(
41506
+ `session ${session.id} stopped; ${tree.removable ? "reap" : "close"} blocked in ${tree.path}: ${reason4}`
41507
+ );
41508
+ session.undurable = { reason: reason4, removesTree: tree.removable };
41509
+ session.closing = void 0;
41510
+ setStatus2(session, "stopped");
41511
+ if (!session.gitWatcher) {
41512
+ let rechecking = false;
41513
+ session.gitWatcher = watchGitState(tree.path, () => {
41514
+ if (session.status !== "stopped" || rechecking) return;
41515
+ rechecking = true;
41516
+ void recheck().finally(() => {
41517
+ rechecking = false;
41518
+ });
41519
+ });
41520
+ }
41521
+ notify2();
41522
+ }
41523
+
41478
41524
  // src/commands/sessions/daemon/worktree/resolveCloseDurability.ts
41479
- async function resolveCloseDurability(session, finalize, notify2) {
41525
+ async function resolveCloseDurability(session, finalize, notify2, graceSpent = false) {
41480
41526
  const tree = treeUnderClose(session);
41481
41527
  if (!tree) {
41482
41528
  finalize();
41483
41529
  return;
41484
41530
  }
41485
41531
  const blocked = await closeBlockReason(session.id, tree);
41532
+ if (blocked?.live && session.closing && !graceSpent) {
41533
+ daemonLog(
41534
+ `session ${session.id} closing: waiting for live processes to leave ${tree.path} (${blocked.reason})`
41535
+ );
41536
+ const outcome = await awaitTreeVacated(session, tree.path);
41537
+ daemonLog(`session ${session.id} closing: live-process wait ${outcome}`);
41538
+ if (outcome === "cancelled") return;
41539
+ return resolveCloseDurability(session, finalize, notify2, true);
41540
+ }
41486
41541
  if (blocked) {
41487
- holdStopped(session, tree, blocked, finalize, notify2);
41542
+ holdStopped(
41543
+ session,
41544
+ tree,
41545
+ blocked.reason,
41546
+ () => resolveCloseDurability(session, finalize, notify2),
41547
+ notify2
41548
+ );
41488
41549
  return;
41489
41550
  }
41490
41551
  if (tree.removable) await reapWorktree(tree.path);
@@ -41496,26 +41557,6 @@ async function resolveCloseDurability(session, finalize, notify2) {
41496
41557
  session.gitWatcher = void 0;
41497
41558
  finalize();
41498
41559
  }
41499
- function holdStopped(session, tree, reason4, finalize, notify2) {
41500
- if (session.undurable?.reason !== reason4)
41501
- daemonLog(
41502
- `session ${session.id} stopped; ${tree.removable ? "reap" : "close"} blocked in ${tree.path}: ${reason4}`
41503
- );
41504
- session.undurable = { reason: reason4, removesTree: tree.removable };
41505
- session.closing = void 0;
41506
- setStatus2(session, "stopped");
41507
- if (!session.gitWatcher) {
41508
- let rechecking = false;
41509
- session.gitWatcher = watchGitState(tree.path, () => {
41510
- if (session.status !== "stopped" || rechecking) return;
41511
- rechecking = true;
41512
- void resolveCloseDurability(session, finalize, notify2).finally(() => {
41513
- rechecking = false;
41514
- });
41515
- });
41516
- }
41517
- notify2();
41518
- }
41519
41560
 
41520
41561
  // src/commands/sessions/daemon/dismissSessionGated.ts
41521
41562
  function dismissSessionGated(sessions, id, notify2, discard = false) {
@@ -41529,6 +41570,7 @@ function dismissSessionGated(sessions, id, notify2, discard = false) {
41529
41570
  if (dismissSession(sessions, id)) notify2();
41530
41571
  };
41531
41572
  const settle = discard ? () => void discardTree(s, removeCard, notify2) : () => void resolveCloseDurability(s, removeCard, notify2);
41573
+ s.closeGrace?.cancel();
41532
41574
  s.closing = true;
41533
41575
  daemonLog(
41534
41576
  `session ${id} closing: ${discard ? "discarding" : "checking durability of"} ${tree.path}`
@@ -42237,7 +42279,7 @@ async function touchedSince(file, sinceMs) {
42237
42279
  }
42238
42280
 
42239
42281
  // src/commands/sessions/daemon/bindCodexSession.ts
42240
- var POLL_MS2 = 1e3;
42282
+ var POLL_MS3 = 1e3;
42241
42283
  var MAX_ATTEMPTS = 120;
42242
42284
  var START_SLACK_MS = 5e3;
42243
42285
  function bindCodexSession(session, notify2) {
@@ -42245,7 +42287,7 @@ function bindCodexSession(session, notify2) {
42245
42287
  if (session.harness !== "codex" || !cwd || session.harnessSessionId) return;
42246
42288
  const since = session.startedAt - START_SLACK_MS;
42247
42289
  let attemptsLeft = MAX_ATTEMPTS;
42248
- const schedule = () => setTimeout(() => void poll(), POLL_MS2).unref();
42290
+ const schedule = () => setTimeout(() => void poll(), POLL_MS3).unref();
42249
42291
  const poll = async () => {
42250
42292
  if (session.harnessSessionId) return;
42251
42293
  const sessionId = await resolveCodexSessionId(cwd, since);
@@ -43217,6 +43259,7 @@ function respawnSession(session, respawn, status2, clients, onStatusChange) {
43217
43259
  session.gitWatcher = void 0;
43218
43260
  session.undurable = void 0;
43219
43261
  session.pendingDismiss = void 0;
43262
+ session.closeGrace?.cancel();
43220
43263
  session.closing = void 0;
43221
43264
  session.scrollback = "";
43222
43265
  session.startedAt = Date.now();
@@ -43842,11 +43885,25 @@ function isOnceLaunch(persisted) {
43842
43885
  return persisted.commandType === "assist" && !!persisted.assistArgs && LAUNCH_COMMANDS.includes(persisted.assistArgs[0]) && persisted.assistArgs.includes("--once");
43843
43886
  }
43844
43887
 
43888
+ // src/commands/sessions/daemon/relaunchConsoleWatcher.ts
43889
+ function isConsoleWatcher(persisted) {
43890
+ return persisted.watcher === true && persisted.commandType === "assist" && !!persisted.assistArgs;
43891
+ }
43892
+ function relaunchConsoleWatcher(id, persisted, base) {
43893
+ const command = ["assist", ...persisted.assistArgs];
43894
+ daemonLog(
43895
+ `relaunching watcher session ${id} running ${command.join(" ")} in ${persisted.cwd ?? "(no cwd)"}`
43896
+ );
43897
+ return runningSession(base, persisted, spawnPty(command, persisted.cwd, id));
43898
+ }
43899
+
43845
43900
  // src/commands/sessions/daemon/restoreSession.ts
43846
43901
  function restoreSession(id, persisted) {
43847
43902
  const base = restoreBase(id, persisted);
43848
43903
  if (persisted.status === "stopped") return stoppedSession(base, persisted);
43849
43904
  if (isUpdate(persisted)) return updatedSession(id, persisted);
43905
+ if (isConsoleWatcher(persisted))
43906
+ return relaunchConsoleWatcher(id, persisted, base);
43850
43907
  const status2 = deriveRestoreStatus(persisted);
43851
43908
  if (needsWrapperRelaunch(persisted)) {
43852
43909
  const resumesWrittenConversation = hasTranscriptOnDisk(persisted);
@@ -44337,26 +44394,35 @@ function startReusedRunPty(session, assistArgs, itemId2, hold, clients, onStatus
44337
44394
  return true;
44338
44395
  }
44339
44396
 
44397
+ // src/commands/sessions/daemon/createAssistSession.ts
44398
+ function createAssistSession(id, assistArgs, cwd, meta, holdPty) {
44399
+ const startedAt = Date.now();
44400
+ return {
44401
+ id,
44402
+ name: `assist ${assistArgs.join(" ")}`,
44403
+ title: meta?.title,
44404
+ subtitle: meta?.subtitle,
44405
+ commandType: "assist",
44406
+ status: "running",
44407
+ startedAt,
44408
+ runningMs: 0,
44409
+ runningSince: startedAt,
44410
+ waitingSince: null,
44411
+ ...startOrHoldPty(
44412
+ () => spawnPty(["assist", ...assistArgs], cwd, id),
44413
+ holdPty
44414
+ ),
44415
+ scrollback: "",
44416
+ assistArgs,
44417
+ cwd
44418
+ };
44419
+ }
44420
+
44340
44421
  // src/commands/sessions/daemon/createWatcherSession.ts
44341
- import { randomUUID as randomUUID24 } from "crypto";
44342
- var WATCH_PROMPT = "/watch";
44422
+ var WATCHER_ARGS = ["watch", "loop"];
44343
44423
  function createWatcherSession(id, cwd) {
44344
- const claudeSessionId = randomUUID24();
44345
44424
  return {
44346
- ...sessionBase(id, "running"),
44347
- name: `Session ${id}`,
44348
- commandType: "claude",
44349
- pty: spawnClaude2({
44350
- prompt: WATCH_PROMPT,
44351
- cwd,
44352
- sessionId: id,
44353
- claudeSessionId,
44354
- auto: true
44355
- }),
44356
- cwd,
44357
- claudeSessionId,
44358
- initialPrompt: WATCH_PROMPT,
44359
- auto: true,
44425
+ ...createAssistSession(id, WATCHER_ARGS, cwd),
44360
44426
  watcher: true
44361
44427
  };
44362
44428
  }
@@ -44424,7 +44490,7 @@ function ensureWatcher(ctx, cwd) {
44424
44490
  { inPlace: true }
44425
44491
  );
44426
44492
  daemonLog(
44427
- `spawned watcher session ${id} running /watch in the clone ${clone}`
44493
+ `spawned watcher session ${id} running assist ${WATCHER_ARGS.join(" ")} in the clone ${clone}`
44428
44494
  );
44429
44495
  return id;
44430
44496
  }
@@ -45494,30 +45560,6 @@ function addAgentToStream(ctx, targetId, request) {
45494
45560
  };
45495
45561
  }
45496
45562
 
45497
- // src/commands/sessions/daemon/createAssistSession.ts
45498
- function createAssistSession(id, assistArgs, cwd, meta, holdPty) {
45499
- const startedAt = Date.now();
45500
- return {
45501
- id,
45502
- name: `assist ${assistArgs.join(" ")}`,
45503
- title: meta?.title,
45504
- subtitle: meta?.subtitle,
45505
- commandType: "assist",
45506
- status: "running",
45507
- startedAt,
45508
- runningMs: 0,
45509
- runningSince: startedAt,
45510
- waitingSince: null,
45511
- ...startOrHoldPty(
45512
- () => spawnPty(["assist", ...assistArgs], cwd, id),
45513
- holdPty
45514
- ),
45515
- scrollback: "",
45516
- assistArgs,
45517
- cwd
45518
- };
45519
- }
45520
-
45521
45563
  // src/commands/sessions/daemon/worktree/spawnInTree.ts
45522
45564
  function spawnInTree(ctx, { prompt, cwd, design, auto, harness, inPlace }, context) {
45523
45565
  return allocateAndBind(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@staff0rd/assist",
3
- "version": "0.758.0",
3
+ "version": "0.759.1",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "bin": {