@staff0rd/assist 0.284.1 → 0.285.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/index.js +390 -65
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -253,6 +253,10 @@ The first backlog command in a repository that still has a local `.assist/backlo
|
|
|
253
253
|
|
|
254
254
|
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.
|
|
255
255
|
|
|
256
|
+
Set `sessions.windowsProjectsRoot` in config to the Windows `.claude/projects` directory as seen from WSL (e.g. `/mnt/c/Users/<user>/.claude/projects`) to surface Windows-host repos in the selector. Their transcripts are discovered alongside local ones and tagged as windows-origin (the project name is derived with Windows path rules so `C:\…` cwds name correctly).
|
|
257
|
+
|
|
258
|
+
Selecting a windows-origin repo (cwd like `C:\…`) from the WSL UI runs an interactive session natively on Windows: the WSL daemon launches a second assist daemon on the Windows host on demand via interop (`cmd.exe /c start "" assist daemon run`), connects to it over TCP (the WSL daemon cannot reach the Windows named pipe directly), and proxies create/resume and I/O to the Windows daemon's native PTY. Windows sessions appear in the same list as local ones (their ids are namespaced internally to avoid collisions) with live output, scrollback, and resume. The TCP connection performs a version handshake on connect; a mismatch is logged (auto-healing comes later). A native Windows daemon listens for the WSL bridge on `sessions.windowsDaemonPort` (default `51764`); the WSL daemon dials `sessions.windowsDaemonHost` (default `127.0.0.1`, which works under WSL2 mirrored networking — NAT-mode setups should set it to the Windows host IP).
|
|
259
|
+
|
|
256
260
|
When iterating on assist itself: web server changes only need the `assist sessions` process restarted — sessions survive. Daemon/session-core changes need `assist daemon restart` to load the new code; this kills the PTYs, then claude sessions — including assist sessions that wrap claude, like `assist draft` — are auto-respawned via `claude --resume` with scrollback starting fresh, while run sessions (and assist sessions whose claude sessionId was never discovered) reappear as not-restored tiles that can be retried.
|
|
257
261
|
- `assist next [id] [--once]` - Alias for `backlog next [id]`; `--once` exits after the first completed item run instead of prompting for another
|
|
258
262
|
- `assist draft [description] [--once]` (alias: `feat`) - Launch Claude in `/draft` mode, chain into next on `/next` signal; an optional `description` is forwarded as `/draft <description>`; `--once` exits when the done signal arrives after the initial draft completes
|
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.285.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";
|
|
@@ -18753,10 +19064,12 @@ var SessionManager = class {
|
|
|
18753
19064
|
clients = /* @__PURE__ */ new Set();
|
|
18754
19065
|
nextId = 1;
|
|
18755
19066
|
shuttingDown = false;
|
|
19067
|
+
// why: dispatch calls windowsProxy.route() to forward windows-origin sessions
|
|
19068
|
+
windowsProxy = new WindowsProxy(this.clients, () => this.notify());
|
|
18756
19069
|
addClient(client) {
|
|
18757
19070
|
this.clients.add(client);
|
|
18758
19071
|
this.onIdleChange?.(this.isIdle());
|
|
18759
|
-
greetClient(client, this.sessions,
|
|
19072
|
+
greetClient(client, this.sessions, this.listSessions, this.windowsProxy);
|
|
18760
19073
|
}
|
|
18761
19074
|
removeClient(client) {
|
|
18762
19075
|
this.clients.delete(client);
|
|
@@ -18825,22 +19138,24 @@ var SessionManager = class {
|
|
|
18825
19138
|
setAutoAdvance(id, enabled) {
|
|
18826
19139
|
if (setAutoAdvance(this.sessions, id, enabled)) this.notify();
|
|
18827
19140
|
}
|
|
18828
|
-
listSessions() {
|
|
18829
|
-
|
|
18830
|
-
|
|
19141
|
+
listSessions = () => {
|
|
19142
|
+
const local = [...this.sessions.values()].map(toSessionInfo);
|
|
19143
|
+
return local.concat(this.windowsProxy.sessions());
|
|
19144
|
+
};
|
|
18831
19145
|
notify = () => {
|
|
18832
19146
|
if (this.shuttingDown) return;
|
|
18833
|
-
|
|
19147
|
+
const windows = this.windowsProxy.sessions();
|
|
19148
|
+
broadcastSessions(this.sessions, this.clients, windows);
|
|
18834
19149
|
this.onIdleChange?.(this.isIdle());
|
|
18835
19150
|
};
|
|
18836
19151
|
};
|
|
18837
19152
|
|
|
18838
19153
|
// src/commands/sessions/daemon/startDaemonServer.ts
|
|
18839
19154
|
import { unlinkSync as unlinkSync19 } from "fs";
|
|
18840
|
-
import * as
|
|
19155
|
+
import * as net3 from "net";
|
|
18841
19156
|
|
|
18842
19157
|
// src/commands/sessions/daemon/handleConnection.ts
|
|
18843
|
-
import { createInterface as
|
|
19158
|
+
import { createInterface as createInterface6 } from "readline";
|
|
18844
19159
|
|
|
18845
19160
|
// src/commands/sessions/shared/parseTranscript.ts
|
|
18846
19161
|
import * as fs28 from "fs";
|
|
@@ -18932,46 +19247,11 @@ function safeParse2(line) {
|
|
|
18932
19247
|
}
|
|
18933
19248
|
|
|
18934
19249
|
// 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
|
-
);
|
|
19250
|
+
function creator(spawn11) {
|
|
19251
|
+
return (client, m, d) => {
|
|
19252
|
+
if (m.windowsProxy.route(client, d)) return;
|
|
19253
|
+
sendTo(client, { type: "created", sessionId: spawn11(m, d) });
|
|
19254
|
+
};
|
|
18975
19255
|
}
|
|
18976
19256
|
function handleHistory(client) {
|
|
18977
19257
|
discoverSessions().then(
|
|
@@ -18989,21 +19269,54 @@ function handleShutdown(client, manager) {
|
|
|
18989
19269
|
sendTo(client, { type: "shutting-down" });
|
|
18990
19270
|
setImmediate(() => process.exit(0));
|
|
18991
19271
|
}
|
|
19272
|
+
function routed(local) {
|
|
19273
|
+
return (client, m, d) => {
|
|
19274
|
+
if (!m.windowsProxy.route(client, d)) local(client, m, d);
|
|
19275
|
+
};
|
|
19276
|
+
}
|
|
18992
19277
|
var handlers = {
|
|
18993
19278
|
ping: (client) => sendTo(client, { type: "pong", pid: process.pid }),
|
|
18994
|
-
|
|
18995
|
-
|
|
18996
|
-
|
|
18997
|
-
|
|
19279
|
+
hello: (client) => sendTo(client, buildHello()),
|
|
19280
|
+
create: creator(
|
|
19281
|
+
(m, d) => m.spawn(d.prompt, d.cwd)
|
|
19282
|
+
),
|
|
19283
|
+
"create-run": creator(
|
|
19284
|
+
(m, d) => m.spawnRun(
|
|
19285
|
+
d.runName,
|
|
19286
|
+
d.runArgs ?? [],
|
|
19287
|
+
d.cwd
|
|
19288
|
+
)
|
|
19289
|
+
),
|
|
19290
|
+
"create-assist": creator(
|
|
19291
|
+
(m, d) => m.spawnAssist(
|
|
19292
|
+
d.assistArgs ?? [],
|
|
19293
|
+
d.cwd
|
|
19294
|
+
)
|
|
19295
|
+
),
|
|
19296
|
+
resume: creator(
|
|
19297
|
+
(m, d) => m.resume(
|
|
19298
|
+
d.sessionId,
|
|
19299
|
+
d.cwd,
|
|
19300
|
+
d.name
|
|
19301
|
+
)
|
|
19302
|
+
),
|
|
18998
19303
|
history: handleHistory,
|
|
18999
19304
|
"fetch-transcript": handleFetchTranscript,
|
|
19000
19305
|
shutdown: handleShutdown,
|
|
19001
|
-
input: (
|
|
19002
|
-
|
|
19003
|
-
|
|
19004
|
-
|
|
19005
|
-
|
|
19006
|
-
|
|
19306
|
+
input: routed(
|
|
19307
|
+
(_client, m, d) => m.writeToSession(d.sessionId, d.data)
|
|
19308
|
+
),
|
|
19309
|
+
resize: routed(
|
|
19310
|
+
(_client, m, d) => m.resizeSession(d.sessionId, d.cols, d.rows)
|
|
19311
|
+
),
|
|
19312
|
+
retry: routed((_client, m, d) => m.retrySession(d.sessionId)),
|
|
19313
|
+
dismiss: routed((_client, m, d) => m.dismissSession(d.sessionId)),
|
|
19314
|
+
"set-autorun": routed(
|
|
19315
|
+
(_client, m, d) => m.setAutoRun(d.sessionId, d.enabled)
|
|
19316
|
+
),
|
|
19317
|
+
"set-autoadvance": routed(
|
|
19318
|
+
(_client, m, d) => m.setAutoAdvance(d.sessionId, d.enabled)
|
|
19319
|
+
)
|
|
19007
19320
|
};
|
|
19008
19321
|
function dispatchMessage(client, manager, data) {
|
|
19009
19322
|
handlers[data.type]?.(client, manager, data);
|
|
@@ -19018,7 +19331,7 @@ function handleConnection(socket, manager) {
|
|
|
19018
19331
|
}
|
|
19019
19332
|
};
|
|
19020
19333
|
manager.addClient(client);
|
|
19021
|
-
const lines =
|
|
19334
|
+
const lines = createInterface6({ input: socket });
|
|
19022
19335
|
lines.on("error", () => {
|
|
19023
19336
|
});
|
|
19024
19337
|
lines.on("line", (line) => {
|
|
@@ -19095,9 +19408,10 @@ function cleanupOwnedFiles() {
|
|
|
19095
19408
|
|
|
19096
19409
|
// src/commands/sessions/daemon/startDaemonServer.ts
|
|
19097
19410
|
function startDaemonServer(manager, checkAutoExit) {
|
|
19098
|
-
const server =
|
|
19411
|
+
const server = net3.createServer(
|
|
19099
19412
|
(socket) => handleConnection(socket, manager)
|
|
19100
19413
|
);
|
|
19414
|
+
if (process.platform === "win32") startWindowsBridge(manager);
|
|
19101
19415
|
let retried = false;
|
|
19102
19416
|
server.on("error", (e) => {
|
|
19103
19417
|
if (e.code !== "EADDRINUSE" || retried) {
|
|
@@ -19109,6 +19423,17 @@ function startDaemonServer(manager, checkAutoExit) {
|
|
|
19109
19423
|
});
|
|
19110
19424
|
server.listen(daemonPaths.socket, () => onListening(manager, checkAutoExit));
|
|
19111
19425
|
}
|
|
19426
|
+
function startWindowsBridge(manager) {
|
|
19427
|
+
const port = windowsDaemonPort();
|
|
19428
|
+
const bridge = net3.createServer(
|
|
19429
|
+
(socket) => handleConnection(socket, manager)
|
|
19430
|
+
);
|
|
19431
|
+
bridge.on(
|
|
19432
|
+
"error",
|
|
19433
|
+
(e) => daemonLog(`windows bridge error: ${e.message}`)
|
|
19434
|
+
);
|
|
19435
|
+
bridge.listen(port, () => daemonLog(`windows bridge listening on :${port}`));
|
|
19436
|
+
}
|
|
19112
19437
|
async function recoverFromAddrInUse(server, manager, checkAutoExit) {
|
|
19113
19438
|
if (await isDaemonRunning()) {
|
|
19114
19439
|
daemonLog("another daemon owns the socket; exiting");
|