@staff0rd/assist 0.297.4 → 0.298.1
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/commands/sessions/web/bundle.js +56 -56
- package/dist/index.js +137 -67
- 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.298.1",
|
|
10
10
|
type: "module",
|
|
11
11
|
main: "dist/index.js",
|
|
12
12
|
bin: {
|
|
@@ -18412,6 +18412,44 @@ function daemonLog(message) {
|
|
|
18412
18412
|
console.log(`${(/* @__PURE__ */ new Date()).toISOString()} [${process.pid}] ${message}`);
|
|
18413
18413
|
}
|
|
18414
18414
|
|
|
18415
|
+
// src/commands/sessions/daemon/loadActiveSelection.ts
|
|
18416
|
+
import { z as z5 } from "zod";
|
|
18417
|
+
var ACTIVE_FILE = "active.json";
|
|
18418
|
+
var activeSelectionSchema = z5.record(z5.string(), z5.string());
|
|
18419
|
+
function loadActiveSelection() {
|
|
18420
|
+
const parsed = activeSelectionSchema.safeParse(
|
|
18421
|
+
loadJson(ACTIVE_FILE)
|
|
18422
|
+
);
|
|
18423
|
+
return parsed.success ? parsed.data : {};
|
|
18424
|
+
}
|
|
18425
|
+
function saveActiveSelection(active) {
|
|
18426
|
+
saveJson(ACTIVE_FILE, active);
|
|
18427
|
+
}
|
|
18428
|
+
|
|
18429
|
+
// src/commands/sessions/daemon/ActiveSelection.ts
|
|
18430
|
+
var ActiveSelection = class {
|
|
18431
|
+
// why: onChange triggers a broadcast only when the selection actually changes
|
|
18432
|
+
constructor(onChange) {
|
|
18433
|
+
this.onChange = onChange;
|
|
18434
|
+
}
|
|
18435
|
+
byRepo = /* @__PURE__ */ new Map();
|
|
18436
|
+
set(cwd, sessionId) {
|
|
18437
|
+
if (!cwd) return;
|
|
18438
|
+
this.byRepo.delete(cwd);
|
|
18439
|
+
this.byRepo.set(cwd, sessionId);
|
|
18440
|
+
saveActiveSelection(this.toJSON());
|
|
18441
|
+
this.onChange();
|
|
18442
|
+
}
|
|
18443
|
+
// why: on daemon restart, reload persisted selections but drop any whose session was not restored
|
|
18444
|
+
restore(isLive) {
|
|
18445
|
+
for (const [cwd, sessionId] of Object.entries(loadActiveSelection()))
|
|
18446
|
+
if (isLive(sessionId)) this.byRepo.set(cwd, sessionId);
|
|
18447
|
+
}
|
|
18448
|
+
toJSON() {
|
|
18449
|
+
return Object.fromEntries(this.byRepo);
|
|
18450
|
+
}
|
|
18451
|
+
};
|
|
18452
|
+
|
|
18415
18453
|
// src/commands/sessions/daemon/broadcast.ts
|
|
18416
18454
|
function sendTo(client, msg) {
|
|
18417
18455
|
client.send(JSON.stringify(msg));
|
|
@@ -18424,17 +18462,17 @@ function broadcast(clients, msg) {
|
|
|
18424
18462
|
}
|
|
18425
18463
|
|
|
18426
18464
|
// src/commands/sessions/daemon/loadPersistedSessions.ts
|
|
18427
|
-
import { z as
|
|
18465
|
+
import { z as z6 } from "zod";
|
|
18428
18466
|
var SESSIONS_FILE = "sessions.json";
|
|
18429
|
-
var persistedSessionSchema =
|
|
18430
|
-
name:
|
|
18431
|
-
commandType:
|
|
18432
|
-
cwd:
|
|
18433
|
-
startedAt:
|
|
18434
|
-
claudeSessionId:
|
|
18435
|
-
runName:
|
|
18436
|
-
runArgs:
|
|
18437
|
-
assistArgs:
|
|
18467
|
+
var persistedSessionSchema = z6.object({
|
|
18468
|
+
name: z6.string(),
|
|
18469
|
+
commandType: z6.enum(["claude", "run", "assist"]),
|
|
18470
|
+
cwd: z6.string(),
|
|
18471
|
+
startedAt: z6.number(),
|
|
18472
|
+
claudeSessionId: z6.string().optional(),
|
|
18473
|
+
runName: z6.string().optional(),
|
|
18474
|
+
runArgs: z6.array(z6.string()).optional(),
|
|
18475
|
+
assistArgs: z6.array(z6.string()).optional(),
|
|
18438
18476
|
activity: activitySchema.optional()
|
|
18439
18477
|
});
|
|
18440
18478
|
function loadPersistedSessions() {
|
|
@@ -18501,11 +18539,13 @@ function toSessionInfo({
|
|
|
18501
18539
|
}
|
|
18502
18540
|
|
|
18503
18541
|
// src/commands/sessions/daemon/broadcastSessions.ts
|
|
18504
|
-
function broadcastSessions(sessions, clients, windowsSessions = []) {
|
|
18542
|
+
function broadcastSessions(sessions, clients, windowsSessions = [], active) {
|
|
18505
18543
|
persistLiveSessions(sessions);
|
|
18544
|
+
const local = [...sessions.values()].map(toSessionInfo);
|
|
18506
18545
|
broadcast(clients, {
|
|
18507
18546
|
type: "sessions",
|
|
18508
|
-
sessions:
|
|
18547
|
+
sessions: local.concat(windowsSessions),
|
|
18548
|
+
active: active?.toJSON() ?? {}
|
|
18509
18549
|
});
|
|
18510
18550
|
}
|
|
18511
18551
|
|
|
@@ -18643,8 +18683,7 @@ function replayScrollback(sessions, client) {
|
|
|
18643
18683
|
}
|
|
18644
18684
|
|
|
18645
18685
|
// src/commands/sessions/daemon/greetClient.ts
|
|
18646
|
-
function greetClient(client, sessions,
|
|
18647
|
-
sendTo(client, { type: "sessions", sessions: list4() });
|
|
18686
|
+
function greetClient(client, sessions, windowsProxy) {
|
|
18648
18687
|
replayScrollback(sessions, client);
|
|
18649
18688
|
windowsProxy.replayScrollback(client);
|
|
18650
18689
|
void windowsProxy.discover();
|
|
@@ -18887,33 +18926,6 @@ function shutdownSessions(sessions) {
|
|
|
18887
18926
|
}
|
|
18888
18927
|
}
|
|
18889
18928
|
|
|
18890
|
-
// src/commands/sessions/daemon/autoHealWindowsDaemon.ts
|
|
18891
|
-
async function autoHealWindowsDaemon(conn, state, heal, version2) {
|
|
18892
|
-
daemonLog(`windows proxy: auto-healing windows daemon (mismatch ${version2})`);
|
|
18893
|
-
notifyHealing(state);
|
|
18894
|
-
try {
|
|
18895
|
-
conn.dispose();
|
|
18896
|
-
await heal();
|
|
18897
|
-
daemonLog("windows proxy: heal complete, reconnecting to windows daemon");
|
|
18898
|
-
await conn.ensure();
|
|
18899
|
-
} catch (error) {
|
|
18900
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
18901
|
-
daemonLog(`windows proxy: auto-heal failed: ${message}`);
|
|
18902
|
-
state.broadcast({
|
|
18903
|
-
type: "error",
|
|
18904
|
-
message: `Windows host auto-update failed: ${message}`
|
|
18905
|
-
});
|
|
18906
|
-
}
|
|
18907
|
-
}
|
|
18908
|
-
function notifyHealing(state) {
|
|
18909
|
-
for (const client of state.pendingCreators)
|
|
18910
|
-
sendTo(client, {
|
|
18911
|
-
type: "error",
|
|
18912
|
-
message: "Windows host is out of date; updating it now \u2014 reselect the repo once the update finishes."
|
|
18913
|
-
});
|
|
18914
|
-
state.pendingCreators = [];
|
|
18915
|
-
}
|
|
18916
|
-
|
|
18917
18929
|
// src/commands/sessions/daemon/connectToWindowsDaemon.ts
|
|
18918
18930
|
import * as net2 from "net";
|
|
18919
18931
|
|
|
@@ -19320,30 +19332,94 @@ var WindowsConnection = class {
|
|
|
19320
19332
|
}
|
|
19321
19333
|
};
|
|
19322
19334
|
|
|
19335
|
+
// src/commands/sessions/daemon/autoHealWindowsDaemon.ts
|
|
19336
|
+
async function autoHealWindowsDaemon(conn, state, heal, version2) {
|
|
19337
|
+
daemonLog(`windows proxy: auto-healing windows daemon (mismatch ${version2})`);
|
|
19338
|
+
notifyHealing(state);
|
|
19339
|
+
try {
|
|
19340
|
+
conn.dispose();
|
|
19341
|
+
await heal();
|
|
19342
|
+
daemonLog("windows proxy: heal complete, reconnecting to windows daemon");
|
|
19343
|
+
await conn.ensure();
|
|
19344
|
+
} catch (error) {
|
|
19345
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
19346
|
+
daemonLog(`windows proxy: auto-heal failed: ${message}`);
|
|
19347
|
+
state.broadcast({
|
|
19348
|
+
type: "error",
|
|
19349
|
+
message: `Windows host auto-update failed: ${message}`
|
|
19350
|
+
});
|
|
19351
|
+
}
|
|
19352
|
+
}
|
|
19353
|
+
function notifyHealing(state) {
|
|
19354
|
+
for (const client of state.pendingCreators)
|
|
19355
|
+
sendTo(client, {
|
|
19356
|
+
type: "error",
|
|
19357
|
+
message: "Windows host is out of date; updating it now \u2014 reselect the repo once the update finishes."
|
|
19358
|
+
});
|
|
19359
|
+
state.pendingCreators = [];
|
|
19360
|
+
}
|
|
19361
|
+
|
|
19362
|
+
// src/commands/sessions/daemon/WindowsVersionHealer.ts
|
|
19363
|
+
var UNRECOVERABLE_MESSAGE = "Windows host is on an incompatible version that auto-update could not resolve; not reconnecting. Update the Windows host manually, then restart.";
|
|
19364
|
+
var WindowsVersionHealer = class {
|
|
19365
|
+
constructor(conn, state, heal) {
|
|
19366
|
+
this.conn = conn;
|
|
19367
|
+
this.state = state;
|
|
19368
|
+
this.heal = heal;
|
|
19369
|
+
}
|
|
19370
|
+
healAttempted = false;
|
|
19371
|
+
healing = false;
|
|
19372
|
+
unrecoverable = false;
|
|
19373
|
+
get blocked() {
|
|
19374
|
+
return this.unrecoverable;
|
|
19375
|
+
}
|
|
19376
|
+
refusal() {
|
|
19377
|
+
return { type: "error", message: UNRECOVERABLE_MESSAGE };
|
|
19378
|
+
}
|
|
19379
|
+
async onMismatch(version2) {
|
|
19380
|
+
if (this.healing || this.unrecoverable) return;
|
|
19381
|
+
if (this.healAttempted) return this.giveUp(version2);
|
|
19382
|
+
this.healing = true;
|
|
19383
|
+
this.healAttempted = true;
|
|
19384
|
+
try {
|
|
19385
|
+
await autoHealWindowsDaemon(this.conn, this.state, this.heal, version2);
|
|
19386
|
+
} finally {
|
|
19387
|
+
this.healing = false;
|
|
19388
|
+
}
|
|
19389
|
+
}
|
|
19390
|
+
giveUp(version2) {
|
|
19391
|
+
this.unrecoverable = true;
|
|
19392
|
+
daemonLog(
|
|
19393
|
+
`windows proxy: version mismatch ${version2} persists after heal; not reconnecting until the WSL daemon restarts`
|
|
19394
|
+
);
|
|
19395
|
+
this.conn.dispose();
|
|
19396
|
+
this.state.broadcast(this.refusal());
|
|
19397
|
+
}
|
|
19398
|
+
};
|
|
19399
|
+
|
|
19323
19400
|
// src/commands/sessions/daemon/WindowsProxy.ts
|
|
19324
19401
|
var WindowsProxy = class {
|
|
19402
|
+
state;
|
|
19403
|
+
conn;
|
|
19404
|
+
healer;
|
|
19325
19405
|
constructor(clients, onSessionsChanged, connect3 = defaultConnect, heal = healWindowsDaemon) {
|
|
19326
|
-
this.heal = heal;
|
|
19327
19406
|
this.state = createState(
|
|
19328
19407
|
(msg) => broadcast(clients, msg),
|
|
19329
19408
|
onSessionsChanged,
|
|
19330
|
-
(version2) => void this.
|
|
19409
|
+
(version2) => void this.healer.onMismatch(version2)
|
|
19331
19410
|
);
|
|
19332
19411
|
this.conn = new WindowsConnection({
|
|
19333
19412
|
connect: connect3,
|
|
19334
19413
|
onLine: (line) => handleInbound(this.state, line),
|
|
19335
19414
|
onClose: () => this.handleClose()
|
|
19336
19415
|
});
|
|
19416
|
+
this.healer = new WindowsVersionHealer(this.conn, this.state, heal);
|
|
19337
19417
|
}
|
|
19338
|
-
state;
|
|
19339
|
-
conn;
|
|
19340
|
-
// why: heal runs once per proxy lifetime; if WSL is the older side, updating Windows can't close the gap, so a repeat mismatch must not loop
|
|
19341
|
-
healAttempted = false;
|
|
19342
|
-
healing = false;
|
|
19343
19418
|
sessions() {
|
|
19344
19419
|
return this.state.windowsSessions;
|
|
19345
19420
|
}
|
|
19346
19421
|
discover() {
|
|
19422
|
+
if (this.healer.blocked) return Promise.resolve();
|
|
19347
19423
|
return discoverWindowsSessions(this.conn);
|
|
19348
19424
|
}
|
|
19349
19425
|
replayScrollback(client) {
|
|
@@ -19353,7 +19429,8 @@ var WindowsProxy = class {
|
|
|
19353
19429
|
// windows-origin cwd is forwarded, as is I/O for a namespaced session id.
|
|
19354
19430
|
route(client, data) {
|
|
19355
19431
|
if (isWindowsCreate(data)) {
|
|
19356
|
-
|
|
19432
|
+
if (this.healer.blocked) sendTo(client, this.healer.refusal());
|
|
19433
|
+
else void forwardWindowsCreate(this.conn, this.state, client, data);
|
|
19357
19434
|
return true;
|
|
19358
19435
|
}
|
|
19359
19436
|
if (isWindowsIo(data)) {
|
|
@@ -19366,16 +19443,6 @@ var WindowsProxy = class {
|
|
|
19366
19443
|
dispose() {
|
|
19367
19444
|
this.conn.dispose();
|
|
19368
19445
|
}
|
|
19369
|
-
async handleVersionMismatch(version2) {
|
|
19370
|
-
if (this.healing || this.healAttempted) return;
|
|
19371
|
-
this.healing = true;
|
|
19372
|
-
this.healAttempted = true;
|
|
19373
|
-
try {
|
|
19374
|
-
await autoHealWindowsDaemon(this.conn, this.state, this.heal, version2);
|
|
19375
|
-
} finally {
|
|
19376
|
-
this.healing = false;
|
|
19377
|
-
}
|
|
19378
|
-
}
|
|
19379
19446
|
handleClose() {
|
|
19380
19447
|
daemonLog("windows proxy: connection to windows daemon closed");
|
|
19381
19448
|
for (const client of this.state.pendingCreators)
|
|
@@ -19688,6 +19755,8 @@ var SessionManager = class {
|
|
|
19688
19755
|
this.onIdleChange = onIdleChange;
|
|
19689
19756
|
}
|
|
19690
19757
|
sessions = /* @__PURE__ */ new Map();
|
|
19758
|
+
// why: dispatch calls active.set() on card click; broadcasts include active.toJSON()
|
|
19759
|
+
active = new ActiveSelection(() => this.notify());
|
|
19691
19760
|
clients = new ClientHub();
|
|
19692
19761
|
nextId = 1;
|
|
19693
19762
|
shuttingDown = false;
|
|
@@ -19695,16 +19764,14 @@ var SessionManager = class {
|
|
|
19695
19764
|
windowsProxy = new WindowsProxy(this.clients, () => this.notify());
|
|
19696
19765
|
addClient(client) {
|
|
19697
19766
|
this.clients.add(client);
|
|
19698
|
-
this.
|
|
19699
|
-
greetClient(client, this.sessions, this.
|
|
19767
|
+
this.notify();
|
|
19768
|
+
greetClient(client, this.sessions, this.windowsProxy);
|
|
19700
19769
|
}
|
|
19701
19770
|
removeClient(client) {
|
|
19702
19771
|
this.clients.delete(client);
|
|
19703
19772
|
this.onIdleChange?.(this.isIdle());
|
|
19704
19773
|
}
|
|
19705
|
-
isIdle()
|
|
19706
|
-
return this.sessions.size === 0 && this.clients.size === 0;
|
|
19707
|
-
}
|
|
19774
|
+
isIdle = () => this.sessions.size === 0 && this.clients.size === 0;
|
|
19708
19775
|
shutdown() {
|
|
19709
19776
|
this.shuttingDown = true;
|
|
19710
19777
|
shutdownSessions(this.sessions);
|
|
@@ -19772,7 +19839,7 @@ var SessionManager = class {
|
|
|
19772
19839
|
notify = () => {
|
|
19773
19840
|
if (this.shuttingDown) return;
|
|
19774
19841
|
const windows = this.windowsProxy.sessions();
|
|
19775
|
-
broadcastSessions(this.sessions, this.clients, windows);
|
|
19842
|
+
broadcastSessions(this.sessions, this.clients, windows, this.active);
|
|
19776
19843
|
this.onIdleChange?.(this.isIdle());
|
|
19777
19844
|
};
|
|
19778
19845
|
};
|
|
@@ -19950,7 +20017,8 @@ var handlers = {
|
|
|
19950
20017
|
),
|
|
19951
20018
|
"set-autoadvance": routed(
|
|
19952
20019
|
(_client, m, d) => m.setAutoAdvance(d.sessionId, d.enabled)
|
|
19953
|
-
)
|
|
20020
|
+
),
|
|
20021
|
+
"set-active": (_client, m, d) => m.active.set(d.cwd, d.sessionId)
|
|
19954
20022
|
};
|
|
19955
20023
|
function dispatchMessage(client, manager, data) {
|
|
19956
20024
|
handlers[data.type]?.(client, manager, data);
|
|
@@ -20021,6 +20089,8 @@ function onListening(manager, checkAutoExit) {
|
|
|
20021
20089
|
});
|
|
20022
20090
|
process.on("exit", cleanupOwnedFiles);
|
|
20023
20091
|
const restored = manager.restore();
|
|
20092
|
+
const liveIds = new Set(manager.listSessions().map((s) => s.id));
|
|
20093
|
+
manager.active.restore((id2) => liveIds.has(id2));
|
|
20024
20094
|
daemonLog(
|
|
20025
20095
|
restored.length > 0 ? `restored ${restored.length} session(s): ${restored.join(", ")}` : "no persisted sessions to restore"
|
|
20026
20096
|
);
|