@staff0rd/assist 0.284.1 → 0.286.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 +4 -0
- package/dist/commands/sessions/web/bundle.js +36 -36
- package/dist/index.js +445 -98
- package/package.json +1 -1
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.286.0",
|
|
10
10
|
type: "module",
|
|
11
11
|
main: "dist/index.js",
|
|
12
12
|
bin: {
|
|
@@ -275,6 +275,14 @@ var assistConfigSchema = z2.strictObject({
|
|
|
275
275
|
screenshot: z2.strictObject({
|
|
276
276
|
outputDir: z2.string().default("./screenshots")
|
|
277
277
|
}).default({ outputDir: "./screenshots" }),
|
|
278
|
+
sessions: z2.strictObject({
|
|
279
|
+
// why: Windows .claude/projects root as seen from WSL (e.g. /mnt/c/Users/<user>/.claude/projects); when set its transcripts are discovered and tagged windows-origin
|
|
280
|
+
windowsProjectsRoot: z2.string().optional(),
|
|
281
|
+
// why: host the WSL daemon dials to reach the native Windows daemon over TCP (WSL can't use the Windows named pipe); defaults to 127.0.0.1 (WSL2 mirrored networking). NAT-mode users set the Windows host IP.
|
|
282
|
+
windowsDaemonHost: z2.string().optional(),
|
|
283
|
+
// why: TCP port the native Windows daemon listens on for the WSL bridge; defaults to 51764
|
|
284
|
+
windowsDaemonPort: z2.number().optional()
|
|
285
|
+
}).optional(),
|
|
278
286
|
backlog: z2.strictObject({
|
|
279
287
|
databaseUrl: z2.string().optional()
|
|
280
288
|
}).default({}),
|
|
@@ -12069,10 +12077,10 @@ async function getAccessToken(apiKey) {
|
|
|
12069
12077
|
}
|
|
12070
12078
|
});
|
|
12071
12079
|
if (!response.ok) {
|
|
12072
|
-
const
|
|
12080
|
+
const errorText2 = await response.text();
|
|
12073
12081
|
throw new Error(
|
|
12074
12082
|
`Failed to get access token: ${response.status} ${response.statusText}
|
|
12075
|
-
${
|
|
12083
|
+
${errorText2}`
|
|
12076
12084
|
);
|
|
12077
12085
|
}
|
|
12078
12086
|
const tokenData = await response.json();
|
|
@@ -18100,11 +18108,11 @@ function toSessionInfo({
|
|
|
18100
18108
|
}
|
|
18101
18109
|
|
|
18102
18110
|
// src/commands/sessions/daemon/broadcastSessions.ts
|
|
18103
|
-
function broadcastSessions(sessions, clients) {
|
|
18111
|
+
function broadcastSessions(sessions, clients, windowsSessions = []) {
|
|
18104
18112
|
persistLiveSessions(sessions);
|
|
18105
18113
|
broadcast(clients, {
|
|
18106
18114
|
type: "sessions",
|
|
18107
|
-
sessions: [...sessions.values()].map(toSessionInfo)
|
|
18115
|
+
sessions: [...sessions.values()].map(toSessionInfo).concat(windowsSessions)
|
|
18108
18116
|
});
|
|
18109
18117
|
}
|
|
18110
18118
|
|
|
@@ -18228,9 +18236,10 @@ function replayScrollback(sessions, client) {
|
|
|
18228
18236
|
}
|
|
18229
18237
|
|
|
18230
18238
|
// src/commands/sessions/daemon/greetClient.ts
|
|
18231
|
-
function greetClient(client, sessions, list4) {
|
|
18239
|
+
function greetClient(client, sessions, list4, windowsProxy) {
|
|
18232
18240
|
sendTo(client, { type: "sessions", sessions: list4() });
|
|
18233
18241
|
replayScrollback(sessions, client);
|
|
18242
|
+
windowsProxy.replayScrollback(client);
|
|
18234
18243
|
}
|
|
18235
18244
|
|
|
18236
18245
|
// src/commands/sessions/daemon/shouldAutoDismiss.ts
|
|
@@ -18470,6 +18479,308 @@ function shutdownSessions(sessions) {
|
|
|
18470
18479
|
}
|
|
18471
18480
|
}
|
|
18472
18481
|
|
|
18482
|
+
// src/commands/sessions/daemon/connectToWindowsDaemon.ts
|
|
18483
|
+
import * as net2 from "net";
|
|
18484
|
+
|
|
18485
|
+
// src/commands/sessions/daemon/windowsDaemonPort.ts
|
|
18486
|
+
var DEFAULT_PORT = 51764;
|
|
18487
|
+
var DEFAULT_HOST = "127.0.0.1";
|
|
18488
|
+
function windowsDaemonPort() {
|
|
18489
|
+
return loadConfig().sessions?.windowsDaemonPort ?? DEFAULT_PORT;
|
|
18490
|
+
}
|
|
18491
|
+
function windowsDaemonHost() {
|
|
18492
|
+
return loadConfig().sessions?.windowsDaemonHost ?? DEFAULT_HOST;
|
|
18493
|
+
}
|
|
18494
|
+
|
|
18495
|
+
// src/commands/sessions/daemon/connectToWindowsDaemon.ts
|
|
18496
|
+
function connectToWindowsDaemon() {
|
|
18497
|
+
return new Promise((resolve16, reject) => {
|
|
18498
|
+
const socket = net2.connect(windowsDaemonPort(), windowsDaemonHost());
|
|
18499
|
+
socket.once("connect", () => resolve16(socket));
|
|
18500
|
+
socket.once("error", reject);
|
|
18501
|
+
});
|
|
18502
|
+
}
|
|
18503
|
+
async function isWindowsDaemonRunning() {
|
|
18504
|
+
try {
|
|
18505
|
+
(await connectToWindowsDaemon()).destroy();
|
|
18506
|
+
return true;
|
|
18507
|
+
} catch {
|
|
18508
|
+
return false;
|
|
18509
|
+
}
|
|
18510
|
+
}
|
|
18511
|
+
|
|
18512
|
+
// src/commands/sessions/daemon/ensureWindowsDaemonRunning.ts
|
|
18513
|
+
import { spawn as spawn10 } from "child_process";
|
|
18514
|
+
var SPAWN_TIMEOUT_MS2 = 3e4;
|
|
18515
|
+
var RETRY_DELAY_MS2 = 300;
|
|
18516
|
+
async function ensureWindowsDaemonRunning() {
|
|
18517
|
+
if (await isWindowsDaemonRunning()) return;
|
|
18518
|
+
launchWindowsDaemon();
|
|
18519
|
+
await waitForWindowsDaemon();
|
|
18520
|
+
}
|
|
18521
|
+
function launchWindowsDaemon() {
|
|
18522
|
+
const child = spawn10(
|
|
18523
|
+
"cmd.exe",
|
|
18524
|
+
["/c", "start", "", "assist", "daemon", "run"],
|
|
18525
|
+
{ detached: true, stdio: "ignore" }
|
|
18526
|
+
);
|
|
18527
|
+
child.on(
|
|
18528
|
+
"error",
|
|
18529
|
+
(e) => daemonLog(`failed to launch windows daemon: ${e.message}`)
|
|
18530
|
+
);
|
|
18531
|
+
child.unref();
|
|
18532
|
+
}
|
|
18533
|
+
async function waitForWindowsDaemon() {
|
|
18534
|
+
const deadline = Date.now() + SPAWN_TIMEOUT_MS2;
|
|
18535
|
+
while (Date.now() < deadline) {
|
|
18536
|
+
await delay2(RETRY_DELAY_MS2);
|
|
18537
|
+
if (await isWindowsDaemonRunning()) return;
|
|
18538
|
+
}
|
|
18539
|
+
throw new Error(
|
|
18540
|
+
"Windows daemon did not start; is assist installed on the Windows host?"
|
|
18541
|
+
);
|
|
18542
|
+
}
|
|
18543
|
+
function delay2(ms) {
|
|
18544
|
+
return new Promise((resolve16) => setTimeout(resolve16, ms));
|
|
18545
|
+
}
|
|
18546
|
+
|
|
18547
|
+
// src/commands/sessions/daemon/buildHello.ts
|
|
18548
|
+
var ASSIST_VERSION = package_default.version;
|
|
18549
|
+
function buildHello() {
|
|
18550
|
+
return { type: "hello", version: ASSIST_VERSION };
|
|
18551
|
+
}
|
|
18552
|
+
function isHello(msg) {
|
|
18553
|
+
return msg.type === "hello" && typeof msg.version === "string";
|
|
18554
|
+
}
|
|
18555
|
+
function versionsMatch(a, b) {
|
|
18556
|
+
return a === b;
|
|
18557
|
+
}
|
|
18558
|
+
|
|
18559
|
+
// src/commands/sessions/daemon/toWindowsSessionId.ts
|
|
18560
|
+
var PREFIX = "w-";
|
|
18561
|
+
function toWindowsSessionId(id) {
|
|
18562
|
+
return `${PREFIX}${id}`;
|
|
18563
|
+
}
|
|
18564
|
+
function isWindowsSessionId(id) {
|
|
18565
|
+
return id.startsWith(PREFIX);
|
|
18566
|
+
}
|
|
18567
|
+
function stripWindowsSessionId(id) {
|
|
18568
|
+
return id.startsWith(PREFIX) ? id.slice(PREFIX.length) : id;
|
|
18569
|
+
}
|
|
18570
|
+
|
|
18571
|
+
// src/commands/sessions/daemon/WindowsProxyState.ts
|
|
18572
|
+
var MAX_SCROLLBACK2 = 256 * 1024;
|
|
18573
|
+
function createState(broadcast2, onSessionsChanged) {
|
|
18574
|
+
return {
|
|
18575
|
+
windowsSessions: [],
|
|
18576
|
+
scrollback: /* @__PURE__ */ new Map(),
|
|
18577
|
+
pendingCreators: [],
|
|
18578
|
+
broadcast: broadcast2,
|
|
18579
|
+
onSessionsChanged
|
|
18580
|
+
};
|
|
18581
|
+
}
|
|
18582
|
+
function resetState(state) {
|
|
18583
|
+
state.windowsSessions = [];
|
|
18584
|
+
state.scrollback.clear();
|
|
18585
|
+
state.pendingCreators = [];
|
|
18586
|
+
}
|
|
18587
|
+
function replayScrollback2(state, client) {
|
|
18588
|
+
for (const [sessionId, data] of state.scrollback)
|
|
18589
|
+
if (data) sendTo(client, { type: "output", sessionId, data });
|
|
18590
|
+
}
|
|
18591
|
+
function appendScrollback2(state, sessionId, data) {
|
|
18592
|
+
const next3 = (state.scrollback.get(sessionId) ?? "") + data;
|
|
18593
|
+
state.scrollback.set(
|
|
18594
|
+
sessionId,
|
|
18595
|
+
next3.length > MAX_SCROLLBACK2 ? next3.slice(-MAX_SCROLLBACK2) : next3
|
|
18596
|
+
);
|
|
18597
|
+
}
|
|
18598
|
+
|
|
18599
|
+
// src/commands/sessions/daemon/handleInbound.ts
|
|
18600
|
+
function handleInbound(state, line) {
|
|
18601
|
+
let msg;
|
|
18602
|
+
try {
|
|
18603
|
+
msg = JSON.parse(line);
|
|
18604
|
+
} catch {
|
|
18605
|
+
return;
|
|
18606
|
+
}
|
|
18607
|
+
inbound[msg.type]?.(state, msg);
|
|
18608
|
+
}
|
|
18609
|
+
var inbound = {
|
|
18610
|
+
hello: (_state, msg) => handleHello(msg),
|
|
18611
|
+
created: handleCreated,
|
|
18612
|
+
sessions: handleSessions,
|
|
18613
|
+
output: handleOutput,
|
|
18614
|
+
clear: relayWithSessionId,
|
|
18615
|
+
error: (state, msg) => state.broadcast(msg)
|
|
18616
|
+
};
|
|
18617
|
+
function handleHello(msg) {
|
|
18618
|
+
if (isHello(msg) && !versionsMatch(msg.version, ASSIST_VERSION))
|
|
18619
|
+
daemonLog(
|
|
18620
|
+
`windows daemon version mismatch: ${msg.version} (wsl ${ASSIST_VERSION})`
|
|
18621
|
+
);
|
|
18622
|
+
}
|
|
18623
|
+
function handleCreated(state, msg) {
|
|
18624
|
+
const client = state.pendingCreators.shift();
|
|
18625
|
+
if (client) sendTo(client, { type: "created", sessionId: nsId(msg) });
|
|
18626
|
+
}
|
|
18627
|
+
function handleSessions(state, msg) {
|
|
18628
|
+
state.windowsSessions = msg.sessions.map((s) => ({
|
|
18629
|
+
...s,
|
|
18630
|
+
id: toWindowsSessionId(s.id)
|
|
18631
|
+
}));
|
|
18632
|
+
const live = new Set(state.windowsSessions.map((s) => s.id));
|
|
18633
|
+
for (const id of state.scrollback.keys())
|
|
18634
|
+
if (!live.has(id)) state.scrollback.delete(id);
|
|
18635
|
+
state.onSessionsChanged();
|
|
18636
|
+
}
|
|
18637
|
+
function handleOutput(state, msg) {
|
|
18638
|
+
const sessionId = nsId(msg);
|
|
18639
|
+
const data = msg.data;
|
|
18640
|
+
appendScrollback2(state, sessionId, data);
|
|
18641
|
+
state.broadcast({ type: "output", sessionId, data });
|
|
18642
|
+
}
|
|
18643
|
+
function relayWithSessionId(state, msg) {
|
|
18644
|
+
state.broadcast({ ...msg, sessionId: nsId(msg) });
|
|
18645
|
+
}
|
|
18646
|
+
function nsId(msg) {
|
|
18647
|
+
return toWindowsSessionId(msg.sessionId);
|
|
18648
|
+
}
|
|
18649
|
+
|
|
18650
|
+
// src/commands/sessions/daemon/isWindowsCwd.ts
|
|
18651
|
+
function isWindowsCwd(cwd) {
|
|
18652
|
+
return /^[A-Za-z]:[\\/]/.test(cwd);
|
|
18653
|
+
}
|
|
18654
|
+
function shouldProxyToWindows(cwd) {
|
|
18655
|
+
return detectPlatform() === "wsl" && isWindowsCwd(cwd);
|
|
18656
|
+
}
|
|
18657
|
+
|
|
18658
|
+
// src/commands/sessions/daemon/WindowsConnection.ts
|
|
18659
|
+
import { createInterface as createInterface5 } from "readline";
|
|
18660
|
+
var WindowsConnection = class {
|
|
18661
|
+
constructor(deps2) {
|
|
18662
|
+
this.deps = deps2;
|
|
18663
|
+
}
|
|
18664
|
+
connection = null;
|
|
18665
|
+
socket = null;
|
|
18666
|
+
ensure() {
|
|
18667
|
+
if (this.connection) return this.connection;
|
|
18668
|
+
const connection = this.open();
|
|
18669
|
+
this.connection = connection;
|
|
18670
|
+
connection.catch(() => {
|
|
18671
|
+
if (this.connection === connection) this.connection = null;
|
|
18672
|
+
});
|
|
18673
|
+
return connection;
|
|
18674
|
+
}
|
|
18675
|
+
write(msg) {
|
|
18676
|
+
this.socket?.write(`${JSON.stringify(msg)}
|
|
18677
|
+
`);
|
|
18678
|
+
}
|
|
18679
|
+
trySend(msg) {
|
|
18680
|
+
if (!this.socket?.writable) return false;
|
|
18681
|
+
this.write(msg);
|
|
18682
|
+
return true;
|
|
18683
|
+
}
|
|
18684
|
+
dispose() {
|
|
18685
|
+
this.socket?.destroy();
|
|
18686
|
+
this.reset();
|
|
18687
|
+
}
|
|
18688
|
+
async open() {
|
|
18689
|
+
const socket = await this.deps.connect();
|
|
18690
|
+
this.socket = socket;
|
|
18691
|
+
const lines = createInterface5({ input: socket });
|
|
18692
|
+
lines.on("error", () => {
|
|
18693
|
+
});
|
|
18694
|
+
lines.on("line", (line) => this.deps.onLine(line));
|
|
18695
|
+
socket.on("error", () => {
|
|
18696
|
+
});
|
|
18697
|
+
socket.on("close", () => {
|
|
18698
|
+
this.reset();
|
|
18699
|
+
this.deps.onClose();
|
|
18700
|
+
});
|
|
18701
|
+
this.write(buildHello());
|
|
18702
|
+
return socket;
|
|
18703
|
+
}
|
|
18704
|
+
reset() {
|
|
18705
|
+
this.socket = null;
|
|
18706
|
+
this.connection = null;
|
|
18707
|
+
}
|
|
18708
|
+
};
|
|
18709
|
+
|
|
18710
|
+
// src/commands/sessions/daemon/WindowsProxy.ts
|
|
18711
|
+
var WindowsProxy = class {
|
|
18712
|
+
state;
|
|
18713
|
+
conn;
|
|
18714
|
+
constructor(clients, onSessionsChanged, connect3 = defaultConnect) {
|
|
18715
|
+
this.state = createState(
|
|
18716
|
+
(msg) => broadcast(clients, msg),
|
|
18717
|
+
onSessionsChanged
|
|
18718
|
+
);
|
|
18719
|
+
this.conn = new WindowsConnection({
|
|
18720
|
+
connect: connect3,
|
|
18721
|
+
onLine: (line) => handleInbound(this.state, line),
|
|
18722
|
+
onClose: () => this.handleClose()
|
|
18723
|
+
});
|
|
18724
|
+
}
|
|
18725
|
+
sessions() {
|
|
18726
|
+
return this.state.windowsSessions;
|
|
18727
|
+
}
|
|
18728
|
+
replayScrollback(client) {
|
|
18729
|
+
replayScrollback2(this.state, client);
|
|
18730
|
+
}
|
|
18731
|
+
// Returns true when the message targets Windows: a create/resume in a
|
|
18732
|
+
// windows-origin cwd is forwarded, as is I/O for a namespaced session id.
|
|
18733
|
+
route(client, data) {
|
|
18734
|
+
if (isWindowsCreate(data)) {
|
|
18735
|
+
void this.forwardCreate(client, data);
|
|
18736
|
+
return true;
|
|
18737
|
+
}
|
|
18738
|
+
if (isWindowsIo(data)) {
|
|
18739
|
+
const sessionId = stripWindowsSessionId(data.sessionId);
|
|
18740
|
+
this.conn.trySend({ ...data, sessionId });
|
|
18741
|
+
return true;
|
|
18742
|
+
}
|
|
18743
|
+
return false;
|
|
18744
|
+
}
|
|
18745
|
+
dispose() {
|
|
18746
|
+
this.conn.dispose();
|
|
18747
|
+
}
|
|
18748
|
+
async forwardCreate(client, data) {
|
|
18749
|
+
try {
|
|
18750
|
+
await this.conn.ensure();
|
|
18751
|
+
this.state.pendingCreators.push(client);
|
|
18752
|
+
this.conn.write(data);
|
|
18753
|
+
} catch (e) {
|
|
18754
|
+
sendTo(client, {
|
|
18755
|
+
type: "error",
|
|
18756
|
+
message: `Windows session unavailable: ${errorText(e)}`
|
|
18757
|
+
});
|
|
18758
|
+
}
|
|
18759
|
+
}
|
|
18760
|
+
handleClose() {
|
|
18761
|
+
for (const client of this.state.pendingCreators)
|
|
18762
|
+
sendTo(client, {
|
|
18763
|
+
type: "error",
|
|
18764
|
+
message: "Windows daemon connection closed"
|
|
18765
|
+
});
|
|
18766
|
+
resetState(this.state);
|
|
18767
|
+
this.state.onSessionsChanged();
|
|
18768
|
+
}
|
|
18769
|
+
};
|
|
18770
|
+
function isWindowsCreate(data) {
|
|
18771
|
+
return typeof data.cwd === "string" && shouldProxyToWindows(data.cwd);
|
|
18772
|
+
}
|
|
18773
|
+
function isWindowsIo(data) {
|
|
18774
|
+
return typeof data.sessionId === "string" && isWindowsSessionId(data.sessionId);
|
|
18775
|
+
}
|
|
18776
|
+
async function defaultConnect() {
|
|
18777
|
+
await ensureWindowsDaemonRunning();
|
|
18778
|
+
return connectToWindowsDaemon();
|
|
18779
|
+
}
|
|
18780
|
+
function errorText(e) {
|
|
18781
|
+
return e instanceof Error ? e.message : String(e);
|
|
18782
|
+
}
|
|
18783
|
+
|
|
18473
18784
|
// src/commands/sessions/daemon/watchClaudeSessionId.ts
|
|
18474
18785
|
import * as fs27 from "fs";
|
|
18475
18786
|
import * as path48 from "path";
|
|
@@ -18567,23 +18878,20 @@ function matchMarker(text3, tag) {
|
|
|
18567
18878
|
}
|
|
18568
18879
|
|
|
18569
18880
|
// src/commands/sessions/shared/parseSessionFile.ts
|
|
18570
|
-
async function parseSessionFile(filePath) {
|
|
18881
|
+
async function parseSessionFile(filePath, origin = "wsl") {
|
|
18571
18882
|
let handle;
|
|
18572
18883
|
try {
|
|
18573
18884
|
handle = await fs25.promises.open(filePath, "r");
|
|
18574
|
-
const
|
|
18575
|
-
const { bytesRead } = await handle.read(buf, 0, buf.length, 0);
|
|
18576
|
-
const lines = buf.toString("utf8", 0, bytesRead).split("\n").filter(Boolean);
|
|
18577
|
-
const meta = extractSessionMeta(lines);
|
|
18885
|
+
const meta = extractSessionMeta(await readHeadLines(handle));
|
|
18578
18886
|
if (!meta.sessionId) return null;
|
|
18579
18887
|
const timestamp = meta.timestamp || (await fs25.promises.stat(filePath)).mtime.toISOString();
|
|
18580
|
-
const project = meta.cwd ? path46.basename(meta.cwd) : dirNameToProject(filePath);
|
|
18581
18888
|
return {
|
|
18582
18889
|
sessionId: meta.sessionId,
|
|
18583
18890
|
name: meta.name || `Session ${meta.sessionId.slice(0, 8)}`,
|
|
18584
|
-
project,
|
|
18891
|
+
project: deriveProject(meta.cwd, filePath, origin),
|
|
18585
18892
|
cwd: meta.cwd,
|
|
18586
18893
|
timestamp,
|
|
18894
|
+
origin,
|
|
18587
18895
|
...deriveHistoryFields(meta.commandName, meta.commandArgs, meta.name)
|
|
18588
18896
|
};
|
|
18589
18897
|
} catch {
|
|
@@ -18592,6 +18900,15 @@ async function parseSessionFile(filePath) {
|
|
|
18592
18900
|
await handle?.close();
|
|
18593
18901
|
}
|
|
18594
18902
|
}
|
|
18903
|
+
async function readHeadLines(handle) {
|
|
18904
|
+
const buf = Buffer.alloc(16384);
|
|
18905
|
+
const { bytesRead } = await handle.read(buf, 0, buf.length, 0);
|
|
18906
|
+
return buf.toString("utf8", 0, bytesRead).split("\n").filter(Boolean);
|
|
18907
|
+
}
|
|
18908
|
+
function deriveProject(cwd, filePath, origin) {
|
|
18909
|
+
if (!cwd) return dirNameToProject(filePath);
|
|
18910
|
+
return origin === "windows" ? path46.win32.basename(cwd) : path46.basename(cwd);
|
|
18911
|
+
}
|
|
18595
18912
|
function dirNameToProject(filePath) {
|
|
18596
18913
|
const dirName = path46.basename(path46.dirname(filePath));
|
|
18597
18914
|
const parts = dirName.split("--");
|
|
@@ -18599,38 +18916,52 @@ function dirNameToProject(filePath) {
|
|
|
18599
18916
|
}
|
|
18600
18917
|
|
|
18601
18918
|
// src/commands/sessions/shared/discoverSessions.ts
|
|
18919
|
+
function sessionRoots() {
|
|
18920
|
+
const roots = [
|
|
18921
|
+
{ dir: path47.join(os.homedir(), ".claude", "projects"), origin: "wsl" }
|
|
18922
|
+
];
|
|
18923
|
+
const windowsRoot = loadConfig().sessions?.windowsProjectsRoot;
|
|
18924
|
+
if (windowsRoot) roots.push({ dir: windowsRoot, origin: "windows" });
|
|
18925
|
+
return roots;
|
|
18926
|
+
}
|
|
18602
18927
|
async function discoverSessionJsonlPaths() {
|
|
18603
|
-
const
|
|
18604
|
-
let projectDirs;
|
|
18605
|
-
try {
|
|
18606
|
-
projectDirs = await fs26.promises.readdir(projectsDir);
|
|
18607
|
-
} catch {
|
|
18608
|
-
return [];
|
|
18609
|
-
}
|
|
18610
|
-
const paths = [];
|
|
18928
|
+
const results = [];
|
|
18611
18929
|
await Promise.all(
|
|
18612
|
-
|
|
18613
|
-
|
|
18614
|
-
let entries;
|
|
18930
|
+
sessionRoots().map(async ({ dir, origin }) => {
|
|
18931
|
+
let projectDirs;
|
|
18615
18932
|
try {
|
|
18616
|
-
|
|
18933
|
+
projectDirs = await fs26.promises.readdir(dir);
|
|
18617
18934
|
} catch {
|
|
18618
18935
|
return;
|
|
18619
18936
|
}
|
|
18620
|
-
|
|
18621
|
-
|
|
18622
|
-
|
|
18623
|
-
|
|
18937
|
+
await Promise.all(
|
|
18938
|
+
projectDirs.map(async (dirName) => {
|
|
18939
|
+
const dirPath = path47.join(dir, dirName);
|
|
18940
|
+
let entries;
|
|
18941
|
+
try {
|
|
18942
|
+
entries = await fs26.promises.readdir(dirPath);
|
|
18943
|
+
} catch {
|
|
18944
|
+
return;
|
|
18945
|
+
}
|
|
18946
|
+
for (const file of entries) {
|
|
18947
|
+
if (file.endsWith(".jsonl"))
|
|
18948
|
+
results.push({ path: path47.join(dirPath, file), origin });
|
|
18949
|
+
}
|
|
18950
|
+
})
|
|
18951
|
+
);
|
|
18624
18952
|
})
|
|
18625
18953
|
);
|
|
18626
|
-
return
|
|
18954
|
+
return results;
|
|
18955
|
+
}
|
|
18956
|
+
async function discoverSessionFiles() {
|
|
18957
|
+
return (await discoverSessionJsonlPaths()).map((p) => p.path);
|
|
18627
18958
|
}
|
|
18628
18959
|
async function discoverSessions() {
|
|
18629
18960
|
const paths = await discoverSessionJsonlPaths();
|
|
18630
18961
|
const sessions = [];
|
|
18631
18962
|
await Promise.all(
|
|
18632
|
-
paths.map(async (filePath) => {
|
|
18633
|
-
const session = await parseSessionFile(filePath);
|
|
18963
|
+
paths.map(async ({ path: filePath, origin }) => {
|
|
18964
|
+
const session = await parseSessionFile(filePath, origin);
|
|
18634
18965
|
if (session) sessions.push(session);
|
|
18635
18966
|
})
|
|
18636
18967
|
);
|
|
@@ -18656,10 +18987,10 @@ async function watchClaudeSessionId(options2) {
|
|
|
18656
18987
|
async function findLatestSessionId(options2) {
|
|
18657
18988
|
const paths = await discoverSessionJsonlPaths();
|
|
18658
18989
|
let latest = null;
|
|
18659
|
-
for (const filePath of paths) {
|
|
18990
|
+
for (const { path: filePath, origin } of paths) {
|
|
18660
18991
|
const createdMs = await createdSince(filePath, options2.sinceMs);
|
|
18661
18992
|
if (createdMs === null) continue;
|
|
18662
|
-
const meta = await parseSessionFile(filePath);
|
|
18993
|
+
const meta = await parseSessionFile(filePath, origin);
|
|
18663
18994
|
if (!meta?.cwd || options2.isClaimed(meta.sessionId)) continue;
|
|
18664
18995
|
if (path48.resolve(meta.cwd) !== path48.resolve(options2.cwd)) continue;
|
|
18665
18996
|
if (!latest || createdMs > latest.createdMs)
|
|
@@ -18753,10 +19084,12 @@ var SessionManager = class {
|
|
|
18753
19084
|
clients = /* @__PURE__ */ new Set();
|
|
18754
19085
|
nextId = 1;
|
|
18755
19086
|
shuttingDown = false;
|
|
19087
|
+
// why: dispatch calls windowsProxy.route() to forward windows-origin sessions
|
|
19088
|
+
windowsProxy = new WindowsProxy(this.clients, () => this.notify());
|
|
18756
19089
|
addClient(client) {
|
|
18757
19090
|
this.clients.add(client);
|
|
18758
19091
|
this.onIdleChange?.(this.isIdle());
|
|
18759
|
-
greetClient(client, this.sessions,
|
|
19092
|
+
greetClient(client, this.sessions, this.listSessions, this.windowsProxy);
|
|
18760
19093
|
}
|
|
18761
19094
|
removeClient(client) {
|
|
18762
19095
|
this.clients.delete(client);
|
|
@@ -18825,22 +19158,24 @@ var SessionManager = class {
|
|
|
18825
19158
|
setAutoAdvance(id, enabled) {
|
|
18826
19159
|
if (setAutoAdvance(this.sessions, id, enabled)) this.notify();
|
|
18827
19160
|
}
|
|
18828
|
-
listSessions() {
|
|
18829
|
-
|
|
18830
|
-
|
|
19161
|
+
listSessions = () => {
|
|
19162
|
+
const local = [...this.sessions.values()].map(toSessionInfo);
|
|
19163
|
+
return local.concat(this.windowsProxy.sessions());
|
|
19164
|
+
};
|
|
18831
19165
|
notify = () => {
|
|
18832
19166
|
if (this.shuttingDown) return;
|
|
18833
|
-
|
|
19167
|
+
const windows = this.windowsProxy.sessions();
|
|
19168
|
+
broadcastSessions(this.sessions, this.clients, windows);
|
|
18834
19169
|
this.onIdleChange?.(this.isIdle());
|
|
18835
19170
|
};
|
|
18836
19171
|
};
|
|
18837
19172
|
|
|
18838
19173
|
// src/commands/sessions/daemon/startDaemonServer.ts
|
|
18839
19174
|
import { unlinkSync as unlinkSync19 } from "fs";
|
|
18840
|
-
import * as
|
|
19175
|
+
import * as net3 from "net";
|
|
18841
19176
|
|
|
18842
19177
|
// src/commands/sessions/daemon/handleConnection.ts
|
|
18843
|
-
import { createInterface as
|
|
19178
|
+
import { createInterface as createInterface6 } from "readline";
|
|
18844
19179
|
|
|
18845
19180
|
// src/commands/sessions/shared/parseTranscript.ts
|
|
18846
19181
|
import * as fs28 from "fs";
|
|
@@ -18894,11 +19229,13 @@ function cleanUserText(value) {
|
|
|
18894
19229
|
import * as path49 from "path";
|
|
18895
19230
|
async function findSessionJsonlPath(sessionId) {
|
|
18896
19231
|
const paths = await discoverSessionJsonlPaths();
|
|
18897
|
-
const direct = paths.find(
|
|
18898
|
-
|
|
19232
|
+
const direct = paths.find(
|
|
19233
|
+
(p) => path49.basename(p.path, ".jsonl") === sessionId
|
|
19234
|
+
);
|
|
19235
|
+
if (direct) return direct.path;
|
|
18899
19236
|
for (const p of paths) {
|
|
18900
|
-
const meta = await parseSessionFile(p);
|
|
18901
|
-
if (meta?.sessionId === sessionId) return p;
|
|
19237
|
+
const meta = await parseSessionFile(p.path, p.origin);
|
|
19238
|
+
if (meta?.sessionId === sessionId) return p.path;
|
|
18902
19239
|
}
|
|
18903
19240
|
return null;
|
|
18904
19241
|
}
|
|
@@ -18932,46 +19269,11 @@ function safeParse2(line) {
|
|
|
18932
19269
|
}
|
|
18933
19270
|
|
|
18934
19271
|
// src/commands/sessions/daemon/dispatchMessage.ts
|
|
18935
|
-
function
|
|
18936
|
-
|
|
18937
|
-
|
|
18938
|
-
|
|
18939
|
-
|
|
18940
|
-
client,
|
|
18941
|
-
manager.spawn(
|
|
18942
|
-
data.prompt,
|
|
18943
|
-
data.cwd
|
|
18944
|
-
)
|
|
18945
|
-
);
|
|
18946
|
-
}
|
|
18947
|
-
function handleCreateRun(client, manager, data) {
|
|
18948
|
-
sendCreated(
|
|
18949
|
-
client,
|
|
18950
|
-
manager.spawnRun(
|
|
18951
|
-
data.runName,
|
|
18952
|
-
data.runArgs ?? [],
|
|
18953
|
-
data.cwd
|
|
18954
|
-
)
|
|
18955
|
-
);
|
|
18956
|
-
}
|
|
18957
|
-
function handleCreateAssist(client, manager, data) {
|
|
18958
|
-
sendCreated(
|
|
18959
|
-
client,
|
|
18960
|
-
manager.spawnAssist(
|
|
18961
|
-
data.assistArgs ?? [],
|
|
18962
|
-
data.cwd
|
|
18963
|
-
)
|
|
18964
|
-
);
|
|
18965
|
-
}
|
|
18966
|
-
function handleResume(client, manager, data) {
|
|
18967
|
-
sendCreated(
|
|
18968
|
-
client,
|
|
18969
|
-
manager.resume(
|
|
18970
|
-
data.sessionId,
|
|
18971
|
-
data.cwd,
|
|
18972
|
-
data.name
|
|
18973
|
-
)
|
|
18974
|
-
);
|
|
19272
|
+
function creator(spawn11) {
|
|
19273
|
+
return (client, m, d) => {
|
|
19274
|
+
if (m.windowsProxy.route(client, d)) return;
|
|
19275
|
+
sendTo(client, { type: "created", sessionId: spawn11(m, d) });
|
|
19276
|
+
};
|
|
18975
19277
|
}
|
|
18976
19278
|
function handleHistory(client) {
|
|
18977
19279
|
discoverSessions().then(
|
|
@@ -18989,21 +19291,54 @@ function handleShutdown(client, manager) {
|
|
|
18989
19291
|
sendTo(client, { type: "shutting-down" });
|
|
18990
19292
|
setImmediate(() => process.exit(0));
|
|
18991
19293
|
}
|
|
19294
|
+
function routed(local) {
|
|
19295
|
+
return (client, m, d) => {
|
|
19296
|
+
if (!m.windowsProxy.route(client, d)) local(client, m, d);
|
|
19297
|
+
};
|
|
19298
|
+
}
|
|
18992
19299
|
var handlers = {
|
|
18993
19300
|
ping: (client) => sendTo(client, { type: "pong", pid: process.pid }),
|
|
18994
|
-
|
|
18995
|
-
|
|
18996
|
-
|
|
18997
|
-
|
|
19301
|
+
hello: (client) => sendTo(client, buildHello()),
|
|
19302
|
+
create: creator(
|
|
19303
|
+
(m, d) => m.spawn(d.prompt, d.cwd)
|
|
19304
|
+
),
|
|
19305
|
+
"create-run": creator(
|
|
19306
|
+
(m, d) => m.spawnRun(
|
|
19307
|
+
d.runName,
|
|
19308
|
+
d.runArgs ?? [],
|
|
19309
|
+
d.cwd
|
|
19310
|
+
)
|
|
19311
|
+
),
|
|
19312
|
+
"create-assist": creator(
|
|
19313
|
+
(m, d) => m.spawnAssist(
|
|
19314
|
+
d.assistArgs ?? [],
|
|
19315
|
+
d.cwd
|
|
19316
|
+
)
|
|
19317
|
+
),
|
|
19318
|
+
resume: creator(
|
|
19319
|
+
(m, d) => m.resume(
|
|
19320
|
+
d.sessionId,
|
|
19321
|
+
d.cwd,
|
|
19322
|
+
d.name
|
|
19323
|
+
)
|
|
19324
|
+
),
|
|
18998
19325
|
history: handleHistory,
|
|
18999
19326
|
"fetch-transcript": handleFetchTranscript,
|
|
19000
19327
|
shutdown: handleShutdown,
|
|
19001
|
-
input: (
|
|
19002
|
-
|
|
19003
|
-
|
|
19004
|
-
|
|
19005
|
-
|
|
19006
|
-
|
|
19328
|
+
input: routed(
|
|
19329
|
+
(_client, m, d) => m.writeToSession(d.sessionId, d.data)
|
|
19330
|
+
),
|
|
19331
|
+
resize: routed(
|
|
19332
|
+
(_client, m, d) => m.resizeSession(d.sessionId, d.cols, d.rows)
|
|
19333
|
+
),
|
|
19334
|
+
retry: routed((_client, m, d) => m.retrySession(d.sessionId)),
|
|
19335
|
+
dismiss: routed((_client, m, d) => m.dismissSession(d.sessionId)),
|
|
19336
|
+
"set-autorun": routed(
|
|
19337
|
+
(_client, m, d) => m.setAutoRun(d.sessionId, d.enabled)
|
|
19338
|
+
),
|
|
19339
|
+
"set-autoadvance": routed(
|
|
19340
|
+
(_client, m, d) => m.setAutoAdvance(d.sessionId, d.enabled)
|
|
19341
|
+
)
|
|
19007
19342
|
};
|
|
19008
19343
|
function dispatchMessage(client, manager, data) {
|
|
19009
19344
|
handlers[data.type]?.(client, manager, data);
|
|
@@ -19018,7 +19353,7 @@ function handleConnection(socket, manager) {
|
|
|
19018
19353
|
}
|
|
19019
19354
|
};
|
|
19020
19355
|
manager.addClient(client);
|
|
19021
|
-
const lines =
|
|
19356
|
+
const lines = createInterface6({ input: socket });
|
|
19022
19357
|
lines.on("error", () => {
|
|
19023
19358
|
});
|
|
19024
19359
|
lines.on("line", (line) => {
|
|
@@ -19095,9 +19430,10 @@ function cleanupOwnedFiles() {
|
|
|
19095
19430
|
|
|
19096
19431
|
// src/commands/sessions/daemon/startDaemonServer.ts
|
|
19097
19432
|
function startDaemonServer(manager, checkAutoExit) {
|
|
19098
|
-
const server =
|
|
19433
|
+
const server = net3.createServer(
|
|
19099
19434
|
(socket) => handleConnection(socket, manager)
|
|
19100
19435
|
);
|
|
19436
|
+
if (process.platform === "win32") startWindowsBridge(manager);
|
|
19101
19437
|
let retried = false;
|
|
19102
19438
|
server.on("error", (e) => {
|
|
19103
19439
|
if (e.code !== "EADDRINUSE" || retried) {
|
|
@@ -19109,6 +19445,17 @@ function startDaemonServer(manager, checkAutoExit) {
|
|
|
19109
19445
|
});
|
|
19110
19446
|
server.listen(daemonPaths.socket, () => onListening(manager, checkAutoExit));
|
|
19111
19447
|
}
|
|
19448
|
+
function startWindowsBridge(manager) {
|
|
19449
|
+
const port = windowsDaemonPort();
|
|
19450
|
+
const bridge = net3.createServer(
|
|
19451
|
+
(socket) => handleConnection(socket, manager)
|
|
19452
|
+
);
|
|
19453
|
+
bridge.on(
|
|
19454
|
+
"error",
|
|
19455
|
+
(e) => daemonLog(`windows bridge error: ${e.message}`)
|
|
19456
|
+
);
|
|
19457
|
+
bridge.listen(port, () => daemonLog(`windows bridge listening on :${port}`));
|
|
19458
|
+
}
|
|
19112
19459
|
async function recoverFromAddrInUse(server, manager, checkAutoExit) {
|
|
19113
19460
|
if (await isDaemonRunning()) {
|
|
19114
19461
|
daemonLog("another daemon owns the socket; exiting");
|
|
@@ -19286,7 +19633,7 @@ ${firstMessage}`);
|
|
|
19286
19633
|
|
|
19287
19634
|
// src/commands/sessions/summarise/index.ts
|
|
19288
19635
|
async function summarise4(options2) {
|
|
19289
|
-
const files = await
|
|
19636
|
+
const files = await discoverSessionFiles();
|
|
19290
19637
|
if (files.length === 0) {
|
|
19291
19638
|
console.log(chalk161.yellow("No sessions found."));
|
|
19292
19639
|
return;
|