@staff0rd/assist 0.774.0 → 0.774.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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.774.
|
|
9
|
+
version: "0.774.1",
|
|
10
10
|
type: "module",
|
|
11
11
|
main: "dist/index.js",
|
|
12
12
|
bin: {
|
|
@@ -546,6 +546,13 @@ var LEGACY_SESSIONS_KEYS = [
|
|
|
546
546
|
"windowsDaemonPort",
|
|
547
547
|
"windowsVersionCheck"
|
|
548
548
|
];
|
|
549
|
+
var LEGACY_WORKTREE_KEYS = ["watcher"];
|
|
550
|
+
function withoutKeys(value, keys) {
|
|
551
|
+
if (!value || typeof value !== "object") return value;
|
|
552
|
+
const rest = { ...value };
|
|
553
|
+
for (const key of keys) delete rest[key];
|
|
554
|
+
return rest;
|
|
555
|
+
}
|
|
549
556
|
function stripLegacyConfigKeys(config) {
|
|
550
557
|
const stripped = { ...config };
|
|
551
558
|
const news = stripped.news;
|
|
@@ -553,12 +560,10 @@ function stripLegacyConfigKeys(config) {
|
|
|
553
560
|
const { feeds: _feeds, ...rest } = news;
|
|
554
561
|
stripped.news = rest;
|
|
555
562
|
}
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
stripped.sessions = rest;
|
|
561
|
-
}
|
|
563
|
+
if (stripped.sessions)
|
|
564
|
+
stripped.sessions = withoutKeys(stripped.sessions, LEGACY_SESSIONS_KEYS);
|
|
565
|
+
if (stripped.worktree)
|
|
566
|
+
stripped.worktree = withoutKeys(stripped.worktree, LEGACY_WORKTREE_KEYS);
|
|
562
567
|
return stripped;
|
|
563
568
|
}
|
|
564
569
|
|
|
@@ -906,9 +911,11 @@ var assistConfigShape = {
|
|
|
906
911
|
})
|
|
907
912
|
).default([])
|
|
908
913
|
}).optional(),
|
|
914
|
+
autoUpdate: z5.strictObject({
|
|
915
|
+
enabled: z5.boolean().default(true)
|
|
916
|
+
}).optional(),
|
|
909
917
|
worktree: z5.strictObject({
|
|
910
918
|
enabled: z5.boolean().default(false),
|
|
911
|
-
watcher: z5.boolean().default(false),
|
|
912
919
|
trunk: z5.boolean().default(false),
|
|
913
920
|
includeDrafts: z5.boolean().default(false),
|
|
914
921
|
root: z5.string().optional(),
|
|
@@ -11241,7 +11248,7 @@ var defaultDeps = {
|
|
|
11241
11248
|
httpsPort: tailnetHttpsPort
|
|
11242
11249
|
};
|
|
11243
11250
|
async function ensureTailscaleServe(port, deps2 = {}) {
|
|
11244
|
-
const { enabled, status: status2, serveStatus, serve, sleep:
|
|
11251
|
+
const { enabled, status: status2, serveStatus, serve, sleep: sleep3, httpsPort } = {
|
|
11245
11252
|
...defaultDeps,
|
|
11246
11253
|
...deps2
|
|
11247
11254
|
};
|
|
@@ -11256,7 +11263,7 @@ async function ensureTailscaleServe(port, deps2 = {}) {
|
|
|
11256
11263
|
return `serving ${plan2.target} at ${plan2.url}`;
|
|
11257
11264
|
} catch (error) {
|
|
11258
11265
|
if (attempt >= SERVE_WRITE_ATTEMPTS) throw error;
|
|
11259
|
-
await
|
|
11266
|
+
await sleep3(attempt * 500 + Math.random() * 500);
|
|
11260
11267
|
}
|
|
11261
11268
|
}
|
|
11262
11269
|
}
|
|
@@ -12786,6 +12793,15 @@ var harnessConfigHelp = [
|
|
|
12786
12793
|
}
|
|
12787
12794
|
];
|
|
12788
12795
|
|
|
12796
|
+
// src/commands/sessions/daemon/autoUpdateConfigHelp.ts
|
|
12797
|
+
var autoUpdateConfigHelp = [
|
|
12798
|
+
{
|
|
12799
|
+
key: "autoUpdate.enabled",
|
|
12800
|
+
setter: "assist config set autoUpdate.enabled false -g",
|
|
12801
|
+
note: "when this install is a git clone, the daemon pulls and builds it in the background as origin moves (assist watch wait --pull --build), logging each lap to ~/.assist/watchers/; a branch that cannot fast-forward starts a claude session to reconcile it and the loop resumes once it closes. Set false to stop it (default on)"
|
|
12802
|
+
}
|
|
12803
|
+
];
|
|
12804
|
+
|
|
12789
12805
|
// src/commands/branch/branchConfigHelp.ts
|
|
12790
12806
|
var branchConfigHelp = [
|
|
12791
12807
|
{
|
|
@@ -13279,11 +13295,6 @@ var sessionsConfigHelp = [
|
|
|
13279
13295
|
setter: "assist config set worktree.enabled true -g --repo",
|
|
13280
13296
|
note: "opt in per repo: spill concurrent sessions into adjacent <clone>-N worktrees (default off)"
|
|
13281
13297
|
},
|
|
13282
|
-
{
|
|
13283
|
-
key: "worktree.watcher",
|
|
13284
|
-
setter: "assist config set worktree.watcher true -g --repo",
|
|
13285
|
-
note: "a backlog run also starts one starred console session in the clone running assist watch loop (no claude, relaunched after a daemon restart), so the built version stays current while the run works in its worktree; requires worktree.enabled and an auto-build run entry (default off)"
|
|
13286
|
-
},
|
|
13287
13298
|
{
|
|
13288
13299
|
key: "worktree.trunk",
|
|
13289
13300
|
setter: "assist config set worktree.trunk true -g --repo",
|
|
@@ -13390,6 +13401,7 @@ var verifyConfigHelp = [
|
|
|
13390
13401
|
var configHelpEntries = [
|
|
13391
13402
|
...Object.values(rootConfigHelp).flat(),
|
|
13392
13403
|
...adviceConfigHelp,
|
|
13404
|
+
...autoUpdateConfigHelp,
|
|
13393
13405
|
...backlogConfigHelp,
|
|
13394
13406
|
...backupConfigHelp,
|
|
13395
13407
|
...branchConfigHelp,
|
|
@@ -25861,7 +25873,6 @@ function wouldDisturbWorkInProgress(clone, forCheckout) {
|
|
|
25861
25873
|
// src/commands/sessions/daemon/worktree/worktreeConfigFor.ts
|
|
25862
25874
|
var DISABLED = {
|
|
25863
25875
|
enabled: false,
|
|
25864
|
-
watcher: false,
|
|
25865
25876
|
trunk: false,
|
|
25866
25877
|
includeDrafts: false,
|
|
25867
25878
|
install: true,
|
|
@@ -25994,7 +26005,6 @@ function toPersistedSession(session) {
|
|
|
25994
26005
|
assistArgs: session.assistArgs,
|
|
25995
26006
|
activity: session.activity,
|
|
25996
26007
|
starred: session.starred,
|
|
25997
|
-
watcher: session.watcher,
|
|
25998
26008
|
divergenceEscalation: session.divergenceEscalation,
|
|
25999
26009
|
design: session.design,
|
|
26000
26010
|
auto: session.auto,
|
|
@@ -41405,7 +41415,6 @@ function toSessionInfo(session) {
|
|
|
41405
41415
|
autoRun,
|
|
41406
41416
|
autoAdvance,
|
|
41407
41417
|
starred,
|
|
41408
|
-
watcher,
|
|
41409
41418
|
design,
|
|
41410
41419
|
verifying,
|
|
41411
41420
|
pendingPrPreview,
|
|
@@ -41432,7 +41441,6 @@ function toSessionInfo(session) {
|
|
|
41432
41441
|
autoRun,
|
|
41433
41442
|
autoAdvance,
|
|
41434
41443
|
starred,
|
|
41435
|
-
watcher,
|
|
41436
41444
|
design,
|
|
41437
41445
|
verifying,
|
|
41438
41446
|
pendingPrPreview,
|
|
@@ -41988,6 +41996,359 @@ function createRunSession(id, { runName, runArgs, cwd, meta }) {
|
|
|
41988
41996
|
};
|
|
41989
41997
|
}
|
|
41990
41998
|
|
|
41999
|
+
// src/commands/sessions/daemon/autoUpdate/startAutoUpdate.ts
|
|
42000
|
+
import { setTimeout as sleep2 } from "timers/promises";
|
|
42001
|
+
|
|
42002
|
+
// src/commands/sessions/daemon/watcherNote.ts
|
|
42003
|
+
function watcherNote(text17) {
|
|
42004
|
+
return `\r
|
|
42005
|
+
\x1B[2m\u2500\u2500 ${text17} \u2500\u2500\x1B[0m\r
|
|
42006
|
+
`;
|
|
42007
|
+
}
|
|
42008
|
+
|
|
42009
|
+
// src/commands/sessions/daemon/worktree/appendWatcherLog.ts
|
|
42010
|
+
import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync34 } from "fs";
|
|
42011
|
+
import { dirname as dirname42 } from "path";
|
|
42012
|
+
|
|
42013
|
+
// src/commands/sessions/daemon/worktree/watcherLogPath.ts
|
|
42014
|
+
import { createHash as createHash4 } from "crypto";
|
|
42015
|
+
import { homedir as homedir26 } from "os";
|
|
42016
|
+
import { basename as basename28, join as join101 } from "path";
|
|
42017
|
+
function watcherLogPath(cwd) {
|
|
42018
|
+
const clone = canonicalTreePath(cwd);
|
|
42019
|
+
const key = createHash4("sha1").update(clone).digest("hex").slice(0, 8);
|
|
42020
|
+
const dir = process.env.ASSIST_WATCHER_LOG_DIR ?? join101(homedir26(), ".assist", "watchers");
|
|
42021
|
+
return join101(dir, `${basename28(clone) || "root"}-${key}.log`);
|
|
42022
|
+
}
|
|
42023
|
+
|
|
42024
|
+
// src/commands/sessions/daemon/worktree/appendWatcherLog.ts
|
|
42025
|
+
function appendWatcherLog(cwd, data) {
|
|
42026
|
+
try {
|
|
42027
|
+
const path106 = watcherLogPath(cwd);
|
|
42028
|
+
mkdirSync34(dirname42(path106), { recursive: true });
|
|
42029
|
+
appendFileSync3(path106, data);
|
|
42030
|
+
} catch {
|
|
42031
|
+
}
|
|
42032
|
+
}
|
|
42033
|
+
|
|
42034
|
+
// src/commands/sessions/daemon/worktree/detectExistingWorktree.ts
|
|
42035
|
+
function detectExistingWorktree(cwd) {
|
|
42036
|
+
const repoRoot2 = findRepoRoot(cwd) ?? cwd;
|
|
42037
|
+
if (!worktreeConfigFor(repoRoot2).enabled) return void 0;
|
|
42038
|
+
const clone = mainWorktree(repoRoot2);
|
|
42039
|
+
if (clone && clone !== repoRoot2) return { path: repoRoot2, clone };
|
|
42040
|
+
return void 0;
|
|
42041
|
+
}
|
|
42042
|
+
|
|
42043
|
+
// src/commands/sessions/daemon/worktree/bindNewWorktree.ts
|
|
42044
|
+
function bindNewWorktree(session, alloc, notify2, startHeld = () => {
|
|
42045
|
+
}) {
|
|
42046
|
+
if (alloc.kind !== "worktree" || !alloc.cwd || !alloc.clone || !session)
|
|
42047
|
+
return;
|
|
42048
|
+
session.worktree = { path: alloc.cwd, clone: alloc.clone };
|
|
42049
|
+
if (alloc.created)
|
|
42050
|
+
seedWorktree(alloc.cwd, alloc.clone, () => startHeld(session));
|
|
42051
|
+
notify2();
|
|
42052
|
+
}
|
|
42053
|
+
function bindRestoredWorktrees(sessions) {
|
|
42054
|
+
for (const session of sessions.values()) {
|
|
42055
|
+
if (session.worktree || !session.cwd) continue;
|
|
42056
|
+
const worktree = detectExistingWorktree(session.cwd);
|
|
42057
|
+
if (worktree) session.worktree = worktree;
|
|
42058
|
+
}
|
|
42059
|
+
}
|
|
42060
|
+
function bindResumedWorktree(session, cwd, notify2) {
|
|
42061
|
+
if (!session) return;
|
|
42062
|
+
const worktree = detectExistingWorktree(cwd);
|
|
42063
|
+
if (!worktree) return;
|
|
42064
|
+
session.worktree = worktree;
|
|
42065
|
+
notify2();
|
|
42066
|
+
}
|
|
42067
|
+
|
|
42068
|
+
// src/commands/sessions/daemon/worktree/boundTreeRoots.ts
|
|
42069
|
+
function boundTreeRoots(sessions, exclude) {
|
|
42070
|
+
const roots = /* @__PURE__ */ new Set();
|
|
42071
|
+
for (const session of sessions.values()) {
|
|
42072
|
+
if (session === exclude) continue;
|
|
42073
|
+
const root2 = treeRootOf(session);
|
|
42074
|
+
if (root2 && !roots.has(root2) && holdsTree(session, root2)) roots.add(root2);
|
|
42075
|
+
}
|
|
42076
|
+
return roots;
|
|
42077
|
+
}
|
|
42078
|
+
function treeRootOf(session) {
|
|
42079
|
+
if (!session.cwd) return void 0;
|
|
42080
|
+
return canonicalTreePath(findRepoRoot(session.cwd) ?? session.cwd);
|
|
42081
|
+
}
|
|
42082
|
+
function holdsTree(session, root2) {
|
|
42083
|
+
if (session.closing) return true;
|
|
42084
|
+
if (session.status !== "done" && session.status !== "error") return true;
|
|
42085
|
+
if (!worktreeConfigFor(root2).enabled) return true;
|
|
42086
|
+
const durability = checkDurabilitySync(root2);
|
|
42087
|
+
daemonLog(`tree ${root2} ${releaseReason(durability, session)}`);
|
|
42088
|
+
return !durability.durable;
|
|
42089
|
+
}
|
|
42090
|
+
function releaseReason(durability, session) {
|
|
42091
|
+
if (!durability.durable)
|
|
42092
|
+
return `still held by finished session ${session.id}: ${durability.reason}`;
|
|
42093
|
+
if (durability.gone)
|
|
42094
|
+
return `free for allocation: session ${session.id}'s directory is gone from disk \u2014 nothing to land, not landed work`;
|
|
42095
|
+
return `free for allocation: session ${session.id} finished and its work is landed`;
|
|
42096
|
+
}
|
|
42097
|
+
|
|
42098
|
+
// src/commands/sessions/daemon/worktree/allocateAndBind.ts
|
|
42099
|
+
function allocateAndBind(ctx, cwd, create, options2 = {}, context) {
|
|
42100
|
+
const alloc = allocateTree(cwd, boundTreeRoots(ctx.sessions), options2);
|
|
42101
|
+
const needsSeeding = alloc.kind === "worktree" && alloc.created === true;
|
|
42102
|
+
const id = ctx.spawnWith(
|
|
42103
|
+
(sid) => create(sid, alloc.cwd, needsSeeding),
|
|
42104
|
+
context
|
|
42105
|
+
);
|
|
42106
|
+
bindNewWorktree(ctx.sessions.get(id), alloc, ctx.notify, ctx.startHeld);
|
|
42107
|
+
return id;
|
|
42108
|
+
}
|
|
42109
|
+
|
|
42110
|
+
// src/commands/sessions/daemon/worktree/divergencePrompt.ts
|
|
42111
|
+
var MAX_REASON_LINES = 20;
|
|
42112
|
+
var MAX_REASON_CHARS = 2e3;
|
|
42113
|
+
var RECONCILE_STEPS = [
|
|
42114
|
+
"Reconcile it yourself, without discarding any commits on either side:",
|
|
42115
|
+
"",
|
|
42116
|
+
"1. Inspect `git status`, `git log --oneline @{u}..@` and `git log --oneline @..@{u}`.",
|
|
42117
|
+
'2. Stash any uncommitted work with `git stash push -u -m "assist divergence"`, and restore it with `git stash pop` once the branch is reconciled.',
|
|
42118
|
+
"3. Rebase the local commits onto the upstream with `git rebase @{u}`, resolving conflicts so both sides' changes survive. If the rebase cannot be resolved cleanly, `git rebase --abort` and `git merge @{u}` instead.",
|
|
42119
|
+
"4. Push the result with a plain `git push` so the branch matches its upstream again.",
|
|
42120
|
+
"5. Build the clone with `assist run auto-build`.",
|
|
42121
|
+
"6. Run /close. The daemon resumes its auto-update loop once this session closes.",
|
|
42122
|
+
"",
|
|
42123
|
+
"Never force-push, `git reset`, or otherwise discard commits. If you cannot reconcile the branch without doing so, explain what is blocking it and leave this session open for the user instead of closing it."
|
|
42124
|
+
];
|
|
42125
|
+
var SIMULATED_STEPS = [
|
|
42126
|
+
"This divergence was simulated to test the escalation; there is nothing to reconcile. Run /close now. The daemon resumes its auto-update loop once this session closes."
|
|
42127
|
+
];
|
|
42128
|
+
function divergenceReason(scrollback) {
|
|
42129
|
+
return stripAnsi(scrollback).split(/\r?\n|\r/).map((line) => line.trimEnd()).filter((line) => line.trim().length > 0).slice(-MAX_REASON_LINES).join("\n").slice(-MAX_REASON_CHARS);
|
|
42130
|
+
}
|
|
42131
|
+
function divergencePrompt(clone, scrollback) {
|
|
42132
|
+
const reason4 = divergenceReason(scrollback);
|
|
42133
|
+
return [
|
|
42134
|
+
`The daemon's auto-update loop for the assist install in ${clone} paused because \`assist watch wait --pull --build\` could not fast-forward the branch (exit 3). The tail of its output:`,
|
|
42135
|
+
"",
|
|
42136
|
+
"```",
|
|
42137
|
+
reason4 || "(no output captured)",
|
|
42138
|
+
"```",
|
|
42139
|
+
"",
|
|
42140
|
+
...reason4.includes(simulatedDivergenceNote) ? SIMULATED_STEPS : RECONCILE_STEPS
|
|
42141
|
+
].join("\n");
|
|
42142
|
+
}
|
|
42143
|
+
|
|
42144
|
+
// src/commands/sessions/daemon/worktree/liveEscalationIn.ts
|
|
42145
|
+
var ENDED = ["done", "error"];
|
|
42146
|
+
function liveEscalationIn(sessions, clone) {
|
|
42147
|
+
return [...sessions.values()].find(
|
|
42148
|
+
(s) => s.divergenceEscalation === true && s.cwd !== void 0 && canonicalTreePath(s.cwd) === clone && !ENDED.includes(s.status)
|
|
42149
|
+
);
|
|
42150
|
+
}
|
|
42151
|
+
|
|
42152
|
+
// src/commands/sessions/daemon/worktree/escalateDivergence.ts
|
|
42153
|
+
function escalateDivergence(ctx, cwd, output) {
|
|
42154
|
+
const clone = canonicalTreePath(cwd);
|
|
42155
|
+
const live = liveEscalationIn(ctx.sessions, clone);
|
|
42156
|
+
if (live) {
|
|
42157
|
+
daemonLog(
|
|
42158
|
+
`auto-update diverged in the clone ${clone}: no escalation spawned, session ${live.id} is already diagnosing it (${live.status})`
|
|
42159
|
+
);
|
|
42160
|
+
return live.id;
|
|
42161
|
+
}
|
|
42162
|
+
const prompt = divergencePrompt(clone, output);
|
|
42163
|
+
const id = allocateAndBind(
|
|
42164
|
+
ctx,
|
|
42165
|
+
clone,
|
|
42166
|
+
(sid, resolvedCwd) => ({
|
|
42167
|
+
...createSession(sid, { prompt, cwd: resolvedCwd ?? clone }),
|
|
42168
|
+
divergenceEscalation: true
|
|
42169
|
+
}),
|
|
42170
|
+
{ inPlace: true }
|
|
42171
|
+
);
|
|
42172
|
+
daemonLog(
|
|
42173
|
+
`auto-update diverged in the clone ${clone}: spawned escalation session ${id} to diagnose it`
|
|
42174
|
+
);
|
|
42175
|
+
return id;
|
|
42176
|
+
}
|
|
42177
|
+
|
|
42178
|
+
// src/commands/sessions/daemon/autoUpdate/autoUpdateEnabled.ts
|
|
42179
|
+
function autoUpdateEnabled(installDir) {
|
|
42180
|
+
try {
|
|
42181
|
+
return loadConfigFrom(installDir).autoUpdate?.enabled ?? true;
|
|
42182
|
+
} catch (error) {
|
|
42183
|
+
daemonLog(
|
|
42184
|
+
`auto-update: config failed to load, keeping the default (on): ${error instanceof Error ? error.message : String(error)}`
|
|
42185
|
+
);
|
|
42186
|
+
return true;
|
|
42187
|
+
}
|
|
42188
|
+
}
|
|
42189
|
+
|
|
42190
|
+
// src/commands/sessions/daemon/autoUpdate/lineRecorder.ts
|
|
42191
|
+
function lineRecorder(onLine) {
|
|
42192
|
+
let pending = "";
|
|
42193
|
+
return (text17) => {
|
|
42194
|
+
const lines2 = (pending + text17).split(/\r?\n|\r/);
|
|
42195
|
+
pending = lines2.pop() ?? "";
|
|
42196
|
+
for (const line of lines2) {
|
|
42197
|
+
const clean2 = stripAnsi(line).trimEnd();
|
|
42198
|
+
if (clean2.trim()) onLine(clean2);
|
|
42199
|
+
}
|
|
42200
|
+
};
|
|
42201
|
+
}
|
|
42202
|
+
|
|
42203
|
+
// src/commands/sessions/daemon/autoUpdate/holdForEscalation.ts
|
|
42204
|
+
var ESCALATION_POLL_MS = 5e3;
|
|
42205
|
+
async function holdForEscalation(sessionId, deps2) {
|
|
42206
|
+
deps2.note(`paused while session ${sessionId} reconciles the divergence`);
|
|
42207
|
+
while (deps2.isLive(sessionId)) await deps2.sleep(ESCALATION_POLL_MS);
|
|
42208
|
+
deps2.note(`session ${sessionId} ended; resuming`);
|
|
42209
|
+
}
|
|
42210
|
+
|
|
42211
|
+
// src/commands/sessions/daemon/autoUpdate/runLapCapturingTail.ts
|
|
42212
|
+
var OUTPUT_TAIL = 16 * 1024;
|
|
42213
|
+
async function runLapCapturingTail(deps2) {
|
|
42214
|
+
let tail = "";
|
|
42215
|
+
const end = await deps2.runLap((text17) => {
|
|
42216
|
+
tail = (tail + text17).slice(-OUTPUT_TAIL);
|
|
42217
|
+
deps2.record(text17);
|
|
42218
|
+
});
|
|
42219
|
+
return { end, tail };
|
|
42220
|
+
}
|
|
42221
|
+
|
|
42222
|
+
// src/commands/sessions/daemon/autoUpdate/runAutoUpdateLoop.ts
|
|
42223
|
+
var DIVERGED_EXIT_CODE = 3;
|
|
42224
|
+
var RETRY_AFTER_FAILURE_MS = 5 * 60 * 1e3;
|
|
42225
|
+
async function runAutoUpdateLoop(deps2, maxLaps = Number.POSITIVE_INFINITY) {
|
|
42226
|
+
let held = deps2.liveEscalation();
|
|
42227
|
+
for (let lap = 1; lap <= maxLaps; lap++) {
|
|
42228
|
+
if (held) await holdForEscalation(held, deps2);
|
|
42229
|
+
held = void 0;
|
|
42230
|
+
deps2.note(
|
|
42231
|
+
`lap ${lap} at ${(/* @__PURE__ */ new Date()).toLocaleString()}: assist ${WATCH_LAP_ARGS.join(" ")}`
|
|
42232
|
+
);
|
|
42233
|
+
let result;
|
|
42234
|
+
try {
|
|
42235
|
+
result = await runLapCapturingTail(deps2);
|
|
42236
|
+
} catch (error) {
|
|
42237
|
+
deps2.note(
|
|
42238
|
+
`lap ${lap} could not start (${error instanceof Error ? error.message : String(error)}); retrying in ${RETRY_AFTER_FAILURE_MS / 6e4}m`
|
|
42239
|
+
);
|
|
42240
|
+
await deps2.sleep(RETRY_AFTER_FAILURE_MS);
|
|
42241
|
+
continue;
|
|
42242
|
+
}
|
|
42243
|
+
const { end, tail } = result;
|
|
42244
|
+
const how = end.signal ? `killed by ${end.signal}` : `exited ${end.code}`;
|
|
42245
|
+
if (end.code === DIVERGED_EXIT_CODE) {
|
|
42246
|
+
held = deps2.escalate(tail);
|
|
42247
|
+
deps2.note(`lap ${lap} ${how} (divergence); escalated to session ${held}`);
|
|
42248
|
+
continue;
|
|
42249
|
+
}
|
|
42250
|
+
if (decideLap(end, false).kind === "relaunch") {
|
|
42251
|
+
deps2.note(`lap ${lap} ${how}; relaunching`);
|
|
42252
|
+
continue;
|
|
42253
|
+
}
|
|
42254
|
+
deps2.note(
|
|
42255
|
+
`lap ${lap} ${how}; retrying in ${RETRY_AFTER_FAILURE_MS / 6e4}m`
|
|
42256
|
+
);
|
|
42257
|
+
await deps2.sleep(RETRY_AFTER_FAILURE_MS);
|
|
42258
|
+
}
|
|
42259
|
+
}
|
|
42260
|
+
|
|
42261
|
+
// src/commands/sessions/daemon/autoUpdate/runUpdateLap.ts
|
|
42262
|
+
import { spawn as spawn11 } from "child_process";
|
|
42263
|
+
function runUpdateLap(cwd, onOutput) {
|
|
42264
|
+
return new Promise((resolve28, reject) => {
|
|
42265
|
+
const { ASSIST_ACTIVITY_ID: _activity, ...env } = process.env;
|
|
42266
|
+
const child = spawn11(
|
|
42267
|
+
process.execPath,
|
|
42268
|
+
[process.argv[1], ...WATCH_LAP_ARGS],
|
|
42269
|
+
{
|
|
42270
|
+
cwd,
|
|
42271
|
+
env,
|
|
42272
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
42273
|
+
windowsHide: true
|
|
42274
|
+
}
|
|
42275
|
+
);
|
|
42276
|
+
const killWithDaemon = () => {
|
|
42277
|
+
child.kill();
|
|
42278
|
+
};
|
|
42279
|
+
process.on("exit", killWithDaemon);
|
|
42280
|
+
child.stdout.on("data", (chunk) => onOutput(chunk.toString()));
|
|
42281
|
+
child.stderr.on("data", (chunk) => onOutput(chunk.toString()));
|
|
42282
|
+
child.on("close", (code, signal) => {
|
|
42283
|
+
process.off("exit", killWithDaemon);
|
|
42284
|
+
resolve28({ code, signal });
|
|
42285
|
+
});
|
|
42286
|
+
child.on("error", (error) => {
|
|
42287
|
+
process.off("exit", killWithDaemon);
|
|
42288
|
+
reject(error);
|
|
42289
|
+
});
|
|
42290
|
+
});
|
|
42291
|
+
}
|
|
42292
|
+
|
|
42293
|
+
// src/commands/sessions/daemon/autoUpdate/trimWatcherLog.ts
|
|
42294
|
+
import { readFileSync as readFileSync65, writeFileSync as writeFileSync54 } from "fs";
|
|
42295
|
+
var KEEP_TAIL = 256 * 1024;
|
|
42296
|
+
var MAX_LOG_SIZE = 4 * 1024 * 1024;
|
|
42297
|
+
function trimWatcherLog(cwd) {
|
|
42298
|
+
const path106 = watcherLogPath(cwd);
|
|
42299
|
+
let log2;
|
|
42300
|
+
try {
|
|
42301
|
+
log2 = readFileSync65(path106, "utf8");
|
|
42302
|
+
} catch {
|
|
42303
|
+
return;
|
|
42304
|
+
}
|
|
42305
|
+
if (log2.length <= MAX_LOG_SIZE) return;
|
|
42306
|
+
writeFileSync54(path106, log2.slice(-KEEP_TAIL));
|
|
42307
|
+
daemonLog(`auto-update: log ${path106} trimmed to its last ${KEEP_TAIL} chars`);
|
|
42308
|
+
}
|
|
42309
|
+
|
|
42310
|
+
// src/commands/sessions/daemon/autoUpdate/startAutoUpdate.ts
|
|
42311
|
+
function loopDeps(installDir, ctx) {
|
|
42312
|
+
const clone = canonicalTreePath(installDir);
|
|
42313
|
+
const logLine = lineRecorder((line) => daemonLog(`auto-update: ${line}`));
|
|
42314
|
+
return {
|
|
42315
|
+
runLap: (onOutput) => runUpdateLap(installDir, onOutput),
|
|
42316
|
+
record: (text17) => {
|
|
42317
|
+
appendWatcherLog(installDir, text17);
|
|
42318
|
+
logLine(text17);
|
|
42319
|
+
},
|
|
42320
|
+
note: (text17) => {
|
|
42321
|
+
appendWatcherLog(installDir, watcherNote(text17));
|
|
42322
|
+
daemonLog(`auto-update: ${text17}`);
|
|
42323
|
+
},
|
|
42324
|
+
liveEscalation: () => liveEscalationIn(ctx().sessions, clone)?.id,
|
|
42325
|
+
escalate: (output) => escalateDivergence(ctx(), installDir, output),
|
|
42326
|
+
isLive: (sessionId) => {
|
|
42327
|
+
const status2 = ctx().sessions.get(sessionId)?.status;
|
|
42328
|
+
return status2 !== void 0 && status2 !== "done" && status2 !== "error";
|
|
42329
|
+
},
|
|
42330
|
+
sleep: (ms) => sleep2(ms)
|
|
42331
|
+
};
|
|
42332
|
+
}
|
|
42333
|
+
function startAutoUpdate(ctx) {
|
|
42334
|
+
const installDir = getInstallDir();
|
|
42335
|
+
if (!autoUpdateEnabled(installDir)) {
|
|
42336
|
+
daemonLog("auto-update: off (autoUpdate.enabled is false)");
|
|
42337
|
+
return;
|
|
42338
|
+
}
|
|
42339
|
+
if (!isGitRepo(installDir)) {
|
|
42340
|
+
daemonLog(`auto-update: off (${installDir} is not a git clone)`);
|
|
42341
|
+
return;
|
|
42342
|
+
}
|
|
42343
|
+
trimWatcherLog(installDir);
|
|
42344
|
+
daemonLog(`auto-update: pulling and building ${installDir} as origin moves`);
|
|
42345
|
+
void runAutoUpdateLoop(loopDeps(installDir, ctx)).catch(
|
|
42346
|
+
(error) => daemonLog(
|
|
42347
|
+
`auto-update: loop stopped: ${error instanceof Error ? error.message : String(error)}`
|
|
42348
|
+
)
|
|
42349
|
+
);
|
|
42350
|
+
}
|
|
42351
|
+
|
|
41991
42352
|
// src/commands/sessions/daemon/setStatus.ts
|
|
41992
42353
|
function setStatus2(session, newStatus) {
|
|
41993
42354
|
const now = Date.now();
|
|
@@ -42010,16 +42371,16 @@ function setStatus2(session, newStatus) {
|
|
|
42010
42371
|
|
|
42011
42372
|
// src/commands/sessions/daemon/worktree/reapWorktree.ts
|
|
42012
42373
|
import { existsSync as existsSync87 } from "fs";
|
|
42013
|
-
import { basename as
|
|
42374
|
+
import { basename as basename29 } from "path";
|
|
42014
42375
|
|
|
42015
42376
|
// src/commands/sessions/daemon/worktree/deleteStrandedTree.ts
|
|
42016
42377
|
import { existsSync as existsSync86 } from "fs";
|
|
42017
|
-
import { join as
|
|
42378
|
+
import { join as join103 } from "path";
|
|
42018
42379
|
|
|
42019
42380
|
// src/commands/sessions/daemon/worktree/deleteTreeDirectly.ts
|
|
42020
42381
|
import { statSync as statSync15 } from "fs";
|
|
42021
42382
|
import { rm as rm3 } from "fs/promises";
|
|
42022
|
-
import { join as
|
|
42383
|
+
import { join as join102 } from "path";
|
|
42023
42384
|
async function deleteTreeDirectly(clone, worktreePath, why) {
|
|
42024
42385
|
if (holdsAGitDirectoryRatherThanALink(worktreePath)) {
|
|
42025
42386
|
const refusal = "it is a clone of its own, not a linked worktree";
|
|
@@ -42047,7 +42408,7 @@ async function deleteTreeDirectly(clone, worktreePath, why) {
|
|
|
42047
42408
|
return { removed: true };
|
|
42048
42409
|
}
|
|
42049
42410
|
function holdsAGitDirectoryRatherThanALink(worktreePath) {
|
|
42050
|
-
return statSync15(
|
|
42411
|
+
return statSync15(join102(worktreePath, ".git"), {
|
|
42051
42412
|
throwIfNoEntry: false
|
|
42052
42413
|
})?.isDirectory() === true;
|
|
42053
42414
|
}
|
|
@@ -42083,7 +42444,7 @@ async function deleteStrandedTree(clone, worktreePath, cause) {
|
|
|
42083
42444
|
);
|
|
42084
42445
|
}
|
|
42085
42446
|
function strandedReason(worktreePath, cause) {
|
|
42086
|
-
if (!existsSync86(
|
|
42447
|
+
if (!existsSync86(join103(worktreePath, ".git")))
|
|
42087
42448
|
return "its .git link is already gone";
|
|
42088
42449
|
if (/not a working tree|not a git repository/i.test(reason2(cause)))
|
|
42089
42450
|
return "git no longer recognises it as a working tree";
|
|
@@ -42153,7 +42514,7 @@ async function reapWorktree(worktreePath, force = false) {
|
|
|
42153
42514
|
const clone = owningClone(worktreePath);
|
|
42154
42515
|
const removal = await removeTree(clone, worktreePath, force);
|
|
42155
42516
|
if (!removal.removed) return removal;
|
|
42156
|
-
await deleteWorktreeBranch(clone,
|
|
42517
|
+
await deleteWorktreeBranch(clone, basename29(worktreePath));
|
|
42157
42518
|
forgetWorktree(worktreePath);
|
|
42158
42519
|
daemonLog(`worktree ${worktreePath} reaped${force ? " (forced)" : ""}`);
|
|
42159
42520
|
return removal;
|
|
@@ -42227,34 +42588,6 @@ function killPtyTree(pty2) {
|
|
|
42227
42588
|
}
|
|
42228
42589
|
}
|
|
42229
42590
|
|
|
42230
|
-
// src/commands/sessions/daemon/worktree/liveWatcherFor.ts
|
|
42231
|
-
function liveWatcherFor(sessions, clone) {
|
|
42232
|
-
for (const session of sessions.values())
|
|
42233
|
-
if (session.watcher === true && session.cwd !== void 0 && (clone === void 0 || canonicalTreePath(session.cwd) === clone) && session.status !== "stopped" && session.status !== "error")
|
|
42234
|
-
return session;
|
|
42235
|
-
return void 0;
|
|
42236
|
-
}
|
|
42237
|
-
|
|
42238
|
-
// src/commands/sessions/daemon/worktree/resolveClone.ts
|
|
42239
|
-
function resolveClone(cwd) {
|
|
42240
|
-
const repoRoot2 = findRepoRoot(cwd) ?? cwd;
|
|
42241
|
-
return canonicalTreePath(mainWorktree(repoRoot2) ?? repoRoot2);
|
|
42242
|
-
}
|
|
42243
|
-
|
|
42244
|
-
// src/commands/sessions/daemon/worktree/orphanedWatcherFor.ts
|
|
42245
|
-
function orphanedWatcherFor(sessions, dismissed) {
|
|
42246
|
-
if (dismissed.watcher === true || !dismissed.cwd) return void 0;
|
|
42247
|
-
if (!liveWatcherFor(sessions)) return void 0;
|
|
42248
|
-
const known = dismissed.worktree?.clone ?? dismissed.releasedFromClone;
|
|
42249
|
-
const clone = known ? canonicalTreePath(known) : resolveClone(dismissed.cwd);
|
|
42250
|
-
const watcher = liveWatcherFor(sessions, clone);
|
|
42251
|
-
if (!watcher) return void 0;
|
|
42252
|
-
const kept = [...sessions.values()].some(
|
|
42253
|
-
(s) => s.watcher !== true && s.cwd !== void 0 && resolveClone(s.cwd) === clone
|
|
42254
|
-
);
|
|
42255
|
-
return kept ? void 0 : { watcher, clone };
|
|
42256
|
-
}
|
|
42257
|
-
|
|
42258
42591
|
// src/commands/sessions/daemon/worktree/treeUnderClose.ts
|
|
42259
42592
|
function treeUnderClose(session) {
|
|
42260
42593
|
if (session.worktree) return { path: session.worktree.path, removable: true };
|
|
@@ -42285,13 +42618,6 @@ function dismissSession(sessions, id) {
|
|
|
42285
42618
|
if (s.activity?.itemId != null) releaseLock(s.activity.itemId);
|
|
42286
42619
|
sessions.delete(id);
|
|
42287
42620
|
daemonLog(`session ${id} dismissed (${s.name})`);
|
|
42288
|
-
const orphan = orphanedWatcherFor(sessions, s);
|
|
42289
|
-
if (orphan) {
|
|
42290
|
-
daemonLog(
|
|
42291
|
-
`reaping watcher session ${orphan.watcher.id} for the clone ${orphan.clone}: its last session ${id} was dismissed`
|
|
42292
|
-
);
|
|
42293
|
-
dismissSession(sessions, orphan.watcher.id);
|
|
42294
|
-
}
|
|
42295
42621
|
if (!s.worktree) return true;
|
|
42296
42622
|
if (sharedWith.length > 0)
|
|
42297
42623
|
daemonLog(
|
|
@@ -42526,43 +42852,6 @@ function dismissSessionGated(sessions, id, notify2, discard = false) {
|
|
|
42526
42852
|
settle();
|
|
42527
42853
|
}
|
|
42528
42854
|
|
|
42529
|
-
// src/commands/sessions/daemon/worktree/resumeWatcherAfterEscalation.ts
|
|
42530
|
-
var ENDED = ["done", "error"];
|
|
42531
|
-
var endedViaDismissOrExit = /* @__PURE__ */ new WeakSet();
|
|
42532
|
-
function resumeWatcherAfterEscalation(sessions, escalation, restart) {
|
|
42533
|
-
if (escalation.divergenceEscalation !== true || !escalation.cwd) return;
|
|
42534
|
-
if (endedViaDismissOrExit.has(escalation)) return;
|
|
42535
|
-
endedViaDismissOrExit.add(escalation);
|
|
42536
|
-
const clone = canonicalTreePath(escalation.cwd);
|
|
42537
|
-
const watcher = [...sessions.values()].find(
|
|
42538
|
-
(s) => s.watcher === true && s.cwd !== void 0 && canonicalTreePath(s.cwd) === clone
|
|
42539
|
-
);
|
|
42540
|
-
if (!watcher) {
|
|
42541
|
-
daemonLog(
|
|
42542
|
-
`escalation session ${escalation.id} ended: no watcher left in the clone ${clone} to restart`
|
|
42543
|
-
);
|
|
42544
|
-
return;
|
|
42545
|
-
}
|
|
42546
|
-
if (!ENDED.includes(watcher.status)) {
|
|
42547
|
-
daemonLog(
|
|
42548
|
-
`escalation session ${escalation.id} ended: watcher session ${watcher.id} in the clone ${clone} is already ${watcher.status}`
|
|
42549
|
-
);
|
|
42550
|
-
return;
|
|
42551
|
-
}
|
|
42552
|
-
daemonLog(
|
|
42553
|
-
`escalation session ${escalation.id} ended: restarting watcher session ${watcher.id} in the clone ${clone}`
|
|
42554
|
-
);
|
|
42555
|
-
watcher.watcherRestartReason = `after escalation session ${escalation.id} ended`;
|
|
42556
|
-
restart(watcher.id);
|
|
42557
|
-
}
|
|
42558
|
-
|
|
42559
|
-
// src/commands/sessions/daemon/dismissAndResumeWatcher.ts
|
|
42560
|
-
function dismissAndResumeWatcher(sessions, id, notify2, restart) {
|
|
42561
|
-
const session = sessions.get(id);
|
|
42562
|
-
dismissSessionGated(sessions, id, notify2);
|
|
42563
|
-
if (session) resumeWatcherAfterEscalation(sessions, session, restart);
|
|
42564
|
-
}
|
|
42565
|
-
|
|
42566
42855
|
// src/commands/sessions/daemon/drainSessions.ts
|
|
42567
42856
|
function drainSessions(sessions, notify2) {
|
|
42568
42857
|
const drained = [...sessions.values()].map((s) => ({
|
|
@@ -42976,37 +43265,10 @@ function startSessionTitleGeneration(session, notify2) {
|
|
|
42976
43265
|
runSessionTitleGeneration(session, prompt, notify2, singleLineTitle(prompt));
|
|
42977
43266
|
}
|
|
42978
43267
|
|
|
42979
|
-
// src/commands/sessions/daemon/worktree/appendWatcherLog.ts
|
|
42980
|
-
import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync34 } from "fs";
|
|
42981
|
-
import { dirname as dirname42 } from "path";
|
|
42982
|
-
|
|
42983
|
-
// src/commands/sessions/daemon/worktree/watcherLogPath.ts
|
|
42984
|
-
import { createHash as createHash4 } from "crypto";
|
|
42985
|
-
import { homedir as homedir26 } from "os";
|
|
42986
|
-
import { basename as basename29, join as join103 } from "path";
|
|
42987
|
-
function watcherLogPath(cwd) {
|
|
42988
|
-
const clone = canonicalTreePath(cwd);
|
|
42989
|
-
const key = createHash4("sha1").update(clone).digest("hex").slice(0, 8);
|
|
42990
|
-
const dir = process.env.ASSIST_WATCHER_LOG_DIR ?? join103(homedir26(), ".assist", "watchers");
|
|
42991
|
-
return join103(dir, `${basename29(clone) || "root"}-${key}.log`);
|
|
42992
|
-
}
|
|
42993
|
-
|
|
42994
|
-
// src/commands/sessions/daemon/worktree/appendWatcherLog.ts
|
|
42995
|
-
function appendWatcherLog(session, data) {
|
|
42996
|
-
if (session.watcher !== true || !session.cwd) return;
|
|
42997
|
-
try {
|
|
42998
|
-
const path106 = watcherLogPath(session.cwd);
|
|
42999
|
-
mkdirSync34(dirname42(path106), { recursive: true });
|
|
43000
|
-
appendFileSync3(path106, data);
|
|
43001
|
-
} catch {
|
|
43002
|
-
}
|
|
43003
|
-
}
|
|
43004
|
-
|
|
43005
43268
|
// src/commands/sessions/daemon/emitSessionOutput.ts
|
|
43006
43269
|
var MAX_SCROLLBACK = 256 * 1024;
|
|
43007
43270
|
function emitSessionOutput(session, clients, data) {
|
|
43008
43271
|
session.scrollback += data;
|
|
43009
|
-
appendWatcherLog(session, data);
|
|
43010
43272
|
if (session.scrollback.length > MAX_SCROLLBACK) {
|
|
43011
43273
|
session.scrollback = session.scrollback.slice(-MAX_SCROLLBACK);
|
|
43012
43274
|
}
|
|
@@ -44077,13 +44339,6 @@ function applyUsageRecord(sessions, windows, notify2, claudeSessionId, transcrip
|
|
|
44077
44339
|
notify2();
|
|
44078
44340
|
}
|
|
44079
44341
|
|
|
44080
|
-
// src/commands/sessions/daemon/watcherNote.ts
|
|
44081
|
-
function watcherNote(text17) {
|
|
44082
|
-
return `\r
|
|
44083
|
-
\x1B[2m\u2500\u2500 ${text17} \u2500\u2500\x1B[0m\r
|
|
44084
|
-
`;
|
|
44085
|
-
}
|
|
44086
|
-
|
|
44087
44342
|
// src/commands/sessions/daemon/shouldAutoRun.ts
|
|
44088
44343
|
function shouldAutoRun(session, status2 = session.status) {
|
|
44089
44344
|
if (!session.autoRun) return { run: false, reason: null };
|
|
@@ -44138,7 +44393,6 @@ function shouldAutoDismiss(session, exitCode) {
|
|
|
44138
44393
|
}
|
|
44139
44394
|
|
|
44140
44395
|
// src/commands/sessions/daemon/finishStatusChange.ts
|
|
44141
|
-
var DIVERGED_EXIT_CODE = 3;
|
|
44142
44396
|
function finishStatusChange(session, status2, exitCode, deps2) {
|
|
44143
44397
|
daemonLog(`session ${session.id} status: ${session.status} -> ${status2}`);
|
|
44144
44398
|
void flushPhaseActiveMs(session);
|
|
@@ -44146,9 +44400,6 @@ function finishStatusChange(session, status2, exitCode, deps2) {
|
|
|
44146
44400
|
if (status2 === "waiting")
|
|
44147
44401
|
startTranscriptTitleGeneration(session, deps2.notify);
|
|
44148
44402
|
if (status2 !== "stopped") session.undurable = void 0;
|
|
44149
|
-
if (session.watcher === true && exitCode === DIVERGED_EXIT_CODE)
|
|
44150
|
-
deps2.escalateDivergence?.(session);
|
|
44151
|
-
if (status2 === "done" || status2 === "error") deps2.resumeWatcher?.(session);
|
|
44152
44403
|
const autoRun = shouldAutoRun(session);
|
|
44153
44404
|
if (autoRun.run) {
|
|
44154
44405
|
deps2.reuseForRun(session, autoRun.itemId);
|
|
@@ -44194,40 +44445,6 @@ function makeStatusChangeHandler(sessions, deps2) {
|
|
|
44194
44445
|
);
|
|
44195
44446
|
}
|
|
44196
44447
|
|
|
44197
|
-
// src/commands/sessions/daemon/worktree/detectExistingWorktree.ts
|
|
44198
|
-
function detectExistingWorktree(cwd) {
|
|
44199
|
-
const repoRoot2 = findRepoRoot(cwd) ?? cwd;
|
|
44200
|
-
if (!worktreeConfigFor(repoRoot2).enabled) return void 0;
|
|
44201
|
-
const clone = mainWorktree(repoRoot2);
|
|
44202
|
-
if (clone && clone !== repoRoot2) return { path: repoRoot2, clone };
|
|
44203
|
-
return void 0;
|
|
44204
|
-
}
|
|
44205
|
-
|
|
44206
|
-
// src/commands/sessions/daemon/worktree/bindNewWorktree.ts
|
|
44207
|
-
function bindNewWorktree(session, alloc, notify2, startHeld = () => {
|
|
44208
|
-
}) {
|
|
44209
|
-
if (alloc.kind !== "worktree" || !alloc.cwd || !alloc.clone || !session)
|
|
44210
|
-
return;
|
|
44211
|
-
session.worktree = { path: alloc.cwd, clone: alloc.clone };
|
|
44212
|
-
if (alloc.created)
|
|
44213
|
-
seedWorktree(alloc.cwd, alloc.clone, () => startHeld(session));
|
|
44214
|
-
notify2();
|
|
44215
|
-
}
|
|
44216
|
-
function bindRestoredWorktrees(sessions) {
|
|
44217
|
-
for (const session of sessions.values()) {
|
|
44218
|
-
if (session.worktree || !session.cwd) continue;
|
|
44219
|
-
const worktree = detectExistingWorktree(session.cwd);
|
|
44220
|
-
if (worktree) session.worktree = worktree;
|
|
44221
|
-
}
|
|
44222
|
-
}
|
|
44223
|
-
function bindResumedWorktree(session, cwd, notify2) {
|
|
44224
|
-
if (!session) return;
|
|
44225
|
-
const worktree = detectExistingWorktree(cwd);
|
|
44226
|
-
if (!worktree) return;
|
|
44227
|
-
session.worktree = worktree;
|
|
44228
|
-
notify2();
|
|
44229
|
-
}
|
|
44230
|
-
|
|
44231
44448
|
// src/commands/sessions/daemon/attachReusedRun.ts
|
|
44232
44449
|
function attachReusedRun(session, alloc, clients, onStatusChange, tree) {
|
|
44233
44450
|
broadcast(clients, { type: "clear", sessionId: session.id });
|
|
@@ -44306,137 +44523,6 @@ function startReusedRunPty(session, assistArgs, itemId2, hold, clients, onStatus
|
|
|
44306
44523
|
return true;
|
|
44307
44524
|
}
|
|
44308
44525
|
|
|
44309
|
-
// src/commands/sessions/daemon/createAssistSession.ts
|
|
44310
|
-
function createAssistSession(id, assistArgs, cwd, meta, holdPty) {
|
|
44311
|
-
const startedAt = Date.now();
|
|
44312
|
-
return {
|
|
44313
|
-
id,
|
|
44314
|
-
name: `assist ${assistArgs.join(" ")}`,
|
|
44315
|
-
title: meta?.title,
|
|
44316
|
-
subtitle: meta?.subtitle,
|
|
44317
|
-
commandType: "assist",
|
|
44318
|
-
status: "running",
|
|
44319
|
-
startedAt,
|
|
44320
|
-
runningMs: 0,
|
|
44321
|
-
runningSince: startedAt,
|
|
44322
|
-
waitingSince: null,
|
|
44323
|
-
...startOrHoldPty(
|
|
44324
|
-
() => spawnPty(["assist", ...assistArgs], cwd, id),
|
|
44325
|
-
holdPty
|
|
44326
|
-
),
|
|
44327
|
-
scrollback: "",
|
|
44328
|
-
assistArgs,
|
|
44329
|
-
cwd
|
|
44330
|
-
};
|
|
44331
|
-
}
|
|
44332
|
-
|
|
44333
|
-
// src/commands/sessions/daemon/worktree/seedWatcherScrollback.ts
|
|
44334
|
-
import { readFileSync as readFileSync65, writeFileSync as writeFileSync54 } from "fs";
|
|
44335
|
-
var SCROLLBACK_TAIL = 256 * 1024;
|
|
44336
|
-
var MAX_LOG_SIZE = 4 * 1024 * 1024;
|
|
44337
|
-
function readLogTail2(path106) {
|
|
44338
|
-
let log2;
|
|
44339
|
-
try {
|
|
44340
|
-
log2 = readFileSync65(path106, "utf8");
|
|
44341
|
-
} catch {
|
|
44342
|
-
return "";
|
|
44343
|
-
}
|
|
44344
|
-
if (log2.length > MAX_LOG_SIZE) {
|
|
44345
|
-
log2 = log2.slice(-SCROLLBACK_TAIL);
|
|
44346
|
-
writeFileSync54(path106, log2);
|
|
44347
|
-
daemonLog(`watcher log ${path106} trimmed to its last ${log2.length} chars`);
|
|
44348
|
-
}
|
|
44349
|
-
return log2.slice(-SCROLLBACK_TAIL);
|
|
44350
|
-
}
|
|
44351
|
-
function seedWatcherScrollback(cwd, event) {
|
|
44352
|
-
const path106 = watcherLogPath(cwd);
|
|
44353
|
-
const history = readLogTail2(path106);
|
|
44354
|
-
const note = watcherNote(`${event} at ${(/* @__PURE__ */ new Date()).toLocaleString()}`);
|
|
44355
|
-
appendWatcherLog({ watcher: true, cwd }, note);
|
|
44356
|
-
daemonLog(
|
|
44357
|
-
`watcher in ${cwd} seeded with ${history.length} chars of history from ${path106} (${event})`
|
|
44358
|
-
);
|
|
44359
|
-
return history + note;
|
|
44360
|
-
}
|
|
44361
|
-
|
|
44362
|
-
// src/commands/sessions/daemon/createWatcherSession.ts
|
|
44363
|
-
var WATCHER_ARGS = ["watch", "loop"];
|
|
44364
|
-
function createWatcherSession(id, cwd) {
|
|
44365
|
-
return {
|
|
44366
|
-
...createAssistSession(id, WATCHER_ARGS, cwd),
|
|
44367
|
-
watcher: true,
|
|
44368
|
-
scrollback: seedWatcherScrollback(cwd, "watcher started")
|
|
44369
|
-
};
|
|
44370
|
-
}
|
|
44371
|
-
|
|
44372
|
-
// src/commands/sessions/daemon/worktree/boundTreeRoots.ts
|
|
44373
|
-
function boundTreeRoots(sessions, exclude) {
|
|
44374
|
-
const roots = /* @__PURE__ */ new Set();
|
|
44375
|
-
for (const session of sessions.values()) {
|
|
44376
|
-
if (session === exclude) continue;
|
|
44377
|
-
const root2 = treeRootOf(session);
|
|
44378
|
-
if (root2 && !roots.has(root2) && holdsTree(session, root2)) roots.add(root2);
|
|
44379
|
-
}
|
|
44380
|
-
return roots;
|
|
44381
|
-
}
|
|
44382
|
-
function treeRootOf(session) {
|
|
44383
|
-
if (!session.cwd) return void 0;
|
|
44384
|
-
return canonicalTreePath(findRepoRoot(session.cwd) ?? session.cwd);
|
|
44385
|
-
}
|
|
44386
|
-
function holdsTree(session, root2) {
|
|
44387
|
-
if (session.closing) return true;
|
|
44388
|
-
if (session.status !== "done" && session.status !== "error") return true;
|
|
44389
|
-
if (!worktreeConfigFor(root2).enabled) return true;
|
|
44390
|
-
const durability = checkDurabilitySync(root2);
|
|
44391
|
-
daemonLog(`tree ${root2} ${releaseReason(durability, session)}`);
|
|
44392
|
-
return !durability.durable;
|
|
44393
|
-
}
|
|
44394
|
-
function releaseReason(durability, session) {
|
|
44395
|
-
if (!durability.durable)
|
|
44396
|
-
return `still held by finished session ${session.id}: ${durability.reason}`;
|
|
44397
|
-
if (durability.gone)
|
|
44398
|
-
return `free for allocation: session ${session.id}'s directory is gone from disk \u2014 nothing to land, not landed work`;
|
|
44399
|
-
return `free for allocation: session ${session.id} finished and its work is landed`;
|
|
44400
|
-
}
|
|
44401
|
-
|
|
44402
|
-
// src/commands/sessions/daemon/worktree/allocateAndBind.ts
|
|
44403
|
-
function allocateAndBind(ctx, cwd, create, options2 = {}, context) {
|
|
44404
|
-
const alloc = allocateTree(cwd, boundTreeRoots(ctx.sessions), options2);
|
|
44405
|
-
const needsSeeding = alloc.kind === "worktree" && alloc.created === true;
|
|
44406
|
-
const id = ctx.spawnWith(
|
|
44407
|
-
(sid) => create(sid, alloc.cwd, needsSeeding),
|
|
44408
|
-
context
|
|
44409
|
-
);
|
|
44410
|
-
bindNewWorktree(ctx.sessions.get(id), alloc, ctx.notify, ctx.startHeld);
|
|
44411
|
-
return id;
|
|
44412
|
-
}
|
|
44413
|
-
|
|
44414
|
-
// src/commands/sessions/daemon/worktree/ensureWatcher.ts
|
|
44415
|
-
function ensureWatcher(ctx, cwd) {
|
|
44416
|
-
if (!cwd) return void 0;
|
|
44417
|
-
const repoRoot2 = findRepoRoot(cwd) ?? cwd;
|
|
44418
|
-
const cfg = worktreeConfigFor(repoRoot2);
|
|
44419
|
-
if (!cfg.enabled || cfg.watcher !== true) return void 0;
|
|
44420
|
-
const clone = resolveClone(repoRoot2);
|
|
44421
|
-
const live = liveWatcherFor(ctx.sessions, clone);
|
|
44422
|
-
if (live) {
|
|
44423
|
-
daemonLog(
|
|
44424
|
-
`no watcher spawned for the clone ${clone}: session ${live.id} is already watching it (${live.status})`
|
|
44425
|
-
);
|
|
44426
|
-
return void 0;
|
|
44427
|
-
}
|
|
44428
|
-
const id = allocateAndBind(
|
|
44429
|
-
ctx,
|
|
44430
|
-
clone,
|
|
44431
|
-
(sid, resolvedCwd) => createWatcherSession(sid, resolvedCwd ?? clone),
|
|
44432
|
-
{ inPlace: true }
|
|
44433
|
-
);
|
|
44434
|
-
daemonLog(
|
|
44435
|
-
`spawned watcher session ${id} running assist ${WATCHER_ARGS.join(" ")} in the clone ${clone}`
|
|
44436
|
-
);
|
|
44437
|
-
return id;
|
|
44438
|
-
}
|
|
44439
|
-
|
|
44440
44526
|
// src/commands/sessions/daemon/worktree/isBacklogRunArgs.ts
|
|
44441
44527
|
var ITEM_ID = /^a?\d+$/;
|
|
44442
44528
|
function isBacklogRunArgs(args) {
|
|
@@ -44500,7 +44586,6 @@ function failChainedRun(session, itemId2, error, clients, tree) {
|
|
|
44500
44586
|
// src/commands/sessions/daemon/reuseSessionForRun.ts
|
|
44501
44587
|
function reuseSessionForRun(session, itemId2, clients, onStatusChange, tree) {
|
|
44502
44588
|
const assistArgs = ["backlog", "run", String(itemId2)];
|
|
44503
|
-
const originCwd = session.cwd;
|
|
44504
44589
|
resetCardForRun(session, assistArgs);
|
|
44505
44590
|
let alloc;
|
|
44506
44591
|
try {
|
|
@@ -44522,13 +44607,71 @@ function reuseSessionForRun(session, itemId2, clients, onStatusChange, tree) {
|
|
|
44522
44607
|
onStatusChange
|
|
44523
44608
|
);
|
|
44524
44609
|
if (!started) return;
|
|
44525
|
-
if (tree) ensureWatcher(tree, originCwd);
|
|
44526
44610
|
attachReusedRun(session, alloc, clients, onStatusChange, tree);
|
|
44527
44611
|
daemonLog(
|
|
44528
44612
|
`session ${session.id} reused for backlog run ${itemId2}: ${session.name}`
|
|
44529
44613
|
);
|
|
44530
44614
|
}
|
|
44531
44615
|
|
|
44616
|
+
// src/commands/sessions/daemon/managerStatusChangeHandler.ts
|
|
44617
|
+
function managerStatusChangeHandler(sessions, clients, dismiss, notify2, treeCtx) {
|
|
44618
|
+
const handler = makeStatusChangeHandler(sessions, {
|
|
44619
|
+
dismiss,
|
|
44620
|
+
notify: notify2,
|
|
44621
|
+
reuseForRun: (session, itemId2) => reuseSessionForRun(session, itemId2, clients, handler, treeCtx())
|
|
44622
|
+
});
|
|
44623
|
+
return handler;
|
|
44624
|
+
}
|
|
44625
|
+
|
|
44626
|
+
// src/commands/sessions/daemon/applySetStatus.ts
|
|
44627
|
+
function applySetStatus(sessions, id, status2, source, onStatusChange, rebind) {
|
|
44628
|
+
const session = sessions.get(id);
|
|
44629
|
+
if (!session) {
|
|
44630
|
+
daemonLog(
|
|
44631
|
+
`set-status for unknown session id=${id} status=${status2} (no live session; ignoring)`
|
|
44632
|
+
);
|
|
44633
|
+
return;
|
|
44634
|
+
}
|
|
44635
|
+
if (session.status === "stopped") {
|
|
44636
|
+
daemonLog(
|
|
44637
|
+
`set-status for stopped session id=${id} status=${status2} (held pending durable close; ignoring)`
|
|
44638
|
+
);
|
|
44639
|
+
return;
|
|
44640
|
+
}
|
|
44641
|
+
rebind?.(session);
|
|
44642
|
+
if (source === "permission") session.permissionActive = true;
|
|
44643
|
+
if (status2 === "running") session.permissionActive = false;
|
|
44644
|
+
onStatusChange(session, status2);
|
|
44645
|
+
}
|
|
44646
|
+
|
|
44647
|
+
// src/commands/sessions/daemon/rebindClaudeSession.ts
|
|
44648
|
+
function rebindClaudeSession(session, claudeSessionId, notify2, onStatusChange) {
|
|
44649
|
+
if (!claudeSessionId) return;
|
|
44650
|
+
if (session.claudeSessionId === claudeSessionId) return;
|
|
44651
|
+
daemonLog(
|
|
44652
|
+
`session ${session.id} claude session moved: ${session.claudeSessionId ?? "none"} -> ${claudeSessionId}`
|
|
44653
|
+
);
|
|
44654
|
+
session.claudeSessionId = claudeSessionId;
|
|
44655
|
+
watchTranscript(session, notify2, onStatusChange);
|
|
44656
|
+
}
|
|
44657
|
+
|
|
44658
|
+
// src/commands/sessions/daemon/setStatusFromHook.ts
|
|
44659
|
+
function setStatusFromHook(sessions, report, notify2, onStatusChange) {
|
|
44660
|
+
applySetStatus(
|
|
44661
|
+
sessions,
|
|
44662
|
+
report.id,
|
|
44663
|
+
report.status,
|
|
44664
|
+
report.source,
|
|
44665
|
+
onStatusChange,
|
|
44666
|
+
(session) => rebindClaudeSession(
|
|
44667
|
+
session,
|
|
44668
|
+
report.claudeSessionId,
|
|
44669
|
+
notify2,
|
|
44670
|
+
onStatusChange
|
|
44671
|
+
)
|
|
44672
|
+
);
|
|
44673
|
+
}
|
|
44674
|
+
|
|
44532
44675
|
// src/commands/sessions/daemon/respawnSession.ts
|
|
44533
44676
|
function respawnSession(session, respawn, status2, clients, onStatusChange) {
|
|
44534
44677
|
session.gitWatcher?.close();
|
|
@@ -44537,8 +44680,7 @@ function respawnSession(session, respawn, status2, clients, onStatusChange) {
|
|
|
44537
44680
|
session.pendingDismiss = void 0;
|
|
44538
44681
|
session.closeGrace?.cancel();
|
|
44539
44682
|
session.closing = void 0;
|
|
44540
|
-
|
|
44541
|
-
if (!keepHistory) session.scrollback = "";
|
|
44683
|
+
session.scrollback = "";
|
|
44542
44684
|
session.startedAt = Date.now();
|
|
44543
44685
|
session.runningMs = 0;
|
|
44544
44686
|
session.runningSince = null;
|
|
@@ -44555,15 +44697,7 @@ function respawnSession(session, respawn, status2, clients, onStatusChange) {
|
|
|
44555
44697
|
session.pty?.resize(session.cols, session.rows);
|
|
44556
44698
|
} catch {
|
|
44557
44699
|
}
|
|
44558
|
-
|
|
44559
|
-
const reason4 = session.watcherRestartReason;
|
|
44560
|
-
session.watcherRestartReason = void 0;
|
|
44561
|
-
emitSessionOutput(
|
|
44562
|
-
session,
|
|
44563
|
-
clients,
|
|
44564
|
-
watcherNote(reason4 ? `watcher restarted ${reason4}` : "watcher restarted")
|
|
44565
|
-
);
|
|
44566
|
-
} else broadcast(clients, { type: "clear", sessionId: session.id });
|
|
44700
|
+
broadcast(clients, { type: "clear", sessionId: session.id });
|
|
44567
44701
|
wirePtyEvents(session, clients, onStatusChange);
|
|
44568
44702
|
}
|
|
44569
44703
|
|
|
@@ -44734,144 +44868,6 @@ function restartManagedSession(sessions, id, clients, onStatusChange) {
|
|
|
44734
44868
|
return { ok: true };
|
|
44735
44869
|
}
|
|
44736
44870
|
|
|
44737
|
-
// src/commands/sessions/daemon/worktree/divergencePrompt.ts
|
|
44738
|
-
var MAX_REASON_LINES = 20;
|
|
44739
|
-
var MAX_REASON_CHARS = 2e3;
|
|
44740
|
-
var RECONCILE_STEPS = [
|
|
44741
|
-
"Reconcile it yourself, without discarding any commits on either side:",
|
|
44742
|
-
"",
|
|
44743
|
-
"1. Inspect `git status`, `git log --oneline @{u}..@` and `git log --oneline @..@{u}`.",
|
|
44744
|
-
'2. Stash any uncommitted work with `git stash push -u -m "assist divergence"`, and restore it with `git stash pop` once the branch is reconciled.',
|
|
44745
|
-
"3. Rebase the local commits onto the upstream with `git rebase @{u}`, resolving conflicts so both sides' changes survive. If the rebase cannot be resolved cleanly, `git rebase --abort` and `git merge @{u}` instead.",
|
|
44746
|
-
"4. Push the result with a plain `git push` so the branch matches its upstream again.",
|
|
44747
|
-
"5. Build the clone with `assist run auto-build`.",
|
|
44748
|
-
"6. Run /close. The daemon restarts the watcher once this session closes.",
|
|
44749
|
-
"",
|
|
44750
|
-
"Never force-push, `git reset`, or otherwise discard commits. If you cannot reconcile the branch without doing so, explain what is blocking it and leave this session open for the user instead of closing it."
|
|
44751
|
-
];
|
|
44752
|
-
var SIMULATED_STEPS = [
|
|
44753
|
-
"This divergence was simulated to test the escalation; there is nothing to reconcile. Run /close now. The daemon restarts the watcher once this session closes."
|
|
44754
|
-
];
|
|
44755
|
-
function divergenceReason(scrollback) {
|
|
44756
|
-
return stripAnsi(scrollback).split(/\r?\n|\r/).map((line) => line.trimEnd()).filter((line) => line.trim().length > 0).slice(-MAX_REASON_LINES).join("\n").slice(-MAX_REASON_CHARS);
|
|
44757
|
-
}
|
|
44758
|
-
function divergencePrompt(clone, scrollback) {
|
|
44759
|
-
const reason4 = divergenceReason(scrollback);
|
|
44760
|
-
return [
|
|
44761
|
-
`The watcher in ${clone} stopped because \`assist watch wait --pull --build\` could not fast-forward the branch (exit 3). The tail of its output:`,
|
|
44762
|
-
"",
|
|
44763
|
-
"```",
|
|
44764
|
-
reason4 || "(no output captured)",
|
|
44765
|
-
"```",
|
|
44766
|
-
"",
|
|
44767
|
-
...reason4.includes(simulatedDivergenceNote) ? SIMULATED_STEPS : RECONCILE_STEPS
|
|
44768
|
-
].join("\n");
|
|
44769
|
-
}
|
|
44770
|
-
|
|
44771
|
-
// src/commands/sessions/daemon/worktree/escalateDivergence.ts
|
|
44772
|
-
var ENDED2 = ["done", "error", "stopped"];
|
|
44773
|
-
function escalateDivergence(ctx, watcher) {
|
|
44774
|
-
if (!watcher.cwd) return void 0;
|
|
44775
|
-
const clone = canonicalTreePath(watcher.cwd);
|
|
44776
|
-
const live = [...ctx.sessions.values()].find(
|
|
44777
|
-
(s) => s.divergenceEscalation === true && s.cwd !== void 0 && canonicalTreePath(s.cwd) === clone && !ENDED2.includes(s.status)
|
|
44778
|
-
);
|
|
44779
|
-
if (live) {
|
|
44780
|
-
daemonLog(
|
|
44781
|
-
`watcher session ${watcher.id} exited 3 (divergence) in the clone ${clone}: no escalation spawned, session ${live.id} is already diagnosing it (${live.status})`
|
|
44782
|
-
);
|
|
44783
|
-
return void 0;
|
|
44784
|
-
}
|
|
44785
|
-
const prompt = divergencePrompt(clone, watcher.scrollback);
|
|
44786
|
-
const id = allocateAndBind(
|
|
44787
|
-
ctx,
|
|
44788
|
-
clone,
|
|
44789
|
-
(sid, resolvedCwd) => ({
|
|
44790
|
-
...createSession(sid, { prompt, cwd: resolvedCwd ?? clone }),
|
|
44791
|
-
divergenceEscalation: true
|
|
44792
|
-
}),
|
|
44793
|
-
{ inPlace: true }
|
|
44794
|
-
);
|
|
44795
|
-
daemonLog(
|
|
44796
|
-
`watcher session ${watcher.id} exited 3 (divergence) in the clone ${clone}: spawned escalation session ${id} to diagnose it`
|
|
44797
|
-
);
|
|
44798
|
-
return id;
|
|
44799
|
-
}
|
|
44800
|
-
|
|
44801
|
-
// src/commands/sessions/daemon/managerStatusChangeHandler.ts
|
|
44802
|
-
function managerStatusChangeHandler(sessions, clients, dismiss, notify2, treeCtx) {
|
|
44803
|
-
const handler = makeStatusChangeHandler(sessions, {
|
|
44804
|
-
dismiss,
|
|
44805
|
-
notify: notify2,
|
|
44806
|
-
reuseForRun: (session, itemId2) => reuseSessionForRun(session, itemId2, clients, handler, treeCtx()),
|
|
44807
|
-
escalateDivergence: (watcher) => {
|
|
44808
|
-
const id = escalateDivergence(treeCtx(), watcher);
|
|
44809
|
-
if (id)
|
|
44810
|
-
emitSessionOutput(
|
|
44811
|
-
watcher,
|
|
44812
|
-
clients,
|
|
44813
|
-
watcherNote(
|
|
44814
|
-
`divergence escalated to session ${id}; the watcher restarts once it ends`
|
|
44815
|
-
)
|
|
44816
|
-
);
|
|
44817
|
-
},
|
|
44818
|
-
resumeWatcher: (escalation) => resumeWatcherAfterEscalation(sessions, escalation, (watcherId) => {
|
|
44819
|
-
if (restartManagedSession(sessions, watcherId, clients, handler).ok)
|
|
44820
|
-
notify2();
|
|
44821
|
-
})
|
|
44822
|
-
});
|
|
44823
|
-
return handler;
|
|
44824
|
-
}
|
|
44825
|
-
|
|
44826
|
-
// src/commands/sessions/daemon/applySetStatus.ts
|
|
44827
|
-
function applySetStatus(sessions, id, status2, source, onStatusChange, rebind) {
|
|
44828
|
-
const session = sessions.get(id);
|
|
44829
|
-
if (!session) {
|
|
44830
|
-
daemonLog(
|
|
44831
|
-
`set-status for unknown session id=${id} status=${status2} (no live session; ignoring)`
|
|
44832
|
-
);
|
|
44833
|
-
return;
|
|
44834
|
-
}
|
|
44835
|
-
if (session.status === "stopped") {
|
|
44836
|
-
daemonLog(
|
|
44837
|
-
`set-status for stopped session id=${id} status=${status2} (held pending durable close; ignoring)`
|
|
44838
|
-
);
|
|
44839
|
-
return;
|
|
44840
|
-
}
|
|
44841
|
-
rebind?.(session);
|
|
44842
|
-
if (source === "permission") session.permissionActive = true;
|
|
44843
|
-
if (status2 === "running") session.permissionActive = false;
|
|
44844
|
-
onStatusChange(session, status2);
|
|
44845
|
-
}
|
|
44846
|
-
|
|
44847
|
-
// src/commands/sessions/daemon/rebindClaudeSession.ts
|
|
44848
|
-
function rebindClaudeSession(session, claudeSessionId, notify2, onStatusChange) {
|
|
44849
|
-
if (!claudeSessionId) return;
|
|
44850
|
-
if (session.claudeSessionId === claudeSessionId) return;
|
|
44851
|
-
daemonLog(
|
|
44852
|
-
`session ${session.id} claude session moved: ${session.claudeSessionId ?? "none"} -> ${claudeSessionId}`
|
|
44853
|
-
);
|
|
44854
|
-
session.claudeSessionId = claudeSessionId;
|
|
44855
|
-
watchTranscript(session, notify2, onStatusChange);
|
|
44856
|
-
}
|
|
44857
|
-
|
|
44858
|
-
// src/commands/sessions/daemon/setStatusFromHook.ts
|
|
44859
|
-
function setStatusFromHook(sessions, report, notify2, onStatusChange) {
|
|
44860
|
-
applySetStatus(
|
|
44861
|
-
sessions,
|
|
44862
|
-
report.id,
|
|
44863
|
-
report.status,
|
|
44864
|
-
report.source,
|
|
44865
|
-
onStatusChange,
|
|
44866
|
-
(session) => rebindClaudeSession(
|
|
44867
|
-
session,
|
|
44868
|
-
report.claudeSessionId,
|
|
44869
|
-
notify2,
|
|
44870
|
-
onStatusChange
|
|
44871
|
-
)
|
|
44872
|
-
);
|
|
44873
|
-
}
|
|
44874
|
-
|
|
44875
44871
|
// src/commands/sessions/daemon/restartAndNotify.ts
|
|
44876
44872
|
function restartAndNotify(sessions, id, clients, onStatusChange, notify2) {
|
|
44877
44873
|
const result = restartManagedSession(sessions, id, clients, onStatusChange);
|
|
@@ -44913,8 +44909,7 @@ function restoreBase(id, persisted) {
|
|
|
44913
44909
|
cwd: persisted.cwd,
|
|
44914
44910
|
assistArgs: persisted.assistArgs,
|
|
44915
44911
|
initialPrompt: persisted.initialPrompt,
|
|
44916
|
-
starred: persisted.
|
|
44917
|
-
watcher: persisted.watcher,
|
|
44912
|
+
starred: persisted.starred,
|
|
44918
44913
|
divergenceEscalation: persisted.divergenceEscalation,
|
|
44919
44914
|
design: persisted.design,
|
|
44920
44915
|
auto: persisted.auto,
|
|
@@ -44974,12 +44969,19 @@ function underTempRoot(path106) {
|
|
|
44974
44969
|
);
|
|
44975
44970
|
}
|
|
44976
44971
|
|
|
44977
|
-
// src/commands/sessions/daemon/
|
|
44978
|
-
function
|
|
44972
|
+
// src/commands/sessions/daemon/dropUnrestorableSessions.ts
|
|
44973
|
+
function droppedReason(entry) {
|
|
44974
|
+
if (underTempRoot(entry.cwd)) return "lies outside any project root";
|
|
44975
|
+
if (entry.watcher === true)
|
|
44976
|
+
return "is a retired watcher session (the daemon now runs the assist auto-update loop itself)";
|
|
44977
|
+
return void 0;
|
|
44978
|
+
}
|
|
44979
|
+
function dropUnrestorableSessions(persisted) {
|
|
44979
44980
|
return persisted.filter((entry) => {
|
|
44980
|
-
|
|
44981
|
+
const reason4 = droppedReason(entry);
|
|
44982
|
+
if (!reason4) return true;
|
|
44981
44983
|
daemonLog(
|
|
44982
|
-
`persisted session ${describePersistedSession(entry)}
|
|
44984
|
+
`persisted session ${describePersistedSession(entry)} ${reason4}; dropped rather than restored`
|
|
44983
44985
|
);
|
|
44984
44986
|
return false;
|
|
44985
44987
|
});
|
|
@@ -45304,25 +45306,6 @@ function isUpdate(persisted) {
|
|
|
45304
45306
|
return persisted.commandType === "assist" && persisted.assistArgs?.[0] === "update";
|
|
45305
45307
|
}
|
|
45306
45308
|
|
|
45307
|
-
// src/commands/sessions/daemon/migrateClaudeWatcher.ts
|
|
45308
|
-
function migrateClaudeWatcher(id, persisted) {
|
|
45309
|
-
if (!persisted.watcher || persisted.commandType !== "claude")
|
|
45310
|
-
return persisted;
|
|
45311
|
-
daemonLog(
|
|
45312
|
-
`migrating claude /watch watcher session ${id} to assist ${WATCHER_ARGS.join(" ")}`
|
|
45313
|
-
);
|
|
45314
|
-
return {
|
|
45315
|
-
...persisted,
|
|
45316
|
-
name: `assist ${WATCHER_ARGS.join(" ")}`,
|
|
45317
|
-
commandType: "assist",
|
|
45318
|
-
assistArgs: WATCHER_ARGS,
|
|
45319
|
-
initialPrompt: void 0,
|
|
45320
|
-
claudeSessionId: void 0,
|
|
45321
|
-
harness: void 0,
|
|
45322
|
-
harnessSessionId: void 0
|
|
45323
|
-
};
|
|
45324
|
-
}
|
|
45325
|
-
|
|
45326
45309
|
// src/commands/sessions/daemon/needsWrapperRelaunch.ts
|
|
45327
45310
|
var LAUNCH_COMMANDS = ["draft", "feat", "bug", "refine"];
|
|
45328
45311
|
function needsWrapperRelaunch(persisted) {
|
|
@@ -45335,36 +45318,11 @@ function isOnceLaunch(persisted) {
|
|
|
45335
45318
|
return persisted.commandType === "assist" && !!persisted.assistArgs && LAUNCH_COMMANDS.includes(persisted.assistArgs[0]) && persisted.assistArgs.includes("--once");
|
|
45336
45319
|
}
|
|
45337
45320
|
|
|
45338
|
-
// src/commands/sessions/daemon/relaunchConsoleWatcher.ts
|
|
45339
|
-
function isConsoleWatcher(persisted) {
|
|
45340
|
-
return persisted.watcher === true && persisted.commandType === "assist" && !!persisted.assistArgs;
|
|
45341
|
-
}
|
|
45342
|
-
function relaunchConsoleWatcher(id, persisted, base) {
|
|
45343
|
-
const command = ["assist", ...persisted.assistArgs];
|
|
45344
|
-
daemonLog(
|
|
45345
|
-
`relaunching watcher session ${id} running ${command.join(" ")} in ${persisted.cwd ?? "(no cwd)"}`
|
|
45346
|
-
);
|
|
45347
|
-
const session = runningSession(
|
|
45348
|
-
base,
|
|
45349
|
-
persisted,
|
|
45350
|
-
spawnPty(command, persisted.cwd, id)
|
|
45351
|
-
);
|
|
45352
|
-
if (persisted.cwd)
|
|
45353
|
-
session.scrollback = seedWatcherScrollback(
|
|
45354
|
-
persisted.cwd,
|
|
45355
|
-
"daemon restarted; watcher relaunched"
|
|
45356
|
-
);
|
|
45357
|
-
return session;
|
|
45358
|
-
}
|
|
45359
|
-
|
|
45360
45321
|
// src/commands/sessions/daemon/restoreSession.ts
|
|
45361
|
-
function restoreSession(id,
|
|
45362
|
-
const persisted = migrateClaudeWatcher(id, stored);
|
|
45322
|
+
function restoreSession(id, persisted) {
|
|
45363
45323
|
const base = restoreBase(id, persisted);
|
|
45364
45324
|
if (persisted.status === "stopped") return stoppedSession(base, persisted);
|
|
45365
45325
|
if (isUpdate(persisted)) return updatedSession(id, persisted);
|
|
45366
|
-
if (isConsoleWatcher(persisted))
|
|
45367
|
-
return relaunchConsoleWatcher(id, persisted, base);
|
|
45368
45326
|
const status2 = deriveRestoreStatus(persisted);
|
|
45369
45327
|
if (needsWrapperRelaunch(persisted)) {
|
|
45370
45328
|
const resumesWrittenConversation = hasTranscriptOnDisk(persisted);
|
|
@@ -45418,7 +45376,7 @@ function logRestoreError(persisted, error) {
|
|
|
45418
45376
|
|
|
45419
45377
|
// src/commands/sessions/daemon/restoreAll.ts
|
|
45420
45378
|
function restoreAll(spawner, sessions) {
|
|
45421
|
-
const persisted =
|
|
45379
|
+
const persisted = dropUnrestorableSessions(loadPersistedSessions());
|
|
45422
45380
|
const cap2 = sessionLimits.maxRestore();
|
|
45423
45381
|
const restoredIds = /* @__PURE__ */ new Map();
|
|
45424
45382
|
const names = persisted.slice(0, cap2).map((entry) => {
|
|
@@ -45674,10 +45632,10 @@ function restoreAllSessions(spawner, sessions, notify2) {
|
|
|
45674
45632
|
|
|
45675
45633
|
// src/commands/sessions/daemon/retrySession.ts
|
|
45676
45634
|
function retrySession(session, clients, onStatusChange) {
|
|
45677
|
-
const
|
|
45678
|
-
if (!
|
|
45635
|
+
const spawn12 = respawnThunk(session);
|
|
45636
|
+
if (!spawn12) return false;
|
|
45679
45637
|
const respawn = () => {
|
|
45680
|
-
respawnSession(session,
|
|
45638
|
+
respawnSession(session, spawn12, "running", clients, onStatusChange);
|
|
45681
45639
|
daemonLog(`session ${session.id} retried: ${session.name}`);
|
|
45682
45640
|
};
|
|
45683
45641
|
const pty2 = session.pty;
|
|
@@ -46706,6 +46664,30 @@ function addAgentToStream(ctx, targetId, request) {
|
|
|
46706
46664
|
};
|
|
46707
46665
|
}
|
|
46708
46666
|
|
|
46667
|
+
// src/commands/sessions/daemon/createAssistSession.ts
|
|
46668
|
+
function createAssistSession(id, assistArgs, cwd, meta, holdPty) {
|
|
46669
|
+
const startedAt = Date.now();
|
|
46670
|
+
return {
|
|
46671
|
+
id,
|
|
46672
|
+
name: `assist ${assistArgs.join(" ")}`,
|
|
46673
|
+
title: meta?.title,
|
|
46674
|
+
subtitle: meta?.subtitle,
|
|
46675
|
+
commandType: "assist",
|
|
46676
|
+
status: "running",
|
|
46677
|
+
startedAt,
|
|
46678
|
+
runningMs: 0,
|
|
46679
|
+
runningSince: startedAt,
|
|
46680
|
+
waitingSince: null,
|
|
46681
|
+
...startOrHoldPty(
|
|
46682
|
+
() => spawnPty(["assist", ...assistArgs], cwd, id),
|
|
46683
|
+
holdPty
|
|
46684
|
+
),
|
|
46685
|
+
scrollback: "",
|
|
46686
|
+
assistArgs,
|
|
46687
|
+
cwd
|
|
46688
|
+
};
|
|
46689
|
+
}
|
|
46690
|
+
|
|
46709
46691
|
// src/commands/sessions/daemon/worktree/spawnInTree.ts
|
|
46710
46692
|
function spawnInTree(ctx, { prompt, cwd, design, auto, harness, inPlace }, context) {
|
|
46711
46693
|
return allocateAndBind(
|
|
@@ -46724,7 +46706,7 @@ function spawnInTree(ctx, { prompt, cwd, design, auto, harness, inPlace }, conte
|
|
|
46724
46706
|
);
|
|
46725
46707
|
}
|
|
46726
46708
|
function spawnAssistInTree(ctx, assistArgs, cwd, meta, context) {
|
|
46727
|
-
|
|
46709
|
+
return allocateAndBind(
|
|
46728
46710
|
ctx,
|
|
46729
46711
|
cwd,
|
|
46730
46712
|
(sid, resolvedCwd, holdUntilSeeded) => createAssistSession(sid, assistArgs, resolvedCwd, meta, holdUntilSeeded),
|
|
@@ -46737,8 +46719,6 @@ function spawnAssistInTree(ctx, assistArgs, cwd, meta, context) {
|
|
|
46737
46719
|
},
|
|
46738
46720
|
context
|
|
46739
46721
|
);
|
|
46740
|
-
if (isBacklogRunArgs(assistArgs)) ensureWatcher(ctx, cwd);
|
|
46741
|
-
return id;
|
|
46742
46722
|
}
|
|
46743
46723
|
|
|
46744
46724
|
// src/commands/sessions/daemon/worktree/resumeInTree.ts
|
|
@@ -46965,13 +46945,11 @@ var SessionManager = class {
|
|
|
46965
46945
|
);
|
|
46966
46946
|
}
|
|
46967
46947
|
dismissSession = (id) => {
|
|
46968
|
-
|
|
46969
|
-
this.sessions,
|
|
46970
|
-
id,
|
|
46971
|
-
this.notify,
|
|
46972
|
-
(watcherId) => this.restart(watcherId)
|
|
46973
|
-
);
|
|
46948
|
+
dismissSessionGated(this.sessions, id, this.notify);
|
|
46974
46949
|
};
|
|
46950
|
+
startAutoUpdate() {
|
|
46951
|
+
startAutoUpdate(() => this.treeCtx());
|
|
46952
|
+
}
|
|
46975
46953
|
discardSession = (id) => {
|
|
46976
46954
|
dismissSessionGated(this.sessions, id, this.notify, true);
|
|
46977
46955
|
};
|
|
@@ -47049,10 +47027,10 @@ var handleHelloRequest = (client, m, d) => {
|
|
|
47049
47027
|
};
|
|
47050
47028
|
|
|
47051
47029
|
// src/commands/sessions/daemon/creator.ts
|
|
47052
|
-
function creator(isNew,
|
|
47030
|
+
function creator(isNew, spawn12) {
|
|
47053
47031
|
return (client, m, d) => {
|
|
47054
47032
|
if (m.links.route(client, d)) return;
|
|
47055
|
-
const result =
|
|
47033
|
+
const result = spawn12(m, d);
|
|
47056
47034
|
if (typeof result !== "string") {
|
|
47057
47035
|
sendTo(client, { type: "error", message: result.error });
|
|
47058
47036
|
return;
|
|
@@ -47718,6 +47696,7 @@ function onListening(manager, checkAutoExit) {
|
|
|
47718
47696
|
);
|
|
47719
47697
|
daemonLog(`listening on ${daemonPaths.socket}`);
|
|
47720
47698
|
manager.links.reload();
|
|
47699
|
+
manager.startAutoUpdate();
|
|
47721
47700
|
checkAutoExit(manager.isIdle());
|
|
47722
47701
|
}
|
|
47723
47702
|
function cleanupOwnedFiles() {
|
|
@@ -47820,6 +47799,7 @@ function registerDaemon(program2) {
|
|
|
47820
47799
|
cmd.command("drain").description(
|
|
47821
47800
|
"Remove all sessions from the local daemon for a clean slate (does not affect the Windows daemon); prompts before closing live sessions, and a session holding unpushed work is stopped, not removed"
|
|
47822
47801
|
).option("--yes", "Close live sessions without confirming").action((options2) => drainDaemon(options2));
|
|
47802
|
+
configHelp(cmd, autoUpdateConfigHelp);
|
|
47823
47803
|
}
|
|
47824
47804
|
|
|
47825
47805
|
// src/commands/sessions/closeSession.ts
|
|
@@ -48080,14 +48060,14 @@ async function linkStatus(name, query = queryNodes) {
|
|
|
48080
48060
|
return (await query())?.links.find((l) => l.name === name);
|
|
48081
48061
|
}
|
|
48082
48062
|
async function awaitLinkReturn(name, options2, deps2 = {}) {
|
|
48083
|
-
const { query = queryNodes, sleep:
|
|
48063
|
+
const { query = queryNodes, sleep: sleep3 = wait, now = Date.now } = deps2;
|
|
48084
48064
|
const deadline = now() + options2.timeoutMs;
|
|
48085
48065
|
let sawDown = options2.sawDown;
|
|
48086
48066
|
while (now() < deadline) {
|
|
48087
48067
|
const link3 = await linkStatus(name, query);
|
|
48088
48068
|
if (link3?.state !== "connected") sawDown = true;
|
|
48089
48069
|
else if (sawDown) return link3;
|
|
48090
|
-
await
|
|
48070
|
+
await sleep3(POLL_MS4);
|
|
48091
48071
|
}
|
|
48092
48072
|
return void 0;
|
|
48093
48073
|
}
|