@staff0rd/assist 0.359.0 → 0.360.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.359.0",
9
+ version: "0.360.1",
10
10
  type: "module",
11
11
  main: "dist/index.js",
12
12
  bin: {
@@ -21975,6 +21975,228 @@ function recordSessionUsage(sessions, claudeSessionId, totalIn, totalOut) {
21975
21975
  }
21976
21976
  }
21977
21977
 
21978
+ // src/commands/sessions/daemon/watchActivity.ts
21979
+ import { existsSync as existsSync53, mkdirSync as mkdirSync23, watch } from "fs";
21980
+ import { dirname as dirname26 } from "path";
21981
+
21982
+ // src/commands/sessions/daemon/applyReviewPause.ts
21983
+ function applyReviewPause(session, activity2) {
21984
+ const inReview = activity2.kind === "backlog" && activity2.phase !== void 0 && activity2.phase === activity2.totalPhases;
21985
+ if (inReview && !session.reviewStarted) {
21986
+ session.autoAdvance = false;
21987
+ session.reviewStarted = true;
21988
+ } else if (!inReview && session.reviewStarted) {
21989
+ session.reviewStarted = false;
21990
+ }
21991
+ }
21992
+
21993
+ // src/shared/db/getPhaseActiveMs.ts
21994
+ import { and as and17, eq as eq36 } from "drizzle-orm";
21995
+ async function getPhaseActiveMs(db, itemId, phaseIdx) {
21996
+ const [row] = await db.select({ activeMs: phaseUsage.activeMs }).from(phaseUsage).where(
21997
+ and17(eq36(phaseUsage.itemId, itemId), eq36(phaseUsage.phaseIdx, phaseIdx))
21998
+ );
21999
+ return row?.activeMs ?? 0;
22000
+ }
22001
+
22002
+ // src/commands/sessions/daemon/loadPhaseActiveMs.ts
22003
+ async function loadPhaseActiveMs(itemId, phaseIdx) {
22004
+ try {
22005
+ if (!process.env.ASSIST_DATABASE_URL && !loadConfig().database.url)
22006
+ return null;
22007
+ return await getPhaseActiveMs(await getDb(), itemId, phaseIdx);
22008
+ } catch (error) {
22009
+ daemonLog(`phase-active-ms load failed: ${String(error)}`);
22010
+ return null;
22011
+ }
22012
+ }
22013
+
22014
+ // src/commands/sessions/daemon/seedRunningMsFromUsage.ts
22015
+ async function seedRunningMsFromUsage(session, notify2) {
22016
+ if (session.usageSeeded) return;
22017
+ const phase = sessionBacklogPhase(session);
22018
+ if (!phase) return;
22019
+ session.usageSeeded = true;
22020
+ const priorMs = await loadPhaseActiveMs(phase.itemId, phase.phaseIdx);
22021
+ if (priorMs == null || priorMs <= session.runningMs) return;
22022
+ session.runningMs = priorMs;
22023
+ daemonLog(
22024
+ `session ${session.id} seeded runningMs=${priorMs} from item ${phase.itemId} phase ${phase.phaseIdx} recorded usage`
22025
+ );
22026
+ notify2();
22027
+ }
22028
+
22029
+ // src/commands/sessions/daemon/watchActivity.ts
22030
+ var DEBOUNCE_MS = 50;
22031
+ function watchActivity(session, notify2) {
22032
+ if (session.commandType !== "assist" || !session.cwd) return;
22033
+ const path57 = activityPath(session.id);
22034
+ const dir = dirname26(path57);
22035
+ try {
22036
+ mkdirSync23(dir, { recursive: true });
22037
+ } catch {
22038
+ return;
22039
+ }
22040
+ reconcileActivity(session.id, session.activity);
22041
+ let timer = null;
22042
+ const read = () => {
22043
+ timer = null;
22044
+ const activity2 = readActivity(path57);
22045
+ if (!activity2) return;
22046
+ session.activity = activity2;
22047
+ if (activity2.claudeSessionId)
22048
+ session.claudeSessionId = activity2.claudeSessionId;
22049
+ applyReviewPause(session, activity2);
22050
+ void seedRunningMsFromUsage(session, notify2);
22051
+ notify2();
22052
+ };
22053
+ session.activityWatcher = watch(dir, (_event, filename) => {
22054
+ if (filename && !path57.endsWith(filename)) return;
22055
+ if (timer) clearTimeout(timer);
22056
+ timer = setTimeout(read, DEBOUNCE_MS);
22057
+ });
22058
+ if (existsSync53(path57)) read();
22059
+ }
22060
+ function refreshActivity(session) {
22061
+ if (session.commandType !== "assist" || !session.cwd) return;
22062
+ const activity2 = readActivity(activityPath(session.id));
22063
+ if (!activity2) return;
22064
+ session.activity = activity2;
22065
+ if (activity2.claudeSessionId)
22066
+ session.claudeSessionId = activity2.claudeSessionId;
22067
+ }
22068
+
22069
+ // src/commands/sessions/daemon/handlePtyExit.ts
22070
+ function handlePtyExit(session, exitCode, onStatusChange) {
22071
+ if (session.pendingRestart) {
22072
+ const resume = session.pendingRestart;
22073
+ session.pendingRestart = void 0;
22074
+ daemonLog(
22075
+ `session ${session.id} ("${session.name}") pty exited (code ${exitCode}) for restart; resuming now the process is gone`
22076
+ );
22077
+ resume();
22078
+ return;
22079
+ }
22080
+ refreshActivity(session);
22081
+ const failedResume = session.restored === true && exitCode !== 0 && session.scrollback.length === 0;
22082
+ if (failedResume) {
22083
+ session.error = `resume process exited with code ${exitCode} before producing any output`;
22084
+ daemonLog(
22085
+ `could not resume restored session "${session.name}" (id ${session.id}): ${session.error}`
22086
+ );
22087
+ onStatusChange(session, "error", exitCode);
22088
+ return;
22089
+ }
22090
+ const priorStatus = session.status;
22091
+ const unexpected = priorStatus === "waiting" || exitCode !== 0;
22092
+ daemonLog(
22093
+ `session ${session.id} ("${session.name}") pty exited with code ${exitCode} from status "${priorStatus}" (${session.scrollback.length} bytes of output) \u2014 ${unexpected ? "unexpected exit, process died mid-session" : "expected completion"}; marking done`
22094
+ );
22095
+ onStatusChange(session, "done", exitCode);
22096
+ }
22097
+
22098
+ // src/commands/sessions/daemon/wirePtyEvents.ts
22099
+ var MAX_SCROLLBACK = 256 * 1024;
22100
+ function appendScrollback(session, data) {
22101
+ session.scrollback += data;
22102
+ if (session.scrollback.length > MAX_SCROLLBACK) {
22103
+ session.scrollback = session.scrollback.slice(-MAX_SCROLLBACK);
22104
+ }
22105
+ }
22106
+ function wirePtyEvents(session, clients, onStatusChange) {
22107
+ if (!session.pty) return;
22108
+ session.pty.onData((data) => {
22109
+ appendScrollback(session, data);
22110
+ noteOutputForEscInterrupt(session, onStatusChange);
22111
+ broadcast(clients, { type: "output", sessionId: session.id, data });
22112
+ });
22113
+ session.pty.onExit(
22114
+ ({ exitCode }) => handlePtyExit(session, exitCode, onStatusChange)
22115
+ );
22116
+ }
22117
+
22118
+ // src/commands/sessions/daemon/respawnSession.ts
22119
+ function respawnSession(session, respawn, status2, clients, onStatusChange) {
22120
+ session.scrollback = "";
22121
+ session.startedAt = Date.now();
22122
+ session.runningMs = 0;
22123
+ session.runningSince = null;
22124
+ setStatus2(session, status2);
22125
+ session.restored = void 0;
22126
+ session.pty = respawn();
22127
+ broadcast(clients, { type: "clear", sessionId: session.id });
22128
+ wirePtyEvents(session, clients, onStatusChange);
22129
+ }
22130
+
22131
+ // src/commands/sessions/daemon/assistResumeArgs.ts
22132
+ function assistResumeArgs(session) {
22133
+ const args = ["assist", ...session.assistArgs];
22134
+ return session.claudeSessionId ? [...args, "--resume-session", session.claudeSessionId] : args;
22135
+ }
22136
+
22137
+ // src/commands/sessions/daemon/respawnPlan.ts
22138
+ function respawnPlan(session) {
22139
+ const { commandType, claudeSessionId, cwd, assistArgs } = session;
22140
+ if (commandType === "claude")
22141
+ return claudeSessionId ? {
22142
+ spawn: () => spawnClaude2({
22143
+ resumeSessionId: claudeSessionId,
22144
+ cwd,
22145
+ sessionId: session.id
22146
+ }),
22147
+ status: "waiting"
22148
+ } : null;
22149
+ if (commandType === "assist" && assistArgs) {
22150
+ const idle = session.status === "waiting";
22151
+ return {
22152
+ spawn: () => spawnPty(
22153
+ assistResumeArgs({ assistArgs, claudeSessionId }),
22154
+ cwd,
22155
+ session.id,
22156
+ idle ? { ASSIST_RESUME_IDLE: "1" } : void 0
22157
+ ),
22158
+ status: idle ? "waiting" : "running"
22159
+ };
22160
+ }
22161
+ return null;
22162
+ }
22163
+
22164
+ // src/commands/sessions/daemon/restartSession.ts
22165
+ function restartSession(session, clients, onStatusChange) {
22166
+ const plan2 = respawnPlan(session);
22167
+ if (!plan2) return false;
22168
+ const via = session.claudeSessionId ? `resume ${session.claudeSessionId}` : "fresh";
22169
+ const respawn = () => {
22170
+ respawnSession(session, plan2.spawn, plan2.status, clients, onStatusChange);
22171
+ daemonLog(`session ${session.id} restarted (${via}): ${session.name}`);
22172
+ };
22173
+ const pty2 = session.pty;
22174
+ if (pty2 && session.status !== "done") {
22175
+ daemonLog(
22176
+ `session ${session.id} restart requested: killing running process, will resume on exit`
22177
+ );
22178
+ session.pendingRestart = respawn;
22179
+ killPtyTree(pty2);
22180
+ } else {
22181
+ respawn();
22182
+ }
22183
+ return true;
22184
+ }
22185
+ function killPtyTree(pty2) {
22186
+ if (process.platform === "win32") {
22187
+ pty2.kill();
22188
+ return;
22189
+ }
22190
+ try {
22191
+ process.kill(-pty2.pid, "SIGHUP");
22192
+ } catch {
22193
+ try {
22194
+ pty2.kill();
22195
+ } catch {
22196
+ }
22197
+ }
22198
+ }
22199
+
21978
22200
  // src/commands/sessions/daemon/restoreBase.ts
21979
22201
  function restoreBase(id2, persisted) {
21980
22202
  return {
@@ -22005,12 +22227,6 @@ function errorSession(id2, persisted, error) {
22005
22227
  };
22006
22228
  }
22007
22229
 
22008
- // src/commands/sessions/daemon/assistResumeArgs.ts
22009
- function assistResumeArgs(persisted) {
22010
- const args = ["assist", ...persisted.assistArgs];
22011
- return persisted.claudeSessionId ? [...args, "--resume-session", persisted.claudeSessionId] : args;
22012
- }
22013
-
22014
22230
  // src/commands/sessions/daemon/runningSession.ts
22015
22231
  function runningSession(base, persisted, pty2) {
22016
22232
  return restoredSession(base, persisted, pty2, "running");
@@ -22194,146 +22410,12 @@ function resumeSession(id2, sessionId, cwd, name) {
22194
22410
  };
22195
22411
  }
22196
22412
 
22197
- // src/commands/sessions/daemon/watchActivity.ts
22198
- import { existsSync as existsSync53, mkdirSync as mkdirSync23, watch } from "fs";
22199
- import { dirname as dirname26 } from "path";
22200
-
22201
- // src/commands/sessions/daemon/applyReviewPause.ts
22202
- function applyReviewPause(session, activity2) {
22203
- const inReview = activity2.kind === "backlog" && activity2.phase !== void 0 && activity2.phase === activity2.totalPhases;
22204
- if (inReview && !session.reviewStarted) {
22205
- session.autoAdvance = false;
22206
- session.reviewStarted = true;
22207
- } else if (!inReview && session.reviewStarted) {
22208
- session.reviewStarted = false;
22209
- }
22210
- }
22211
-
22212
- // src/shared/db/getPhaseActiveMs.ts
22213
- import { and as and17, eq as eq36 } from "drizzle-orm";
22214
- async function getPhaseActiveMs(db, itemId, phaseIdx) {
22215
- const [row] = await db.select({ activeMs: phaseUsage.activeMs }).from(phaseUsage).where(
22216
- and17(eq36(phaseUsage.itemId, itemId), eq36(phaseUsage.phaseIdx, phaseIdx))
22217
- );
22218
- return row?.activeMs ?? 0;
22219
- }
22220
-
22221
- // src/commands/sessions/daemon/loadPhaseActiveMs.ts
22222
- async function loadPhaseActiveMs(itemId, phaseIdx) {
22223
- try {
22224
- if (!process.env.ASSIST_DATABASE_URL && !loadConfig().database.url)
22225
- return null;
22226
- return await getPhaseActiveMs(await getDb(), itemId, phaseIdx);
22227
- } catch (error) {
22228
- daemonLog(`phase-active-ms load failed: ${String(error)}`);
22229
- return null;
22230
- }
22231
- }
22232
-
22233
- // src/commands/sessions/daemon/seedRunningMsFromUsage.ts
22234
- async function seedRunningMsFromUsage(session, notify2) {
22235
- if (session.usageSeeded) return;
22236
- const phase = sessionBacklogPhase(session);
22237
- if (!phase) return;
22238
- session.usageSeeded = true;
22239
- const priorMs = await loadPhaseActiveMs(phase.itemId, phase.phaseIdx);
22240
- if (priorMs == null || priorMs <= session.runningMs) return;
22241
- session.runningMs = priorMs;
22242
- daemonLog(
22243
- `session ${session.id} seeded runningMs=${priorMs} from item ${phase.itemId} phase ${phase.phaseIdx} recorded usage`
22244
- );
22245
- notify2();
22246
- }
22247
-
22248
- // src/commands/sessions/daemon/watchActivity.ts
22249
- var DEBOUNCE_MS = 50;
22250
- function watchActivity(session, notify2) {
22251
- if (session.commandType !== "assist" || !session.cwd) return;
22252
- const path57 = activityPath(session.id);
22253
- const dir = dirname26(path57);
22254
- try {
22255
- mkdirSync23(dir, { recursive: true });
22256
- } catch {
22257
- return;
22258
- }
22259
- reconcileActivity(session.id, session.activity);
22260
- let timer = null;
22261
- const read = () => {
22262
- timer = null;
22263
- const activity2 = readActivity(path57);
22264
- if (!activity2) return;
22265
- session.activity = activity2;
22266
- if (activity2.claudeSessionId)
22267
- session.claudeSessionId = activity2.claudeSessionId;
22268
- applyReviewPause(session, activity2);
22269
- void seedRunningMsFromUsage(session, notify2);
22270
- notify2();
22271
- };
22272
- session.activityWatcher = watch(dir, (_event, filename) => {
22273
- if (filename && !path57.endsWith(filename)) return;
22274
- if (timer) clearTimeout(timer);
22275
- timer = setTimeout(read, DEBOUNCE_MS);
22276
- });
22277
- if (existsSync53(path57)) read();
22278
- }
22279
- function refreshActivity(session) {
22280
- if (session.commandType !== "assist" || !session.cwd) return;
22281
- const activity2 = readActivity(activityPath(session.id));
22282
- if (!activity2) return;
22283
- session.activity = activity2;
22284
- if (activity2.claudeSessionId)
22285
- session.claudeSessionId = activity2.claudeSessionId;
22286
- }
22287
-
22288
- // src/commands/sessions/daemon/wirePtyEvents.ts
22289
- var MAX_SCROLLBACK = 256 * 1024;
22290
- function appendScrollback(session, data) {
22291
- session.scrollback += data;
22292
- if (session.scrollback.length > MAX_SCROLLBACK) {
22293
- session.scrollback = session.scrollback.slice(-MAX_SCROLLBACK);
22294
- }
22295
- }
22296
- function wirePtyEvents(session, clients, onStatusChange) {
22297
- if (!session.pty) return;
22298
- session.pty.onData((data) => {
22299
- appendScrollback(session, data);
22300
- noteOutputForEscInterrupt(session, onStatusChange);
22301
- broadcast(clients, { type: "output", sessionId: session.id, data });
22302
- });
22303
- session.pty.onExit(({ exitCode }) => {
22304
- refreshActivity(session);
22305
- const failedResume = session.restored === true && exitCode !== 0 && session.scrollback.length === 0;
22306
- if (failedResume) {
22307
- session.error = `resume process exited with code ${exitCode} before producing any output`;
22308
- daemonLog(
22309
- `could not resume restored session "${session.name}" (id ${session.id}): ${session.error}`
22310
- );
22311
- onStatusChange(session, "error", exitCode);
22312
- return;
22313
- }
22314
- const priorStatus = session.status;
22315
- const unexpected = priorStatus === "waiting" || exitCode !== 0;
22316
- daemonLog(
22317
- `session ${session.id} ("${session.name}") pty exited with code ${exitCode} from status "${priorStatus}" (${session.scrollback.length} bytes of output) \u2014 ${unexpected ? "unexpected exit, process died mid-session" : "expected completion"}; marking done`
22318
- );
22319
- onStatusChange(session, "done", exitCode);
22320
- });
22321
- }
22322
-
22323
22413
  // src/commands/sessions/daemon/retrySession.ts
22324
22414
  function retrySession(session, clients, onStatusChange) {
22325
22415
  const respawn = respawnThunk(session);
22326
22416
  if (!respawn) return false;
22327
22417
  if (session.status !== "done") session.pty?.kill();
22328
- session.scrollback = "";
22329
- session.startedAt = Date.now();
22330
- session.runningMs = 0;
22331
- session.runningSince = null;
22332
- setStatus2(session, "running");
22333
- session.restored = void 0;
22334
- session.pty = respawn();
22335
- broadcast(clients, { type: "clear", sessionId: session.id });
22336
- wirePtyEvents(session, clients, onStatusChange);
22418
+ respawnSession(session, respawn, "running", clients, onStatusChange);
22337
22419
  daemonLog(`session ${session.id} retried: ${session.name}`);
22338
22420
  return true;
22339
22421
  }
@@ -23116,6 +23198,11 @@ var SessionManager = class {
23116
23198
  const s = this.sessions.get(id2);
23117
23199
  if (s && retrySession(s, this.clients, this.onStatusChange)) this.notify();
23118
23200
  }
23201
+ restart(id2) {
23202
+ const s = this.sessions.get(id2);
23203
+ if (s && restartSession(s, this.clients, this.onStatusChange))
23204
+ this.notify();
23205
+ }
23119
23206
  dismissSession = (id2) => {
23120
23207
  if (dismissSession(this.sessions, id2)) this.notify();
23121
23208
  };
@@ -23534,6 +23621,7 @@ var messageHandlers = {
23534
23621
  (_client, m, d) => m.resizeSession(d.sessionId, d.cols, d.rows)
23535
23622
  ),
23536
23623
  retry: routed((_client, m, d) => m.retrySession(d.sessionId)),
23624
+ restart: routed((_client, m, d) => m.restart(d.sessionId)),
23537
23625
  dismiss: routed((_client, m, d) => m.dismissSession(d.sessionId)),
23538
23626
  "set-autorun": routed(
23539
23627
  (_client, m, d) => m.setAutoRun(d.sessionId, d.enabled)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@staff0rd/assist",
3
- "version": "0.359.0",
3
+ "version": "0.360.1",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "bin": {