@staff0rd/assist 0.359.0 → 0.360.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.359.0",
9
+ version: "0.360.0",
10
10
  type: "module",
11
11
  main: "dist/index.js",
12
12
  bin: {
@@ -21975,6 +21975,187 @@ function recordSessionUsage(sessions, claudeSessionId, totalIn, totalOut) {
21975
21975
  }
21976
21976
  }
21977
21977
 
21978
+ // src/commands/sessions/daemon/assistResumeArgs.ts
21979
+ function assistResumeArgs(session) {
21980
+ const args = ["assist", ...session.assistArgs];
21981
+ return session.claudeSessionId ? [...args, "--resume-session", session.claudeSessionId] : args;
21982
+ }
21983
+
21984
+ // src/commands/sessions/daemon/watchActivity.ts
21985
+ import { existsSync as existsSync53, mkdirSync as mkdirSync23, watch } from "fs";
21986
+ import { dirname as dirname26 } from "path";
21987
+
21988
+ // src/commands/sessions/daemon/applyReviewPause.ts
21989
+ function applyReviewPause(session, activity2) {
21990
+ const inReview = activity2.kind === "backlog" && activity2.phase !== void 0 && activity2.phase === activity2.totalPhases;
21991
+ if (inReview && !session.reviewStarted) {
21992
+ session.autoAdvance = false;
21993
+ session.reviewStarted = true;
21994
+ } else if (!inReview && session.reviewStarted) {
21995
+ session.reviewStarted = false;
21996
+ }
21997
+ }
21998
+
21999
+ // src/shared/db/getPhaseActiveMs.ts
22000
+ import { and as and17, eq as eq36 } from "drizzle-orm";
22001
+ async function getPhaseActiveMs(db, itemId, phaseIdx) {
22002
+ const [row] = await db.select({ activeMs: phaseUsage.activeMs }).from(phaseUsage).where(
22003
+ and17(eq36(phaseUsage.itemId, itemId), eq36(phaseUsage.phaseIdx, phaseIdx))
22004
+ );
22005
+ return row?.activeMs ?? 0;
22006
+ }
22007
+
22008
+ // src/commands/sessions/daemon/loadPhaseActiveMs.ts
22009
+ async function loadPhaseActiveMs(itemId, phaseIdx) {
22010
+ try {
22011
+ if (!process.env.ASSIST_DATABASE_URL && !loadConfig().database.url)
22012
+ return null;
22013
+ return await getPhaseActiveMs(await getDb(), itemId, phaseIdx);
22014
+ } catch (error) {
22015
+ daemonLog(`phase-active-ms load failed: ${String(error)}`);
22016
+ return null;
22017
+ }
22018
+ }
22019
+
22020
+ // src/commands/sessions/daemon/seedRunningMsFromUsage.ts
22021
+ async function seedRunningMsFromUsage(session, notify2) {
22022
+ if (session.usageSeeded) return;
22023
+ const phase = sessionBacklogPhase(session);
22024
+ if (!phase) return;
22025
+ session.usageSeeded = true;
22026
+ const priorMs = await loadPhaseActiveMs(phase.itemId, phase.phaseIdx);
22027
+ if (priorMs == null || priorMs <= session.runningMs) return;
22028
+ session.runningMs = priorMs;
22029
+ daemonLog(
22030
+ `session ${session.id} seeded runningMs=${priorMs} from item ${phase.itemId} phase ${phase.phaseIdx} recorded usage`
22031
+ );
22032
+ notify2();
22033
+ }
22034
+
22035
+ // src/commands/sessions/daemon/watchActivity.ts
22036
+ var DEBOUNCE_MS = 50;
22037
+ function watchActivity(session, notify2) {
22038
+ if (session.commandType !== "assist" || !session.cwd) return;
22039
+ const path57 = activityPath(session.id);
22040
+ const dir = dirname26(path57);
22041
+ try {
22042
+ mkdirSync23(dir, { recursive: true });
22043
+ } catch {
22044
+ return;
22045
+ }
22046
+ reconcileActivity(session.id, session.activity);
22047
+ let timer = null;
22048
+ const read = () => {
22049
+ timer = null;
22050
+ const activity2 = readActivity(path57);
22051
+ if (!activity2) return;
22052
+ session.activity = activity2;
22053
+ if (activity2.claudeSessionId)
22054
+ session.claudeSessionId = activity2.claudeSessionId;
22055
+ applyReviewPause(session, activity2);
22056
+ void seedRunningMsFromUsage(session, notify2);
22057
+ notify2();
22058
+ };
22059
+ session.activityWatcher = watch(dir, (_event, filename) => {
22060
+ if (filename && !path57.endsWith(filename)) return;
22061
+ if (timer) clearTimeout(timer);
22062
+ timer = setTimeout(read, DEBOUNCE_MS);
22063
+ });
22064
+ if (existsSync53(path57)) read();
22065
+ }
22066
+ function refreshActivity(session) {
22067
+ if (session.commandType !== "assist" || !session.cwd) return;
22068
+ const activity2 = readActivity(activityPath(session.id));
22069
+ if (!activity2) return;
22070
+ session.activity = activity2;
22071
+ if (activity2.claudeSessionId)
22072
+ session.claudeSessionId = activity2.claudeSessionId;
22073
+ }
22074
+
22075
+ // src/commands/sessions/daemon/wirePtyEvents.ts
22076
+ var MAX_SCROLLBACK = 256 * 1024;
22077
+ function appendScrollback(session, data) {
22078
+ session.scrollback += data;
22079
+ if (session.scrollback.length > MAX_SCROLLBACK) {
22080
+ session.scrollback = session.scrollback.slice(-MAX_SCROLLBACK);
22081
+ }
22082
+ }
22083
+ function wirePtyEvents(session, clients, onStatusChange) {
22084
+ if (!session.pty) return;
22085
+ session.pty.onData((data) => {
22086
+ appendScrollback(session, data);
22087
+ noteOutputForEscInterrupt(session, onStatusChange);
22088
+ broadcast(clients, { type: "output", sessionId: session.id, data });
22089
+ });
22090
+ session.pty.onExit(({ exitCode }) => {
22091
+ refreshActivity(session);
22092
+ const failedResume = session.restored === true && exitCode !== 0 && session.scrollback.length === 0;
22093
+ if (failedResume) {
22094
+ session.error = `resume process exited with code ${exitCode} before producing any output`;
22095
+ daemonLog(
22096
+ `could not resume restored session "${session.name}" (id ${session.id}): ${session.error}`
22097
+ );
22098
+ onStatusChange(session, "error", exitCode);
22099
+ return;
22100
+ }
22101
+ const priorStatus = session.status;
22102
+ const unexpected = priorStatus === "waiting" || exitCode !== 0;
22103
+ daemonLog(
22104
+ `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`
22105
+ );
22106
+ onStatusChange(session, "done", exitCode);
22107
+ });
22108
+ }
22109
+
22110
+ // src/commands/sessions/daemon/respawnSession.ts
22111
+ function respawnSession(session, respawn, status2, clients, onStatusChange) {
22112
+ session.scrollback = "";
22113
+ session.startedAt = Date.now();
22114
+ session.runningMs = 0;
22115
+ session.runningSince = null;
22116
+ setStatus2(session, status2);
22117
+ session.restored = void 0;
22118
+ session.pty = respawn();
22119
+ broadcast(clients, { type: "clear", sessionId: session.id });
22120
+ wirePtyEvents(session, clients, onStatusChange);
22121
+ }
22122
+
22123
+ // src/commands/sessions/daemon/restartSession.ts
22124
+ function restartSession(session, clients, onStatusChange) {
22125
+ const plan2 = respawnPlan(session);
22126
+ if (!plan2) return false;
22127
+ session.pty?.kill();
22128
+ respawnSession(session, plan2.spawn, plan2.status, clients, onStatusChange);
22129
+ const via = session.claudeSessionId ? `resume ${session.claudeSessionId}` : "fresh";
22130
+ daemonLog(`session ${session.id} restarted (${via}): ${session.name}`);
22131
+ return true;
22132
+ }
22133
+ function respawnPlan(session) {
22134
+ const { commandType, claudeSessionId, cwd, assistArgs } = session;
22135
+ if (commandType === "claude")
22136
+ return claudeSessionId ? {
22137
+ spawn: () => spawnClaude2({
22138
+ resumeSessionId: claudeSessionId,
22139
+ cwd,
22140
+ sessionId: session.id
22141
+ }),
22142
+ status: "waiting"
22143
+ } : null;
22144
+ if (commandType === "assist" && assistArgs) {
22145
+ const idle = session.status === "waiting";
22146
+ return {
22147
+ spawn: () => spawnPty(
22148
+ assistResumeArgs({ assistArgs, claudeSessionId }),
22149
+ cwd,
22150
+ session.id,
22151
+ idle ? { ASSIST_RESUME_IDLE: "1" } : void 0
22152
+ ),
22153
+ status: idle ? "waiting" : "running"
22154
+ };
22155
+ }
22156
+ return null;
22157
+ }
22158
+
21978
22159
  // src/commands/sessions/daemon/restoreBase.ts
21979
22160
  function restoreBase(id2, persisted) {
21980
22161
  return {
@@ -22005,12 +22186,6 @@ function errorSession(id2, persisted, error) {
22005
22186
  };
22006
22187
  }
22007
22188
 
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
22189
  // src/commands/sessions/daemon/runningSession.ts
22015
22190
  function runningSession(base, persisted, pty2) {
22016
22191
  return restoredSession(base, persisted, pty2, "running");
@@ -22194,146 +22369,12 @@ function resumeSession(id2, sessionId, cwd, name) {
22194
22369
  };
22195
22370
  }
22196
22371
 
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
22372
  // src/commands/sessions/daemon/retrySession.ts
22324
22373
  function retrySession(session, clients, onStatusChange) {
22325
22374
  const respawn = respawnThunk(session);
22326
22375
  if (!respawn) return false;
22327
22376
  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);
22377
+ respawnSession(session, respawn, "running", clients, onStatusChange);
22337
22378
  daemonLog(`session ${session.id} retried: ${session.name}`);
22338
22379
  return true;
22339
22380
  }
@@ -23116,6 +23157,11 @@ var SessionManager = class {
23116
23157
  const s = this.sessions.get(id2);
23117
23158
  if (s && retrySession(s, this.clients, this.onStatusChange)) this.notify();
23118
23159
  }
23160
+ restart(id2) {
23161
+ const s = this.sessions.get(id2);
23162
+ if (s && restartSession(s, this.clients, this.onStatusChange))
23163
+ this.notify();
23164
+ }
23119
23165
  dismissSession = (id2) => {
23120
23166
  if (dismissSession(this.sessions, id2)) this.notify();
23121
23167
  };
@@ -23534,6 +23580,7 @@ var messageHandlers = {
23534
23580
  (_client, m, d) => m.resizeSession(d.sessionId, d.cols, d.rows)
23535
23581
  ),
23536
23582
  retry: routed((_client, m, d) => m.retrySession(d.sessionId)),
23583
+ restart: routed((_client, m, d) => m.restart(d.sessionId)),
23537
23584
  dismiss: routed((_client, m, d) => m.dismissSession(d.sessionId)),
23538
23585
  "set-autorun": routed(
23539
23586
  (_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.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "bin": {