@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 +1 -1
- package/claude/CLAUDE.md +1 -1
- package/dist/index.js +175 -55
- package/package.json +1 -1
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 `
|
|
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.
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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,
|
|
12449
|
+
collectRscText(resolve17(v), resolve17, sink2, seen);
|
|
12406
12450
|
}
|
|
12407
|
-
} else if (isHashtag(v))
|
|
12408
|
-
else if (isVisibleText(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,
|
|
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,
|
|
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
|
|
12457
|
-
collectRscText(o.children, resolve17,
|
|
12458
|
-
const name =
|
|
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
|
|
12560
|
-
collectRscText(o.children, resolve17,
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
20102
|
-
|
|
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 =
|
|
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
|
|
21017
|
+
import { createInterface as createInterface7 } from "readline";
|
|
20894
21018
|
function logChildStream(stream, label2) {
|
|
20895
21019
|
if (!stream) return;
|
|
20896
|
-
const lines =
|
|
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
|
|
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 =
|
|
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,
|
|
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:
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|