@staff0rd/assist 0.318.9 → 0.319.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.
Files changed (3) hide show
  1. package/README.md +1 -0
  2. package/dist/index.js +108 -58
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -266,6 +266,7 @@ The first backlog command in a repository that still has a local `.assist/backlo
266
266
  - `assist daemon status` - Show sessions daemon status, live sessions, and any stray daemon processes or stolen socket
267
267
  - `assist daemon stop` - Stop the sessions daemon; running claude sessions resume on next start
268
268
  - `assist daemon restart` - Restart the sessions daemon, resuming previously running claude sessions
269
+ - `assist daemon drain` - Remove all sessions from the local daemon for a clean slate (does not affect the Windows daemon)
269
270
 
270
271
  Web sessions are owned by a long-lived daemon process, not the web server: the server is a thin client that relays WebSocket traffic to the daemon over a local IPC socket (unix domain socket at `~/.assist/daemon/daemon.sock`; named pipe `\\.\pipe\assist-sessions-daemon` on Windows). Restarting the web server leaves sessions running with scrollback intact. The daemon logs to `~/.assist/daemon/daemon.log` (timestamped lines tagged with the daemon's pid, including why it spawned and which sessions it restored) and auto-exits once no sessions remain and no client has been connected for 60 seconds (it is respawned on demand by the web server). Daemon spawning is arbitrated by an `O_EXCL` lockfile so racing clients start at most one daemon; sessions are only restored after the daemon owns the IPC socket, and a daemon that loses ownership of `daemon.pid` shuts down its sessions and exits rather than running orphaned.
271
272
 
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.318.9",
9
+ version: "0.319.0",
10
10
  type: "module",
11
11
  main: "dist/index.js",
12
12
  bin: {
@@ -19934,6 +19934,94 @@ function reportStrays(pids) {
19934
19934
  );
19935
19935
  }
19936
19936
 
19937
+ // src/commands/sessions/daemon/drainDaemon.ts
19938
+ import { createInterface as createInterface5 } from "readline";
19939
+
19940
+ // src/commands/sessions/daemon/loadPersistedSessions.ts
19941
+ import { z as z5 } from "zod";
19942
+ var SESSIONS_FILE = "sessions.json";
19943
+ var persistedSessionSchema = z5.object({
19944
+ name: z5.string(),
19945
+ commandType: z5.enum(["claude", "run", "assist"]),
19946
+ cwd: z5.string(),
19947
+ startedAt: z5.number(),
19948
+ runningMs: z5.number().optional(),
19949
+ claudeSessionId: z5.string().optional(),
19950
+ runName: z5.string().optional(),
19951
+ runArgs: z5.array(z5.string()).optional(),
19952
+ assistArgs: z5.array(z5.string()).optional(),
19953
+ activity: activitySchema.optional()
19954
+ });
19955
+ function loadPersistedSessions() {
19956
+ const data = loadJson(SESSIONS_FILE);
19957
+ if (!Array.isArray(data)) return [];
19958
+ return data.flatMap((entry) => {
19959
+ const parsed = persistedSessionSchema.safeParse(entry);
19960
+ return parsed.success ? [parsed.data] : [];
19961
+ });
19962
+ }
19963
+ function savePersistedSessions(sessions) {
19964
+ saveJson(SESSIONS_FILE, sessions);
19965
+ }
19966
+ function persistLiveSessions(sessions) {
19967
+ savePersistedSessions(
19968
+ [...sessions.values()].filter((s) => s.pty && s.status !== "done").map(toPersistedSession)
19969
+ );
19970
+ }
19971
+ function toPersistedSession(session) {
19972
+ return {
19973
+ name: session.name,
19974
+ commandType: session.commandType,
19975
+ cwd: session.cwd ?? process.cwd(),
19976
+ startedAt: session.startedAt,
19977
+ runningMs: accumulatedRunningMs(session),
19978
+ claudeSessionId: session.claudeSessionId,
19979
+ runName: session.runName,
19980
+ runArgs: session.runArgs,
19981
+ assistArgs: session.assistArgs,
19982
+ activity: session.activity
19983
+ };
19984
+ }
19985
+ function accumulatedRunningMs(session) {
19986
+ return session.runningSince != null ? session.runningMs + Date.now() - session.runningSince : session.runningMs;
19987
+ }
19988
+
19989
+ // src/commands/sessions/daemon/drainDaemon.ts
19990
+ var DRAIN_TIMEOUT_MS = 5e3;
19991
+ async function drainDaemon() {
19992
+ let socket;
19993
+ try {
19994
+ socket = await connectToDaemon();
19995
+ } catch {
19996
+ savePersistedSessions([]);
19997
+ console.log("Sessions daemon is not running; cleared persisted sessions");
19998
+ return;
19999
+ }
20000
+ const count6 = await requestDrain(socket);
20001
+ socket.destroy();
20002
+ console.log(`Drained ${count6} session(s)`);
20003
+ }
20004
+ function requestDrain(socket) {
20005
+ socket.write(`${JSON.stringify({ type: "drain" })}
20006
+ `);
20007
+ return new Promise((resolve17) => {
20008
+ const timer = setTimeout(() => resolve17(0), DRAIN_TIMEOUT_MS);
20009
+ const lines = createInterface5({ input: socket });
20010
+ lines.on("error", () => {
20011
+ });
20012
+ lines.on("line", (line) => {
20013
+ try {
20014
+ const data = JSON.parse(line);
20015
+ if (data.type === "drained") {
20016
+ clearTimeout(timer);
20017
+ resolve17(data.count ?? 0);
20018
+ }
20019
+ } catch {
20020
+ }
20021
+ });
20022
+ });
20023
+ }
20024
+
19937
20025
  // src/commands/sessions/daemon/runDaemon.ts
19938
20026
  import { mkdirSync as mkdirSync23 } from "fs";
19939
20027
 
@@ -19957,9 +20045,9 @@ function daemonLog(message) {
19957
20045
  }
19958
20046
 
19959
20047
  // src/commands/sessions/daemon/loadActiveSelection.ts
19960
- import { z as z5 } from "zod";
20048
+ import { z as z6 } from "zod";
19961
20049
  var ACTIVE_FILE = "active.json";
19962
- var activeSelectionSchema = z5.record(z5.string(), z5.string());
20050
+ var activeSelectionSchema = z6.record(z6.string(), z6.string());
19963
20051
  function loadActiveSelection() {
19964
20052
  const parsed = activeSelectionSchema.safeParse(
19965
20053
  loadJson(ACTIVE_FILE)
@@ -20005,55 +20093,6 @@ function broadcast(clients, msg) {
20005
20093
  }
20006
20094
  }
20007
20095
 
20008
- // src/commands/sessions/daemon/loadPersistedSessions.ts
20009
- import { z as z6 } from "zod";
20010
- var SESSIONS_FILE = "sessions.json";
20011
- var persistedSessionSchema = z6.object({
20012
- name: z6.string(),
20013
- commandType: z6.enum(["claude", "run", "assist"]),
20014
- cwd: z6.string(),
20015
- startedAt: z6.number(),
20016
- runningMs: z6.number().optional(),
20017
- claudeSessionId: z6.string().optional(),
20018
- runName: z6.string().optional(),
20019
- runArgs: z6.array(z6.string()).optional(),
20020
- assistArgs: z6.array(z6.string()).optional(),
20021
- activity: activitySchema.optional()
20022
- });
20023
- function loadPersistedSessions() {
20024
- const data = loadJson(SESSIONS_FILE);
20025
- if (!Array.isArray(data)) return [];
20026
- return data.flatMap((entry) => {
20027
- const parsed = persistedSessionSchema.safeParse(entry);
20028
- return parsed.success ? [parsed.data] : [];
20029
- });
20030
- }
20031
- function savePersistedSessions(sessions) {
20032
- saveJson(SESSIONS_FILE, sessions);
20033
- }
20034
- function persistLiveSessions(sessions) {
20035
- savePersistedSessions(
20036
- [...sessions.values()].filter((s) => s.pty && s.status !== "done").map(toPersistedSession)
20037
- );
20038
- }
20039
- function toPersistedSession(session) {
20040
- return {
20041
- name: session.name,
20042
- commandType: session.commandType,
20043
- cwd: session.cwd ?? process.cwd(),
20044
- startedAt: session.startedAt,
20045
- runningMs: accumulatedRunningMs(session),
20046
- claudeSessionId: session.claudeSessionId,
20047
- runName: session.runName,
20048
- runArgs: session.runArgs,
20049
- assistArgs: session.assistArgs,
20050
- activity: session.activity
20051
- };
20052
- }
20053
- function accumulatedRunningMs(session) {
20054
- return session.runningSince != null ? session.runningMs + Date.now() - session.runningSince : session.runningMs;
20055
- }
20056
-
20057
20096
  // src/commands/sessions/daemon/toSessionInfo.ts
20058
20097
  function toSessionInfo({
20059
20098
  id: id2,
@@ -20717,10 +20756,10 @@ async function isWindowsDaemonRunning() {
20717
20756
  import { spawn as spawn10 } from "child_process";
20718
20757
 
20719
20758
  // src/commands/sessions/daemon/logChildStream.ts
20720
- import { createInterface as createInterface5 } from "readline";
20759
+ import { createInterface as createInterface6 } from "readline";
20721
20760
  function logChildStream(stream, label2) {
20722
20761
  if (!stream) return;
20723
- const lines = createInterface5({ input: stream });
20762
+ const lines = createInterface6({ input: stream });
20724
20763
  lines.on("line", (line) => daemonLog(`[${label2}] ${line}`));
20725
20764
  lines.on("error", () => {
20726
20765
  });
@@ -20992,7 +21031,7 @@ function isWindowsIo(data) {
20992
21031
  }
20993
21032
 
20994
21033
  // src/commands/sessions/daemon/WindowsConnection.ts
20995
- import { createInterface as createInterface6 } from "readline";
21034
+ import { createInterface as createInterface7 } from "readline";
20996
21035
 
20997
21036
  // src/commands/sessions/daemon/LaunchCircuitBreaker.ts
20998
21037
  var MAX_FAILURES = 3;
@@ -21072,7 +21111,7 @@ var WindowsConnection = class {
21072
21111
  return socket;
21073
21112
  }
21074
21113
  wire(socket) {
21075
- const lines = createInterface6({ input: socket });
21114
+ const lines = createInterface7({ input: socket });
21076
21115
  lines.on("error", () => {
21077
21116
  });
21078
21117
  lines.on("line", (line) => this.deps.onLine(line));
@@ -21250,6 +21289,12 @@ function dismissSession(sessions, id2) {
21250
21289
  sessions.delete(id2);
21251
21290
  return true;
21252
21291
  }
21292
+ function drainSessions(sessions, onDrained) {
21293
+ const ids = [...sessions.keys()];
21294
+ for (const id2 of ids) dismissSession(sessions, id2);
21295
+ onDrained();
21296
+ return ids.length;
21297
+ }
21253
21298
 
21254
21299
  // src/commands/sessions/daemon/SessionManager.ts
21255
21300
  var SessionManager = class {
@@ -21281,6 +21326,7 @@ var SessionManager = class {
21281
21326
  restore() {
21282
21327
  return restoreAll(this.spawnWith, this.sessions);
21283
21328
  }
21329
+ drain = () => drainSessions(this.sessions, this.notify);
21284
21330
  add(session) {
21285
21331
  this.wire(session);
21286
21332
  watchActivity(session, this.notify);
@@ -21349,7 +21395,7 @@ import { unlinkSync as unlinkSync19 } from "fs";
21349
21395
  import * as net3 from "net";
21350
21396
 
21351
21397
  // src/commands/sessions/daemon/handleConnection.ts
21352
- import { createInterface as createInterface7 } from "readline";
21398
+ import { createInterface as createInterface8 } from "readline";
21353
21399
 
21354
21400
  // src/commands/sessions/shared/discoverSessions.ts
21355
21401
  import * as fs30 from "fs";
@@ -21689,6 +21735,7 @@ var messageHandlers = {
21689
21735
  history: handleHistory,
21690
21736
  "fetch-transcript": handleFetchTranscript,
21691
21737
  shutdown: handleShutdown,
21738
+ drain: (client, m) => sendTo(client, { type: "drained", count: m.drain() }),
21692
21739
  limits: (_client, m, d) => m.clients.updateLimits(d.rateLimits),
21693
21740
  input: routed(
21694
21741
  (_client, m, d) => m.writeToSession(d.sessionId, d.data)
@@ -21725,7 +21772,7 @@ function handleConnection(socket, manager) {
21725
21772
  };
21726
21773
  manager.addClient(client);
21727
21774
  manager.clients.greet(client);
21728
- const lines = createInterface7({ input: socket });
21775
+ const lines = createInterface8({ input: socket });
21729
21776
  lines.on("error", () => {
21730
21777
  });
21731
21778
  lines.on("line", (line) => {
@@ -21879,6 +21926,9 @@ function registerDaemon(program2) {
21879
21926
  cmd.command("restart").description(
21880
21927
  "Restart the sessions daemon, resuming previously running claude sessions"
21881
21928
  ).action(restartDaemon);
21929
+ cmd.command("drain").description(
21930
+ "Remove all sessions from the local daemon for a clean slate (does not affect the Windows daemon)"
21931
+ ).action(drainDaemon);
21882
21932
  }
21883
21933
 
21884
21934
  // src/commands/sessions/summarise/index.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@staff0rd/assist",
3
- "version": "0.318.9",
3
+ "version": "0.319.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "bin": {