@staff0rd/assist 0.775.6 → 0.777.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 +2 -2
- package/claude/skills/review-ci/scripts/review.mjs +21 -21
- package/dist/commands/sessions/web/bundle.js +481 -481
- package/dist/index.js +348 -262
- 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.777.0",
|
|
10
10
|
type: "module",
|
|
11
11
|
main: "dist/index.js",
|
|
12
12
|
bin: {
|
|
@@ -1936,6 +1936,7 @@ var daemonPaths = {
|
|
|
1936
1936
|
socket: process.platform === "win32" ? perUserWindowsPipe() : join9(DAEMON_DIR, "daemon.sock"),
|
|
1937
1937
|
log: join9(DAEMON_DIR, "daemon.log"),
|
|
1938
1938
|
pid: join9(DAEMON_DIR, "daemon.pid"),
|
|
1939
|
+
autoUpdatePaused: join9(DAEMON_DIR, "auto-update-paused"),
|
|
1939
1940
|
spawnLock: join9(DAEMON_DIR, "spawn.lock"),
|
|
1940
1941
|
hooksSettings: join9(DAEMON_DIR, "hooks-settings.json")
|
|
1941
1942
|
};
|
|
@@ -11055,7 +11056,7 @@ var defaultDeps = {
|
|
|
11055
11056
|
httpsPort: tailnetHttpsPort
|
|
11056
11057
|
};
|
|
11057
11058
|
async function ensureTailscaleServe(port, deps2 = {}) {
|
|
11058
|
-
const { enabled, status: status2, serveStatus, serve, sleep:
|
|
11059
|
+
const { enabled, status: status2, serveStatus, serve, sleep: sleep2, httpsPort } = {
|
|
11059
11060
|
...defaultDeps,
|
|
11060
11061
|
...deps2
|
|
11061
11062
|
};
|
|
@@ -11070,7 +11071,7 @@ async function ensureTailscaleServe(port, deps2 = {}) {
|
|
|
11070
11071
|
return `serving ${plan2.target} at ${plan2.url}`;
|
|
11071
11072
|
} catch (error) {
|
|
11072
11073
|
if (attempt >= SERVE_WRITE_ATTEMPTS) throw error;
|
|
11073
|
-
await
|
|
11074
|
+
await sleep2(attempt * 500 + Math.random() * 500);
|
|
11074
11075
|
}
|
|
11075
11076
|
}
|
|
11076
11077
|
}
|
|
@@ -13009,7 +13010,7 @@ var nodesConfigHelp = [
|
|
|
13009
13010
|
{
|
|
13010
13011
|
key: "sessions.linkVersionCheck",
|
|
13011
13012
|
setter: "assist config set sessions.linkVersionCheck block -g",
|
|
13012
|
-
note: "reaction
|
|
13013
|
+
note: "reaction when a linked node's protocol range doesn't overlap this node's (app versions may differ; never restarts a peer): block (default) holds the link version-blocked until the older node is updated, warn proceeds, off skips the check"
|
|
13013
13014
|
}
|
|
13014
13015
|
];
|
|
13015
13016
|
|
|
@@ -13592,11 +13593,13 @@ function harnessCapabilities(_req, res) {
|
|
|
13592
13593
|
// src/commands/sessions/daemon/buildHello.ts
|
|
13593
13594
|
var ASSIST_VERSION = package_default.version;
|
|
13594
13595
|
var PROTOCOL_VERSION = 2;
|
|
13596
|
+
var MIN_PROTOCOL_VERSION = 1;
|
|
13595
13597
|
function buildHello(extra = {}) {
|
|
13596
13598
|
return {
|
|
13597
13599
|
type: "hello",
|
|
13598
13600
|
version: ASSIST_VERSION,
|
|
13599
13601
|
protocol: PROTOCOL_VERSION,
|
|
13602
|
+
minProtocol: MIN_PROTOCOL_VERSION,
|
|
13600
13603
|
nodeName: resolveNodeName(),
|
|
13601
13604
|
...extra
|
|
13602
13605
|
};
|
|
@@ -13604,15 +13607,26 @@ function buildHello(extra = {}) {
|
|
|
13604
13607
|
function isHello(msg) {
|
|
13605
13608
|
return msg.type === "hello" && typeof msg.version === "string" && (msg.protocol === void 0 || typeof msg.protocol === "number");
|
|
13606
13609
|
}
|
|
13607
|
-
function
|
|
13608
|
-
if (typeof msg.protocol
|
|
13609
|
-
|
|
13610
|
+
function peerProtocolRange(msg) {
|
|
13611
|
+
if (typeof msg.protocol !== "number") return void 0;
|
|
13612
|
+
const min = typeof msg.minProtocol === "number" ? Math.min(msg.minProtocol, msg.protocol) : msg.protocol;
|
|
13613
|
+
return { min, max: msg.protocol };
|
|
13610
13614
|
}
|
|
13611
|
-
function
|
|
13612
|
-
|
|
13615
|
+
function negotiateProtocol(msg) {
|
|
13616
|
+
const peer = peerProtocolRange(msg);
|
|
13617
|
+
if (!peer) return void 0;
|
|
13618
|
+
const common = Math.min(peer.max, PROTOCOL_VERSION);
|
|
13619
|
+
return common >= Math.max(peer.min, MIN_PROTOCOL_VERSION) ? common : void 0;
|
|
13613
13620
|
}
|
|
13614
|
-
function
|
|
13615
|
-
|
|
13621
|
+
function formatRange(range) {
|
|
13622
|
+
if (!range) return "legacy (no protocol)";
|
|
13623
|
+
return range.min === range.max ? `protocol ${range.max}` : `protocols ${range.min}\u2013${range.max}`;
|
|
13624
|
+
}
|
|
13625
|
+
function describeProtocolGap(node, msg) {
|
|
13626
|
+
const peer = peerProtocolRange(msg);
|
|
13627
|
+
const local = { min: MIN_PROTOCOL_VERSION, max: PROTOCOL_VERSION };
|
|
13628
|
+
const behind = peer && peer.min > local.max ? "this node" : node;
|
|
13629
|
+
return `no common protocol: ${node} ${msg.version} speaks ${formatRange(peer)}, this node ${ASSIST_VERSION} speaks ${formatRange(local)}; update ${behind} and restart it when convenient \u2014 the link reconnects once the ranges overlap`;
|
|
13616
13630
|
}
|
|
13617
13631
|
|
|
13618
13632
|
// src/commands/sessions/daemon/requestDaemonReply.ts
|
|
@@ -16194,19 +16208,17 @@ async function helloHop(spec, probes) {
|
|
|
16194
16208
|
`malformed hello: ${JSON.stringify(msg)}`,
|
|
16195
16209
|
`run \`assist update\` on ${spec.name}`
|
|
16196
16210
|
);
|
|
16197
|
-
const
|
|
16198
|
-
if (
|
|
16199
|
-
|
|
16211
|
+
const protocol = negotiateProtocol(msg);
|
|
16212
|
+
if (protocol !== void 0)
|
|
16213
|
+
return passed("ws", `hello ${msg.version} (protocol ${protocol})`);
|
|
16214
|
+
const gap = describeProtocolGap(spec.name, msg);
|
|
16200
16215
|
const mode = linkVersionCheck();
|
|
16201
16216
|
if (mode !== "block")
|
|
16202
|
-
return passed(
|
|
16203
|
-
"ws",
|
|
16204
|
-
`${mismatch}; allowed by sessions.linkVersionCheck=${mode}`
|
|
16205
|
-
);
|
|
16217
|
+
return passed("ws", `${gap}; allowed by sessions.linkVersionCheck=${mode}`);
|
|
16206
16218
|
return failed(
|
|
16207
16219
|
"ws",
|
|
16208
|
-
|
|
16209
|
-
`
|
|
16220
|
+
gap,
|
|
16221
|
+
`run \`assist update\` on the older node, then restart its daemon (\`assist daemon restart\`) when convenient`
|
|
16210
16222
|
);
|
|
16211
16223
|
}
|
|
16212
16224
|
|
|
@@ -16228,7 +16240,11 @@ function linkStateHop(spec, probes) {
|
|
|
16228
16240
|
if (status2.state === "connected") return passed("link", "connected");
|
|
16229
16241
|
const error = status2.error ?? status2.state;
|
|
16230
16242
|
if (status2.state === "version-blocked")
|
|
16231
|
-
return failed(
|
|
16243
|
+
return failed(
|
|
16244
|
+
"link",
|
|
16245
|
+
error,
|
|
16246
|
+
"no common protocol \u2014 update the older node and restart its daemon when convenient; the link retries every minute"
|
|
16247
|
+
);
|
|
16232
16248
|
return failed(
|
|
16233
16249
|
"link",
|
|
16234
16250
|
error,
|
|
@@ -17206,6 +17222,33 @@ async function updates(_req, res) {
|
|
|
17206
17222
|
respondJson(res, 200, status2);
|
|
17207
17223
|
}
|
|
17208
17224
|
|
|
17225
|
+
// src/commands/sessions/shared/AutoUpdateAction.ts
|
|
17226
|
+
var AUTO_UPDATE_ACTIONS = ["pause", "resume", "check"];
|
|
17227
|
+
|
|
17228
|
+
// src/commands/sessions/web/updates/updatesControl.ts
|
|
17229
|
+
async function updatesControl(req, res) {
|
|
17230
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
17231
|
+
const action = url.searchParams.get("action");
|
|
17232
|
+
if (!AUTO_UPDATE_ACTIONS.includes(action)) {
|
|
17233
|
+
respondJson(res, 400, { error: "Invalid action" });
|
|
17234
|
+
return;
|
|
17235
|
+
}
|
|
17236
|
+
console.log(`auto-update ${action} ${linkedRequester(req)}`);
|
|
17237
|
+
const reply2 = await requestDaemonReply(
|
|
17238
|
+
{ type: "auto-update-control", action },
|
|
17239
|
+
"auto-update-control"
|
|
17240
|
+
);
|
|
17241
|
+
if (!reply2) {
|
|
17242
|
+
respondJson(res, 503, { error: "Daemon not reachable" });
|
|
17243
|
+
return;
|
|
17244
|
+
}
|
|
17245
|
+
if (!reply2.ok) {
|
|
17246
|
+
respondJson(res, 409, { error: reply2.error ?? `${action} failed` });
|
|
17247
|
+
return;
|
|
17248
|
+
}
|
|
17249
|
+
respondJson(res, 200, { ok: true });
|
|
17250
|
+
}
|
|
17251
|
+
|
|
17209
17252
|
// src/commands/sessions/web/readRequestBuffer.ts
|
|
17210
17253
|
async function readRequestBuffer(req, limit) {
|
|
17211
17254
|
const chunks = [];
|
|
@@ -17479,7 +17522,8 @@ var routes2 = {
|
|
|
17479
17522
|
"POST /api/config/set": setConfig,
|
|
17480
17523
|
"POST /api/config/unset": unsetConfig,
|
|
17481
17524
|
"GET /api/review/synthesis": getReviewSynthesis,
|
|
17482
|
-
"GET /api/updates": updates
|
|
17525
|
+
"GET /api/updates": updates,
|
|
17526
|
+
"POST /api/updates/control": updatesControl
|
|
17483
17527
|
};
|
|
17484
17528
|
var handleRequest = createFallbackHandler(
|
|
17485
17529
|
routes2,
|
|
@@ -39995,15 +40039,20 @@ function gitFailureReason(error) {
|
|
|
39995
40039
|
return message3.trim() || "git failed without reporting a reason";
|
|
39996
40040
|
}
|
|
39997
40041
|
|
|
39998
|
-
// src/commands/watch/
|
|
40042
|
+
// src/commands/watch/gitMarkerPath.ts
|
|
39999
40043
|
import { resolve as resolve25 } from "path";
|
|
40000
|
-
function
|
|
40044
|
+
function gitMarkerPath(name, cwd) {
|
|
40001
40045
|
return resolve25(
|
|
40002
40046
|
cwd ?? process.cwd(),
|
|
40003
|
-
runGit3(["rev-parse", "--git-path",
|
|
40047
|
+
runGit3(["rev-parse", "--git-path", name], cwd)
|
|
40004
40048
|
);
|
|
40005
40049
|
}
|
|
40006
40050
|
|
|
40051
|
+
// src/commands/watch/simulatedDivergencePath.ts
|
|
40052
|
+
function simulatedDivergencePath(cwd) {
|
|
40053
|
+
return gitMarkerPath("assist-simulate-divergence", cwd);
|
|
40054
|
+
}
|
|
40055
|
+
|
|
40007
40056
|
// src/commands/watch/simulateDivergence.ts
|
|
40008
40057
|
function simulateDivergence() {
|
|
40009
40058
|
try {
|
|
@@ -40511,11 +40560,10 @@ ${buildWatchReport(from)}`);
|
|
|
40511
40560
|
process.exit(0);
|
|
40512
40561
|
}
|
|
40513
40562
|
|
|
40514
|
-
// src/commands/watch/
|
|
40563
|
+
// src/commands/watch/consumeMarker.ts
|
|
40515
40564
|
import { existsSync as existsSync81, rmSync as rmSync8 } from "fs";
|
|
40516
|
-
function
|
|
40565
|
+
function consumeMarker(path106) {
|
|
40517
40566
|
try {
|
|
40518
|
-
const path106 = simulatedDivergencePath(cwd);
|
|
40519
40567
|
if (!existsSync81(path106)) return false;
|
|
40520
40568
|
rmSync8(path106);
|
|
40521
40569
|
return true;
|
|
@@ -40524,6 +40572,15 @@ function consumeSimulatedDivergence(cwd) {
|
|
|
40524
40572
|
}
|
|
40525
40573
|
}
|
|
40526
40574
|
|
|
40575
|
+
// src/commands/watch/consumeSimulatedDivergence.ts
|
|
40576
|
+
function consumeSimulatedDivergence(cwd) {
|
|
40577
|
+
try {
|
|
40578
|
+
return consumeMarker(simulatedDivergencePath(cwd));
|
|
40579
|
+
} catch {
|
|
40580
|
+
return false;
|
|
40581
|
+
}
|
|
40582
|
+
}
|
|
40583
|
+
|
|
40527
40584
|
// src/commands/watch/fetchQuietly.ts
|
|
40528
40585
|
import { execFileSync as execFileSync21 } from "child_process";
|
|
40529
40586
|
var MIN_FETCH_TIMEOUT_MS = 6e4;
|
|
@@ -40558,26 +40615,33 @@ function readMovement(cwd) {
|
|
|
40558
40615
|
}
|
|
40559
40616
|
|
|
40560
40617
|
// src/commands/watch/pollForMovement.ts
|
|
40618
|
+
var CONTROL_POLL_MS = 1e3;
|
|
40561
40619
|
function pollForMovement(options2) {
|
|
40562
|
-
const { upstream, intervalMs, timeoutMs, timeout, cwd } = options2;
|
|
40620
|
+
const { upstream, intervalMs, timeoutMs, timeout, cwd, control } = options2;
|
|
40563
40621
|
return new Promise((resolve28) => {
|
|
40564
40622
|
let settled = false;
|
|
40565
40623
|
const finish2 = (outcome) => {
|
|
40566
40624
|
if (settled) return;
|
|
40567
40625
|
settled = true;
|
|
40568
40626
|
clearInterval(ticker);
|
|
40627
|
+
clearInterval(controller);
|
|
40569
40628
|
clearTimeout(deadline);
|
|
40570
40629
|
process.off("SIGINT", onInterrupt);
|
|
40571
40630
|
resolve28(outcome);
|
|
40572
40631
|
};
|
|
40573
40632
|
const onInterrupt = () => finish2({ kind: "interrupted" });
|
|
40574
|
-
const
|
|
40633
|
+
const check2 = () => {
|
|
40575
40634
|
if (consumeSimulatedDivergence(cwd))
|
|
40576
40635
|
return finish2({ kind: "simulated-divergence", upstream });
|
|
40577
40636
|
fetchQuietly(cwd, intervalMs);
|
|
40578
40637
|
const found = readMovement(cwd);
|
|
40579
40638
|
if (found) finish2({ kind: "moved", upstream, ...found });
|
|
40580
|
-
}
|
|
40639
|
+
};
|
|
40640
|
+
const ticker = setInterval(check2, intervalMs);
|
|
40641
|
+
const controller = control && setInterval(() => {
|
|
40642
|
+
if (consumeMarker(control.stop)) return finish2({ kind: "interrupted" });
|
|
40643
|
+
if (consumeMarker(control.check)) check2();
|
|
40644
|
+
}, CONTROL_POLL_MS);
|
|
40581
40645
|
const deadline = timeoutMs === void 0 ? void 0 : setTimeout(
|
|
40582
40646
|
() => finish2({ kind: "timeout", upstream, timeout }),
|
|
40583
40647
|
timeoutMs
|
|
@@ -40586,6 +40650,18 @@ function pollForMovement(options2) {
|
|
|
40586
40650
|
});
|
|
40587
40651
|
}
|
|
40588
40652
|
|
|
40653
|
+
// src/commands/watch/watchControlPaths.ts
|
|
40654
|
+
function watchControlPaths(cwd) {
|
|
40655
|
+
try {
|
|
40656
|
+
return {
|
|
40657
|
+
check: gitMarkerPath("assist-watch-check", cwd),
|
|
40658
|
+
stop: gitMarkerPath("assist-watch-stop", cwd)
|
|
40659
|
+
};
|
|
40660
|
+
} catch {
|
|
40661
|
+
return void 0;
|
|
40662
|
+
}
|
|
40663
|
+
}
|
|
40664
|
+
|
|
40589
40665
|
// src/commands/watch/waitForUpstream.ts
|
|
40590
40666
|
function waitForUpstream(options2) {
|
|
40591
40667
|
const { intervalMs, timeoutMs, timeout, cwd, onStart } = options2;
|
|
@@ -40601,10 +40677,20 @@ function waitForUpstream(options2) {
|
|
|
40601
40677
|
onStart?.(upstream);
|
|
40602
40678
|
if (consumeSimulatedDivergence(cwd))
|
|
40603
40679
|
return Promise.resolve({ kind: "simulated-divergence", upstream });
|
|
40680
|
+
const control = watchControlPaths(cwd);
|
|
40604
40681
|
fetchQuietly(cwd, intervalMs);
|
|
40682
|
+
if (control && consumeMarker(control.stop))
|
|
40683
|
+
return Promise.resolve({ kind: "interrupted" });
|
|
40605
40684
|
const moved = readMovement(cwd);
|
|
40606
40685
|
if (moved) return Promise.resolve({ kind: "moved", upstream, ...moved });
|
|
40607
|
-
return pollForMovement({
|
|
40686
|
+
return pollForMovement({
|
|
40687
|
+
upstream,
|
|
40688
|
+
intervalMs,
|
|
40689
|
+
timeoutMs,
|
|
40690
|
+
timeout,
|
|
40691
|
+
cwd,
|
|
40692
|
+
control
|
|
40693
|
+
});
|
|
40608
40694
|
}
|
|
40609
40695
|
|
|
40610
40696
|
// src/commands/watch/watchWait.ts
|
|
@@ -42121,6 +42207,39 @@ function createRunSession(id, { runName, runArgs, cwd, meta }) {
|
|
|
42121
42207
|
};
|
|
42122
42208
|
}
|
|
42123
42209
|
|
|
42210
|
+
// src/commands/sessions/daemon/autoUpdate/autoUpdateControl.ts
|
|
42211
|
+
import { existsSync as existsSync86, rmSync as rmSync9, writeFileSync as writeFileSync54 } from "fs";
|
|
42212
|
+
var paused;
|
|
42213
|
+
var wake;
|
|
42214
|
+
function persist2(value) {
|
|
42215
|
+
try {
|
|
42216
|
+
if (value) writeFileSync54(daemonPaths.autoUpdatePaused, "");
|
|
42217
|
+
else rmSync9(daemonPaths.autoUpdatePaused, { force: true });
|
|
42218
|
+
} catch {
|
|
42219
|
+
}
|
|
42220
|
+
}
|
|
42221
|
+
var autoUpdateControl = {
|
|
42222
|
+
paused: () => {
|
|
42223
|
+
paused ??= existsSync86(daemonPaths.autoUpdatePaused);
|
|
42224
|
+
return paused;
|
|
42225
|
+
},
|
|
42226
|
+
setPaused: (value) => {
|
|
42227
|
+
paused = value;
|
|
42228
|
+
persist2(value);
|
|
42229
|
+
wake?.();
|
|
42230
|
+
},
|
|
42231
|
+
sleep: (ms) => new Promise((resolve28) => {
|
|
42232
|
+
const done2 = () => {
|
|
42233
|
+
clearTimeout(timer);
|
|
42234
|
+
if (wake === done2) wake = void 0;
|
|
42235
|
+
resolve28();
|
|
42236
|
+
};
|
|
42237
|
+
const timer = setTimeout(done2, ms);
|
|
42238
|
+
wake = done2;
|
|
42239
|
+
}),
|
|
42240
|
+
wake: () => wake?.()
|
|
42241
|
+
};
|
|
42242
|
+
|
|
42124
42243
|
// src/commands/sessions/daemon/autoUpdate/autoUpdateEnabled.ts
|
|
42125
42244
|
function autoUpdateEnabled(installDir) {
|
|
42126
42245
|
try {
|
|
@@ -42151,6 +42270,14 @@ var autoUpdateState = {
|
|
|
42151
42270
|
}
|
|
42152
42271
|
};
|
|
42153
42272
|
|
|
42273
|
+
// src/commands/sessions/daemon/autoUpdate/backOff.ts
|
|
42274
|
+
var RETRY_AFTER_FAILURE_MS = 5 * 60 * 1e3;
|
|
42275
|
+
async function backOff(reason4, deps2) {
|
|
42276
|
+
deps2.enter("retrying", { reason: reason4 });
|
|
42277
|
+
deps2.note(`${reason4}; retrying in ${RETRY_AFTER_FAILURE_MS / 6e4}m`);
|
|
42278
|
+
await deps2.sleep(RETRY_AFTER_FAILURE_MS);
|
|
42279
|
+
}
|
|
42280
|
+
|
|
42154
42281
|
// src/commands/sessions/daemon/autoUpdate/holdForEscalation.ts
|
|
42155
42282
|
var ESCALATION_POLL_MS = 5e3;
|
|
42156
42283
|
async function holdForEscalation(sessionId, deps2) {
|
|
@@ -42171,50 +42298,47 @@ async function runLapCapturingTail(deps2) {
|
|
|
42171
42298
|
return { end, tail };
|
|
42172
42299
|
}
|
|
42173
42300
|
|
|
42174
|
-
// src/commands/sessions/daemon/autoUpdate/
|
|
42301
|
+
// src/commands/sessions/daemon/autoUpdate/settleLap.ts
|
|
42175
42302
|
var DIVERGED_EXIT_CODE = 3;
|
|
42176
|
-
|
|
42303
|
+
async function settleLap(lap, { end, tail }, deps2) {
|
|
42304
|
+
const how = end.signal ? `killed by ${end.signal}` : `exited ${end.code}`;
|
|
42305
|
+
if (end.code === DIVERGED_EXIT_CODE) {
|
|
42306
|
+
const held = deps2.escalate(tail);
|
|
42307
|
+
deps2.note(`lap ${lap} ${how} (divergence); escalated to session ${held}`);
|
|
42308
|
+
return held;
|
|
42309
|
+
}
|
|
42310
|
+
if (deps2.paused()) deps2.note(`lap ${lap} ${how}; paused`);
|
|
42311
|
+
else if (decideLap(end, false).kind === "relaunch")
|
|
42312
|
+
deps2.note(`lap ${lap} ${how}; relaunching`);
|
|
42313
|
+
else await backOff(`lap ${lap} ${how}`, deps2);
|
|
42314
|
+
return void 0;
|
|
42315
|
+
}
|
|
42316
|
+
|
|
42317
|
+
// src/commands/sessions/daemon/autoUpdate/runAutoUpdateLoop.ts
|
|
42318
|
+
var PAUSE_POLL_MS = 60 * 1e3;
|
|
42177
42319
|
async function runAutoUpdateLoop(deps2, maxLaps = Number.POSITIVE_INFINITY) {
|
|
42178
42320
|
let held = deps2.liveEscalation();
|
|
42179
42321
|
for (let lap = 1; lap <= maxLaps; lap++) {
|
|
42180
42322
|
if (held) await holdForEscalation(held, deps2);
|
|
42181
42323
|
held = void 0;
|
|
42324
|
+
while (deps2.paused()) await deps2.sleep(PAUSE_POLL_MS);
|
|
42182
42325
|
deps2.enter("waiting");
|
|
42183
42326
|
deps2.note(
|
|
42184
42327
|
`lap ${lap} at ${(/* @__PURE__ */ new Date()).toLocaleString()}: assist ${WATCH_LAP_ARGS.join(" ")}`
|
|
42185
42328
|
);
|
|
42186
|
-
let result;
|
|
42187
42329
|
try {
|
|
42188
|
-
|
|
42330
|
+
held = await settleLap(lap, await runLapCapturingTail(deps2), deps2);
|
|
42189
42331
|
} catch (error) {
|
|
42190
42332
|
await backOff(
|
|
42191
42333
|
`lap ${lap} could not start (${error instanceof Error ? error.message : String(error)})`,
|
|
42192
42334
|
deps2
|
|
42193
42335
|
);
|
|
42194
|
-
continue;
|
|
42195
|
-
}
|
|
42196
|
-
const { end, tail } = result;
|
|
42197
|
-
const how = end.signal ? `killed by ${end.signal}` : `exited ${end.code}`;
|
|
42198
|
-
if (end.code === DIVERGED_EXIT_CODE) {
|
|
42199
|
-
held = deps2.escalate(tail);
|
|
42200
|
-
deps2.note(`lap ${lap} ${how} (divergence); escalated to session ${held}`);
|
|
42201
|
-
continue;
|
|
42202
|
-
}
|
|
42203
|
-
if (decideLap(end, false).kind === "relaunch") {
|
|
42204
|
-
deps2.note(`lap ${lap} ${how}; relaunching`);
|
|
42205
|
-
continue;
|
|
42206
42336
|
}
|
|
42207
|
-
await backOff(`lap ${lap} ${how}`, deps2);
|
|
42208
42337
|
}
|
|
42209
42338
|
}
|
|
42210
|
-
async function backOff(reason4, deps2) {
|
|
42211
|
-
deps2.enter("retrying", { reason: reason4 });
|
|
42212
|
-
deps2.note(`${reason4}; retrying in ${RETRY_AFTER_FAILURE_MS / 6e4}m`);
|
|
42213
|
-
await deps2.sleep(RETRY_AFTER_FAILURE_MS);
|
|
42214
|
-
}
|
|
42215
42339
|
|
|
42216
42340
|
// src/commands/sessions/daemon/autoUpdate/trimWatcherLog.ts
|
|
42217
|
-
import { readFileSync as readFileSync65, writeFileSync as
|
|
42341
|
+
import { readFileSync as readFileSync65, writeFileSync as writeFileSync55 } from "fs";
|
|
42218
42342
|
var KEEP_TAIL = 256 * 1024;
|
|
42219
42343
|
var MAX_LOG_SIZE = 4 * 1024 * 1024;
|
|
42220
42344
|
function trimWatcherLog(cwd) {
|
|
@@ -42226,20 +42350,10 @@ function trimWatcherLog(cwd) {
|
|
|
42226
42350
|
return;
|
|
42227
42351
|
}
|
|
42228
42352
|
if (log2.length <= MAX_LOG_SIZE) return;
|
|
42229
|
-
|
|
42353
|
+
writeFileSync55(path106, log2.slice(-KEEP_TAIL));
|
|
42230
42354
|
daemonLog(`auto-update: log ${path106} trimmed to its last ${KEEP_TAIL} chars`);
|
|
42231
42355
|
}
|
|
42232
42356
|
|
|
42233
|
-
// src/commands/sessions/daemon/autoUpdate/loopDeps.ts
|
|
42234
|
-
import { setTimeout as sleep2 } from "timers/promises";
|
|
42235
|
-
|
|
42236
|
-
// src/commands/sessions/daemon/watcherNote.ts
|
|
42237
|
-
function watcherNote(text17) {
|
|
42238
|
-
return `\r
|
|
42239
|
-
\x1B[2m\u2500\u2500 ${text17} \u2500\u2500\x1B[0m\r
|
|
42240
|
-
`;
|
|
42241
|
-
}
|
|
42242
|
-
|
|
42243
42357
|
// src/commands/sessions/daemon/worktree/appendWatcherLog.ts
|
|
42244
42358
|
import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync34 } from "fs";
|
|
42245
42359
|
import { dirname as dirname42 } from "path";
|
|
@@ -42409,9 +42523,30 @@ function lineRecorder(onLine) {
|
|
|
42409
42523
|
};
|
|
42410
42524
|
}
|
|
42411
42525
|
|
|
42526
|
+
// src/commands/sessions/daemon/watcherNote.ts
|
|
42527
|
+
function watcherNote(text17) {
|
|
42528
|
+
return `\r
|
|
42529
|
+
\x1B[2m\u2500\u2500 ${text17} \u2500\u2500\x1B[0m\r
|
|
42530
|
+
`;
|
|
42531
|
+
}
|
|
42532
|
+
|
|
42533
|
+
// src/commands/sessions/daemon/autoUpdate/noteAutoUpdate.ts
|
|
42534
|
+
function noteAutoUpdate(installDir, text17) {
|
|
42535
|
+
appendWatcherLog(installDir, watcherNote(text17));
|
|
42536
|
+
daemonLog(`auto-update: ${text17}`);
|
|
42537
|
+
}
|
|
42538
|
+
|
|
42412
42539
|
// src/commands/sessions/daemon/autoUpdate/runUpdateLap.ts
|
|
42413
42540
|
import { spawn as spawn11 } from "child_process";
|
|
42541
|
+
import { rmSync as rmSync10 } from "fs";
|
|
42542
|
+
function clearLapSignals(cwd) {
|
|
42543
|
+
const control = watchControlPaths(cwd);
|
|
42544
|
+
if (!control) return;
|
|
42545
|
+
for (const path106 of [control.check, control.stop])
|
|
42546
|
+
rmSync10(path106, { force: true });
|
|
42547
|
+
}
|
|
42414
42548
|
function runUpdateLap(cwd, onOutput) {
|
|
42549
|
+
clearLapSignals(cwd);
|
|
42415
42550
|
return new Promise((resolve28, reject) => {
|
|
42416
42551
|
const { ASSIST_ACTIVITY_ID: _activity, ...env } = process.env;
|
|
42417
42552
|
const child = spawn11(
|
|
@@ -42451,10 +42586,7 @@ function loopDeps(installDir, ctx) {
|
|
|
42451
42586
|
appendWatcherLog(installDir, text17);
|
|
42452
42587
|
logLine(text17);
|
|
42453
42588
|
},
|
|
42454
|
-
note: (text17) =>
|
|
42455
|
-
appendWatcherLog(installDir, watcherNote(text17));
|
|
42456
|
-
daemonLog(`auto-update: ${text17}`);
|
|
42457
|
-
},
|
|
42589
|
+
note: (text17) => noteAutoUpdate(installDir, text17),
|
|
42458
42590
|
enter: autoUpdateState.enter,
|
|
42459
42591
|
liveEscalation: () => liveEscalationIn(ctx().sessions, clone)?.id,
|
|
42460
42592
|
escalate: (output) => escalateDivergence(ctx(), installDir, output),
|
|
@@ -42462,7 +42594,8 @@ function loopDeps(installDir, ctx) {
|
|
|
42462
42594
|
const status2 = ctx().sessions.get(sessionId)?.status;
|
|
42463
42595
|
return status2 !== void 0 && status2 !== "done" && status2 !== "error";
|
|
42464
42596
|
},
|
|
42465
|
-
|
|
42597
|
+
paused: autoUpdateControl.paused,
|
|
42598
|
+
sleep: autoUpdateControl.sleep
|
|
42466
42599
|
};
|
|
42467
42600
|
}
|
|
42468
42601
|
|
|
@@ -42483,7 +42616,9 @@ function startAutoUpdate(ctx) {
|
|
|
42483
42616
|
return;
|
|
42484
42617
|
}
|
|
42485
42618
|
trimWatcherLog(installDir);
|
|
42486
|
-
daemonLog(
|
|
42619
|
+
daemonLog(
|
|
42620
|
+
autoUpdateControl.paused() ? `auto-update: paused for ${installDir}; resume it from the Updates page` : `auto-update: pulling and building ${installDir} as origin moves`
|
|
42621
|
+
);
|
|
42487
42622
|
void runAutoUpdateLoop(loopDeps(installDir, ctx)).catch(
|
|
42488
42623
|
(error) => daemonLog(
|
|
42489
42624
|
`auto-update: loop stopped: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -42512,11 +42647,11 @@ function setStatus2(session, newStatus) {
|
|
|
42512
42647
|
}
|
|
42513
42648
|
|
|
42514
42649
|
// src/commands/sessions/daemon/worktree/reapWorktree.ts
|
|
42515
|
-
import { existsSync as
|
|
42650
|
+
import { existsSync as existsSync88 } from "fs";
|
|
42516
42651
|
import { basename as basename30 } from "path";
|
|
42517
42652
|
|
|
42518
42653
|
// src/commands/sessions/daemon/worktree/deleteStrandedTree.ts
|
|
42519
|
-
import { existsSync as
|
|
42654
|
+
import { existsSync as existsSync87 } from "fs";
|
|
42520
42655
|
import { join as join103 } from "path";
|
|
42521
42656
|
|
|
42522
42657
|
// src/commands/sessions/daemon/worktree/deleteTreeDirectly.ts
|
|
@@ -42586,7 +42721,7 @@ async function deleteStrandedTree(clone, worktreePath, cause) {
|
|
|
42586
42721
|
);
|
|
42587
42722
|
}
|
|
42588
42723
|
function strandedReason(worktreePath, cause) {
|
|
42589
|
-
if (!
|
|
42724
|
+
if (!existsSync87(join103(worktreePath, ".git")))
|
|
42590
42725
|
return "its .git link is already gone";
|
|
42591
42726
|
if (/not a working tree|not a git repository/i.test(reason2(cause)))
|
|
42592
42727
|
return "git no longer recognises it as a working tree";
|
|
@@ -42638,7 +42773,7 @@ function reason3(error) {
|
|
|
42638
42773
|
|
|
42639
42774
|
// src/commands/sessions/daemon/worktree/reapWorktree.ts
|
|
42640
42775
|
async function reapWorktree(worktreePath, force = false) {
|
|
42641
|
-
if (!
|
|
42776
|
+
if (!existsSync88(worktreePath)) {
|
|
42642
42777
|
forgetWorktree(worktreePath);
|
|
42643
42778
|
daemonLog(
|
|
42644
42779
|
`worktree ${worktreePath} already gone; its record was forgotten`
|
|
@@ -42663,7 +42798,7 @@ async function reapWorktree(worktreePath, force = false) {
|
|
|
42663
42798
|
}
|
|
42664
42799
|
function owningClone(worktreePath) {
|
|
42665
42800
|
const recorded = worktreeAttributionIncludingReaped(worktreePath)?.clone;
|
|
42666
|
-
if (recorded &&
|
|
42801
|
+
if (recorded && existsSync88(recorded)) return recorded;
|
|
42667
42802
|
const detected = mainWorktree(worktreePath);
|
|
42668
42803
|
if (detected) return detected;
|
|
42669
42804
|
daemonLog(
|
|
@@ -42866,12 +43001,12 @@ async function closeBlockReason(sessionId, tree) {
|
|
|
42866
43001
|
}
|
|
42867
43002
|
|
|
42868
43003
|
// src/commands/sessions/daemon/worktree/watchGitState.ts
|
|
42869
|
-
import { existsSync as
|
|
43004
|
+
import { existsSync as existsSync89, watch } from "fs";
|
|
42870
43005
|
var DEBOUNCE_MS = 500;
|
|
42871
43006
|
var POLL_MS2 = 3e4;
|
|
42872
43007
|
function watchGitState(cwd, onChange) {
|
|
42873
43008
|
const common = gitCommonDir(cwd);
|
|
42874
|
-
if (!common || !
|
|
43009
|
+
if (!common || !existsSync89(common)) return void 0;
|
|
42875
43010
|
const watchers = [
|
|
42876
43011
|
watchGitDir(common, onChange),
|
|
42877
43012
|
pollGitState(cwd, onChange)
|
|
@@ -43418,10 +43553,10 @@ function emitSessionOutput(session, clients, data) {
|
|
|
43418
43553
|
}
|
|
43419
43554
|
|
|
43420
43555
|
// src/commands/sessions/daemon/exitReason.ts
|
|
43421
|
-
import { existsSync as
|
|
43556
|
+
import { existsSync as existsSync90 } from "fs";
|
|
43422
43557
|
import { resolve as resolve27 } from "path";
|
|
43423
43558
|
function exitDetail(session) {
|
|
43424
|
-
if (session.cwd && !
|
|
43559
|
+
if (session.cwd && !existsSync90(session.cwd))
|
|
43425
43560
|
return `working directory ${session.cwd} no longer exists`;
|
|
43426
43561
|
return missingRunConfigCwd(session);
|
|
43427
43562
|
}
|
|
@@ -43435,7 +43570,7 @@ function missingRunConfigCwd(session) {
|
|
|
43435
43570
|
const config = resolveRunConfig(session.runName, dir);
|
|
43436
43571
|
if (!config?.cwd) return void 0;
|
|
43437
43572
|
const configured = resolve27(runConfigBaseDirFrom(dir), config.cwd);
|
|
43438
|
-
if (
|
|
43573
|
+
if (existsSync90(configured)) return void 0;
|
|
43439
43574
|
return `run config "${config.name}": cwd ${configured} does not exist`;
|
|
43440
43575
|
}
|
|
43441
43576
|
|
|
@@ -43483,7 +43618,7 @@ function handleRefusedRun(session, exitCode, onStatusChange) {
|
|
|
43483
43618
|
}
|
|
43484
43619
|
|
|
43485
43620
|
// src/commands/sessions/daemon/watchActivity.ts
|
|
43486
|
-
import { existsSync as
|
|
43621
|
+
import { existsSync as existsSync91, mkdirSync as mkdirSync35, watch as watch2 } from "fs";
|
|
43487
43622
|
import { dirname as dirname43 } from "path";
|
|
43488
43623
|
|
|
43489
43624
|
// src/commands/sessions/daemon/applyActivityToSession.ts
|
|
@@ -43569,7 +43704,7 @@ function watchActivity(session, notify2, onClaudeSessionId) {
|
|
|
43569
43704
|
if (timer) clearTimeout(timer);
|
|
43570
43705
|
timer = setTimeout(read3, DEBOUNCE_MS2);
|
|
43571
43706
|
});
|
|
43572
|
-
if (
|
|
43707
|
+
if (existsSync91(path106)) read3();
|
|
43573
43708
|
}
|
|
43574
43709
|
function refreshActivity(session) {
|
|
43575
43710
|
if (session.commandType !== "assist" || !session.cwd) return;
|
|
@@ -45560,7 +45695,7 @@ function rearmStoppedSessions(sessions, notify2) {
|
|
|
45560
45695
|
}
|
|
45561
45696
|
|
|
45562
45697
|
// src/commands/sessions/daemon/worktree/reconcileWorktreesOnRestore.ts
|
|
45563
|
-
import { existsSync as
|
|
45698
|
+
import { existsSync as existsSync94 } from "fs";
|
|
45564
45699
|
import { basename as basename32 } from "path";
|
|
45565
45700
|
|
|
45566
45701
|
// src/commands/sessions/daemon/worktree/accountedTrees.ts
|
|
@@ -45581,9 +45716,9 @@ function accountedTrees(sessions) {
|
|
|
45581
45716
|
}
|
|
45582
45717
|
|
|
45583
45718
|
// src/commands/sessions/daemon/worktree/reclaimVanishedWorktrees.ts
|
|
45584
|
-
import { existsSync as
|
|
45719
|
+
import { existsSync as existsSync93 } from "fs";
|
|
45585
45720
|
async function reclaimVanishedWorktrees(clone, paths) {
|
|
45586
|
-
if (!
|
|
45721
|
+
if (!existsSync93(clone)) {
|
|
45587
45722
|
for (const { path: path106 } of paths) forgetWorktree(path106);
|
|
45588
45723
|
daemonLog(
|
|
45589
45724
|
`clone ${clone} is gone; forgot ${paths.length} worktree record(s) it owned`
|
|
@@ -45749,7 +45884,7 @@ async function recoverOrphanedWorktrees(sessions, spawnWith, notify2) {
|
|
|
45749
45884
|
);
|
|
45750
45885
|
continue;
|
|
45751
45886
|
}
|
|
45752
|
-
if (!
|
|
45887
|
+
if (!existsSync94(path106)) {
|
|
45753
45888
|
logVanishedTree(sessions, path106);
|
|
45754
45889
|
vanished.set(clone, [
|
|
45755
45890
|
...vanished.get(clone) ?? [],
|
|
@@ -46060,18 +46195,6 @@ function guardLinkCallback(label2, callback) {
|
|
|
46060
46195
|
};
|
|
46061
46196
|
}
|
|
46062
46197
|
|
|
46063
|
-
// src/commands/sessions/daemon/links/selfUpdatePeer.ts
|
|
46064
|
-
var SELF_UPDATE_TIMEOUT_MS = 10 * 6e4;
|
|
46065
|
-
async function selfUpdatePeer(url) {
|
|
46066
|
-
const response = await fetch(new URL("/api/self-update", url), {
|
|
46067
|
-
method: "POST",
|
|
46068
|
-
signal: AbortSignal.timeout(SELF_UPDATE_TIMEOUT_MS)
|
|
46069
|
-
});
|
|
46070
|
-
if (response.ok) return;
|
|
46071
|
-
const body = await response.json().catch(() => ({}));
|
|
46072
|
-
throw new Error(body.error ?? `self-update responded ${response.status}`);
|
|
46073
|
-
}
|
|
46074
|
-
|
|
46075
46198
|
// src/commands/sessions/daemon/links/linkDeps.ts
|
|
46076
46199
|
function linkDeps(options2, callbacks) {
|
|
46077
46200
|
return {
|
|
@@ -46089,7 +46212,6 @@ function linkDeps(options2, callbacks) {
|
|
|
46089
46212
|
callbacks.onHistoryChanged
|
|
46090
46213
|
),
|
|
46091
46214
|
transport: options2.transport ?? connectLinkWebSocket,
|
|
46092
|
-
heal: options2.heal ?? selfUpdatePeer,
|
|
46093
46215
|
reconnectMs: options2.reconnectMs ?? 3e3,
|
|
46094
46216
|
blockedRetryMs: options2.blockedRetryMs ?? 6e4,
|
|
46095
46217
|
createTimeoutMs: options2.createTimeoutMs ?? 15e3
|
|
@@ -46201,10 +46323,6 @@ function createLinkContext(spec, deps2) {
|
|
|
46201
46323
|
state: "disconnected",
|
|
46202
46324
|
disposed: false,
|
|
46203
46325
|
connect: () => {
|
|
46204
|
-
},
|
|
46205
|
-
onMismatch: () => {
|
|
46206
|
-
},
|
|
46207
|
-
onCompatible: () => {
|
|
46208
46326
|
}
|
|
46209
46327
|
};
|
|
46210
46328
|
}
|
|
@@ -46218,7 +46336,8 @@ function describeLink(ctx) {
|
|
|
46218
46336
|
error: ctx.blockedMessage ?? ctx.lastError?.message,
|
|
46219
46337
|
errorAt: ctx.lastError?.at,
|
|
46220
46338
|
peerVersion: ctx.peer?.version,
|
|
46221
|
-
peerProtocol: ctx.peer?.protocol
|
|
46339
|
+
peerProtocol: ctx.peer?.protocol,
|
|
46340
|
+
protocol: ctx.protocol
|
|
46222
46341
|
};
|
|
46223
46342
|
}
|
|
46224
46343
|
function unavailableReason(ctx) {
|
|
@@ -46299,139 +46418,12 @@ function forwardToLink(ctx, client, data) {
|
|
|
46299
46418
|
daemonLog(`link ${node} ws: forwarded ${data.type} for ${data.sessionId}`);
|
|
46300
46419
|
}
|
|
46301
46420
|
|
|
46302
|
-
// src/commands/sessions/daemon/links/linkHealRefusal.ts
|
|
46303
|
-
import semver2 from "semver";
|
|
46304
|
-
function peerIsNewer(peerVersion) {
|
|
46305
|
-
return semver2.valid(peerVersion) !== null && semver2.valid(ASSIST_VERSION) !== null && semver2.gt(peerVersion, ASSIST_VERSION);
|
|
46306
|
-
}
|
|
46307
|
-
function linkHealRefusal(node, peerVersion, healAttempted) {
|
|
46308
|
-
if (peerIsNewer(peerVersion))
|
|
46309
|
-
return `This node (${ASSIST_VERSION}) is older than ${node} (${peerVersion}); update this node and restart its daemon.`;
|
|
46310
|
-
if (healAttempted)
|
|
46311
|
-
return `${node} is still on ${peerVersion} after auto-update (this node is ${ASSIST_VERSION}); update ${node} manually; this node reconnects once it reports a compatible version.`;
|
|
46312
|
-
return void 0;
|
|
46313
|
-
}
|
|
46314
|
-
|
|
46315
|
-
// src/commands/sessions/daemon/links/scheduleReconnect.ts
|
|
46316
|
-
function armReconnect(ctx, delay2) {
|
|
46317
|
-
clearTimeout(ctx.reconnectTimer);
|
|
46318
|
-
ctx.reconnectTimer = setTimeout(ctx.connect, delay2);
|
|
46319
|
-
ctx.reconnectTimer.unref?.();
|
|
46320
|
-
}
|
|
46321
|
-
function scheduleReconnect2(ctx) {
|
|
46322
|
-
if (ctx.disposed) return;
|
|
46323
|
-
if (ctx.blockedMessage) return armReconnect(ctx, ctx.deps.blockedRetryMs);
|
|
46324
|
-
const delay2 = ctx.breaker.tripped() ? ctx.breaker.remainingMs() : ctx.deps.reconnectMs;
|
|
46325
|
-
armReconnect(ctx, delay2);
|
|
46326
|
-
}
|
|
46327
|
-
function reconnectNow(ctx) {
|
|
46328
|
-
ctx.breaker.clear();
|
|
46329
|
-
armReconnect(ctx, ctx.deps.reconnectMs);
|
|
46330
|
-
}
|
|
46331
|
-
|
|
46332
|
-
// src/commands/sessions/daemon/links/teardownLink.ts
|
|
46333
|
-
function teardownLink(ctx) {
|
|
46334
|
-
const hadSessions = ctx.relay.sessions.length > 0;
|
|
46335
|
-
ctx.socket = null;
|
|
46336
|
-
ctx.greeted = false;
|
|
46337
|
-
resetRelayState(ctx.relay, `${ctx.spec.name} connection closed`);
|
|
46338
|
-
if (!ctx.blockedMessage) setLinkState(ctx, "disconnected");
|
|
46339
|
-
if (hadSessions) ctx.deps.onSessionsChanged();
|
|
46340
|
-
}
|
|
46341
|
-
function disconnectLink(ctx) {
|
|
46342
|
-
const socket = ctx.socket;
|
|
46343
|
-
teardownLink(ctx);
|
|
46344
|
-
socket?.close();
|
|
46345
|
-
}
|
|
46346
|
-
function failLink(ctx, message3) {
|
|
46347
|
-
daemonLog(`link ${ctx.spec.name} ws: ${message3}`);
|
|
46348
|
-
ctx.lastError = { message: message3, at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
46349
|
-
ctx.breaker.fail(message3);
|
|
46350
|
-
setLinkState(ctx, ctx.blockedMessage ? "version-blocked" : "disconnected");
|
|
46351
|
-
scheduleReconnect2(ctx);
|
|
46352
|
-
}
|
|
46353
|
-
function blockLink(ctx, message3) {
|
|
46354
|
-
const repeat = ctx.blockedMessage === message3;
|
|
46355
|
-
ctx.blockedMessage = message3;
|
|
46356
|
-
disconnectLink(ctx);
|
|
46357
|
-
setLinkState(ctx, "version-blocked");
|
|
46358
|
-
if (!repeat) broadcastToViewers(ctx.relay, { type: "error", message: message3 });
|
|
46359
|
-
scheduleReconnect2(ctx);
|
|
46360
|
-
}
|
|
46361
|
-
|
|
46362
|
-
// src/commands/sessions/daemon/links/runLinkHeal.ts
|
|
46363
|
-
async function runLinkHeal(ctx, peerVersion) {
|
|
46364
|
-
const node = ctx.spec.name;
|
|
46365
|
-
daemonLog(
|
|
46366
|
-
`link ${node} heal: peer ${peerVersion} is older than ${ASSIST_VERSION}; requesting self-update`
|
|
46367
|
-
);
|
|
46368
|
-
failPendingCreators(
|
|
46369
|
-
ctx.relay,
|
|
46370
|
-
`${node} is out of date; updating it now \u2014 retry once the update finishes.`
|
|
46371
|
-
);
|
|
46372
|
-
disconnectLink(ctx);
|
|
46373
|
-
try {
|
|
46374
|
-
await ctx.deps.heal(ctx.spec.url);
|
|
46375
|
-
daemonLog(`link ${node} heal: self-update done, reconnecting`);
|
|
46376
|
-
} catch (error) {
|
|
46377
|
-
const message3 = error instanceof Error ? error.message : String(error);
|
|
46378
|
-
daemonLog(`link ${node} heal: self-update failed: ${message3}`);
|
|
46379
|
-
broadcastToViewers(ctx.relay, {
|
|
46380
|
-
type: "error",
|
|
46381
|
-
message: `${node} auto-update failed: ${message3}`
|
|
46382
|
-
});
|
|
46383
|
-
}
|
|
46384
|
-
reconnectNow(ctx);
|
|
46385
|
-
}
|
|
46386
|
-
|
|
46387
|
-
// src/commands/sessions/daemon/links/LinkHealer.ts
|
|
46388
|
-
var LinkHealer = class {
|
|
46389
|
-
constructor(ctx) {
|
|
46390
|
-
this.ctx = ctx;
|
|
46391
|
-
}
|
|
46392
|
-
healAttempted = false;
|
|
46393
|
-
healing = false;
|
|
46394
|
-
awaitingConfirmation = false;
|
|
46395
|
-
async onMismatch(peerVersion) {
|
|
46396
|
-
if (this.healing) return;
|
|
46397
|
-
const node = this.ctx.spec.name;
|
|
46398
|
-
const refusal = linkHealRefusal(node, peerVersion, this.healAttempted);
|
|
46399
|
-
if (refusal) {
|
|
46400
|
-
this.awaitingConfirmation = false;
|
|
46401
|
-
if (this.ctx.blockedMessage !== refusal)
|
|
46402
|
-
daemonLog(`link ${node} heal: latched, retrying slowly: ${refusal}`);
|
|
46403
|
-
return blockLink(this.ctx, refusal);
|
|
46404
|
-
}
|
|
46405
|
-
this.healing = true;
|
|
46406
|
-
this.healAttempted = true;
|
|
46407
|
-
this.awaitingConfirmation = true;
|
|
46408
|
-
try {
|
|
46409
|
-
await runLinkHeal(this.ctx, peerVersion);
|
|
46410
|
-
} finally {
|
|
46411
|
-
this.healing = false;
|
|
46412
|
-
}
|
|
46413
|
-
}
|
|
46414
|
-
reset() {
|
|
46415
|
-
this.healAttempted = false;
|
|
46416
|
-
this.awaitingConfirmation = false;
|
|
46417
|
-
}
|
|
46418
|
-
onCompatible() {
|
|
46419
|
-
if (!this.awaitingConfirmation) return;
|
|
46420
|
-
this.awaitingConfirmation = false;
|
|
46421
|
-
const node = this.ctx.spec.name;
|
|
46422
|
-
daemonLog(`link ${node} heal: post-heal handshake compatible`);
|
|
46423
|
-
broadcastToViewers(this.ctx.relay, {
|
|
46424
|
-
type: "notice",
|
|
46425
|
-
message: `${node} is up to date.`
|
|
46426
|
-
});
|
|
46427
|
-
}
|
|
46428
|
-
};
|
|
46429
|
-
|
|
46430
46421
|
// src/commands/sessions/daemon/links/acceptPeerHello.ts
|
|
46431
46422
|
var helloFields = /* @__PURE__ */ new Set([
|
|
46432
46423
|
"type",
|
|
46433
46424
|
"version",
|
|
46434
46425
|
"protocol",
|
|
46426
|
+
"minProtocol",
|
|
46435
46427
|
"nodeName",
|
|
46436
46428
|
"peer"
|
|
46437
46429
|
]);
|
|
@@ -46448,22 +46440,22 @@ function acceptPeerHello(linkName, msg) {
|
|
|
46448
46440
|
reason: `peer reports nodeName ${msg.nodeName}, link expects ${linkName}`,
|
|
46449
46441
|
peer
|
|
46450
46442
|
};
|
|
46451
|
-
|
|
46443
|
+
const protocol = negotiateProtocol(msg);
|
|
46444
|
+
if (protocol !== void 0) {
|
|
46452
46445
|
daemonLog(
|
|
46453
|
-
`link ${linkName} ws: hello ok (${msg.version})${unrecognisedHelloFields(msg)}`
|
|
46446
|
+
`link ${linkName} ws: hello ok (${msg.version}, protocol ${protocol})${unrecognisedHelloFields(msg)}`
|
|
46454
46447
|
);
|
|
46455
|
-
return { kind: "accept", peer };
|
|
46448
|
+
return { kind: "accept", peer, protocol };
|
|
46456
46449
|
}
|
|
46457
46450
|
const mode = linkVersionCheck();
|
|
46458
|
-
const
|
|
46451
|
+
const reason4 = describeProtocolGap(linkName, msg);
|
|
46459
46452
|
if (mode !== "block") {
|
|
46460
46453
|
daemonLog(
|
|
46461
|
-
`link ${linkName} ws: ${
|
|
46454
|
+
`link ${linkName} ws: ${reason4}; proceeding (sessions.linkVersionCheck=${mode})`
|
|
46462
46455
|
);
|
|
46463
46456
|
return { kind: "accept", peer };
|
|
46464
46457
|
}
|
|
46465
|
-
|
|
46466
|
-
return { kind: "heal", peer };
|
|
46458
|
+
return { kind: "block", reason: reason4, peer };
|
|
46467
46459
|
}
|
|
46468
46460
|
|
|
46469
46461
|
// src/commands/sessions/daemon/logUnrecognisedType.ts
|
|
@@ -46566,6 +46558,55 @@ function relayLinkMessage(state, msg) {
|
|
|
46566
46558
|
);
|
|
46567
46559
|
}
|
|
46568
46560
|
|
|
46561
|
+
// src/commands/sessions/daemon/links/scheduleReconnect.ts
|
|
46562
|
+
function armReconnect(ctx, delay2) {
|
|
46563
|
+
clearTimeout(ctx.reconnectTimer);
|
|
46564
|
+
ctx.reconnectTimer = setTimeout(ctx.connect, delay2);
|
|
46565
|
+
ctx.reconnectTimer.unref?.();
|
|
46566
|
+
}
|
|
46567
|
+
function scheduleReconnect2(ctx) {
|
|
46568
|
+
if (ctx.disposed) return;
|
|
46569
|
+
if (ctx.blockedMessage) return armReconnect(ctx, ctx.deps.blockedRetryMs);
|
|
46570
|
+
const delay2 = ctx.breaker.tripped() ? ctx.breaker.remainingMs() : ctx.deps.reconnectMs;
|
|
46571
|
+
armReconnect(ctx, delay2);
|
|
46572
|
+
}
|
|
46573
|
+
function reconnectNow(ctx) {
|
|
46574
|
+
ctx.breaker.clear();
|
|
46575
|
+
armReconnect(ctx, ctx.deps.reconnectMs);
|
|
46576
|
+
}
|
|
46577
|
+
|
|
46578
|
+
// src/commands/sessions/daemon/links/teardownLink.ts
|
|
46579
|
+
function teardownLink(ctx) {
|
|
46580
|
+
const hadSessions = ctx.relay.sessions.length > 0;
|
|
46581
|
+
ctx.socket = null;
|
|
46582
|
+
ctx.greeted = false;
|
|
46583
|
+
ctx.protocol = void 0;
|
|
46584
|
+
resetRelayState(ctx.relay, `${ctx.spec.name} connection closed`);
|
|
46585
|
+
if (!ctx.blockedMessage) setLinkState(ctx, "disconnected");
|
|
46586
|
+
if (hadSessions) ctx.deps.onSessionsChanged();
|
|
46587
|
+
}
|
|
46588
|
+
function disconnectLink(ctx) {
|
|
46589
|
+
const socket = ctx.socket;
|
|
46590
|
+
teardownLink(ctx);
|
|
46591
|
+
socket?.close();
|
|
46592
|
+
}
|
|
46593
|
+
function failLink(ctx, message3) {
|
|
46594
|
+
daemonLog(`link ${ctx.spec.name} ws: ${message3}`);
|
|
46595
|
+
ctx.lastError = { message: message3, at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
46596
|
+
ctx.breaker.fail(message3);
|
|
46597
|
+
setLinkState(ctx, ctx.blockedMessage ? "version-blocked" : "disconnected");
|
|
46598
|
+
scheduleReconnect2(ctx);
|
|
46599
|
+
}
|
|
46600
|
+
function blockLink(ctx, message3) {
|
|
46601
|
+
const repeat = ctx.blockedMessage === message3;
|
|
46602
|
+
if (!repeat) daemonLog(`link ${ctx.spec.name} ws: ${message3}`);
|
|
46603
|
+
ctx.blockedMessage = message3;
|
|
46604
|
+
disconnectLink(ctx);
|
|
46605
|
+
setLinkState(ctx, "version-blocked");
|
|
46606
|
+
if (!repeat) broadcastToViewers(ctx.relay, { type: "error", message: message3 });
|
|
46607
|
+
scheduleReconnect2(ctx);
|
|
46608
|
+
}
|
|
46609
|
+
|
|
46569
46610
|
// src/commands/sessions/daemon/links/onLinkLine.ts
|
|
46570
46611
|
function onHello(ctx, msg) {
|
|
46571
46612
|
const verdict = acceptPeerHello(ctx.spec.name, msg);
|
|
@@ -46577,18 +46618,18 @@ function onHello(ctx, msg) {
|
|
|
46577
46618
|
disconnectLink(ctx);
|
|
46578
46619
|
return failLink(ctx, verdict.reason);
|
|
46579
46620
|
}
|
|
46580
|
-
if (verdict.kind === "
|
|
46621
|
+
if (verdict.kind === "block") return blockLink(ctx, verdict.reason);
|
|
46581
46622
|
if (ctx.blockedMessage)
|
|
46582
46623
|
daemonLog(
|
|
46583
|
-
`link ${ctx.spec.name}
|
|
46624
|
+
`link ${ctx.spec.name} ws: peer now reports ${verdict.peer.version}; protocol block cleared`
|
|
46584
46625
|
);
|
|
46585
46626
|
ctx.blockedMessage = void 0;
|
|
46627
|
+
ctx.protocol = verdict.protocol;
|
|
46586
46628
|
ctx.greeted = true;
|
|
46587
46629
|
ctx.breaker.clear();
|
|
46588
46630
|
ctx.lastError = void 0;
|
|
46589
46631
|
setLinkState(ctx, "connected");
|
|
46590
46632
|
ctx.deps.onHistoryChanged();
|
|
46591
|
-
ctx.onCompatible();
|
|
46592
46633
|
}
|
|
46593
46634
|
function onLinkLine(ctx, line) {
|
|
46594
46635
|
let msg;
|
|
@@ -46661,21 +46702,15 @@ var NodeLink = class {
|
|
|
46661
46702
|
constructor(spec, deps2) {
|
|
46662
46703
|
this.spec = spec;
|
|
46663
46704
|
const ctx = createLinkContext(spec, deps2);
|
|
46664
|
-
const healer = new LinkHealer(ctx);
|
|
46665
46705
|
ctx.connect = () => void openLink(ctx);
|
|
46666
|
-
ctx.onMismatch = (version2) => void healer.onMismatch(version2);
|
|
46667
|
-
ctx.onCompatible = () => healer.onCompatible();
|
|
46668
46706
|
this.ctx = ctx;
|
|
46669
|
-
this.healer = healer;
|
|
46670
46707
|
}
|
|
46671
46708
|
ctx;
|
|
46672
|
-
healer;
|
|
46673
46709
|
start = () => this.ctx.connect();
|
|
46674
46710
|
unlatch() {
|
|
46675
46711
|
if (!this.ctx.blockedMessage) return;
|
|
46676
|
-
daemonLog(`link ${this.spec.name}
|
|
46712
|
+
daemonLog(`link ${this.spec.name} ws: protocol block cleared by reload`);
|
|
46677
46713
|
this.ctx.blockedMessage = void 0;
|
|
46678
|
-
this.healer.reset();
|
|
46679
46714
|
setLinkState(this.ctx, "disconnected");
|
|
46680
46715
|
reconnectNow(this.ctx);
|
|
46681
46716
|
}
|
|
@@ -46894,7 +46929,7 @@ function spawnAssistInTree(ctx, assistArgs, cwd, meta, context) {
|
|
|
46894
46929
|
}
|
|
46895
46930
|
|
|
46896
46931
|
// src/commands/sessions/daemon/worktree/resumeInTree.ts
|
|
46897
|
-
import { existsSync as
|
|
46932
|
+
import { existsSync as existsSync97 } from "fs";
|
|
46898
46933
|
|
|
46899
46934
|
// src/commands/sessions/daemon/resumeSession.ts
|
|
46900
46935
|
function resumeSession(id, sessionId, cwd, name, holdPty, harness) {
|
|
@@ -46925,10 +46960,10 @@ function resumeSession(id, sessionId, cwd, name, holdPty, harness) {
|
|
|
46925
46960
|
}
|
|
46926
46961
|
|
|
46927
46962
|
// src/commands/sessions/daemon/worktree/resumeInReplacementTree.ts
|
|
46928
|
-
import { existsSync as
|
|
46963
|
+
import { existsSync as existsSync96 } from "fs";
|
|
46929
46964
|
|
|
46930
46965
|
// src/commands/sessions/daemon/worktree/carryTranscriptToTree.ts
|
|
46931
|
-
import { copyFileSync as copyFileSync5, existsSync as
|
|
46966
|
+
import { copyFileSync as copyFileSync5, existsSync as existsSync95, mkdirSync as mkdirSync37 } from "fs";
|
|
46932
46967
|
import { join as join105 } from "path";
|
|
46933
46968
|
function carryTranscriptToTree(claudeSessionId, fromCwd, toCwd) {
|
|
46934
46969
|
const dir = projectDirForCwd(toCwd);
|
|
@@ -46939,7 +46974,7 @@ function carryTranscriptToTree(claudeSessionId, fromCwd, toCwd) {
|
|
|
46939
46974
|
return;
|
|
46940
46975
|
}
|
|
46941
46976
|
const dest = join105(dir, `${claudeSessionId}.jsonl`);
|
|
46942
|
-
if (
|
|
46977
|
+
if (existsSync95(dest)) {
|
|
46943
46978
|
daemonLog(`transcript ${claudeSessionId} already present in ${dir}`);
|
|
46944
46979
|
return;
|
|
46945
46980
|
}
|
|
@@ -46990,7 +47025,7 @@ function resumeInReplacementTree(ctx, claudeSessionId, missingCwd, name, harness
|
|
|
46990
47025
|
}
|
|
46991
47026
|
function cloneForReapedTree(missingCwd) {
|
|
46992
47027
|
const clone = worktreeAttributionIncludingReaped(missingCwd)?.clone;
|
|
46993
|
-
if (!clone || !
|
|
47028
|
+
if (!clone || !existsSync96(clone))
|
|
46994
47029
|
throw new Error(
|
|
46995
47030
|
`working directory no longer exists and no clone is recorded to re-allocate from: ${missingCwd}`
|
|
46996
47031
|
);
|
|
@@ -46999,7 +47034,7 @@ function cloneForReapedTree(missingCwd) {
|
|
|
46999
47034
|
|
|
47000
47035
|
// src/commands/sessions/daemon/worktree/resumeInTree.ts
|
|
47001
47036
|
function resumeInTree(ctx, sessionId, cwd, name, harness) {
|
|
47002
|
-
if (!
|
|
47037
|
+
if (!existsSync97(cwd))
|
|
47003
47038
|
return resumeInReplacementTree(ctx, sessionId, cwd, name, harness);
|
|
47004
47039
|
const id = ctx.spawnWith(
|
|
47005
47040
|
(sid) => resumeSession(sid, sessionId, cwd, name, void 0, harness)
|
|
@@ -47192,10 +47227,60 @@ function autoUpdateReply() {
|
|
|
47192
47227
|
type: "auto-update",
|
|
47193
47228
|
version: ASSIST_VERSION,
|
|
47194
47229
|
startCommit: autoUpdateState.startCommit(),
|
|
47195
|
-
loop: autoUpdateState.loop()
|
|
47230
|
+
loop: { ...autoUpdateState.loop(), paused: autoUpdateControl.paused() }
|
|
47196
47231
|
};
|
|
47197
47232
|
}
|
|
47198
47233
|
|
|
47234
|
+
// src/commands/sessions/daemon/autoUpdate/applyAutoUpdateAction.ts
|
|
47235
|
+
import { rmSync as rmSync11, writeFileSync as writeFileSync56 } from "fs";
|
|
47236
|
+
function signalLap(installDir, marker2) {
|
|
47237
|
+
const control = watchControlPaths(installDir);
|
|
47238
|
+
if (control) writeFileSync56(control[marker2], "");
|
|
47239
|
+
}
|
|
47240
|
+
function applyAutoUpdateAction(action) {
|
|
47241
|
+
const installDir = getInstallDir();
|
|
47242
|
+
const loop2 = autoUpdateState.loop();
|
|
47243
|
+
if (action === "resume") {
|
|
47244
|
+
if (!autoUpdateControl.paused()) return void 0;
|
|
47245
|
+
const control = watchControlPaths(installDir);
|
|
47246
|
+
if (control) rmSync11(control.stop, { force: true });
|
|
47247
|
+
autoUpdateControl.setPaused(false);
|
|
47248
|
+
noteAutoUpdate(installDir, "resumed from Updates");
|
|
47249
|
+
return void 0;
|
|
47250
|
+
}
|
|
47251
|
+
if (loop2.phase === "off")
|
|
47252
|
+
return `auto-update is off (${loop2.reason ?? "not running"})`;
|
|
47253
|
+
if (action === "pause") {
|
|
47254
|
+
if (autoUpdateControl.paused()) return void 0;
|
|
47255
|
+
autoUpdateControl.setPaused(true);
|
|
47256
|
+
signalLap(installDir, "stop");
|
|
47257
|
+
noteAutoUpdate(installDir, "paused from Updates");
|
|
47258
|
+
return void 0;
|
|
47259
|
+
}
|
|
47260
|
+
if (autoUpdateControl.paused())
|
|
47261
|
+
return "auto-update is paused; resume it first";
|
|
47262
|
+
signalLap(installDir, "check");
|
|
47263
|
+
autoUpdateControl.wake();
|
|
47264
|
+
noteAutoUpdate(installDir, "check requested from Updates");
|
|
47265
|
+
return void 0;
|
|
47266
|
+
}
|
|
47267
|
+
|
|
47268
|
+
// src/commands/sessions/daemon/autoUpdate/handleAutoUpdateControl.ts
|
|
47269
|
+
function tryAction(action) {
|
|
47270
|
+
if (!AUTO_UPDATE_ACTIONS.includes(action))
|
|
47271
|
+
return `unknown auto-update action ${String(action)}`;
|
|
47272
|
+
try {
|
|
47273
|
+
return applyAutoUpdateAction(action);
|
|
47274
|
+
} catch (error) {
|
|
47275
|
+
return error instanceof Error ? error.message : String(error);
|
|
47276
|
+
}
|
|
47277
|
+
}
|
|
47278
|
+
var handleAutoUpdateControl = (client, _m, d) => {
|
|
47279
|
+
const error = tryAction(d.action);
|
|
47280
|
+
daemonLog(`auto-update-control ${String(d.action)}: ${error ?? "ok"}`);
|
|
47281
|
+
sendTo(client, { type: "auto-update-control", ok: !error, error });
|
|
47282
|
+
};
|
|
47283
|
+
|
|
47199
47284
|
// src/commands/sessions/daemon/handleHelloRequest.ts
|
|
47200
47285
|
var handleHelloRequest = (client, m, d) => {
|
|
47201
47286
|
sendTo(client, buildHello());
|
|
@@ -47722,6 +47807,7 @@ var messageHandlers = {
|
|
|
47722
47807
|
output: handleOutputRequest,
|
|
47723
47808
|
nodes: (client, m) => m.links.sendNodes(client),
|
|
47724
47809
|
"auto-update": (client) => sendTo(client, autoUpdateReply()),
|
|
47810
|
+
"auto-update-control": handleAutoUpdateControl,
|
|
47725
47811
|
"reload-links": (client, m) => {
|
|
47726
47812
|
daemonLog("reload-links received");
|
|
47727
47813
|
m.links.reload();
|
|
@@ -47838,7 +47924,7 @@ function handleConnection(socket, manager) {
|
|
|
47838
47924
|
}
|
|
47839
47925
|
|
|
47840
47926
|
// src/commands/sessions/daemon/onListening.ts
|
|
47841
|
-
import { unlinkSync as unlinkSync22, writeFileSync as
|
|
47927
|
+
import { unlinkSync as unlinkSync22, writeFileSync as writeFileSync57 } from "fs";
|
|
47842
47928
|
|
|
47843
47929
|
// src/commands/sessions/daemon/startPidFileWatchdog.ts
|
|
47844
47930
|
import { readFileSync as readFileSync66 } from "fs";
|
|
@@ -47860,7 +47946,7 @@ function ownsPidFile() {
|
|
|
47860
47946
|
|
|
47861
47947
|
// src/commands/sessions/daemon/onListening.ts
|
|
47862
47948
|
function onListening(manager, checkAutoExit) {
|
|
47863
|
-
|
|
47949
|
+
writeFileSync57(daemonPaths.pid, String(process.pid));
|
|
47864
47950
|
startPidFileWatchdog(() => {
|
|
47865
47951
|
daemonLog("lost daemon.pid ownership; shutting down sessions and exiting");
|
|
47866
47952
|
void manager.flushActiveMs().finally(() => {
|
|
@@ -48246,14 +48332,14 @@ async function linkStatus(name, query = queryNodes) {
|
|
|
48246
48332
|
return (await query())?.links.find((l) => l.name === name);
|
|
48247
48333
|
}
|
|
48248
48334
|
async function awaitLinkReturn(name, options2, deps2 = {}) {
|
|
48249
|
-
const { query = queryNodes, sleep:
|
|
48335
|
+
const { query = queryNodes, sleep: sleep2 = wait, now = Date.now } = deps2;
|
|
48250
48336
|
const deadline = now() + options2.timeoutMs;
|
|
48251
48337
|
let sawDown = options2.sawDown;
|
|
48252
48338
|
while (now() < deadline) {
|
|
48253
48339
|
const link3 = await linkStatus(name, query);
|
|
48254
48340
|
if (link3?.state !== "connected") sawDown = true;
|
|
48255
48341
|
else if (sawDown) return link3;
|
|
48256
|
-
await
|
|
48342
|
+
await sleep2(POLL_MS4);
|
|
48257
48343
|
}
|
|
48258
48344
|
return void 0;
|
|
48259
48345
|
}
|