@staff0rd/assist 0.773.1 → 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.
|
|
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,
|
|
@@ -21316,10 +21328,15 @@ import { readFile as readFile4 } from "fs/promises";
|
|
|
21316
21328
|
import { join as join49 } from "path";
|
|
21317
21329
|
import chalk138 from "chalk";
|
|
21318
21330
|
|
|
21331
|
+
// src/commands/criteriaExtension/latestUpdate.ts
|
|
21332
|
+
function latestUpdate(manifest, updates) {
|
|
21333
|
+
const { id } = JSON.parse(manifest).browser_specific_settings.gecko;
|
|
21334
|
+
return JSON.parse(updates).addons[id]?.updates.at(-1);
|
|
21335
|
+
}
|
|
21336
|
+
|
|
21319
21337
|
// src/commands/criteriaExtension/latestUpdateLink.ts
|
|
21320
21338
|
function latestUpdateLink(manifest, updates) {
|
|
21321
|
-
|
|
21322
|
-
return JSON.parse(updates).addons[id]?.updates.at(-1)?.update_link;
|
|
21339
|
+
return latestUpdate(manifest, updates)?.update_link;
|
|
21323
21340
|
}
|
|
21324
21341
|
|
|
21325
21342
|
// src/commands/criteriaExtension/printCriteriaExtensionUrl.ts
|
|
@@ -25856,7 +25873,6 @@ function wouldDisturbWorkInProgress(clone, forCheckout) {
|
|
|
25856
25873
|
// src/commands/sessions/daemon/worktree/worktreeConfigFor.ts
|
|
25857
25874
|
var DISABLED = {
|
|
25858
25875
|
enabled: false,
|
|
25859
|
-
watcher: false,
|
|
25860
25876
|
trunk: false,
|
|
25861
25877
|
includeDrafts: false,
|
|
25862
25878
|
install: true,
|
|
@@ -25989,7 +26005,6 @@ function toPersistedSession(session) {
|
|
|
25989
26005
|
assistArgs: session.assistArgs,
|
|
25990
26006
|
activity: session.activity,
|
|
25991
26007
|
starred: session.starred,
|
|
25992
|
-
watcher: session.watcher,
|
|
25993
26008
|
divergenceEscalation: session.divergenceEscalation,
|
|
25994
26009
|
design: session.design,
|
|
25995
26010
|
auto: session.auto,
|
|
@@ -41400,7 +41415,6 @@ function toSessionInfo(session) {
|
|
|
41400
41415
|
autoRun,
|
|
41401
41416
|
autoAdvance,
|
|
41402
41417
|
starred,
|
|
41403
|
-
watcher,
|
|
41404
41418
|
design,
|
|
41405
41419
|
verifying,
|
|
41406
41420
|
pendingPrPreview,
|
|
@@ -41427,7 +41441,6 @@ function toSessionInfo(session) {
|
|
|
41427
41441
|
autoRun,
|
|
41428
41442
|
autoAdvance,
|
|
41429
41443
|
starred,
|
|
41430
|
-
watcher,
|
|
41431
41444
|
design,
|
|
41432
41445
|
verifying,
|
|
41433
41446
|
pendingPrPreview,
|
|
@@ -41983,6 +41996,359 @@ function createRunSession(id, { runName, runArgs, cwd, meta }) {
|
|
|
41983
41996
|
};
|
|
41984
41997
|
}
|
|
41985
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
|
+
|
|
41986
42352
|
// src/commands/sessions/daemon/setStatus.ts
|
|
41987
42353
|
function setStatus2(session, newStatus) {
|
|
41988
42354
|
const now = Date.now();
|
|
@@ -42005,16 +42371,16 @@ function setStatus2(session, newStatus) {
|
|
|
42005
42371
|
|
|
42006
42372
|
// src/commands/sessions/daemon/worktree/reapWorktree.ts
|
|
42007
42373
|
import { existsSync as existsSync87 } from "fs";
|
|
42008
|
-
import { basename as
|
|
42374
|
+
import { basename as basename29 } from "path";
|
|
42009
42375
|
|
|
42010
42376
|
// src/commands/sessions/daemon/worktree/deleteStrandedTree.ts
|
|
42011
42377
|
import { existsSync as existsSync86 } from "fs";
|
|
42012
|
-
import { join as
|
|
42378
|
+
import { join as join103 } from "path";
|
|
42013
42379
|
|
|
42014
42380
|
// src/commands/sessions/daemon/worktree/deleteTreeDirectly.ts
|
|
42015
42381
|
import { statSync as statSync15 } from "fs";
|
|
42016
42382
|
import { rm as rm3 } from "fs/promises";
|
|
42017
|
-
import { join as
|
|
42383
|
+
import { join as join102 } from "path";
|
|
42018
42384
|
async function deleteTreeDirectly(clone, worktreePath, why) {
|
|
42019
42385
|
if (holdsAGitDirectoryRatherThanALink(worktreePath)) {
|
|
42020
42386
|
const refusal = "it is a clone of its own, not a linked worktree";
|
|
@@ -42042,7 +42408,7 @@ async function deleteTreeDirectly(clone, worktreePath, why) {
|
|
|
42042
42408
|
return { removed: true };
|
|
42043
42409
|
}
|
|
42044
42410
|
function holdsAGitDirectoryRatherThanALink(worktreePath) {
|
|
42045
|
-
return statSync15(
|
|
42411
|
+
return statSync15(join102(worktreePath, ".git"), {
|
|
42046
42412
|
throwIfNoEntry: false
|
|
42047
42413
|
})?.isDirectory() === true;
|
|
42048
42414
|
}
|
|
@@ -42078,7 +42444,7 @@ async function deleteStrandedTree(clone, worktreePath, cause) {
|
|
|
42078
42444
|
);
|
|
42079
42445
|
}
|
|
42080
42446
|
function strandedReason(worktreePath, cause) {
|
|
42081
|
-
if (!existsSync86(
|
|
42447
|
+
if (!existsSync86(join103(worktreePath, ".git")))
|
|
42082
42448
|
return "its .git link is already gone";
|
|
42083
42449
|
if (/not a working tree|not a git repository/i.test(reason2(cause)))
|
|
42084
42450
|
return "git no longer recognises it as a working tree";
|
|
@@ -42148,7 +42514,7 @@ async function reapWorktree(worktreePath, force = false) {
|
|
|
42148
42514
|
const clone = owningClone(worktreePath);
|
|
42149
42515
|
const removal = await removeTree(clone, worktreePath, force);
|
|
42150
42516
|
if (!removal.removed) return removal;
|
|
42151
|
-
await deleteWorktreeBranch(clone,
|
|
42517
|
+
await deleteWorktreeBranch(clone, basename29(worktreePath));
|
|
42152
42518
|
forgetWorktree(worktreePath);
|
|
42153
42519
|
daemonLog(`worktree ${worktreePath} reaped${force ? " (forced)" : ""}`);
|
|
42154
42520
|
return removal;
|
|
@@ -42222,34 +42588,6 @@ function killPtyTree(pty2) {
|
|
|
42222
42588
|
}
|
|
42223
42589
|
}
|
|
42224
42590
|
|
|
42225
|
-
// src/commands/sessions/daemon/worktree/liveWatcherFor.ts
|
|
42226
|
-
function liveWatcherFor(sessions, clone) {
|
|
42227
|
-
for (const session of sessions.values())
|
|
42228
|
-
if (session.watcher === true && session.cwd !== void 0 && (clone === void 0 || canonicalTreePath(session.cwd) === clone) && session.status !== "stopped" && session.status !== "error")
|
|
42229
|
-
return session;
|
|
42230
|
-
return void 0;
|
|
42231
|
-
}
|
|
42232
|
-
|
|
42233
|
-
// src/commands/sessions/daemon/worktree/resolveClone.ts
|
|
42234
|
-
function resolveClone(cwd) {
|
|
42235
|
-
const repoRoot2 = findRepoRoot(cwd) ?? cwd;
|
|
42236
|
-
return canonicalTreePath(mainWorktree(repoRoot2) ?? repoRoot2);
|
|
42237
|
-
}
|
|
42238
|
-
|
|
42239
|
-
// src/commands/sessions/daemon/worktree/orphanedWatcherFor.ts
|
|
42240
|
-
function orphanedWatcherFor(sessions, dismissed) {
|
|
42241
|
-
if (dismissed.watcher === true || !dismissed.cwd) return void 0;
|
|
42242
|
-
if (!liveWatcherFor(sessions)) return void 0;
|
|
42243
|
-
const known = dismissed.worktree?.clone ?? dismissed.releasedFromClone;
|
|
42244
|
-
const clone = known ? canonicalTreePath(known) : resolveClone(dismissed.cwd);
|
|
42245
|
-
const watcher = liveWatcherFor(sessions, clone);
|
|
42246
|
-
if (!watcher) return void 0;
|
|
42247
|
-
const kept = [...sessions.values()].some(
|
|
42248
|
-
(s) => s.watcher !== true && s.cwd !== void 0 && resolveClone(s.cwd) === clone
|
|
42249
|
-
);
|
|
42250
|
-
return kept ? void 0 : { watcher, clone };
|
|
42251
|
-
}
|
|
42252
|
-
|
|
42253
42591
|
// src/commands/sessions/daemon/worktree/treeUnderClose.ts
|
|
42254
42592
|
function treeUnderClose(session) {
|
|
42255
42593
|
if (session.worktree) return { path: session.worktree.path, removable: true };
|
|
@@ -42280,13 +42618,6 @@ function dismissSession(sessions, id) {
|
|
|
42280
42618
|
if (s.activity?.itemId != null) releaseLock(s.activity.itemId);
|
|
42281
42619
|
sessions.delete(id);
|
|
42282
42620
|
daemonLog(`session ${id} dismissed (${s.name})`);
|
|
42283
|
-
const orphan = orphanedWatcherFor(sessions, s);
|
|
42284
|
-
if (orphan) {
|
|
42285
|
-
daemonLog(
|
|
42286
|
-
`reaping watcher session ${orphan.watcher.id} for the clone ${orphan.clone}: its last session ${id} was dismissed`
|
|
42287
|
-
);
|
|
42288
|
-
dismissSession(sessions, orphan.watcher.id);
|
|
42289
|
-
}
|
|
42290
42621
|
if (!s.worktree) return true;
|
|
42291
42622
|
if (sharedWith.length > 0)
|
|
42292
42623
|
daemonLog(
|
|
@@ -42521,43 +42852,6 @@ function dismissSessionGated(sessions, id, notify2, discard = false) {
|
|
|
42521
42852
|
settle();
|
|
42522
42853
|
}
|
|
42523
42854
|
|
|
42524
|
-
// src/commands/sessions/daemon/worktree/resumeWatcherAfterEscalation.ts
|
|
42525
|
-
var ENDED = ["done", "error"];
|
|
42526
|
-
var endedViaDismissOrExit = /* @__PURE__ */ new WeakSet();
|
|
42527
|
-
function resumeWatcherAfterEscalation(sessions, escalation, restart) {
|
|
42528
|
-
if (escalation.divergenceEscalation !== true || !escalation.cwd) return;
|
|
42529
|
-
if (endedViaDismissOrExit.has(escalation)) return;
|
|
42530
|
-
endedViaDismissOrExit.add(escalation);
|
|
42531
|
-
const clone = canonicalTreePath(escalation.cwd);
|
|
42532
|
-
const watcher = [...sessions.values()].find(
|
|
42533
|
-
(s) => s.watcher === true && s.cwd !== void 0 && canonicalTreePath(s.cwd) === clone
|
|
42534
|
-
);
|
|
42535
|
-
if (!watcher) {
|
|
42536
|
-
daemonLog(
|
|
42537
|
-
`escalation session ${escalation.id} ended: no watcher left in the clone ${clone} to restart`
|
|
42538
|
-
);
|
|
42539
|
-
return;
|
|
42540
|
-
}
|
|
42541
|
-
if (!ENDED.includes(watcher.status)) {
|
|
42542
|
-
daemonLog(
|
|
42543
|
-
`escalation session ${escalation.id} ended: watcher session ${watcher.id} in the clone ${clone} is already ${watcher.status}`
|
|
42544
|
-
);
|
|
42545
|
-
return;
|
|
42546
|
-
}
|
|
42547
|
-
daemonLog(
|
|
42548
|
-
`escalation session ${escalation.id} ended: restarting watcher session ${watcher.id} in the clone ${clone}`
|
|
42549
|
-
);
|
|
42550
|
-
watcher.watcherRestartReason = `after escalation session ${escalation.id} ended`;
|
|
42551
|
-
restart(watcher.id);
|
|
42552
|
-
}
|
|
42553
|
-
|
|
42554
|
-
// src/commands/sessions/daemon/dismissAndResumeWatcher.ts
|
|
42555
|
-
function dismissAndResumeWatcher(sessions, id, notify2, restart) {
|
|
42556
|
-
const session = sessions.get(id);
|
|
42557
|
-
dismissSessionGated(sessions, id, notify2);
|
|
42558
|
-
if (session) resumeWatcherAfterEscalation(sessions, session, restart);
|
|
42559
|
-
}
|
|
42560
|
-
|
|
42561
42855
|
// src/commands/sessions/daemon/drainSessions.ts
|
|
42562
42856
|
function drainSessions(sessions, notify2) {
|
|
42563
42857
|
const drained = [...sessions.values()].map((s) => ({
|
|
@@ -42971,37 +43265,10 @@ function startSessionTitleGeneration(session, notify2) {
|
|
|
42971
43265
|
runSessionTitleGeneration(session, prompt, notify2, singleLineTitle(prompt));
|
|
42972
43266
|
}
|
|
42973
43267
|
|
|
42974
|
-
// src/commands/sessions/daemon/worktree/appendWatcherLog.ts
|
|
42975
|
-
import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync34 } from "fs";
|
|
42976
|
-
import { dirname as dirname42 } from "path";
|
|
42977
|
-
|
|
42978
|
-
// src/commands/sessions/daemon/worktree/watcherLogPath.ts
|
|
42979
|
-
import { createHash as createHash4 } from "crypto";
|
|
42980
|
-
import { homedir as homedir26 } from "os";
|
|
42981
|
-
import { basename as basename29, join as join103 } from "path";
|
|
42982
|
-
function watcherLogPath(cwd) {
|
|
42983
|
-
const clone = canonicalTreePath(cwd);
|
|
42984
|
-
const key = createHash4("sha1").update(clone).digest("hex").slice(0, 8);
|
|
42985
|
-
const dir = process.env.ASSIST_WATCHER_LOG_DIR ?? join103(homedir26(), ".assist", "watchers");
|
|
42986
|
-
return join103(dir, `${basename29(clone) || "root"}-${key}.log`);
|
|
42987
|
-
}
|
|
42988
|
-
|
|
42989
|
-
// src/commands/sessions/daemon/worktree/appendWatcherLog.ts
|
|
42990
|
-
function appendWatcherLog(session, data) {
|
|
42991
|
-
if (session.watcher !== true || !session.cwd) return;
|
|
42992
|
-
try {
|
|
42993
|
-
const path106 = watcherLogPath(session.cwd);
|
|
42994
|
-
mkdirSync34(dirname42(path106), { recursive: true });
|
|
42995
|
-
appendFileSync3(path106, data);
|
|
42996
|
-
} catch {
|
|
42997
|
-
}
|
|
42998
|
-
}
|
|
42999
|
-
|
|
43000
43268
|
// src/commands/sessions/daemon/emitSessionOutput.ts
|
|
43001
43269
|
var MAX_SCROLLBACK = 256 * 1024;
|
|
43002
43270
|
function emitSessionOutput(session, clients, data) {
|
|
43003
43271
|
session.scrollback += data;
|
|
43004
|
-
appendWatcherLog(session, data);
|
|
43005
43272
|
if (session.scrollback.length > MAX_SCROLLBACK) {
|
|
43006
43273
|
session.scrollback = session.scrollback.slice(-MAX_SCROLLBACK);
|
|
43007
43274
|
}
|
|
@@ -44072,13 +44339,6 @@ function applyUsageRecord(sessions, windows, notify2, claudeSessionId, transcrip
|
|
|
44072
44339
|
notify2();
|
|
44073
44340
|
}
|
|
44074
44341
|
|
|
44075
|
-
// src/commands/sessions/daemon/watcherNote.ts
|
|
44076
|
-
function watcherNote(text17) {
|
|
44077
|
-
return `\r
|
|
44078
|
-
\x1B[2m\u2500\u2500 ${text17} \u2500\u2500\x1B[0m\r
|
|
44079
|
-
`;
|
|
44080
|
-
}
|
|
44081
|
-
|
|
44082
44342
|
// src/commands/sessions/daemon/shouldAutoRun.ts
|
|
44083
44343
|
function shouldAutoRun(session, status2 = session.status) {
|
|
44084
44344
|
if (!session.autoRun) return { run: false, reason: null };
|
|
@@ -44133,7 +44393,6 @@ function shouldAutoDismiss(session, exitCode) {
|
|
|
44133
44393
|
}
|
|
44134
44394
|
|
|
44135
44395
|
// src/commands/sessions/daemon/finishStatusChange.ts
|
|
44136
|
-
var DIVERGED_EXIT_CODE = 3;
|
|
44137
44396
|
function finishStatusChange(session, status2, exitCode, deps2) {
|
|
44138
44397
|
daemonLog(`session ${session.id} status: ${session.status} -> ${status2}`);
|
|
44139
44398
|
void flushPhaseActiveMs(session);
|
|
@@ -44141,9 +44400,6 @@ function finishStatusChange(session, status2, exitCode, deps2) {
|
|
|
44141
44400
|
if (status2 === "waiting")
|
|
44142
44401
|
startTranscriptTitleGeneration(session, deps2.notify);
|
|
44143
44402
|
if (status2 !== "stopped") session.undurable = void 0;
|
|
44144
|
-
if (session.watcher === true && exitCode === DIVERGED_EXIT_CODE)
|
|
44145
|
-
deps2.escalateDivergence?.(session);
|
|
44146
|
-
if (status2 === "done" || status2 === "error") deps2.resumeWatcher?.(session);
|
|
44147
44403
|
const autoRun = shouldAutoRun(session);
|
|
44148
44404
|
if (autoRun.run) {
|
|
44149
44405
|
deps2.reuseForRun(session, autoRun.itemId);
|
|
@@ -44189,40 +44445,6 @@ function makeStatusChangeHandler(sessions, deps2) {
|
|
|
44189
44445
|
);
|
|
44190
44446
|
}
|
|
44191
44447
|
|
|
44192
|
-
// src/commands/sessions/daemon/worktree/detectExistingWorktree.ts
|
|
44193
|
-
function detectExistingWorktree(cwd) {
|
|
44194
|
-
const repoRoot2 = findRepoRoot(cwd) ?? cwd;
|
|
44195
|
-
if (!worktreeConfigFor(repoRoot2).enabled) return void 0;
|
|
44196
|
-
const clone = mainWorktree(repoRoot2);
|
|
44197
|
-
if (clone && clone !== repoRoot2) return { path: repoRoot2, clone };
|
|
44198
|
-
return void 0;
|
|
44199
|
-
}
|
|
44200
|
-
|
|
44201
|
-
// src/commands/sessions/daemon/worktree/bindNewWorktree.ts
|
|
44202
|
-
function bindNewWorktree(session, alloc, notify2, startHeld = () => {
|
|
44203
|
-
}) {
|
|
44204
|
-
if (alloc.kind !== "worktree" || !alloc.cwd || !alloc.clone || !session)
|
|
44205
|
-
return;
|
|
44206
|
-
session.worktree = { path: alloc.cwd, clone: alloc.clone };
|
|
44207
|
-
if (alloc.created)
|
|
44208
|
-
seedWorktree(alloc.cwd, alloc.clone, () => startHeld(session));
|
|
44209
|
-
notify2();
|
|
44210
|
-
}
|
|
44211
|
-
function bindRestoredWorktrees(sessions) {
|
|
44212
|
-
for (const session of sessions.values()) {
|
|
44213
|
-
if (session.worktree || !session.cwd) continue;
|
|
44214
|
-
const worktree = detectExistingWorktree(session.cwd);
|
|
44215
|
-
if (worktree) session.worktree = worktree;
|
|
44216
|
-
}
|
|
44217
|
-
}
|
|
44218
|
-
function bindResumedWorktree(session, cwd, notify2) {
|
|
44219
|
-
if (!session) return;
|
|
44220
|
-
const worktree = detectExistingWorktree(cwd);
|
|
44221
|
-
if (!worktree) return;
|
|
44222
|
-
session.worktree = worktree;
|
|
44223
|
-
notify2();
|
|
44224
|
-
}
|
|
44225
|
-
|
|
44226
44448
|
// src/commands/sessions/daemon/attachReusedRun.ts
|
|
44227
44449
|
function attachReusedRun(session, alloc, clients, onStatusChange, tree) {
|
|
44228
44450
|
broadcast(clients, { type: "clear", sessionId: session.id });
|
|
@@ -44301,137 +44523,6 @@ function startReusedRunPty(session, assistArgs, itemId2, hold, clients, onStatus
|
|
|
44301
44523
|
return true;
|
|
44302
44524
|
}
|
|
44303
44525
|
|
|
44304
|
-
// src/commands/sessions/daemon/createAssistSession.ts
|
|
44305
|
-
function createAssistSession(id, assistArgs, cwd, meta, holdPty) {
|
|
44306
|
-
const startedAt = Date.now();
|
|
44307
|
-
return {
|
|
44308
|
-
id,
|
|
44309
|
-
name: `assist ${assistArgs.join(" ")}`,
|
|
44310
|
-
title: meta?.title,
|
|
44311
|
-
subtitle: meta?.subtitle,
|
|
44312
|
-
commandType: "assist",
|
|
44313
|
-
status: "running",
|
|
44314
|
-
startedAt,
|
|
44315
|
-
runningMs: 0,
|
|
44316
|
-
runningSince: startedAt,
|
|
44317
|
-
waitingSince: null,
|
|
44318
|
-
...startOrHoldPty(
|
|
44319
|
-
() => spawnPty(["assist", ...assistArgs], cwd, id),
|
|
44320
|
-
holdPty
|
|
44321
|
-
),
|
|
44322
|
-
scrollback: "",
|
|
44323
|
-
assistArgs,
|
|
44324
|
-
cwd
|
|
44325
|
-
};
|
|
44326
|
-
}
|
|
44327
|
-
|
|
44328
|
-
// src/commands/sessions/daemon/worktree/seedWatcherScrollback.ts
|
|
44329
|
-
import { readFileSync as readFileSync65, writeFileSync as writeFileSync54 } from "fs";
|
|
44330
|
-
var SCROLLBACK_TAIL = 256 * 1024;
|
|
44331
|
-
var MAX_LOG_SIZE = 4 * 1024 * 1024;
|
|
44332
|
-
function readLogTail2(path106) {
|
|
44333
|
-
let log2;
|
|
44334
|
-
try {
|
|
44335
|
-
log2 = readFileSync65(path106, "utf8");
|
|
44336
|
-
} catch {
|
|
44337
|
-
return "";
|
|
44338
|
-
}
|
|
44339
|
-
if (log2.length > MAX_LOG_SIZE) {
|
|
44340
|
-
log2 = log2.slice(-SCROLLBACK_TAIL);
|
|
44341
|
-
writeFileSync54(path106, log2);
|
|
44342
|
-
daemonLog(`watcher log ${path106} trimmed to its last ${log2.length} chars`);
|
|
44343
|
-
}
|
|
44344
|
-
return log2.slice(-SCROLLBACK_TAIL);
|
|
44345
|
-
}
|
|
44346
|
-
function seedWatcherScrollback(cwd, event) {
|
|
44347
|
-
const path106 = watcherLogPath(cwd);
|
|
44348
|
-
const history = readLogTail2(path106);
|
|
44349
|
-
const note = watcherNote(`${event} at ${(/* @__PURE__ */ new Date()).toLocaleString()}`);
|
|
44350
|
-
appendWatcherLog({ watcher: true, cwd }, note);
|
|
44351
|
-
daemonLog(
|
|
44352
|
-
`watcher in ${cwd} seeded with ${history.length} chars of history from ${path106} (${event})`
|
|
44353
|
-
);
|
|
44354
|
-
return history + note;
|
|
44355
|
-
}
|
|
44356
|
-
|
|
44357
|
-
// src/commands/sessions/daemon/createWatcherSession.ts
|
|
44358
|
-
var WATCHER_ARGS = ["watch", "loop"];
|
|
44359
|
-
function createWatcherSession(id, cwd) {
|
|
44360
|
-
return {
|
|
44361
|
-
...createAssistSession(id, WATCHER_ARGS, cwd),
|
|
44362
|
-
watcher: true,
|
|
44363
|
-
scrollback: seedWatcherScrollback(cwd, "watcher started")
|
|
44364
|
-
};
|
|
44365
|
-
}
|
|
44366
|
-
|
|
44367
|
-
// src/commands/sessions/daemon/worktree/boundTreeRoots.ts
|
|
44368
|
-
function boundTreeRoots(sessions, exclude) {
|
|
44369
|
-
const roots = /* @__PURE__ */ new Set();
|
|
44370
|
-
for (const session of sessions.values()) {
|
|
44371
|
-
if (session === exclude) continue;
|
|
44372
|
-
const root2 = treeRootOf(session);
|
|
44373
|
-
if (root2 && !roots.has(root2) && holdsTree(session, root2)) roots.add(root2);
|
|
44374
|
-
}
|
|
44375
|
-
return roots;
|
|
44376
|
-
}
|
|
44377
|
-
function treeRootOf(session) {
|
|
44378
|
-
if (!session.cwd) return void 0;
|
|
44379
|
-
return canonicalTreePath(findRepoRoot(session.cwd) ?? session.cwd);
|
|
44380
|
-
}
|
|
44381
|
-
function holdsTree(session, root2) {
|
|
44382
|
-
if (session.closing) return true;
|
|
44383
|
-
if (session.status !== "done" && session.status !== "error") return true;
|
|
44384
|
-
if (!worktreeConfigFor(root2).enabled) return true;
|
|
44385
|
-
const durability = checkDurabilitySync(root2);
|
|
44386
|
-
daemonLog(`tree ${root2} ${releaseReason(durability, session)}`);
|
|
44387
|
-
return !durability.durable;
|
|
44388
|
-
}
|
|
44389
|
-
function releaseReason(durability, session) {
|
|
44390
|
-
if (!durability.durable)
|
|
44391
|
-
return `still held by finished session ${session.id}: ${durability.reason}`;
|
|
44392
|
-
if (durability.gone)
|
|
44393
|
-
return `free for allocation: session ${session.id}'s directory is gone from disk \u2014 nothing to land, not landed work`;
|
|
44394
|
-
return `free for allocation: session ${session.id} finished and its work is landed`;
|
|
44395
|
-
}
|
|
44396
|
-
|
|
44397
|
-
// src/commands/sessions/daemon/worktree/allocateAndBind.ts
|
|
44398
|
-
function allocateAndBind(ctx, cwd, create, options2 = {}, context) {
|
|
44399
|
-
const alloc = allocateTree(cwd, boundTreeRoots(ctx.sessions), options2);
|
|
44400
|
-
const needsSeeding = alloc.kind === "worktree" && alloc.created === true;
|
|
44401
|
-
const id = ctx.spawnWith(
|
|
44402
|
-
(sid) => create(sid, alloc.cwd, needsSeeding),
|
|
44403
|
-
context
|
|
44404
|
-
);
|
|
44405
|
-
bindNewWorktree(ctx.sessions.get(id), alloc, ctx.notify, ctx.startHeld);
|
|
44406
|
-
return id;
|
|
44407
|
-
}
|
|
44408
|
-
|
|
44409
|
-
// src/commands/sessions/daemon/worktree/ensureWatcher.ts
|
|
44410
|
-
function ensureWatcher(ctx, cwd) {
|
|
44411
|
-
if (!cwd) return void 0;
|
|
44412
|
-
const repoRoot2 = findRepoRoot(cwd) ?? cwd;
|
|
44413
|
-
const cfg = worktreeConfigFor(repoRoot2);
|
|
44414
|
-
if (!cfg.enabled || cfg.watcher !== true) return void 0;
|
|
44415
|
-
const clone = resolveClone(repoRoot2);
|
|
44416
|
-
const live = liveWatcherFor(ctx.sessions, clone);
|
|
44417
|
-
if (live) {
|
|
44418
|
-
daemonLog(
|
|
44419
|
-
`no watcher spawned for the clone ${clone}: session ${live.id} is already watching it (${live.status})`
|
|
44420
|
-
);
|
|
44421
|
-
return void 0;
|
|
44422
|
-
}
|
|
44423
|
-
const id = allocateAndBind(
|
|
44424
|
-
ctx,
|
|
44425
|
-
clone,
|
|
44426
|
-
(sid, resolvedCwd) => createWatcherSession(sid, resolvedCwd ?? clone),
|
|
44427
|
-
{ inPlace: true }
|
|
44428
|
-
);
|
|
44429
|
-
daemonLog(
|
|
44430
|
-
`spawned watcher session ${id} running assist ${WATCHER_ARGS.join(" ")} in the clone ${clone}`
|
|
44431
|
-
);
|
|
44432
|
-
return id;
|
|
44433
|
-
}
|
|
44434
|
-
|
|
44435
44526
|
// src/commands/sessions/daemon/worktree/isBacklogRunArgs.ts
|
|
44436
44527
|
var ITEM_ID = /^a?\d+$/;
|
|
44437
44528
|
function isBacklogRunArgs(args) {
|
|
@@ -44495,7 +44586,6 @@ function failChainedRun(session, itemId2, error, clients, tree) {
|
|
|
44495
44586
|
// src/commands/sessions/daemon/reuseSessionForRun.ts
|
|
44496
44587
|
function reuseSessionForRun(session, itemId2, clients, onStatusChange, tree) {
|
|
44497
44588
|
const assistArgs = ["backlog", "run", String(itemId2)];
|
|
44498
|
-
const originCwd = session.cwd;
|
|
44499
44589
|
resetCardForRun(session, assistArgs);
|
|
44500
44590
|
let alloc;
|
|
44501
44591
|
try {
|
|
@@ -44517,13 +44607,71 @@ function reuseSessionForRun(session, itemId2, clients, onStatusChange, tree) {
|
|
|
44517
44607
|
onStatusChange
|
|
44518
44608
|
);
|
|
44519
44609
|
if (!started) return;
|
|
44520
|
-
if (tree) ensureWatcher(tree, originCwd);
|
|
44521
44610
|
attachReusedRun(session, alloc, clients, onStatusChange, tree);
|
|
44522
44611
|
daemonLog(
|
|
44523
44612
|
`session ${session.id} reused for backlog run ${itemId2}: ${session.name}`
|
|
44524
44613
|
);
|
|
44525
44614
|
}
|
|
44526
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
|
+
|
|
44527
44675
|
// src/commands/sessions/daemon/respawnSession.ts
|
|
44528
44676
|
function respawnSession(session, respawn, status2, clients, onStatusChange) {
|
|
44529
44677
|
session.gitWatcher?.close();
|
|
@@ -44532,8 +44680,7 @@ function respawnSession(session, respawn, status2, clients, onStatusChange) {
|
|
|
44532
44680
|
session.pendingDismiss = void 0;
|
|
44533
44681
|
session.closeGrace?.cancel();
|
|
44534
44682
|
session.closing = void 0;
|
|
44535
|
-
|
|
44536
|
-
if (!keepHistory) session.scrollback = "";
|
|
44683
|
+
session.scrollback = "";
|
|
44537
44684
|
session.startedAt = Date.now();
|
|
44538
44685
|
session.runningMs = 0;
|
|
44539
44686
|
session.runningSince = null;
|
|
@@ -44550,15 +44697,7 @@ function respawnSession(session, respawn, status2, clients, onStatusChange) {
|
|
|
44550
44697
|
session.pty?.resize(session.cols, session.rows);
|
|
44551
44698
|
} catch {
|
|
44552
44699
|
}
|
|
44553
|
-
|
|
44554
|
-
const reason4 = session.watcherRestartReason;
|
|
44555
|
-
session.watcherRestartReason = void 0;
|
|
44556
|
-
emitSessionOutput(
|
|
44557
|
-
session,
|
|
44558
|
-
clients,
|
|
44559
|
-
watcherNote(reason4 ? `watcher restarted ${reason4}` : "watcher restarted")
|
|
44560
|
-
);
|
|
44561
|
-
} else broadcast(clients, { type: "clear", sessionId: session.id });
|
|
44700
|
+
broadcast(clients, { type: "clear", sessionId: session.id });
|
|
44562
44701
|
wirePtyEvents(session, clients, onStatusChange);
|
|
44563
44702
|
}
|
|
44564
44703
|
|
|
@@ -44729,144 +44868,6 @@ function restartManagedSession(sessions, id, clients, onStatusChange) {
|
|
|
44729
44868
|
return { ok: true };
|
|
44730
44869
|
}
|
|
44731
44870
|
|
|
44732
|
-
// src/commands/sessions/daemon/worktree/divergencePrompt.ts
|
|
44733
|
-
var MAX_REASON_LINES = 20;
|
|
44734
|
-
var MAX_REASON_CHARS = 2e3;
|
|
44735
|
-
var RECONCILE_STEPS = [
|
|
44736
|
-
"Reconcile it yourself, without discarding any commits on either side:",
|
|
44737
|
-
"",
|
|
44738
|
-
"1. Inspect `git status`, `git log --oneline @{u}..@` and `git log --oneline @..@{u}`.",
|
|
44739
|
-
'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.',
|
|
44740
|
-
"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.",
|
|
44741
|
-
"4. Push the result with a plain `git push` so the branch matches its upstream again.",
|
|
44742
|
-
"5. Build the clone with `assist run auto-build`.",
|
|
44743
|
-
"6. Run /close. The daemon restarts the watcher once this session closes.",
|
|
44744
|
-
"",
|
|
44745
|
-
"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."
|
|
44746
|
-
];
|
|
44747
|
-
var SIMULATED_STEPS = [
|
|
44748
|
-
"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."
|
|
44749
|
-
];
|
|
44750
|
-
function divergenceReason(scrollback) {
|
|
44751
|
-
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);
|
|
44752
|
-
}
|
|
44753
|
-
function divergencePrompt(clone, scrollback) {
|
|
44754
|
-
const reason4 = divergenceReason(scrollback);
|
|
44755
|
-
return [
|
|
44756
|
-
`The watcher in ${clone} stopped because \`assist watch wait --pull --build\` could not fast-forward the branch (exit 3). The tail of its output:`,
|
|
44757
|
-
"",
|
|
44758
|
-
"```",
|
|
44759
|
-
reason4 || "(no output captured)",
|
|
44760
|
-
"```",
|
|
44761
|
-
"",
|
|
44762
|
-
...reason4.includes(simulatedDivergenceNote) ? SIMULATED_STEPS : RECONCILE_STEPS
|
|
44763
|
-
].join("\n");
|
|
44764
|
-
}
|
|
44765
|
-
|
|
44766
|
-
// src/commands/sessions/daemon/worktree/escalateDivergence.ts
|
|
44767
|
-
var ENDED2 = ["done", "error", "stopped"];
|
|
44768
|
-
function escalateDivergence(ctx, watcher) {
|
|
44769
|
-
if (!watcher.cwd) return void 0;
|
|
44770
|
-
const clone = canonicalTreePath(watcher.cwd);
|
|
44771
|
-
const live = [...ctx.sessions.values()].find(
|
|
44772
|
-
(s) => s.divergenceEscalation === true && s.cwd !== void 0 && canonicalTreePath(s.cwd) === clone && !ENDED2.includes(s.status)
|
|
44773
|
-
);
|
|
44774
|
-
if (live) {
|
|
44775
|
-
daemonLog(
|
|
44776
|
-
`watcher session ${watcher.id} exited 3 (divergence) in the clone ${clone}: no escalation spawned, session ${live.id} is already diagnosing it (${live.status})`
|
|
44777
|
-
);
|
|
44778
|
-
return void 0;
|
|
44779
|
-
}
|
|
44780
|
-
const prompt = divergencePrompt(clone, watcher.scrollback);
|
|
44781
|
-
const id = allocateAndBind(
|
|
44782
|
-
ctx,
|
|
44783
|
-
clone,
|
|
44784
|
-
(sid, resolvedCwd) => ({
|
|
44785
|
-
...createSession(sid, { prompt, cwd: resolvedCwd ?? clone }),
|
|
44786
|
-
divergenceEscalation: true
|
|
44787
|
-
}),
|
|
44788
|
-
{ inPlace: true }
|
|
44789
|
-
);
|
|
44790
|
-
daemonLog(
|
|
44791
|
-
`watcher session ${watcher.id} exited 3 (divergence) in the clone ${clone}: spawned escalation session ${id} to diagnose it`
|
|
44792
|
-
);
|
|
44793
|
-
return id;
|
|
44794
|
-
}
|
|
44795
|
-
|
|
44796
|
-
// src/commands/sessions/daemon/managerStatusChangeHandler.ts
|
|
44797
|
-
function managerStatusChangeHandler(sessions, clients, dismiss, notify2, treeCtx) {
|
|
44798
|
-
const handler = makeStatusChangeHandler(sessions, {
|
|
44799
|
-
dismiss,
|
|
44800
|
-
notify: notify2,
|
|
44801
|
-
reuseForRun: (session, itemId2) => reuseSessionForRun(session, itemId2, clients, handler, treeCtx()),
|
|
44802
|
-
escalateDivergence: (watcher) => {
|
|
44803
|
-
const id = escalateDivergence(treeCtx(), watcher);
|
|
44804
|
-
if (id)
|
|
44805
|
-
emitSessionOutput(
|
|
44806
|
-
watcher,
|
|
44807
|
-
clients,
|
|
44808
|
-
watcherNote(
|
|
44809
|
-
`divergence escalated to session ${id}; the watcher restarts once it ends`
|
|
44810
|
-
)
|
|
44811
|
-
);
|
|
44812
|
-
},
|
|
44813
|
-
resumeWatcher: (escalation) => resumeWatcherAfterEscalation(sessions, escalation, (watcherId) => {
|
|
44814
|
-
if (restartManagedSession(sessions, watcherId, clients, handler).ok)
|
|
44815
|
-
notify2();
|
|
44816
|
-
})
|
|
44817
|
-
});
|
|
44818
|
-
return handler;
|
|
44819
|
-
}
|
|
44820
|
-
|
|
44821
|
-
// src/commands/sessions/daemon/applySetStatus.ts
|
|
44822
|
-
function applySetStatus(sessions, id, status2, source, onStatusChange, rebind) {
|
|
44823
|
-
const session = sessions.get(id);
|
|
44824
|
-
if (!session) {
|
|
44825
|
-
daemonLog(
|
|
44826
|
-
`set-status for unknown session id=${id} status=${status2} (no live session; ignoring)`
|
|
44827
|
-
);
|
|
44828
|
-
return;
|
|
44829
|
-
}
|
|
44830
|
-
if (session.status === "stopped") {
|
|
44831
|
-
daemonLog(
|
|
44832
|
-
`set-status for stopped session id=${id} status=${status2} (held pending durable close; ignoring)`
|
|
44833
|
-
);
|
|
44834
|
-
return;
|
|
44835
|
-
}
|
|
44836
|
-
rebind?.(session);
|
|
44837
|
-
if (source === "permission") session.permissionActive = true;
|
|
44838
|
-
if (status2 === "running") session.permissionActive = false;
|
|
44839
|
-
onStatusChange(session, status2);
|
|
44840
|
-
}
|
|
44841
|
-
|
|
44842
|
-
// src/commands/sessions/daemon/rebindClaudeSession.ts
|
|
44843
|
-
function rebindClaudeSession(session, claudeSessionId, notify2, onStatusChange) {
|
|
44844
|
-
if (!claudeSessionId) return;
|
|
44845
|
-
if (session.claudeSessionId === claudeSessionId) return;
|
|
44846
|
-
daemonLog(
|
|
44847
|
-
`session ${session.id} claude session moved: ${session.claudeSessionId ?? "none"} -> ${claudeSessionId}`
|
|
44848
|
-
);
|
|
44849
|
-
session.claudeSessionId = claudeSessionId;
|
|
44850
|
-
watchTranscript(session, notify2, onStatusChange);
|
|
44851
|
-
}
|
|
44852
|
-
|
|
44853
|
-
// src/commands/sessions/daemon/setStatusFromHook.ts
|
|
44854
|
-
function setStatusFromHook(sessions, report, notify2, onStatusChange) {
|
|
44855
|
-
applySetStatus(
|
|
44856
|
-
sessions,
|
|
44857
|
-
report.id,
|
|
44858
|
-
report.status,
|
|
44859
|
-
report.source,
|
|
44860
|
-
onStatusChange,
|
|
44861
|
-
(session) => rebindClaudeSession(
|
|
44862
|
-
session,
|
|
44863
|
-
report.claudeSessionId,
|
|
44864
|
-
notify2,
|
|
44865
|
-
onStatusChange
|
|
44866
|
-
)
|
|
44867
|
-
);
|
|
44868
|
-
}
|
|
44869
|
-
|
|
44870
44871
|
// src/commands/sessions/daemon/restartAndNotify.ts
|
|
44871
44872
|
function restartAndNotify(sessions, id, clients, onStatusChange, notify2) {
|
|
44872
44873
|
const result = restartManagedSession(sessions, id, clients, onStatusChange);
|
|
@@ -44908,8 +44909,7 @@ function restoreBase(id, persisted) {
|
|
|
44908
44909
|
cwd: persisted.cwd,
|
|
44909
44910
|
assistArgs: persisted.assistArgs,
|
|
44910
44911
|
initialPrompt: persisted.initialPrompt,
|
|
44911
|
-
starred: persisted.
|
|
44912
|
-
watcher: persisted.watcher,
|
|
44912
|
+
starred: persisted.starred,
|
|
44913
44913
|
divergenceEscalation: persisted.divergenceEscalation,
|
|
44914
44914
|
design: persisted.design,
|
|
44915
44915
|
auto: persisted.auto,
|
|
@@ -44969,12 +44969,19 @@ function underTempRoot(path106) {
|
|
|
44969
44969
|
);
|
|
44970
44970
|
}
|
|
44971
44971
|
|
|
44972
|
-
// src/commands/sessions/daemon/
|
|
44973
|
-
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) {
|
|
44974
44980
|
return persisted.filter((entry) => {
|
|
44975
|
-
|
|
44981
|
+
const reason4 = droppedReason(entry);
|
|
44982
|
+
if (!reason4) return true;
|
|
44976
44983
|
daemonLog(
|
|
44977
|
-
`persisted session ${describePersistedSession(entry)}
|
|
44984
|
+
`persisted session ${describePersistedSession(entry)} ${reason4}; dropped rather than restored`
|
|
44978
44985
|
);
|
|
44979
44986
|
return false;
|
|
44980
44987
|
});
|
|
@@ -45299,25 +45306,6 @@ function isUpdate(persisted) {
|
|
|
45299
45306
|
return persisted.commandType === "assist" && persisted.assistArgs?.[0] === "update";
|
|
45300
45307
|
}
|
|
45301
45308
|
|
|
45302
|
-
// src/commands/sessions/daemon/migrateClaudeWatcher.ts
|
|
45303
|
-
function migrateClaudeWatcher(id, persisted) {
|
|
45304
|
-
if (!persisted.watcher || persisted.commandType !== "claude")
|
|
45305
|
-
return persisted;
|
|
45306
|
-
daemonLog(
|
|
45307
|
-
`migrating claude /watch watcher session ${id} to assist ${WATCHER_ARGS.join(" ")}`
|
|
45308
|
-
);
|
|
45309
|
-
return {
|
|
45310
|
-
...persisted,
|
|
45311
|
-
name: `assist ${WATCHER_ARGS.join(" ")}`,
|
|
45312
|
-
commandType: "assist",
|
|
45313
|
-
assistArgs: WATCHER_ARGS,
|
|
45314
|
-
initialPrompt: void 0,
|
|
45315
|
-
claudeSessionId: void 0,
|
|
45316
|
-
harness: void 0,
|
|
45317
|
-
harnessSessionId: void 0
|
|
45318
|
-
};
|
|
45319
|
-
}
|
|
45320
|
-
|
|
45321
45309
|
// src/commands/sessions/daemon/needsWrapperRelaunch.ts
|
|
45322
45310
|
var LAUNCH_COMMANDS = ["draft", "feat", "bug", "refine"];
|
|
45323
45311
|
function needsWrapperRelaunch(persisted) {
|
|
@@ -45330,36 +45318,11 @@ function isOnceLaunch(persisted) {
|
|
|
45330
45318
|
return persisted.commandType === "assist" && !!persisted.assistArgs && LAUNCH_COMMANDS.includes(persisted.assistArgs[0]) && persisted.assistArgs.includes("--once");
|
|
45331
45319
|
}
|
|
45332
45320
|
|
|
45333
|
-
// src/commands/sessions/daemon/relaunchConsoleWatcher.ts
|
|
45334
|
-
function isConsoleWatcher(persisted) {
|
|
45335
|
-
return persisted.watcher === true && persisted.commandType === "assist" && !!persisted.assistArgs;
|
|
45336
|
-
}
|
|
45337
|
-
function relaunchConsoleWatcher(id, persisted, base) {
|
|
45338
|
-
const command = ["assist", ...persisted.assistArgs];
|
|
45339
|
-
daemonLog(
|
|
45340
|
-
`relaunching watcher session ${id} running ${command.join(" ")} in ${persisted.cwd ?? "(no cwd)"}`
|
|
45341
|
-
);
|
|
45342
|
-
const session = runningSession(
|
|
45343
|
-
base,
|
|
45344
|
-
persisted,
|
|
45345
|
-
spawnPty(command, persisted.cwd, id)
|
|
45346
|
-
);
|
|
45347
|
-
if (persisted.cwd)
|
|
45348
|
-
session.scrollback = seedWatcherScrollback(
|
|
45349
|
-
persisted.cwd,
|
|
45350
|
-
"daemon restarted; watcher relaunched"
|
|
45351
|
-
);
|
|
45352
|
-
return session;
|
|
45353
|
-
}
|
|
45354
|
-
|
|
45355
45321
|
// src/commands/sessions/daemon/restoreSession.ts
|
|
45356
|
-
function restoreSession(id,
|
|
45357
|
-
const persisted = migrateClaudeWatcher(id, stored);
|
|
45322
|
+
function restoreSession(id, persisted) {
|
|
45358
45323
|
const base = restoreBase(id, persisted);
|
|
45359
45324
|
if (persisted.status === "stopped") return stoppedSession(base, persisted);
|
|
45360
45325
|
if (isUpdate(persisted)) return updatedSession(id, persisted);
|
|
45361
|
-
if (isConsoleWatcher(persisted))
|
|
45362
|
-
return relaunchConsoleWatcher(id, persisted, base);
|
|
45363
45326
|
const status2 = deriveRestoreStatus(persisted);
|
|
45364
45327
|
if (needsWrapperRelaunch(persisted)) {
|
|
45365
45328
|
const resumesWrittenConversation = hasTranscriptOnDisk(persisted);
|
|
@@ -45413,7 +45376,7 @@ function logRestoreError(persisted, error) {
|
|
|
45413
45376
|
|
|
45414
45377
|
// src/commands/sessions/daemon/restoreAll.ts
|
|
45415
45378
|
function restoreAll(spawner, sessions) {
|
|
45416
|
-
const persisted =
|
|
45379
|
+
const persisted = dropUnrestorableSessions(loadPersistedSessions());
|
|
45417
45380
|
const cap2 = sessionLimits.maxRestore();
|
|
45418
45381
|
const restoredIds = /* @__PURE__ */ new Map();
|
|
45419
45382
|
const names = persisted.slice(0, cap2).map((entry) => {
|
|
@@ -45669,10 +45632,10 @@ function restoreAllSessions(spawner, sessions, notify2) {
|
|
|
45669
45632
|
|
|
45670
45633
|
// src/commands/sessions/daemon/retrySession.ts
|
|
45671
45634
|
function retrySession(session, clients, onStatusChange) {
|
|
45672
|
-
const
|
|
45673
|
-
if (!
|
|
45635
|
+
const spawn12 = respawnThunk(session);
|
|
45636
|
+
if (!spawn12) return false;
|
|
45674
45637
|
const respawn = () => {
|
|
45675
|
-
respawnSession(session,
|
|
45638
|
+
respawnSession(session, spawn12, "running", clients, onStatusChange);
|
|
45676
45639
|
daemonLog(`session ${session.id} retried: ${session.name}`);
|
|
45677
45640
|
};
|
|
45678
45641
|
const pty2 = session.pty;
|
|
@@ -46701,6 +46664,30 @@ function addAgentToStream(ctx, targetId, request) {
|
|
|
46701
46664
|
};
|
|
46702
46665
|
}
|
|
46703
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
|
+
|
|
46704
46691
|
// src/commands/sessions/daemon/worktree/spawnInTree.ts
|
|
46705
46692
|
function spawnInTree(ctx, { prompt, cwd, design, auto, harness, inPlace }, context) {
|
|
46706
46693
|
return allocateAndBind(
|
|
@@ -46719,7 +46706,7 @@ function spawnInTree(ctx, { prompt, cwd, design, auto, harness, inPlace }, conte
|
|
|
46719
46706
|
);
|
|
46720
46707
|
}
|
|
46721
46708
|
function spawnAssistInTree(ctx, assistArgs, cwd, meta, context) {
|
|
46722
|
-
|
|
46709
|
+
return allocateAndBind(
|
|
46723
46710
|
ctx,
|
|
46724
46711
|
cwd,
|
|
46725
46712
|
(sid, resolvedCwd, holdUntilSeeded) => createAssistSession(sid, assistArgs, resolvedCwd, meta, holdUntilSeeded),
|
|
@@ -46732,8 +46719,6 @@ function spawnAssistInTree(ctx, assistArgs, cwd, meta, context) {
|
|
|
46732
46719
|
},
|
|
46733
46720
|
context
|
|
46734
46721
|
);
|
|
46735
|
-
if (isBacklogRunArgs(assistArgs)) ensureWatcher(ctx, cwd);
|
|
46736
|
-
return id;
|
|
46737
46722
|
}
|
|
46738
46723
|
|
|
46739
46724
|
// src/commands/sessions/daemon/worktree/resumeInTree.ts
|
|
@@ -46960,13 +46945,11 @@ var SessionManager = class {
|
|
|
46960
46945
|
);
|
|
46961
46946
|
}
|
|
46962
46947
|
dismissSession = (id) => {
|
|
46963
|
-
|
|
46964
|
-
this.sessions,
|
|
46965
|
-
id,
|
|
46966
|
-
this.notify,
|
|
46967
|
-
(watcherId) => this.restart(watcherId)
|
|
46968
|
-
);
|
|
46948
|
+
dismissSessionGated(this.sessions, id, this.notify);
|
|
46969
46949
|
};
|
|
46950
|
+
startAutoUpdate() {
|
|
46951
|
+
startAutoUpdate(() => this.treeCtx());
|
|
46952
|
+
}
|
|
46970
46953
|
discardSession = (id) => {
|
|
46971
46954
|
dismissSessionGated(this.sessions, id, this.notify, true);
|
|
46972
46955
|
};
|
|
@@ -47044,10 +47027,10 @@ var handleHelloRequest = (client, m, d) => {
|
|
|
47044
47027
|
};
|
|
47045
47028
|
|
|
47046
47029
|
// src/commands/sessions/daemon/creator.ts
|
|
47047
|
-
function creator(isNew,
|
|
47030
|
+
function creator(isNew, spawn12) {
|
|
47048
47031
|
return (client, m, d) => {
|
|
47049
47032
|
if (m.links.route(client, d)) return;
|
|
47050
|
-
const result =
|
|
47033
|
+
const result = spawn12(m, d);
|
|
47051
47034
|
if (typeof result !== "string") {
|
|
47052
47035
|
sendTo(client, { type: "error", message: result.error });
|
|
47053
47036
|
return;
|
|
@@ -47713,6 +47696,7 @@ function onListening(manager, checkAutoExit) {
|
|
|
47713
47696
|
);
|
|
47714
47697
|
daemonLog(`listening on ${daemonPaths.socket}`);
|
|
47715
47698
|
manager.links.reload();
|
|
47699
|
+
manager.startAutoUpdate();
|
|
47716
47700
|
checkAutoExit(manager.isIdle());
|
|
47717
47701
|
}
|
|
47718
47702
|
function cleanupOwnedFiles() {
|
|
@@ -47815,6 +47799,7 @@ function registerDaemon(program2) {
|
|
|
47815
47799
|
cmd.command("drain").description(
|
|
47816
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"
|
|
47817
47801
|
).option("--yes", "Close live sessions without confirming").action((options2) => drainDaemon(options2));
|
|
47802
|
+
configHelp(cmd, autoUpdateConfigHelp);
|
|
47818
47803
|
}
|
|
47819
47804
|
|
|
47820
47805
|
// src/commands/sessions/closeSession.ts
|
|
@@ -48075,14 +48060,14 @@ async function linkStatus(name, query = queryNodes) {
|
|
|
48075
48060
|
return (await query())?.links.find((l) => l.name === name);
|
|
48076
48061
|
}
|
|
48077
48062
|
async function awaitLinkReturn(name, options2, deps2 = {}) {
|
|
48078
|
-
const { query = queryNodes, sleep:
|
|
48063
|
+
const { query = queryNodes, sleep: sleep3 = wait, now = Date.now } = deps2;
|
|
48079
48064
|
const deadline = now() + options2.timeoutMs;
|
|
48080
48065
|
let sawDown = options2.sawDown;
|
|
48081
48066
|
while (now() < deadline) {
|
|
48082
48067
|
const link3 = await linkStatus(name, query);
|
|
48083
48068
|
if (link3?.state !== "connected") sawDown = true;
|
|
48084
48069
|
else if (sawDown) return link3;
|
|
48085
|
-
await
|
|
48070
|
+
await sleep3(POLL_MS4);
|
|
48086
48071
|
}
|
|
48087
48072
|
return void 0;
|
|
48088
48073
|
}
|