@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.
- package/README.md +1 -0
- package/dist/index.js +108 -58
- 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.
|
|
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
|
|
20048
|
+
import { z as z6 } from "zod";
|
|
19961
20049
|
var ACTIVE_FILE = "active.json";
|
|
19962
|
-
var activeSelectionSchema =
|
|
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
|
|
20759
|
+
import { createInterface as createInterface6 } from "readline";
|
|
20721
20760
|
function logChildStream(stream, label2) {
|
|
20722
20761
|
if (!stream) return;
|
|
20723
|
-
const lines =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|