@staff0rd/assist 0.320.0 → 0.322.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 CHANGED
@@ -247,7 +247,7 @@ The first backlog command in a repository that still has a local `.assist/backlo
247
247
  - `assist complexity <pattern>` - Analyze a file (all metrics if single match, maintainability if multiple)
248
248
  - `assist complexity cyclomatic [pattern]` - Calculate cyclomatic complexity per function
249
249
  - `assist complexity halstead [pattern]` - Calculate Halstead metrics per function
250
- - `assist complexity maintainability [pattern]` - Calculate maintainability index per file (`--ignore <glob>`, repeatable, excludes extra files on top of `complexity.ignore`)
250
+ - `assist complexity maintainability [pattern]` - Calculate maintainability index per file (`--ignore <glob>`, repeatable, excludes extra files on top of `complexity.ignore`). A file can declare its own threshold with a `// assist-maintainability-override: N` comment in its first ~10 lines (integer 0-100); that value replaces the global `--threshold` for that file only and the output annotates it with `(override: N)`
251
251
  - `assist complexity sloc [pattern]` - Count source lines of code per file
252
252
  - `assist transcript configure` - Configure transcript directories
253
253
  - `assist transcript format` - Convert VTT files to formatted markdown transcripts
package/claude/CLAUDE.md CHANGED
@@ -8,7 +8,7 @@ When renaming TypeScript files or symbols, use the refactor commands instead of
8
8
  - `assist refactor rename symbol <file> <oldName> <newName>` — rename a variable, function, class, or type across the project
9
9
  - `assist refactor extract <file> <functionName> <destination>` — extract a function and its private dependencies to a new file
10
10
  All default to dry-run; add `--apply` to execute.
11
- When using extract, name the destination file after the exported function (e.g. `updateWorkerCapacity.ts` for `updateWorkerCapacity`) to satisfy `useFilenamingConvention` lint rules.
11
+ When using extract, name the destination file after the exported function (e.g. `updateWorkerCapacity.ts` for `updateWorkerCapacity`) to satisfy the `oxlint-rules/filenameConvention.ts` lint rule.
12
12
 
13
13
  Do not modify `claude/settings.json` without asking the user first. Only read-only commands should be added to the allow list — write operations (add, remove, set, delete) must require confirmation.
14
14
 
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.320.0",
9
+ version: "0.322.0",
10
10
  type: "module",
11
11
  main: "dist/index.js",
12
12
  bin: {
@@ -1556,19 +1556,19 @@ function moveCaseInsensitive(absSource, absDest) {
1556
1556
  fs9.renameSync(absSource, tmp);
1557
1557
  fs9.renameSync(tmp, absDest);
1558
1558
  }
1559
- function applyMoves(project, moves, cwd, emit2) {
1559
+ function applyMoves(project, moves, cwd, emit4) {
1560
1560
  for (const { sourcePath, destPath } of moves) {
1561
1561
  const start3 = performance.now();
1562
1562
  const absSource = path15.resolve(sourcePath);
1563
1563
  const absDest = path15.resolve(destPath);
1564
1564
  for (const r of renameExports(project, absSource, absDest)) {
1565
- emit2(` Renamed export ${r} in ${sourcePath}`);
1565
+ emit4(` Renamed export ${r} in ${sourcePath}`);
1566
1566
  }
1567
1567
  const sourceFile = project.getSourceFile(absSource);
1568
1568
  if (sourceFile) sourceFile.move(absDest);
1569
1569
  const ms = (performance.now() - start3).toFixed(0);
1570
1570
  const rel = `${path15.relative(cwd, absSource)} \u2192 ${path15.relative(cwd, absDest)}`;
1571
- emit2(` Renamed ${rel} (${ms}ms)`);
1571
+ emit4(` Renamed ${rel} (${ms}ms)`);
1572
1572
  }
1573
1573
  project.saveSync();
1574
1574
  for (const { sourcePath, destPath } of moves) {
@@ -6728,6 +6728,44 @@ function installRestartMenu(options2 = {}) {
6728
6728
  return cleanup;
6729
6729
  }
6730
6730
 
6731
+ // src/commands/sessions/web/streamDaemonLogs.ts
6732
+ import { createInterface as createInterface3 } from "readline";
6733
+ var RECONNECT_MS = 3e3;
6734
+ function streamDaemonLogs() {
6735
+ void connect2();
6736
+ }
6737
+ async function connect2() {
6738
+ try {
6739
+ await ensureDaemonRunning("web log stream");
6740
+ const socket = await connectToDaemon();
6741
+ wire(socket);
6742
+ socket.write(`${JSON.stringify({ type: "subscribe-logs" })}
6743
+ `);
6744
+ } catch {
6745
+ scheduleReconnect();
6746
+ }
6747
+ }
6748
+ function wire(socket) {
6749
+ const lines = createInterface3({ input: socket });
6750
+ lines.on("error", () => {
6751
+ });
6752
+ lines.on("line", emit);
6753
+ socket.on("error", () => {
6754
+ });
6755
+ socket.on("close", scheduleReconnect);
6756
+ }
6757
+ function emit(line) {
6758
+ try {
6759
+ const data = JSON.parse(line);
6760
+ if (data.type === "log" && typeof data.line === "string")
6761
+ console.log(data.line);
6762
+ } catch {
6763
+ }
6764
+ }
6765
+ function scheduleReconnect() {
6766
+ setTimeout(() => void connect2(), RECONNECT_MS).unref();
6767
+ }
6768
+
6731
6769
  // src/commands/sessions/web/index.ts
6732
6770
  async function web(options2) {
6733
6771
  const port = Number.parseInt(options2.port, 10);
@@ -6754,6 +6792,7 @@ async function web(options2) {
6754
6792
  }
6755
6793
  });
6756
6794
  installRestartMenu();
6795
+ streamDaemonLogs();
6757
6796
  void ensureDaemonRunning("web server start").catch((error) => {
6758
6797
  console.error(
6759
6798
  chalk55.yellow(
@@ -9692,7 +9731,7 @@ import chalk95 from "chalk";
9692
9731
  function formatResultLine(entry, failing) {
9693
9732
  const { file, avgMaintainability, minMaintainability, override } = entry;
9694
9733
  const name = failing ? chalk95.red(file) : chalk95.white(file);
9695
- const suffix = failing && override !== void 0 ? chalk95.magenta(` (override threshold ${override})`) : "";
9734
+ const suffix = override !== void 0 ? chalk95.magenta(` (override: ${override})`) : "";
9696
9735
  return `${name} \u2192 avg: ${chalk95.cyan(avgMaintainability.toFixed(1))}, min: ${chalk95.yellow(minMaintainability.toFixed(1))}${suffix}`;
9697
9736
  }
9698
9737
 
@@ -9703,7 +9742,7 @@ function printMaintainabilityFailure(failingCount, threshold) {
9703
9742
  console.error(
9704
9743
  chalk96.red(
9705
9744
  `
9706
- Fail: ${failingCount} file(s) below threshold${thresholdLabel} (files marked "override threshold" were judged against their own marker). Maintainability index (0\u2013100) is derived from Halstead volume, cyclomatic complexity, and lines of code.
9745
+ Fail: ${failingCount} file(s) below threshold${thresholdLabel} (files marked "(override: N)" were judged against their own marker). Maintainability index (0\u2013100) is derived from Halstead volume, cyclomatic complexity, and lines of code.
9707
9746
 
9708
9747
  \u26A0\uFE0F ${chalk96.bold("Diagnose and fix one file at a time")} \u2014 do not investigate or fix multiple files in parallel.
9709
9748
 
@@ -9730,6 +9769,11 @@ Analyzed ${results.length} files`));
9730
9769
  } else {
9731
9770
  for (const entry of failing) console.log(formatResultLine(entry, true));
9732
9771
  }
9772
+ const passingOverrides = results.filter(
9773
+ (r) => r.override !== void 0 && !failing.includes(r)
9774
+ );
9775
+ for (const entry of passingOverrides)
9776
+ console.log(formatResultLine(entry, false));
9733
9777
  console.log(chalk97.dim(`
9734
9778
  Analyzed ${results.length} files`));
9735
9779
  if (failing.length > 0) {
@@ -11664,7 +11708,7 @@ function advisory(count6) {
11664
11708
  const noun = count6 === 1 ? "handover" : "handovers";
11665
11709
  return `${count6} unrecalled ${noun} for this repo. Run /recall to load.`;
11666
11710
  }
11667
- function emit(message) {
11711
+ function emit2(message) {
11668
11712
  const json = JSON.stringify({
11669
11713
  hookSpecificOutput: { hookEventName: "SessionStart" },
11670
11714
  systemMessage: message
@@ -11681,7 +11725,7 @@ async function load2(options2 = {}) {
11681
11725
  await migrateDiskHandovers(orm, origin, cwd);
11682
11726
  const count6 = await countPendingHandovers(orm, origin);
11683
11727
  if (count6 === 0) return null;
11684
- return emit(advisory(count6));
11728
+ return emit2(advisory(count6));
11685
11729
  }
11686
11730
 
11687
11731
  // src/commands/handover/listPendingHandovers.ts
@@ -12396,25 +12440,25 @@ function isVisibleText(t) {
12396
12440
  return /[a-zA-Z]{3,}/.test(t);
12397
12441
  }
12398
12442
  var isHashtag = (t) => /^#[A-Za-z0-9_]+$/.test(t);
12399
- function collectRscText(v, resolve17, sink, seen) {
12443
+ function collectRscText(v, resolve17, sink2, seen) {
12400
12444
  if (v == null) return;
12401
12445
  if (typeof v === "string") {
12402
12446
  if (isRscRef(v)) {
12403
12447
  if (!seen.has(v)) {
12404
12448
  seen.add(v);
12405
- collectRscText(resolve17(v), resolve17, sink, seen);
12449
+ collectRscText(resolve17(v), resolve17, sink2, seen);
12406
12450
  }
12407
- } else if (isHashtag(v)) sink.hashtags.push(v);
12408
- else if (isVisibleText(v)) sink.text.push(v);
12451
+ } else if (isHashtag(v)) sink2.hashtags.push(v);
12452
+ else if (isVisibleText(v)) sink2.text.push(v);
12409
12453
  return;
12410
12454
  }
12411
12455
  if (Array.isArray(v)) {
12412
- for (const x of v) collectRscText(x, resolve17, sink, seen);
12456
+ for (const x of v) collectRscText(x, resolve17, sink2, seen);
12413
12457
  return;
12414
12458
  }
12415
12459
  if (typeof v === "object") {
12416
12460
  for (const val of Object.values(v)) {
12417
- collectRscText(val, resolve17, sink, seen);
12461
+ collectRscText(val, resolve17, sink2, seen);
12418
12462
  }
12419
12463
  }
12420
12464
  }
@@ -12453,9 +12497,9 @@ function buildMentionMap(rows, resolve17) {
12453
12497
  if (!url || o.children == null) return;
12454
12498
  const slug = slugFromProfileUrl(url);
12455
12499
  if (!slug || map.has(slug)) return;
12456
- const sink = { text: [], hashtags: [] };
12457
- collectRscText(o.children, resolve17, sink, /* @__PURE__ */ new Set());
12458
- const name = sink.text.join(" ").replace(/\s+/g, " ").trim();
12500
+ const sink2 = { text: [], hashtags: [] };
12501
+ collectRscText(o.children, resolve17, sink2, /* @__PURE__ */ new Set());
12502
+ const name = sink2.text.join(" ").replace(/\s+/g, " ").trim();
12459
12503
  map.set(slug, name ? { slug, name, url } : { slug, url });
12460
12504
  });
12461
12505
  return map;
@@ -12556,8 +12600,8 @@ function walkPostRow(v, resolve17, raw) {
12556
12600
  if (a) raw.related.push(a[0]);
12557
12601
  }
12558
12602
  if (isCommentary(o)) {
12559
- const sink = { text: raw.text, hashtags: raw.hashtags };
12560
- collectRscText(o.children, resolve17, sink, /* @__PURE__ */ new Set());
12603
+ const sink2 = { text: raw.text, hashtags: raw.hashtags };
12604
+ collectRscText(o.children, resolve17, sink2, /* @__PURE__ */ new Set());
12561
12605
  }
12562
12606
  for (const val of Object.values(o)) walkPostRow(val, resolve17, raw);
12563
12607
  }
@@ -19980,7 +20024,7 @@ function listDaemonPids() {
19980
20024
  }
19981
20025
 
19982
20026
  // src/commands/sessions/daemon/queryDaemon.ts
19983
- import { createInterface as createInterface4 } from "readline";
20027
+ import { createInterface as createInterface5 } from "readline";
19984
20028
  var STATUS_TIMEOUT_MS = 5e3;
19985
20029
  function queryDaemon(socket) {
19986
20030
  socket.write(`${JSON.stringify({ type: "ping" })}
@@ -19989,7 +20033,7 @@ function queryDaemon(socket) {
19989
20033
  const result = { sessions: [] };
19990
20034
  const pending = /* @__PURE__ */ new Set(["sessions", "pong"]);
19991
20035
  const timer = setTimeout(() => resolve17(result), STATUS_TIMEOUT_MS);
19992
- const lines = createInterface4({ input: socket });
20036
+ const lines = createInterface5({ input: socket });
19993
20037
  lines.on("error", () => {
19994
20038
  });
19995
20039
  lines.on("line", (line) => {
@@ -20069,10 +20113,35 @@ function reportStrays(pids) {
20069
20113
  }
20070
20114
 
20071
20115
  // src/commands/sessions/daemon/drainDaemon.ts
20072
- import { createInterface as createInterface5 } from "readline";
20116
+ import { createInterface as createInterface6 } from "readline";
20073
20117
 
20074
20118
  // src/commands/sessions/daemon/loadPersistedSessions.ts
20075
20119
  import { z as z5 } from "zod";
20120
+
20121
+ // src/commands/sessions/daemon/daemonLog.ts
20122
+ var RING_CAPACITY = 1e3;
20123
+ var ring = [];
20124
+ var sink;
20125
+ function daemonLog(message) {
20126
+ emit3(`${(/* @__PURE__ */ new Date()).toISOString()} [${process.pid}] ${message}`);
20127
+ }
20128
+ function relayDaemonLog(line) {
20129
+ emit3(`[windows] ${line}`);
20130
+ }
20131
+ function emit3(line) {
20132
+ console.log(line);
20133
+ ring.push(line);
20134
+ if (ring.length > RING_CAPACITY) ring.shift();
20135
+ sink?.(line);
20136
+ }
20137
+ function setDaemonLogSink(next3) {
20138
+ sink = next3;
20139
+ }
20140
+ function recentDaemonLogLines() {
20141
+ return [...ring];
20142
+ }
20143
+
20144
+ // src/commands/sessions/daemon/loadPersistedSessions.ts
20076
20145
  var SESSIONS_FILE = "sessions.json";
20077
20146
  var persistedSessionSchema = z5.object({
20078
20147
  name: z5.string(),
@@ -20098,8 +20167,19 @@ function savePersistedSessions(sessions) {
20098
20167
  saveJson(SESSIONS_FILE, sessions);
20099
20168
  }
20100
20169
  function persistLiveSessions(sessions) {
20101
- savePersistedSessions(
20102
- [...sessions.values()].filter((s) => s.pty && s.status !== "done").map(toPersistedSession)
20170
+ const live = [...sessions.values()].filter(
20171
+ (s) => s.pty && s.status !== "done"
20172
+ );
20173
+ savePersistedSessions(live.map(toPersistedSession));
20174
+ logPersist(live);
20175
+ }
20176
+ var lastPersistSignature = "";
20177
+ function logPersist(live) {
20178
+ const signature = live.map((s) => `${s.id}:${s.status}`).join(",");
20179
+ if (signature === lastPersistSignature) return;
20180
+ lastPersistSignature = signature;
20181
+ daemonLog(
20182
+ live.length > 0 ? `persisted ${live.length} session(s): ${live.map((s) => s.name).join(", ")}` : "persisted 0 sessions (sessions.json cleared)"
20103
20183
  );
20104
20184
  }
20105
20185
  function toPersistedSession(session) {
@@ -20140,7 +20220,7 @@ function requestDrain(socket) {
20140
20220
  `);
20141
20221
  return new Promise((resolve17) => {
20142
20222
  const timer = setTimeout(() => resolve17(0), DRAIN_TIMEOUT_MS);
20143
- const lines = createInterface5({ input: socket });
20223
+ const lines = createInterface6({ input: socket });
20144
20224
  lines.on("error", () => {
20145
20225
  });
20146
20226
  lines.on("line", (line) => {
@@ -20173,11 +20253,6 @@ function createAutoExit(exit, graceMs = DEFAULT_GRACE_MS) {
20173
20253
  };
20174
20254
  }
20175
20255
 
20176
- // src/commands/sessions/daemon/daemonLog.ts
20177
- function daemonLog(message) {
20178
- console.log(`${(/* @__PURE__ */ new Date()).toISOString()} [${process.pid}] ${message}`);
20179
- }
20180
-
20181
20256
  // src/commands/sessions/daemon/loadActiveSelection.ts
20182
20257
  import { z as z6 } from "zod";
20183
20258
  var ACTIVE_FILE = "active.json";
@@ -20306,6 +20381,8 @@ var ClientHub = class extends Set {
20306
20381
  this.persistPeak = persistPeak;
20307
20382
  }
20308
20383
  latestLimits;
20384
+ // why: log delivery is opt-in so browser tabs aren't spammed; only connections that ask via subscribe-logs receive daemonLog lines.
20385
+ logSubscribers = /* @__PURE__ */ new Set();
20309
20386
  updateLimits(rateLimits) {
20310
20387
  this.latestLimits = rateLimits;
20311
20388
  broadcast(this, { type: "limits", rateLimits });
@@ -20316,6 +20393,21 @@ var ClientHub = class extends Set {
20316
20393
  sendTo(client, { type: "limits", rateLimits: this.latestLimits });
20317
20394
  }
20318
20395
  }
20396
+ subscribeLogs(client) {
20397
+ for (const line of recentDaemonLogLines()) {
20398
+ sendTo(client, { type: "log", line });
20399
+ }
20400
+ this.logSubscribers.add(client);
20401
+ }
20402
+ unsubscribeLogs(client) {
20403
+ this.logSubscribers.delete(client);
20404
+ }
20405
+ // why: bound so it can be handed to setDaemonLogSink as a bare reference.
20406
+ emitLog = (line) => {
20407
+ for (const client of this.logSubscribers) {
20408
+ sendTo(client, { type: "log", line });
20409
+ }
20410
+ };
20319
20411
  };
20320
20412
 
20321
20413
  // src/commands/sessions/daemon/spawnPty.ts
@@ -20384,6 +20476,29 @@ function createAssistSession(id2, assistArgs, cwd) {
20384
20476
  };
20385
20477
  }
20386
20478
 
20479
+ // src/commands/sessions/daemon/dismissSession.ts
20480
+ function dismissSession(sessions, id2) {
20481
+ const s = sessions.get(id2);
20482
+ if (!s) return false;
20483
+ if (s.status !== "done") s.pty?.kill();
20484
+ s.activityWatcher?.close();
20485
+ removeActivity(s.id);
20486
+ if (s.activity?.itemId != null) releaseLock(s.activity.itemId);
20487
+ sessions.delete(id2);
20488
+ daemonLog(`session ${id2} dismissed (${s.name})`);
20489
+ return true;
20490
+ }
20491
+ function drainSessions(sessions, onDrained) {
20492
+ const names = [...sessions.values()].map((s) => s.name);
20493
+ const ids = [...sessions.keys()];
20494
+ for (const id2 of ids) dismissSession(sessions, id2);
20495
+ onDrained();
20496
+ daemonLog(
20497
+ ids.length > 0 ? `drained ${ids.length} session(s): ${names.join(", ")}` : "drained 0 sessions"
20498
+ );
20499
+ return ids.length;
20500
+ }
20501
+
20387
20502
  // src/commands/sessions/daemon/createSession.ts
20388
20503
  import { randomUUID as randomUUID4 } from "crypto";
20389
20504
 
@@ -20458,6 +20573,13 @@ function greetClient(client, sessions, windowsProxy) {
20458
20573
  void windowsProxy.discover();
20459
20574
  }
20460
20575
 
20576
+ // src/commands/sessions/daemon/logSpawnedSession.ts
20577
+ function logSpawnedSession(session) {
20578
+ daemonLog(
20579
+ `session ${session.id} spawned: ${session.name} [${session.commandType}] ${session.cwd ?? ""}`
20580
+ );
20581
+ }
20582
+
20461
20583
  // src/commands/sessions/daemon/setStatus.ts
20462
20584
  function setStatus2(session, newStatus) {
20463
20585
  const now = Date.now();
@@ -20821,6 +20943,7 @@ function retrySession(session, clients, onStatusChange) {
20821
20943
  session.pty = respawn();
20822
20944
  broadcast(clients, { type: "clear", sessionId: session.id });
20823
20945
  wirePtyEvents(session, clients, onStatusChange);
20946
+ daemonLog(`session ${session.id} retried: ${session.name}`);
20824
20947
  return true;
20825
20948
  }
20826
20949
  function respawnThunk(session) {
@@ -20850,6 +20973,7 @@ function reuseSessionForRun(session, itemId, clients, onStatusChange) {
20850
20973
 
20851
20974
  // src/commands/sessions/daemon/shutdownSessions.ts
20852
20975
  function shutdownSessions(sessions) {
20976
+ daemonLog(`shutting down: killing ${sessions.size} session(s)`);
20853
20977
  for (const session of sessions.values()) {
20854
20978
  if (session.status !== "done") session.pty?.kill();
20855
20979
  }
@@ -20890,10 +21014,10 @@ async function isWindowsDaemonRunning() {
20890
21014
  import { spawn as spawn10 } from "child_process";
20891
21015
 
20892
21016
  // src/commands/sessions/daemon/logChildStream.ts
20893
- import { createInterface as createInterface6 } from "readline";
21017
+ import { createInterface as createInterface7 } from "readline";
20894
21018
  function logChildStream(stream, label2) {
20895
21019
  if (!stream) return;
20896
- const lines = createInterface6({ input: stream });
21020
+ const lines = createInterface7({ input: stream });
20897
21021
  lines.on("line", (line) => daemonLog(`[${label2}] ${line}`));
20898
21022
  lines.on("error", () => {
20899
21023
  });
@@ -21049,6 +21173,10 @@ var inbound = {
21049
21173
  created: handleCreated,
21050
21174
  sessions: handleSessions,
21051
21175
  output: handleOutput,
21176
+ // why: relay the Windows daemon's own log lines into this daemon's stream (ring + subscribers) so assist.log shows both daemons; not state.broadcast — browsers must not receive log spam.
21177
+ log: (_state, msg) => {
21178
+ if (typeof msg.line === "string") relayDaemonLog(msg.line);
21179
+ },
21052
21180
  clear: relayWithSessionId,
21053
21181
  error: (state, msg) => {
21054
21182
  daemonLog(`windows daemon: inbound error: ${msg.message}`);
@@ -21165,7 +21293,7 @@ function isWindowsIo(data) {
21165
21293
  }
21166
21294
 
21167
21295
  // src/commands/sessions/daemon/WindowsConnection.ts
21168
- import { createInterface as createInterface7 } from "readline";
21296
+ import { createInterface as createInterface8 } from "readline";
21169
21297
 
21170
21298
  // src/commands/sessions/daemon/LaunchCircuitBreaker.ts
21171
21299
  var MAX_FAILURES = 3;
@@ -21242,10 +21370,11 @@ var WindowsConnection = class {
21242
21370
  this.socket = socket;
21243
21371
  this.wire(socket);
21244
21372
  this.write(buildHello());
21373
+ this.write({ type: "subscribe-logs" });
21245
21374
  return socket;
21246
21375
  }
21247
21376
  wire(socket) {
21248
- const lines = createInterface7({ input: socket });
21377
+ const lines = createInterface8({ input: socket });
21249
21378
  lines.on("error", () => {
21250
21379
  });
21251
21380
  lines.on("line", (line) => this.deps.onLine(line));
@@ -21332,14 +21461,14 @@ var WindowsProxy = class {
21332
21461
  state;
21333
21462
  conn;
21334
21463
  healer;
21335
- constructor(clients, onSessionsChanged, connect3 = defaultConnect, heal = healWindowsDaemon) {
21464
+ constructor(clients, onSessionsChanged, connect4 = defaultConnect, heal = healWindowsDaemon) {
21336
21465
  this.state = createState(
21337
21466
  (msg) => broadcast(clients, msg),
21338
21467
  onSessionsChanged,
21339
21468
  (version2) => void this.healer.onMismatch(version2)
21340
21469
  );
21341
21470
  this.conn = new WindowsConnection({
21342
- connect: connect3,
21471
+ connect: connect4,
21343
21472
  onLine: (line) => handleInbound(this.state, line),
21344
21473
  onClose: () => this.handleClose()
21345
21474
  });
@@ -21400,6 +21529,7 @@ function setAutoRun(sessions, id2, enabled) {
21400
21529
  const s = sessions.get(id2);
21401
21530
  if (!s) return false;
21402
21531
  s.autoRun = enabled;
21532
+ daemonLog(`session ${id2} autorun ${enabled ? "on" : "off"}`);
21403
21533
  return true;
21404
21534
  }
21405
21535
  function setAutoAdvance(sessions, id2, enabled) {
@@ -21411,24 +21541,9 @@ function setAutoAdvance(sessions, id2, enabled) {
21411
21541
  if (enabled) clearPause(itemId);
21412
21542
  else requestPause(itemId);
21413
21543
  }
21544
+ daemonLog(`session ${id2} autoadvance ${enabled ? "on" : "off"}`);
21414
21545
  return true;
21415
21546
  }
21416
- function dismissSession(sessions, id2) {
21417
- const s = sessions.get(id2);
21418
- if (!s) return false;
21419
- if (s.status !== "done") s.pty?.kill();
21420
- s.activityWatcher?.close();
21421
- removeActivity(s.id);
21422
- if (s.activity?.itemId != null) releaseLock(s.activity.itemId);
21423
- sessions.delete(id2);
21424
- return true;
21425
- }
21426
- function drainSessions(sessions, onDrained) {
21427
- const ids = [...sessions.keys()];
21428
- for (const id2 of ids) dismissSession(sessions, id2);
21429
- onDrained();
21430
- return ids.length;
21431
- }
21432
21547
 
21433
21548
  // src/commands/sessions/daemon/SessionManager.ts
21434
21549
  var SessionManager = class {
@@ -21450,6 +21565,7 @@ var SessionManager = class {
21450
21565
  }
21451
21566
  removeClient(client) {
21452
21567
  this.clients.delete(client);
21568
+ this.clients.unsubscribeLogs(client);
21453
21569
  this.onIdleChange?.(this.isIdle());
21454
21570
  }
21455
21571
  isIdle = () => this.sessions.size === 0 && this.clients.size === 0;
@@ -21464,6 +21580,7 @@ var SessionManager = class {
21464
21580
  add(session) {
21465
21581
  this.wire(session);
21466
21582
  watchActivity(session, this.notify);
21583
+ logSpawnedSession(session);
21467
21584
  return session.id;
21468
21585
  }
21469
21586
  spawnWith = (create) => this.add(create(sessionLimits.nextId(this.sessions.size, this.idCounter)));
@@ -21529,7 +21646,7 @@ import { unlinkSync as unlinkSync19 } from "fs";
21529
21646
  import * as net3 from "net";
21530
21647
 
21531
21648
  // src/commands/sessions/daemon/handleConnection.ts
21532
- import { createInterface as createInterface8 } from "readline";
21649
+ import { createInterface as createInterface9 } from "readline";
21533
21650
 
21534
21651
  // src/commands/sessions/shared/discoverSessions.ts
21535
21652
  import * as fs31 from "fs";
@@ -21838,6 +21955,7 @@ function routed(local) {
21838
21955
  }
21839
21956
  var messageHandlers = {
21840
21957
  ping: (client) => sendTo(client, { type: "pong", pid: process.pid }),
21958
+ "subscribe-logs": (client, m) => m.clients.subscribeLogs(client),
21841
21959
  hello: (client) => sendTo(client, buildHello()),
21842
21960
  create: creator(
21843
21961
  true,
@@ -21906,7 +22024,7 @@ function handleConnection(socket, manager) {
21906
22024
  };
21907
22025
  manager.addClient(client);
21908
22026
  manager.clients.greet(client);
21909
- const lines = createInterface8({ input: socket });
22027
+ const lines = createInterface9({ input: socket });
21910
22028
  lines.on("error", () => {
21911
22029
  });
21912
22030
  lines.on("line", (line) => {
@@ -22042,7 +22160,9 @@ async function runDaemon() {
22042
22160
  daemonLog("no sessions and no connected server; exiting");
22043
22161
  process.exit(0);
22044
22162
  });
22045
- startDaemonServer(new SessionManager(checkAutoExit), checkAutoExit);
22163
+ const manager = new SessionManager(checkAutoExit);
22164
+ setDaemonLogSink(manager.clients.emitLog);
22165
+ startDaemonServer(manager, checkAutoExit);
22046
22166
  }
22047
22167
 
22048
22168
  // src/commands/sessions/daemon/registerDaemon.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@staff0rd/assist",
3
- "version": "0.320.0",
3
+ "version": "0.322.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "bin": {