@staff0rd/assist 0.764.0 → 0.765.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.764.0",
9
+ version: "0.765.0",
10
10
  type: "module",
11
11
  main: "dist/index.js",
12
12
  bin: {
@@ -39717,6 +39717,9 @@ function watchReport(options2) {
39717
39717
  }
39718
39718
  }
39719
39719
 
39720
+ // src/commands/watch/simulatedDivergenceNote.ts
39721
+ var simulatedDivergenceNote = "requested by assist watch simulate-divergence; nothing has actually diverged";
39722
+
39720
39723
  // src/commands/watch/describeOutcome.ts
39721
39724
  var short = (sha) => sha.slice(0, 7);
39722
39725
  function describeOutcome(outcome) {
@@ -39734,7 +39737,7 @@ function describeOutcome(outcome) {
39734
39737
  case "simulated-divergence":
39735
39738
  return {
39736
39739
  exitCode: 3,
39737
- message: `simulated divergence from ${outcome.upstream} (requested by assist watch simulate-divergence; nothing has actually diverged)`
39740
+ message: `simulated divergence from ${outcome.upstream} (${simulatedDivergenceNote})`
39738
39741
  };
39739
39742
  case "unavailable":
39740
39743
  return { exitCode: 1, message: `cannot wait: ${outcome.reason}` };
@@ -42204,6 +42207,39 @@ function dismissSessionGated(sessions, id, notify2, discard = false) {
42204
42207
  settle();
42205
42208
  }
42206
42209
 
42210
+ // src/commands/sessions/daemon/worktree/resumeWatcherAfterEscalation.ts
42211
+ var ENDED = ["done", "error"];
42212
+ function resumeWatcherAfterEscalation(sessions, escalation, restart) {
42213
+ if (escalation.divergenceEscalation !== true || !escalation.cwd) return;
42214
+ const clone = canonicalTreePath(escalation.cwd);
42215
+ const watcher = [...sessions.values()].find(
42216
+ (s) => s.watcher === true && s.cwd !== void 0 && canonicalTreePath(s.cwd) === clone
42217
+ );
42218
+ if (!watcher) {
42219
+ daemonLog(
42220
+ `escalation session ${escalation.id} ended (${escalation.status}): no watcher left in the clone ${clone} to restart`
42221
+ );
42222
+ return;
42223
+ }
42224
+ if (!ENDED.includes(watcher.status)) {
42225
+ daemonLog(
42226
+ `escalation session ${escalation.id} ended (${escalation.status}): watcher session ${watcher.id} in the clone ${clone} is already ${watcher.status}`
42227
+ );
42228
+ return;
42229
+ }
42230
+ daemonLog(
42231
+ `escalation session ${escalation.id} ended (${escalation.status}): restarting watcher session ${watcher.id} in the clone ${clone}`
42232
+ );
42233
+ restart(watcher.id);
42234
+ }
42235
+
42236
+ // src/commands/sessions/daemon/dismissAndResumeWatcher.ts
42237
+ function dismissAndResumeWatcher(sessions, id, notify2, restart) {
42238
+ const session = sessions.get(id);
42239
+ dismissSessionGated(sessions, id, notify2);
42240
+ if (session) resumeWatcherAfterEscalation(sessions, session, restart);
42241
+ }
42242
+
42207
42243
  // src/commands/sessions/daemon/drainSessions.ts
42208
42244
  function drainSessions(sessions, notify2) {
42209
42245
  const drained = [...sessions.values()].map((s) => ({
@@ -43755,6 +43791,7 @@ function finishStatusChange(session, status2, exitCode, deps2) {
43755
43791
  if (status2 !== "stopped") session.undurable = void 0;
43756
43792
  if (session.watcher === true && exitCode === DIVERGED_EXIT_CODE)
43757
43793
  deps2.escalateDivergence?.(session);
43794
+ if (status2 === "done" || status2 === "error") deps2.resumeWatcher?.(session);
43758
43795
  const autoRun = shouldAutoRun(session);
43759
43796
  if (autoRun.run) {
43760
43797
  deps2.reuseForRun(session, autoRun.itemId);
@@ -44105,112 +44142,6 @@ function reuseSessionForRun(session, itemId2, clients, onStatusChange, tree) {
44105
44142
  );
44106
44143
  }
44107
44144
 
44108
- // src/commands/sessions/daemon/worktree/divergencePrompt.ts
44109
- var MAX_REASON_LINES = 20;
44110
- var MAX_REASON_CHARS = 2e3;
44111
- function divergencePrompt(clone, scrollback) {
44112
- const reason4 = stripAnsi(scrollback).split(/\r?\n|\r/).map((line) => line.trimEnd()).filter((line) => line.trim().length > 0).slice(-MAX_REASON_LINES).join("\n").slice(-MAX_REASON_CHARS);
44113
- return [
44114
- `The watcher in ${clone} stopped because \`assist watch wait --pull --build\` could not fast-forward the branch (exit 3). The tail of its output:`,
44115
- "",
44116
- "```",
44117
- reason4 || "(no output captured)",
44118
- "```",
44119
- "",
44120
- "Diagnose why the local branch has diverged from its upstream and report what you find and the options for reconciling it. Do not force-push, `git reset`, or otherwise discard commits on either side \u2014 leave the decision to the user."
44121
- ].join("\n");
44122
- }
44123
-
44124
- // src/commands/sessions/daemon/worktree/escalateDivergence.ts
44125
- var ENDED = ["done", "error", "stopped"];
44126
- function escalateDivergence(ctx, watcher) {
44127
- if (!watcher.cwd) return void 0;
44128
- const clone = canonicalTreePath(watcher.cwd);
44129
- const live = [...ctx.sessions.values()].find(
44130
- (s) => s.divergenceEscalation === true && s.cwd !== void 0 && canonicalTreePath(s.cwd) === clone && !ENDED.includes(s.status)
44131
- );
44132
- if (live) {
44133
- daemonLog(
44134
- `watcher session ${watcher.id} exited 3 (divergence) in the clone ${clone}: no escalation spawned, session ${live.id} is already diagnosing it (${live.status})`
44135
- );
44136
- return void 0;
44137
- }
44138
- const prompt = divergencePrompt(clone, watcher.scrollback);
44139
- const id = allocateAndBind(
44140
- ctx,
44141
- clone,
44142
- (sid, resolvedCwd) => ({
44143
- ...createSession(sid, { prompt, cwd: resolvedCwd ?? clone }),
44144
- divergenceEscalation: true
44145
- }),
44146
- { inPlace: true }
44147
- );
44148
- daemonLog(
44149
- `watcher session ${watcher.id} exited 3 (divergence) in the clone ${clone}: spawned escalation session ${id} to diagnose it`
44150
- );
44151
- return id;
44152
- }
44153
-
44154
- // src/commands/sessions/daemon/managerStatusChangeHandler.ts
44155
- function managerStatusChangeHandler(sessions, clients, dismiss, notify2, treeCtx) {
44156
- const handler = makeStatusChangeHandler(sessions, {
44157
- dismiss,
44158
- notify: notify2,
44159
- reuseForRun: (session, itemId2) => reuseSessionForRun(session, itemId2, clients, handler, treeCtx()),
44160
- escalateDivergence: (watcher) => escalateDivergence(treeCtx(), watcher)
44161
- });
44162
- return handler;
44163
- }
44164
-
44165
- // src/commands/sessions/daemon/applySetStatus.ts
44166
- function applySetStatus(sessions, id, status2, source, onStatusChange, rebind) {
44167
- const session = sessions.get(id);
44168
- if (!session) {
44169
- daemonLog(
44170
- `set-status for unknown session id=${id} status=${status2} (no live session; ignoring)`
44171
- );
44172
- return;
44173
- }
44174
- if (session.status === "stopped") {
44175
- daemonLog(
44176
- `set-status for stopped session id=${id} status=${status2} (held pending durable close; ignoring)`
44177
- );
44178
- return;
44179
- }
44180
- rebind?.(session);
44181
- if (source === "permission") session.permissionActive = true;
44182
- if (status2 === "running") session.permissionActive = false;
44183
- onStatusChange(session, status2);
44184
- }
44185
-
44186
- // src/commands/sessions/daemon/rebindClaudeSession.ts
44187
- function rebindClaudeSession(session, claudeSessionId, notify2, onStatusChange) {
44188
- if (!claudeSessionId) return;
44189
- if (session.claudeSessionId === claudeSessionId) return;
44190
- daemonLog(
44191
- `session ${session.id} claude session moved: ${session.claudeSessionId ?? "none"} -> ${claudeSessionId}`
44192
- );
44193
- session.claudeSessionId = claudeSessionId;
44194
- watchTranscript(session, notify2, onStatusChange);
44195
- }
44196
-
44197
- // src/commands/sessions/daemon/setStatusFromHook.ts
44198
- function setStatusFromHook(sessions, report, notify2, onStatusChange) {
44199
- applySetStatus(
44200
- sessions,
44201
- report.id,
44202
- report.status,
44203
- report.source,
44204
- onStatusChange,
44205
- (session) => rebindClaudeSession(
44206
- session,
44207
- report.claudeSessionId,
44208
- notify2,
44209
- onStatusChange
44210
- )
44211
- );
44212
- }
44213
-
44214
44145
  // src/commands/sessions/daemon/respawnSession.ts
44215
44146
  function respawnSession(session, respawn, status2, clients, onStatusChange) {
44216
44147
  session.gitWatcher?.close();
@@ -44407,6 +44338,134 @@ function restartManagedSession(sessions, id, clients, onStatusChange) {
44407
44338
  return { ok: true };
44408
44339
  }
44409
44340
 
44341
+ // src/commands/sessions/daemon/worktree/divergencePrompt.ts
44342
+ var MAX_REASON_LINES = 20;
44343
+ var MAX_REASON_CHARS = 2e3;
44344
+ var RECONCILE_STEPS = [
44345
+ "Reconcile it yourself, without discarding any commits on either side:",
44346
+ "",
44347
+ "1. Inspect `git status`, `git log --oneline @{u}..@` and `git log --oneline @..@{u}`.",
44348
+ '2. Stash any uncommitted work with `git stash push -u -m "assist divergence"`, and restore it with `git stash pop` once the branch is reconciled.',
44349
+ "3. Rebase the local commits onto the upstream with `git rebase @{u}`, resolving conflicts so both sides' changes survive. If the rebase cannot be resolved cleanly, `git rebase --abort` and `git merge @{u}` instead.",
44350
+ "4. Push the result with a plain `git push` so the branch matches its upstream again.",
44351
+ "5. Build the clone with `assist run auto-build`.",
44352
+ "6. Run /close. The daemon restarts the watcher once this session closes.",
44353
+ "",
44354
+ "Never force-push, `git reset`, or otherwise discard commits. If you cannot reconcile the branch without doing so, explain what is blocking it and leave this session open for the user instead of closing it."
44355
+ ];
44356
+ var SIMULATED_STEPS = [
44357
+ "This divergence was simulated to test the escalation; there is nothing to reconcile. Run /close now. The daemon restarts the watcher once this session closes."
44358
+ ];
44359
+ function divergenceReason(scrollback) {
44360
+ return stripAnsi(scrollback).split(/\r?\n|\r/).map((line) => line.trimEnd()).filter((line) => line.trim().length > 0).slice(-MAX_REASON_LINES).join("\n").slice(-MAX_REASON_CHARS);
44361
+ }
44362
+ function divergencePrompt(clone, scrollback) {
44363
+ const reason4 = divergenceReason(scrollback);
44364
+ return [
44365
+ `The watcher in ${clone} stopped because \`assist watch wait --pull --build\` could not fast-forward the branch (exit 3). The tail of its output:`,
44366
+ "",
44367
+ "```",
44368
+ reason4 || "(no output captured)",
44369
+ "```",
44370
+ "",
44371
+ ...reason4.includes(simulatedDivergenceNote) ? SIMULATED_STEPS : RECONCILE_STEPS
44372
+ ].join("\n");
44373
+ }
44374
+
44375
+ // src/commands/sessions/daemon/worktree/escalateDivergence.ts
44376
+ var ENDED2 = ["done", "error", "stopped"];
44377
+ function escalateDivergence(ctx, watcher) {
44378
+ if (!watcher.cwd) return void 0;
44379
+ const clone = canonicalTreePath(watcher.cwd);
44380
+ const live = [...ctx.sessions.values()].find(
44381
+ (s) => s.divergenceEscalation === true && s.cwd !== void 0 && canonicalTreePath(s.cwd) === clone && !ENDED2.includes(s.status)
44382
+ );
44383
+ if (live) {
44384
+ daemonLog(
44385
+ `watcher session ${watcher.id} exited 3 (divergence) in the clone ${clone}: no escalation spawned, session ${live.id} is already diagnosing it (${live.status})`
44386
+ );
44387
+ return void 0;
44388
+ }
44389
+ const prompt = divergencePrompt(clone, watcher.scrollback);
44390
+ const id = allocateAndBind(
44391
+ ctx,
44392
+ clone,
44393
+ (sid, resolvedCwd) => ({
44394
+ ...createSession(sid, { prompt, cwd: resolvedCwd ?? clone }),
44395
+ divergenceEscalation: true
44396
+ }),
44397
+ { inPlace: true }
44398
+ );
44399
+ daemonLog(
44400
+ `watcher session ${watcher.id} exited 3 (divergence) in the clone ${clone}: spawned escalation session ${id} to diagnose it`
44401
+ );
44402
+ return id;
44403
+ }
44404
+
44405
+ // src/commands/sessions/daemon/managerStatusChangeHandler.ts
44406
+ function managerStatusChangeHandler(sessions, clients, dismiss, notify2, treeCtx) {
44407
+ const handler = makeStatusChangeHandler(sessions, {
44408
+ dismiss,
44409
+ notify: notify2,
44410
+ reuseForRun: (session, itemId2) => reuseSessionForRun(session, itemId2, clients, handler, treeCtx()),
44411
+ escalateDivergence: (watcher) => escalateDivergence(treeCtx(), watcher),
44412
+ resumeWatcher: (escalation) => resumeWatcherAfterEscalation(sessions, escalation, (watcherId) => {
44413
+ if (restartManagedSession(sessions, watcherId, clients, handler).ok)
44414
+ notify2();
44415
+ })
44416
+ });
44417
+ return handler;
44418
+ }
44419
+
44420
+ // src/commands/sessions/daemon/applySetStatus.ts
44421
+ function applySetStatus(sessions, id, status2, source, onStatusChange, rebind) {
44422
+ const session = sessions.get(id);
44423
+ if (!session) {
44424
+ daemonLog(
44425
+ `set-status for unknown session id=${id} status=${status2} (no live session; ignoring)`
44426
+ );
44427
+ return;
44428
+ }
44429
+ if (session.status === "stopped") {
44430
+ daemonLog(
44431
+ `set-status for stopped session id=${id} status=${status2} (held pending durable close; ignoring)`
44432
+ );
44433
+ return;
44434
+ }
44435
+ rebind?.(session);
44436
+ if (source === "permission") session.permissionActive = true;
44437
+ if (status2 === "running") session.permissionActive = false;
44438
+ onStatusChange(session, status2);
44439
+ }
44440
+
44441
+ // src/commands/sessions/daemon/rebindClaudeSession.ts
44442
+ function rebindClaudeSession(session, claudeSessionId, notify2, onStatusChange) {
44443
+ if (!claudeSessionId) return;
44444
+ if (session.claudeSessionId === claudeSessionId) return;
44445
+ daemonLog(
44446
+ `session ${session.id} claude session moved: ${session.claudeSessionId ?? "none"} -> ${claudeSessionId}`
44447
+ );
44448
+ session.claudeSessionId = claudeSessionId;
44449
+ watchTranscript(session, notify2, onStatusChange);
44450
+ }
44451
+
44452
+ // src/commands/sessions/daemon/setStatusFromHook.ts
44453
+ function setStatusFromHook(sessions, report, notify2, onStatusChange) {
44454
+ applySetStatus(
44455
+ sessions,
44456
+ report.id,
44457
+ report.status,
44458
+ report.source,
44459
+ onStatusChange,
44460
+ (session) => rebindClaudeSession(
44461
+ session,
44462
+ report.claudeSessionId,
44463
+ notify2,
44464
+ onStatusChange
44465
+ )
44466
+ );
44467
+ }
44468
+
44410
44469
  // src/commands/sessions/daemon/restartAndNotify.ts
44411
44470
  function restartAndNotify(sessions, id, clients, onStatusChange, notify2) {
44412
44471
  const result = restartManagedSession(sessions, id, clients, onStatusChange);
@@ -46487,7 +46546,12 @@ var SessionManager = class {
46487
46546
  );
46488
46547
  }
46489
46548
  dismissSession = (id) => {
46490
- dismissSessionGated(this.sessions, id, this.notify);
46549
+ dismissAndResumeWatcher(
46550
+ this.sessions,
46551
+ id,
46552
+ this.notify,
46553
+ (watcherId) => this.restart(watcherId)
46554
+ );
46491
46555
  };
46492
46556
  discardSession = (id) => {
46493
46557
  dismissSessionGated(this.sessions, id, this.notify, true);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@staff0rd/assist",
3
- "version": "0.764.0",
3
+ "version": "0.765.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "bin": {