@staff0rd/assist 0.448.0 → 0.450.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/dist/commands/sessions/web/bundle.js +378 -378
- package/dist/index.js +941 -771
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { Command } from "commander";
|
|
|
6
6
|
// package.json
|
|
7
7
|
var package_default = {
|
|
8
8
|
name: "@staff0rd/assist",
|
|
9
|
-
version: "0.
|
|
9
|
+
version: "0.450.0",
|
|
10
10
|
type: "module",
|
|
11
11
|
main: "dist/index.js",
|
|
12
12
|
bin: {
|
|
@@ -1770,6 +1770,8 @@ function collectCommitRefs(message2) {
|
|
|
1770
1770
|
const subject = message2.split("\n", 1)[0];
|
|
1771
1771
|
if (subject) ref.title = subject;
|
|
1772
1772
|
refs.push(ref);
|
|
1773
|
+
const parent = git("rev-parse HEAD^");
|
|
1774
|
+
if (parent) refs.push({ kind: "commit-parent", ref: parent });
|
|
1773
1775
|
}
|
|
1774
1776
|
return refs;
|
|
1775
1777
|
}
|
|
@@ -3715,9 +3717,9 @@ function forbiddenStrings() {
|
|
|
3715
3717
|
console.log("No forbidden-strings rules configured.");
|
|
3716
3718
|
process.exit(0);
|
|
3717
3719
|
}
|
|
3718
|
-
const
|
|
3720
|
+
const cache4 = /* @__PURE__ */ new Map();
|
|
3719
3721
|
const readJson = (file) => {
|
|
3720
|
-
if (
|
|
3722
|
+
if (cache4.has(file)) return cache4.get(file);
|
|
3721
3723
|
if (!existsSync12(file)) {
|
|
3722
3724
|
console.log(`Forbidden-strings file not found: ${file}`);
|
|
3723
3725
|
process.exit(1);
|
|
@@ -3729,7 +3731,7 @@ function forbiddenStrings() {
|
|
|
3729
3731
|
console.log(`Could not parse ${file}: ${error.message}`);
|
|
3730
3732
|
process.exit(1);
|
|
3731
3733
|
}
|
|
3732
|
-
|
|
3734
|
+
cache4.set(file, parsed);
|
|
3733
3735
|
return parsed;
|
|
3734
3736
|
};
|
|
3735
3737
|
const violations = findForbiddenStrings(rules, readJson);
|
|
@@ -6091,7 +6093,13 @@ var backlogCommentSchema = z3.strictObject({
|
|
|
6091
6093
|
timestamp: z3.string(),
|
|
6092
6094
|
type: backlogCommentTypeSchema
|
|
6093
6095
|
});
|
|
6094
|
-
var gitRefKindSchema = z3.enum([
|
|
6096
|
+
var gitRefKindSchema = z3.enum([
|
|
6097
|
+
"branch",
|
|
6098
|
+
"commit",
|
|
6099
|
+
"commit-parent",
|
|
6100
|
+
"pr",
|
|
6101
|
+
"slack"
|
|
6102
|
+
]);
|
|
6095
6103
|
var gitRefSchema = z3.strictObject({
|
|
6096
6104
|
kind: gitRefKindSchema,
|
|
6097
6105
|
ref: z3.string(),
|
|
@@ -8372,19 +8380,19 @@ import { dirname as dirname19, join as join23 } from "path";
|
|
|
8372
8380
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
8373
8381
|
function createBundleHandler(importMetaUrl, bundlePath, contentType = "application/javascript") {
|
|
8374
8382
|
const file = join23(dirname19(fileURLToPath4(importMetaUrl)), bundlePath);
|
|
8375
|
-
let
|
|
8383
|
+
let cache4;
|
|
8376
8384
|
return (req, res) => {
|
|
8377
8385
|
const mtimeMs = statSync2(file).mtimeMs;
|
|
8378
|
-
if (
|
|
8386
|
+
if (cache4?.mtimeMs !== mtimeMs) {
|
|
8379
8387
|
const body = readFileSync19(file, "utf8");
|
|
8380
8388
|
const etag = `"${createHash("sha256").update(body).digest("hex").slice(0, 16)}"`;
|
|
8381
|
-
|
|
8389
|
+
cache4 = { body, etag, mtimeMs };
|
|
8382
8390
|
}
|
|
8383
8391
|
const headers = {
|
|
8384
|
-
ETag:
|
|
8392
|
+
ETag: cache4.etag,
|
|
8385
8393
|
"Cache-Control": "no-cache"
|
|
8386
8394
|
};
|
|
8387
|
-
if (req.headers["if-none-match"] ===
|
|
8395
|
+
if (req.headers["if-none-match"] === cache4.etag) {
|
|
8388
8396
|
res.writeHead(304, headers);
|
|
8389
8397
|
res.end();
|
|
8390
8398
|
return;
|
|
@@ -8393,7 +8401,7 @@ function createBundleHandler(importMetaUrl, bundlePath, contentType = "applicati
|
|
|
8393
8401
|
"Content-Type": contentType,
|
|
8394
8402
|
...headers
|
|
8395
8403
|
});
|
|
8396
|
-
res.end(
|
|
8404
|
+
res.end(cache4.body);
|
|
8397
8405
|
};
|
|
8398
8406
|
}
|
|
8399
8407
|
|
|
@@ -8881,6 +8889,73 @@ function getCwdParam(req, res) {
|
|
|
8881
8889
|
return cwd;
|
|
8882
8890
|
}
|
|
8883
8891
|
|
|
8892
|
+
// src/commands/sessions/web/getSessionParam.ts
|
|
8893
|
+
function getSessionParam(req) {
|
|
8894
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
8895
|
+
return url.searchParams.get("session") ?? void 0;
|
|
8896
|
+
}
|
|
8897
|
+
|
|
8898
|
+
// src/shared/db/findCommitAnchor.ts
|
|
8899
|
+
import { and as and7, asc as asc6, eq as eq16, inArray as inArray3 } from "drizzle-orm";
|
|
8900
|
+
async function findCommitAnchor(db, itemId2) {
|
|
8901
|
+
const rows = await db.select({ kind: itemGitRefs.kind, ref: itemGitRefs.ref }).from(itemGitRefs).where(
|
|
8902
|
+
and7(
|
|
8903
|
+
eq16(itemGitRefs.itemId, itemId2),
|
|
8904
|
+
inArray3(itemGitRefs.kind, ["commit", "commit-parent"])
|
|
8905
|
+
)
|
|
8906
|
+
).orderBy(asc6(itemGitRefs.createdAt));
|
|
8907
|
+
const anchor = {};
|
|
8908
|
+
for (const row of rows) {
|
|
8909
|
+
if (row.kind === "commit") anchor.commit ??= row.ref;
|
|
8910
|
+
else anchor.parent ??= row.ref;
|
|
8911
|
+
}
|
|
8912
|
+
return anchor;
|
|
8913
|
+
}
|
|
8914
|
+
|
|
8915
|
+
// src/shared/db/findItemBySessionId.ts
|
|
8916
|
+
import { asc as asc7, eq as eq17 } from "drizzle-orm";
|
|
8917
|
+
async function findItemBySessionId(db, claudeSessionId) {
|
|
8918
|
+
const rows = await db.select({ itemId: phaseUsage.itemId }).from(phaseUsage).where(eq17(phaseUsage.claudeSessionId, claudeSessionId)).orderBy(asc7(phaseUsage.itemId), asc7(phaseUsage.phaseIdx)).limit(1);
|
|
8919
|
+
return rows[0]?.itemId;
|
|
8920
|
+
}
|
|
8921
|
+
|
|
8922
|
+
// src/commands/sessions/web/sessionCommitAnchor.ts
|
|
8923
|
+
var EMPTY_TTL_MS = 3e4;
|
|
8924
|
+
var cache3 = /* @__PURE__ */ new Map();
|
|
8925
|
+
var inFlight = /* @__PURE__ */ new Map();
|
|
8926
|
+
async function sessionCommitAnchor(sessionId) {
|
|
8927
|
+
const cached = cache3.get(sessionId);
|
|
8928
|
+
if (cached && cached.expiresAt > Date.now()) return cached.anchor;
|
|
8929
|
+
const pending = inFlight.get(sessionId);
|
|
8930
|
+
if (pending) return pending;
|
|
8931
|
+
const lookup = loadAnchor(sessionId).finally(
|
|
8932
|
+
() => inFlight.delete(sessionId)
|
|
8933
|
+
);
|
|
8934
|
+
inFlight.set(sessionId, lookup);
|
|
8935
|
+
return lookup;
|
|
8936
|
+
}
|
|
8937
|
+
async function loadAnchor(sessionId) {
|
|
8938
|
+
const anchor = await queryAnchor(sessionId);
|
|
8939
|
+
const found = Boolean(anchor.commit || anchor.parent);
|
|
8940
|
+
cache3.set(sessionId, {
|
|
8941
|
+
anchor,
|
|
8942
|
+
expiresAt: found ? Number.POSITIVE_INFINITY : Date.now() + EMPTY_TTL_MS
|
|
8943
|
+
});
|
|
8944
|
+
return anchor;
|
|
8945
|
+
}
|
|
8946
|
+
async function queryAnchor(sessionId) {
|
|
8947
|
+
try {
|
|
8948
|
+
if (!process.env.ASSIST_DATABASE_URL && !loadConfig().database.url)
|
|
8949
|
+
return {};
|
|
8950
|
+
const db = await getDb();
|
|
8951
|
+
const itemId2 = await findItemBySessionId(db, sessionId);
|
|
8952
|
+
if (itemId2 === void 0) return {};
|
|
8953
|
+
return await findCommitAnchor(db, itemId2);
|
|
8954
|
+
} catch {
|
|
8955
|
+
return {};
|
|
8956
|
+
}
|
|
8957
|
+
}
|
|
8958
|
+
|
|
8884
8959
|
// src/commands/sessions/web/windowsCwdToWslPath.ts
|
|
8885
8960
|
function windowsCwdToWslPath(cwd) {
|
|
8886
8961
|
const match = /^([A-Za-z]):[\\/](.*)$/.exec(cwd);
|
|
@@ -8914,7 +8989,13 @@ async function refExists2(cwd, ref) {
|
|
|
8914
8989
|
return false;
|
|
8915
8990
|
}
|
|
8916
8991
|
}
|
|
8917
|
-
async function
|
|
8992
|
+
async function anchoredBase(cwd, sessionId) {
|
|
8993
|
+
const { commit: commit2, parent } = await sessionCommitAnchor(sessionId);
|
|
8994
|
+
if (parent && await refExists2(cwd, parent)) return parent;
|
|
8995
|
+
if (commit2 && await refExists2(cwd, `${commit2}^`)) return `${commit2}^`;
|
|
8996
|
+
return void 0;
|
|
8997
|
+
}
|
|
8998
|
+
async function resolveDiffBase(cwd, sessionId) {
|
|
8918
8999
|
let defaultBranch;
|
|
8919
9000
|
try {
|
|
8920
9001
|
const config = loadConfigFrom(toGitCwd(cwd));
|
|
@@ -8923,6 +9004,10 @@ async function resolveDiffBase(cwd) {
|
|
|
8923
9004
|
} catch {
|
|
8924
9005
|
return "HEAD";
|
|
8925
9006
|
}
|
|
9007
|
+
if (sessionId) {
|
|
9008
|
+
const anchored = await anchoredBase(cwd, sessionId);
|
|
9009
|
+
if (anchored) return anchored;
|
|
9010
|
+
}
|
|
8926
9011
|
for (const candidate of baseCandidates(defaultBranch)) {
|
|
8927
9012
|
if (!await refExists2(cwd, candidate)) continue;
|
|
8928
9013
|
try {
|
|
@@ -8960,7 +9045,7 @@ async function diff2(req, res) {
|
|
|
8960
9045
|
const cwd = getCwdParam(req, res);
|
|
8961
9046
|
if (!cwd) return;
|
|
8962
9047
|
try {
|
|
8963
|
-
const base = await resolveDiffBase(cwd);
|
|
9048
|
+
const base = await resolveDiffBase(cwd, getSessionParam(req));
|
|
8964
9049
|
const tracked = await execGit(cwd, ["diff", base], {
|
|
8965
9050
|
maxBuffer: MAX_DIFF_BYTES
|
|
8966
9051
|
});
|
|
@@ -9280,7 +9365,7 @@ async function gitStatus(req, res) {
|
|
|
9280
9365
|
const cwd = getCwdParam(req, res);
|
|
9281
9366
|
if (!cwd) return;
|
|
9282
9367
|
try {
|
|
9283
|
-
const base = await resolveDiffBase(cwd);
|
|
9368
|
+
const base = await resolveDiffBase(cwd, getSessionParam(req));
|
|
9284
9369
|
respondJson(
|
|
9285
9370
|
res,
|
|
9286
9371
|
200,
|
|
@@ -9569,7 +9654,7 @@ async function listNewsItems(_req, res) {
|
|
|
9569
9654
|
}
|
|
9570
9655
|
|
|
9571
9656
|
// src/shared/db/listUsagePeaks.ts
|
|
9572
|
-
import { and as
|
|
9657
|
+
import { and as and8, count, desc as desc2, eq as eq18, getTableColumns, sql as sql16 } from "drizzle-orm";
|
|
9573
9658
|
async function listUsagePeaks(db, paging) {
|
|
9574
9659
|
const avgContext = db.select({
|
|
9575
9660
|
window: phaseCycleContext.window,
|
|
@@ -9585,9 +9670,9 @@ async function listUsagePeaks(db, paging) {
|
|
|
9585
9670
|
phaseCount: avgContext.phaseCount
|
|
9586
9671
|
}).from(usagePeaks).leftJoin(
|
|
9587
9672
|
avgContext,
|
|
9588
|
-
|
|
9589
|
-
|
|
9590
|
-
|
|
9673
|
+
and8(
|
|
9674
|
+
eq18(usagePeaks.window, avgContext.window),
|
|
9675
|
+
eq18(usagePeaks.resetsAt, avgContext.resetsAt)
|
|
9591
9676
|
)
|
|
9592
9677
|
).orderBy(
|
|
9593
9678
|
desc2(usagePeaks.resetsAt),
|
|
@@ -9644,11 +9729,11 @@ var execFileAsync4 = promisify5(execFile5);
|
|
|
9644
9729
|
var CACHE_TTL_MS2 = 3e4;
|
|
9645
9730
|
function createCachedGhJson(args, parse3, fallback, options2 = {}) {
|
|
9646
9731
|
const { cacheFallback = true } = options2;
|
|
9647
|
-
const
|
|
9732
|
+
const cache4 = /* @__PURE__ */ new Map();
|
|
9648
9733
|
return async (cwd, extraArgs = []) => {
|
|
9649
9734
|
const key = extraArgs.length ? `${cwd}\0${extraArgs.join("\0")}` : cwd;
|
|
9650
9735
|
const now = Date.now();
|
|
9651
|
-
const cached =
|
|
9736
|
+
const cached = cache4.get(key);
|
|
9652
9737
|
if (cached && cached.expires > now) return cached.value;
|
|
9653
9738
|
let value = fallback;
|
|
9654
9739
|
try {
|
|
@@ -9661,7 +9746,7 @@ function createCachedGhJson(args, parse3, fallback, options2 = {}) {
|
|
|
9661
9746
|
value = fallback;
|
|
9662
9747
|
}
|
|
9663
9748
|
if (cacheFallback || value !== fallback)
|
|
9664
|
-
|
|
9749
|
+
cache4.set(key, { value, expires: now + CACHE_TTL_MS2 });
|
|
9665
9750
|
return value;
|
|
9666
9751
|
};
|
|
9667
9752
|
}
|
|
@@ -10172,22 +10257,22 @@ import { readFileSync as readFileSync21 } from "fs";
|
|
|
10172
10257
|
import { createRequire as createRequire2 } from "module";
|
|
10173
10258
|
var require3 = createRequire2(import.meta.url);
|
|
10174
10259
|
function createCssHandler(packageEntry) {
|
|
10175
|
-
let
|
|
10260
|
+
let cache4;
|
|
10176
10261
|
return (req, res) => {
|
|
10177
|
-
if (!
|
|
10262
|
+
if (!cache4) {
|
|
10178
10263
|
const resolved = require3.resolve(packageEntry);
|
|
10179
10264
|
const body = readFileSync21(resolved, "utf8");
|
|
10180
10265
|
const etag = `"${createHash2("sha256").update(body).digest("hex").slice(0, 16)}"`;
|
|
10181
|
-
|
|
10266
|
+
cache4 = { body, etag };
|
|
10182
10267
|
}
|
|
10183
|
-
const headers = { ETag:
|
|
10184
|
-
if (req.headers["if-none-match"] ===
|
|
10268
|
+
const headers = { ETag: cache4.etag, "Cache-Control": "no-cache" };
|
|
10269
|
+
if (req.headers["if-none-match"] === cache4.etag) {
|
|
10185
10270
|
res.writeHead(304, headers);
|
|
10186
10271
|
res.end();
|
|
10187
10272
|
return;
|
|
10188
10273
|
}
|
|
10189
10274
|
res.writeHead(200, { "Content-Type": "text/css", ...headers });
|
|
10190
|
-
res.end(
|
|
10275
|
+
res.end(cache4.body);
|
|
10191
10276
|
};
|
|
10192
10277
|
}
|
|
10193
10278
|
|
|
@@ -10685,11 +10770,11 @@ function registerActivityCommands(cmd) {
|
|
|
10685
10770
|
|
|
10686
10771
|
// src/commands/backlog/associate-github/index.ts
|
|
10687
10772
|
import chalk64 from "chalk";
|
|
10688
|
-
import { eq as
|
|
10773
|
+
import { eq as eq20 } from "drizzle-orm";
|
|
10689
10774
|
|
|
10690
10775
|
// src/commands/backlog/beginAssociation.ts
|
|
10691
10776
|
import chalk63 from "chalk";
|
|
10692
|
-
import { eq as
|
|
10777
|
+
import { eq as eq19 } from "drizzle-orm";
|
|
10693
10778
|
async function beginAssociation(id, options2, clearPatch, label2) {
|
|
10694
10779
|
const found = await findOneItem(id);
|
|
10695
10780
|
if (!found) {
|
|
@@ -10699,7 +10784,7 @@ async function beginAssociation(id, options2, clearPatch, label2) {
|
|
|
10699
10784
|
const { orm } = found;
|
|
10700
10785
|
const itemId2 = found.item.id;
|
|
10701
10786
|
if (options2.clear) {
|
|
10702
|
-
await orm.update(items).set(clearPatch).where(
|
|
10787
|
+
await orm.update(items).set(clearPatch).where(eq19(items.id, itemId2));
|
|
10703
10788
|
console.log(
|
|
10704
10789
|
chalk63.green(
|
|
10705
10790
|
`Cleared ${label2} association on item ${formatItemId(itemId2)}.`
|
|
@@ -10767,7 +10852,7 @@ async function associateGithub(id, issue, options2) {
|
|
|
10767
10852
|
return;
|
|
10768
10853
|
}
|
|
10769
10854
|
const title = fetchGithubIssueTitle(normalized);
|
|
10770
|
-
await orm.update(items).set({ githubIssue: normalized, jiraKey: null }).where(
|
|
10855
|
+
await orm.update(items).set({ githubIssue: normalized, jiraKey: null }).where(eq20(items.id, itemId2));
|
|
10771
10856
|
console.log(
|
|
10772
10857
|
chalk64.green(`Associated ${normalized} with item ${formatItemId(itemId2)}.`),
|
|
10773
10858
|
title ? chalk64.dim(`(${title})`) : ""
|
|
@@ -10781,7 +10866,7 @@ function registerAssociateGithubCommand(cmd) {
|
|
|
10781
10866
|
|
|
10782
10867
|
// src/commands/backlog/associate-jira/index.ts
|
|
10783
10868
|
import chalk66 from "chalk";
|
|
10784
|
-
import { eq as
|
|
10869
|
+
import { eq as eq21 } from "drizzle-orm";
|
|
10785
10870
|
|
|
10786
10871
|
// src/commands/jira/fetchIssue.ts
|
|
10787
10872
|
import { execSync as execSync32 } from "child_process";
|
|
@@ -10846,7 +10931,7 @@ async function associateJira(id, key, options2) {
|
|
|
10846
10931
|
const parsed = fetchIssue(normalized, "summary");
|
|
10847
10932
|
const fields = parsed?.fields;
|
|
10848
10933
|
const summary = fields?.summary;
|
|
10849
|
-
await orm.update(items).set({ jiraKey: normalized, githubIssue: null }).where(
|
|
10934
|
+
await orm.update(items).set({ jiraKey: normalized, githubIssue: null }).where(eq21(items.id, itemId2));
|
|
10850
10935
|
console.log(
|
|
10851
10936
|
chalk66.green(`Associated ${normalized} with item ${formatItemId(itemId2)}.`),
|
|
10852
10937
|
summary ? chalk66.dim(`(${summary})`) : ""
|
|
@@ -11219,15 +11304,15 @@ function registerImportCommand(cmd) {
|
|
|
11219
11304
|
import chalk74 from "chalk";
|
|
11220
11305
|
|
|
11221
11306
|
// src/commands/backlog/insertPhaseAt.ts
|
|
11222
|
-
import { eq as
|
|
11307
|
+
import { eq as eq23 } from "drizzle-orm";
|
|
11223
11308
|
|
|
11224
11309
|
// src/commands/backlog/shiftPhasesUp.ts
|
|
11225
|
-
import { and as
|
|
11310
|
+
import { and as and9, desc as desc3, eq as eq22, gte } from "drizzle-orm";
|
|
11226
11311
|
async function shiftPhasesUp(db, itemId2, fromIdx) {
|
|
11227
|
-
const toShift = await db.select({ idx: planPhases.idx }).from(planPhases).where(
|
|
11312
|
+
const toShift = await db.select({ idx: planPhases.idx }).from(planPhases).where(and9(eq22(planPhases.itemId, itemId2), gte(planPhases.idx, fromIdx))).orderBy(desc3(planPhases.idx));
|
|
11228
11313
|
for (const p of toShift) {
|
|
11229
|
-
await db.update(planTasks).set({ phaseIdx: p.idx + 1 }).where(
|
|
11230
|
-
await db.update(planPhases).set({ idx: p.idx + 1 }).where(
|
|
11314
|
+
await db.update(planTasks).set({ phaseIdx: p.idx + 1 }).where(and9(eq22(planTasks.itemId, itemId2), eq22(planTasks.phaseIdx, p.idx)));
|
|
11315
|
+
await db.update(planPhases).set({ idx: p.idx + 1 }).where(and9(eq22(planPhases.itemId, itemId2), eq22(planPhases.idx, p.idx)));
|
|
11231
11316
|
}
|
|
11232
11317
|
}
|
|
11233
11318
|
|
|
@@ -11240,7 +11325,7 @@ async function insertPhaseAt(orm, itemId2, phaseIdx, name, tasks, manualChecks,
|
|
|
11240
11325
|
await tx.insert(planTasks).values(tasks.map((task, i) => ({ itemId: itemId2, phaseIdx, idx: i, task })));
|
|
11241
11326
|
}
|
|
11242
11327
|
if (currentPhase !== void 0 && currentPhase - 1 >= phaseIdx) {
|
|
11243
|
-
await tx.update(items).set({ currentPhase: phaseIdx + 1 }).where(
|
|
11328
|
+
await tx.update(items).set({ currentPhase: phaseIdx + 1 }).where(eq23(items.id, itemId2));
|
|
11244
11329
|
}
|
|
11245
11330
|
});
|
|
11246
11331
|
}
|
|
@@ -11403,9 +11488,9 @@ import chalk76 from "chalk";
|
|
|
11403
11488
|
|
|
11404
11489
|
// src/commands/backlog/resolveInsertPosition.ts
|
|
11405
11490
|
import chalk75 from "chalk";
|
|
11406
|
-
import { count as count2, eq as
|
|
11491
|
+
import { count as count2, eq as eq24 } from "drizzle-orm";
|
|
11407
11492
|
async function resolveInsertPosition(orm, itemId2, position) {
|
|
11408
|
-
const [row] = await orm.select({ cnt: count2() }).from(planPhases).where(
|
|
11493
|
+
const [row] = await orm.select({ cnt: count2() }).from(planPhases).where(eq24(planPhases.itemId, itemId2));
|
|
11409
11494
|
const phaseCount = row?.cnt ?? 0;
|
|
11410
11495
|
if (position === void 0) return phaseCount;
|
|
11411
11496
|
const pos = Number.parseInt(position, 10);
|
|
@@ -11734,9 +11819,9 @@ function hasCycle(adjacency, fromId, toId) {
|
|
|
11734
11819
|
}
|
|
11735
11820
|
|
|
11736
11821
|
// src/commands/backlog/loadDependencyGraph.ts
|
|
11737
|
-
import { eq as
|
|
11822
|
+
import { eq as eq25 } from "drizzle-orm";
|
|
11738
11823
|
async function loadDependencyGraph(orm) {
|
|
11739
|
-
const rows = await orm.select({ itemId: links.itemId, targetId: links.targetId }).from(links).where(
|
|
11824
|
+
const rows = await orm.select({ itemId: links.itemId, targetId: links.targetId }).from(links).where(eq25(links.type, "depends-on"));
|
|
11740
11825
|
const graph = /* @__PURE__ */ new Map();
|
|
11741
11826
|
for (const { itemId: itemId2, targetId } of rows) {
|
|
11742
11827
|
const bucket = graph.get(itemId2);
|
|
@@ -11803,7 +11888,7 @@ async function link(fromId, toId, opts) {
|
|
|
11803
11888
|
|
|
11804
11889
|
// src/commands/backlog/unlink.ts
|
|
11805
11890
|
import chalk82 from "chalk";
|
|
11806
|
-
import { and as
|
|
11891
|
+
import { and as and10, eq as eq26 } from "drizzle-orm";
|
|
11807
11892
|
async function unlink(fromId, toId) {
|
|
11808
11893
|
const fromNum = parseItemId(fromId);
|
|
11809
11894
|
const toNum = parseItemId(toId);
|
|
@@ -11827,7 +11912,7 @@ async function unlink(fromId, toId) {
|
|
|
11827
11912
|
);
|
|
11828
11913
|
return;
|
|
11829
11914
|
}
|
|
11830
|
-
await orm.delete(links).where(
|
|
11915
|
+
await orm.delete(links).where(and10(eq26(links.itemId, fromNum), eq26(links.targetId, toNum)));
|
|
11831
11916
|
console.log(
|
|
11832
11917
|
chalk82.green(
|
|
11833
11918
|
`Removed link from ${formatItemId(fromNum)} to ${formatItemId(toNum)}.`
|
|
@@ -11847,7 +11932,7 @@ function registerLinkCommands(cmd) {
|
|
|
11847
11932
|
|
|
11848
11933
|
// src/commands/backlog/move-repo/index.ts
|
|
11849
11934
|
import chalk84 from "chalk";
|
|
11850
|
-
import { eq as
|
|
11935
|
+
import { eq as eq28 } from "drizzle-orm";
|
|
11851
11936
|
|
|
11852
11937
|
// src/commands/backlog/move-repo/confirmMove.ts
|
|
11853
11938
|
import chalk83 from "chalk";
|
|
@@ -11862,9 +11947,9 @@ async function confirmMove(cnt, oldOrigin, newOrigin) {
|
|
|
11862
11947
|
}
|
|
11863
11948
|
|
|
11864
11949
|
// src/commands/backlog/move-repo/countByOrigin.ts
|
|
11865
|
-
import { count as count3, eq as
|
|
11950
|
+
import { count as count3, eq as eq27 } from "drizzle-orm";
|
|
11866
11951
|
async function countByOrigin(orm, origin) {
|
|
11867
|
-
const [{ cnt }] = await orm.select({ cnt: count3() }).from(items).where(
|
|
11952
|
+
const [{ cnt }] = await orm.select({ cnt: count3() }).from(items).where(eq27(items.origin, origin));
|
|
11868
11953
|
return cnt;
|
|
11869
11954
|
}
|
|
11870
11955
|
|
|
@@ -11907,7 +11992,7 @@ async function moveRepo(oldOriginRaw, newOriginRaw, options2 = {}) {
|
|
|
11907
11992
|
console.log(chalk84.yellow("Move cancelled; no changes made."));
|
|
11908
11993
|
return;
|
|
11909
11994
|
}
|
|
11910
|
-
await orm.update(items).set({ origin: newOrigin }).where(
|
|
11995
|
+
await orm.update(items).set({ origin: newOrigin }).where(eq28(items.origin, oldOrigin));
|
|
11911
11996
|
console.log(
|
|
11912
11997
|
chalk84.green(
|
|
11913
11998
|
`Moved ${pluralItems(cnt)} from "${oldOrigin}" to "${newOrigin}".`
|
|
@@ -12324,10 +12409,10 @@ async function start(id) {
|
|
|
12324
12409
|
|
|
12325
12410
|
// src/commands/backlog/stop/index.ts
|
|
12326
12411
|
import chalk96 from "chalk";
|
|
12327
|
-
import { and as
|
|
12412
|
+
import { and as and11, eq as eq29 } from "drizzle-orm";
|
|
12328
12413
|
async function stop() {
|
|
12329
12414
|
const { orm } = await getReady();
|
|
12330
|
-
const stopped = await orm.update(items).set({ status: "todo", currentPhase: 1 }).where(
|
|
12415
|
+
const stopped = await orm.update(items).set({ status: "todo", currentPhase: 1 }).where(and11(eq29(items.status, "in-progress"), eq29(items.origin, getOrigin()))).returning({ id: items.id, name: items.name });
|
|
12331
12416
|
if (stopped.length === 0) {
|
|
12332
12417
|
console.log(chalk96.yellow("No in-progress items to stop."));
|
|
12333
12418
|
return;
|
|
@@ -12444,13 +12529,13 @@ async function findSubtask(id, index3) {
|
|
|
12444
12529
|
}
|
|
12445
12530
|
|
|
12446
12531
|
// src/commands/backlog/updateSubtask.ts
|
|
12447
|
-
import { and as
|
|
12532
|
+
import { and as and12, eq as eq30 } from "drizzle-orm";
|
|
12448
12533
|
async function updateSubtask(orm, itemId2, idx, fields) {
|
|
12449
12534
|
const set = {};
|
|
12450
12535
|
if (fields.title !== void 0) set.title = fields.title;
|
|
12451
12536
|
if (fields.description !== void 0) set.description = fields.description;
|
|
12452
12537
|
if (fields.status !== void 0) set.status = fields.status;
|
|
12453
|
-
const [row] = await orm.update(itemSubtasks).set(set).where(
|
|
12538
|
+
const [row] = await orm.update(itemSubtasks).set(set).where(and12(eq30(itemSubtasks.itemId, itemId2), eq30(itemSubtasks.idx, idx))).returning({ title: itemSubtasks.title });
|
|
12454
12539
|
return row?.title;
|
|
12455
12540
|
}
|
|
12456
12541
|
|
|
@@ -12505,16 +12590,16 @@ async function editSubtask(id, index3, options2) {
|
|
|
12505
12590
|
import chalk102 from "chalk";
|
|
12506
12591
|
|
|
12507
12592
|
// src/commands/backlog/deleteSubtask.ts
|
|
12508
|
-
import { and as
|
|
12593
|
+
import { and as and13, asc as asc8, eq as eq31 } from "drizzle-orm";
|
|
12509
12594
|
async function deleteSubtask(orm, itemId2, idx) {
|
|
12510
|
-
const [row] = await orm.delete(itemSubtasks).where(
|
|
12595
|
+
const [row] = await orm.delete(itemSubtasks).where(and13(eq31(itemSubtasks.itemId, itemId2), eq31(itemSubtasks.idx, idx))).returning({ title: itemSubtasks.title });
|
|
12511
12596
|
if (!row) return void 0;
|
|
12512
|
-
const remaining = await orm.select({ idx: itemSubtasks.idx }).from(itemSubtasks).where(
|
|
12597
|
+
const remaining = await orm.select({ idx: itemSubtasks.idx }).from(itemSubtasks).where(eq31(itemSubtasks.itemId, itemId2)).orderBy(asc8(itemSubtasks.idx));
|
|
12513
12598
|
for (let i = 0; i < remaining.length; i++) {
|
|
12514
12599
|
const oldIdx = remaining[i].idx;
|
|
12515
12600
|
if (oldIdx === i) continue;
|
|
12516
12601
|
await orm.update(itemSubtasks).set({ idx: i }).where(
|
|
12517
|
-
|
|
12602
|
+
and13(eq31(itemSubtasks.itemId, itemId2), eq31(itemSubtasks.idx, oldIdx))
|
|
12518
12603
|
);
|
|
12519
12604
|
}
|
|
12520
12605
|
return row.title;
|
|
@@ -12583,20 +12668,20 @@ function registerSubtaskCommands(cmd) {
|
|
|
12583
12668
|
|
|
12584
12669
|
// src/commands/backlog/movePhase.ts
|
|
12585
12670
|
import chalk104 from "chalk";
|
|
12586
|
-
import { count as count5, eq as
|
|
12671
|
+
import { count as count5, eq as eq34 } from "drizzle-orm";
|
|
12587
12672
|
|
|
12588
12673
|
// src/commands/backlog/reorderPhaseRows.ts
|
|
12589
|
-
import { and as
|
|
12674
|
+
import { and as and15, asc as asc10, eq as eq33 } from "drizzle-orm";
|
|
12590
12675
|
|
|
12591
12676
|
// src/commands/backlog/reindexPhases.ts
|
|
12592
|
-
import { and as
|
|
12677
|
+
import { and as and14, asc as asc9, count as count4, eq as eq32 } from "drizzle-orm";
|
|
12593
12678
|
async function reindexPhases(db, itemId2) {
|
|
12594
|
-
const remaining = await db.select({ idx: planPhases.idx }).from(planPhases).where(
|
|
12679
|
+
const remaining = await db.select({ idx: planPhases.idx }).from(planPhases).where(eq32(planPhases.itemId, itemId2)).orderBy(asc9(planPhases.idx));
|
|
12595
12680
|
for (let i = 0; i < remaining.length; i++) {
|
|
12596
12681
|
const oldIdx = remaining[i].idx;
|
|
12597
12682
|
if (oldIdx === i) continue;
|
|
12598
|
-
await db.update(planTasks).set({ phaseIdx: i }).where(
|
|
12599
|
-
await db.update(planPhases).set({ idx: i }).where(
|
|
12683
|
+
await db.update(planTasks).set({ phaseIdx: i }).where(and14(eq32(planTasks.itemId, itemId2), eq32(planTasks.phaseIdx, oldIdx)));
|
|
12684
|
+
await db.update(planPhases).set({ idx: i }).where(and14(eq32(planPhases.itemId, itemId2), eq32(planPhases.idx, oldIdx)));
|
|
12600
12685
|
}
|
|
12601
12686
|
}
|
|
12602
12687
|
async function adjustCurrentPhase(db, item, removedIdx) {
|
|
@@ -12604,24 +12689,24 @@ async function adjustCurrentPhase(db, item, removedIdx) {
|
|
|
12604
12689
|
if (currentPhase === void 0) return;
|
|
12605
12690
|
const currentIdx = currentPhase - 1;
|
|
12606
12691
|
if (removedIdx < currentIdx) {
|
|
12607
|
-
await db.update(items).set({ currentPhase: currentPhase - 1 }).where(
|
|
12692
|
+
await db.update(items).set({ currentPhase: currentPhase - 1 }).where(eq32(items.id, item.id));
|
|
12608
12693
|
return;
|
|
12609
12694
|
}
|
|
12610
12695
|
if (removedIdx !== currentIdx) return;
|
|
12611
|
-
const [row] = await db.select({ cnt: count4() }).from(planPhases).where(
|
|
12696
|
+
const [row] = await db.select({ cnt: count4() }).from(planPhases).where(eq32(planPhases.itemId, item.id));
|
|
12612
12697
|
const cnt = row?.cnt ?? 0;
|
|
12613
|
-
await db.update(items).set({ currentPhase: cnt === 0 ? null : Math.min(currentPhase, cnt) }).where(
|
|
12698
|
+
await db.update(items).set({ currentPhase: cnt === 0 ? null : Math.min(currentPhase, cnt) }).where(eq32(items.id, item.id));
|
|
12614
12699
|
}
|
|
12615
12700
|
|
|
12616
12701
|
// src/commands/backlog/reorderPhaseRows.ts
|
|
12617
12702
|
async function reorderPhaseRows(orm, itemId2, fromIdx, toIdx) {
|
|
12618
12703
|
await orm.transaction(async (tx) => {
|
|
12619
|
-
const [phaseRow] = await tx.select({ name: planPhases.name, manualChecks: planPhases.manualChecks }).from(planPhases).where(
|
|
12620
|
-
const tasks = await tx.select({ idx: planTasks.idx, task: planTasks.task }).from(planTasks).where(
|
|
12704
|
+
const [phaseRow] = await tx.select({ name: planPhases.name, manualChecks: planPhases.manualChecks }).from(planPhases).where(and15(eq33(planPhases.itemId, itemId2), eq33(planPhases.idx, fromIdx)));
|
|
12705
|
+
const tasks = await tx.select({ idx: planTasks.idx, task: planTasks.task }).from(planTasks).where(and15(eq33(planTasks.itemId, itemId2), eq33(planTasks.phaseIdx, fromIdx))).orderBy(asc10(planTasks.idx));
|
|
12621
12706
|
await tx.delete(planTasks).where(
|
|
12622
|
-
|
|
12707
|
+
and15(eq33(planTasks.itemId, itemId2), eq33(planTasks.phaseIdx, fromIdx))
|
|
12623
12708
|
);
|
|
12624
|
-
await tx.delete(planPhases).where(
|
|
12709
|
+
await tx.delete(planPhases).where(and15(eq33(planPhases.itemId, itemId2), eq33(planPhases.idx, fromIdx)));
|
|
12625
12710
|
await reindexPhases(tx, itemId2);
|
|
12626
12711
|
await shiftPhasesUp(tx, itemId2, toIdx);
|
|
12627
12712
|
await tx.insert(planPhases).values({
|
|
@@ -12662,7 +12747,7 @@ async function movePhase(id, from, to) {
|
|
|
12662
12747
|
if (!found) return;
|
|
12663
12748
|
const { orm, item } = found;
|
|
12664
12749
|
const itemId2 = item.id;
|
|
12665
|
-
const [row] = await orm.select({ cnt: count5() }).from(planPhases).where(
|
|
12750
|
+
const [row] = await orm.select({ cnt: count5() }).from(planPhases).where(eq34(planPhases.itemId, itemId2));
|
|
12666
12751
|
const phaseCount = row?.cnt ?? 0;
|
|
12667
12752
|
const fromIdx = toIndex(from, phaseCount);
|
|
12668
12753
|
if (fromIdx === void 0) return;
|
|
@@ -12678,22 +12763,22 @@ async function movePhase(id, from, to) {
|
|
|
12678
12763
|
import chalk106 from "chalk";
|
|
12679
12764
|
|
|
12680
12765
|
// src/commands/backlog/applyPhaseUpdate.ts
|
|
12681
|
-
import { and as
|
|
12766
|
+
import { and as and16, eq as eq35 } from "drizzle-orm";
|
|
12682
12767
|
async function applyPhaseUpdate(orm, itemId2, phaseIdx, fields) {
|
|
12683
12768
|
await orm.transaction(async (tx) => {
|
|
12684
12769
|
if (fields.name) {
|
|
12685
12770
|
await tx.update(planPhases).set({ name: fields.name }).where(
|
|
12686
|
-
|
|
12771
|
+
and16(eq35(planPhases.itemId, itemId2), eq35(planPhases.idx, phaseIdx))
|
|
12687
12772
|
);
|
|
12688
12773
|
}
|
|
12689
12774
|
if (fields.manualCheck) {
|
|
12690
12775
|
await tx.update(planPhases).set({ manualChecks: JSON.stringify(fields.manualCheck) }).where(
|
|
12691
|
-
|
|
12776
|
+
and16(eq35(planPhases.itemId, itemId2), eq35(planPhases.idx, phaseIdx))
|
|
12692
12777
|
);
|
|
12693
12778
|
}
|
|
12694
12779
|
if (fields.task) {
|
|
12695
12780
|
await tx.delete(planTasks).where(
|
|
12696
|
-
|
|
12781
|
+
and16(eq35(planTasks.itemId, itemId2), eq35(planTasks.phaseIdx, phaseIdx))
|
|
12697
12782
|
);
|
|
12698
12783
|
if (fields.task.length) {
|
|
12699
12784
|
await tx.insert(planTasks).values(
|
|
@@ -12711,14 +12796,14 @@ async function applyPhaseUpdate(orm, itemId2, phaseIdx, fields) {
|
|
|
12711
12796
|
|
|
12712
12797
|
// src/commands/backlog/findPhase.ts
|
|
12713
12798
|
import chalk105 from "chalk";
|
|
12714
|
-
import { and as
|
|
12799
|
+
import { and as and17, count as count6, eq as eq36 } from "drizzle-orm";
|
|
12715
12800
|
async function findPhase(id, phase) {
|
|
12716
12801
|
const found = await findOneItem(id);
|
|
12717
12802
|
if (!found) return void 0;
|
|
12718
12803
|
const { orm, item } = found;
|
|
12719
12804
|
const itemId2 = item.id;
|
|
12720
12805
|
const phaseIdx = Number.parseInt(phase, 10) - 1;
|
|
12721
|
-
const [row] = await orm.select({ cnt: count6() }).from(planPhases).where(
|
|
12806
|
+
const [row] = await orm.select({ cnt: count6() }).from(planPhases).where(and17(eq36(planPhases.itemId, itemId2), eq36(planPhases.idx, phaseIdx)));
|
|
12722
12807
|
if (!row || row.cnt === 0) {
|
|
12723
12808
|
console.log(
|
|
12724
12809
|
chalk105.red(
|
|
@@ -12891,16 +12976,16 @@ function registerUpdatePhaseCommand(cmd) {
|
|
|
12891
12976
|
|
|
12892
12977
|
// src/commands/backlog/removePhase.ts
|
|
12893
12978
|
import chalk107 from "chalk";
|
|
12894
|
-
import { and as
|
|
12979
|
+
import { and as and18, eq as eq37 } from "drizzle-orm";
|
|
12895
12980
|
async function removePhase(id, phase) {
|
|
12896
12981
|
const found = await findPhase(id, phase);
|
|
12897
12982
|
if (!found) return;
|
|
12898
12983
|
const { item, orm, itemId: itemId2, phaseIdx } = found;
|
|
12899
12984
|
await orm.transaction(async (tx) => {
|
|
12900
12985
|
await tx.delete(planTasks).where(
|
|
12901
|
-
|
|
12986
|
+
and18(eq37(planTasks.itemId, itemId2), eq37(planTasks.phaseIdx, phaseIdx))
|
|
12902
12987
|
);
|
|
12903
|
-
await tx.delete(planPhases).where(
|
|
12988
|
+
await tx.delete(planPhases).where(and18(eq37(planPhases.itemId, itemId2), eq37(planPhases.idx, phaseIdx)));
|
|
12904
12989
|
await reindexPhases(tx, itemId2);
|
|
12905
12990
|
await adjustCurrentPhase(tx, item, phaseIdx);
|
|
12906
12991
|
});
|
|
@@ -12913,7 +12998,7 @@ async function removePhase(id, phase) {
|
|
|
12913
12998
|
|
|
12914
12999
|
// src/commands/backlog/update/update.ts
|
|
12915
13000
|
import chalk111 from "chalk";
|
|
12916
|
-
import { eq as
|
|
13001
|
+
import { eq as eq38 } from "drizzle-orm";
|
|
12917
13002
|
|
|
12918
13003
|
// src/commands/backlog/update/buildUpdateValues.ts
|
|
12919
13004
|
import chalk108 from "chalk";
|
|
@@ -13024,7 +13109,7 @@ async function update(id, options2) {
|
|
|
13024
13109
|
if (!built) return;
|
|
13025
13110
|
const { orm } = found;
|
|
13026
13111
|
const itemId2 = found.item.id;
|
|
13027
|
-
await orm.update(items).set(built.set).where(
|
|
13112
|
+
await orm.update(items).set(built.set).where(eq38(items.id, itemId2));
|
|
13028
13113
|
console.log(
|
|
13029
13114
|
chalk111.green(`Updated ${built.fields} on item ${formatItemId(itemId2)}.`)
|
|
13030
13115
|
);
|
|
@@ -17107,9 +17192,9 @@ function registerGithub(program2) {
|
|
|
17107
17192
|
}
|
|
17108
17193
|
|
|
17109
17194
|
// src/commands/handover/countPendingHandovers.ts
|
|
17110
|
-
import { and as
|
|
17195
|
+
import { and as and19, eq as eq39, isNull, sql as sql18 } from "drizzle-orm";
|
|
17111
17196
|
async function countPendingHandovers(orm, origin) {
|
|
17112
|
-
const [row] = await orm.select({ count: sql18`count(*)::int` }).from(handovers).where(
|
|
17197
|
+
const [row] = await orm.select({ count: sql18`count(*)::int` }).from(handovers).where(and19(eq39(handovers.origin, origin), isNull(handovers.recalledAt)));
|
|
17113
17198
|
return row?.count ?? 0;
|
|
17114
17199
|
}
|
|
17115
17200
|
|
|
@@ -17249,13 +17334,13 @@ async function load2(options2 = {}) {
|
|
|
17249
17334
|
}
|
|
17250
17335
|
|
|
17251
17336
|
// src/commands/handover/listPendingHandovers.ts
|
|
17252
|
-
import { and as
|
|
17337
|
+
import { and as and20, desc as desc4, eq as eq40, isNull as isNull2 } from "drizzle-orm";
|
|
17253
17338
|
async function listPendingHandovers(orm, origin) {
|
|
17254
17339
|
return orm.select({
|
|
17255
17340
|
id: handovers.id,
|
|
17256
17341
|
summary: handovers.summary,
|
|
17257
17342
|
createdAt: handovers.createdAt
|
|
17258
|
-
}).from(handovers).where(
|
|
17343
|
+
}).from(handovers).where(and20(eq40(handovers.origin, origin), isNull2(handovers.recalledAt))).orderBy(desc4(handovers.createdAt), desc4(handovers.id));
|
|
17259
17344
|
}
|
|
17260
17345
|
|
|
17261
17346
|
// src/commands/handover/printPendingHandovers.ts
|
|
@@ -17268,17 +17353,17 @@ async function printPendingHandovers() {
|
|
|
17268
17353
|
}
|
|
17269
17354
|
|
|
17270
17355
|
// src/commands/handover/recallHandover.ts
|
|
17271
|
-
import { and as
|
|
17356
|
+
import { and as and21, desc as desc5, eq as eq41, isNull as isNull3 } from "drizzle-orm";
|
|
17272
17357
|
async function recallHandover(orm, origin, id) {
|
|
17273
17358
|
const [row] = await orm.select().from(handovers).where(
|
|
17274
|
-
|
|
17275
|
-
|
|
17359
|
+
and21(
|
|
17360
|
+
eq41(handovers.origin, origin),
|
|
17276
17361
|
isNull3(handovers.recalledAt),
|
|
17277
|
-
...id === void 0 ? [] : [
|
|
17362
|
+
...id === void 0 ? [] : [eq41(handovers.id, id)]
|
|
17278
17363
|
)
|
|
17279
17364
|
).orderBy(desc5(handovers.createdAt), desc5(handovers.id)).limit(1);
|
|
17280
17365
|
if (!row) return void 0;
|
|
17281
|
-
await orm.update(handovers).set({ recalledAt: /* @__PURE__ */ new Date() }).where(
|
|
17366
|
+
await orm.update(handovers).set({ recalledAt: /* @__PURE__ */ new Date() }).where(eq41(handovers.id, row.id));
|
|
17282
17367
|
return row.content;
|
|
17283
17368
|
}
|
|
17284
17369
|
|
|
@@ -19281,20 +19366,20 @@ function resolveThread(threadId) {
|
|
|
19281
19366
|
}
|
|
19282
19367
|
}
|
|
19283
19368
|
function requireCache(prNumber) {
|
|
19284
|
-
const
|
|
19285
|
-
if (!
|
|
19369
|
+
const cache4 = loadCommentsCache(prNumber);
|
|
19370
|
+
if (!cache4) {
|
|
19286
19371
|
console.error(
|
|
19287
19372
|
`Error: No cached comments found for PR #${prNumber}. Run "assist prs list-comments" first.`
|
|
19288
19373
|
);
|
|
19289
19374
|
process.exit(1);
|
|
19290
19375
|
}
|
|
19291
|
-
return
|
|
19376
|
+
return cache4;
|
|
19292
19377
|
}
|
|
19293
19378
|
function findLineComment(comments3, commentId) {
|
|
19294
19379
|
return comments3.find((c) => c.type === "line" && c.id === commentId);
|
|
19295
19380
|
}
|
|
19296
|
-
function requireLineComment(
|
|
19297
|
-
const comment3 = findLineComment(
|
|
19381
|
+
function requireLineComment(cache4, commentId) {
|
|
19382
|
+
const comment3 = findLineComment(cache4.comments, commentId);
|
|
19298
19383
|
if (!comment3 || comment3.type !== "line" || !comment3.threadId) {
|
|
19299
19384
|
console.error(
|
|
19300
19385
|
`Error: Comment #${commentId} not found or has no thread ID.`
|
|
@@ -19303,8 +19388,8 @@ function requireLineComment(cache3, commentId) {
|
|
|
19303
19388
|
}
|
|
19304
19389
|
return comment3;
|
|
19305
19390
|
}
|
|
19306
|
-
function cleanupCacheIfDone(
|
|
19307
|
-
const hasRemaining =
|
|
19391
|
+
function cleanupCacheIfDone(cache4, prNumber, commentId) {
|
|
19392
|
+
const hasRemaining = cache4.comments.some(
|
|
19308
19393
|
(c) => c.type === "line" && c.id !== commentId
|
|
19309
19394
|
);
|
|
19310
19395
|
if (!hasRemaining) deleteCommentsCache(prNumber);
|
|
@@ -19312,13 +19397,13 @@ function cleanupCacheIfDone(cache3, prNumber, commentId) {
|
|
|
19312
19397
|
function resolveCommentWithReply(commentId, message2) {
|
|
19313
19398
|
const prNumber = getCurrentPrNumber();
|
|
19314
19399
|
const { org, repo } = getRepoInfo();
|
|
19315
|
-
const
|
|
19316
|
-
const comment3 = requireLineComment(
|
|
19400
|
+
const cache4 = requireCache(prNumber);
|
|
19401
|
+
const comment3 = requireLineComment(cache4, commentId);
|
|
19317
19402
|
replyToComment(org, repo, prNumber, commentId, message2);
|
|
19318
19403
|
console.log("Reply posted successfully.");
|
|
19319
19404
|
resolveThread(comment3.threadId);
|
|
19320
19405
|
console.log("Thread resolved successfully.");
|
|
19321
|
-
cleanupCacheIfDone(
|
|
19406
|
+
cleanupCacheIfDone(cache4, prNumber, commentId);
|
|
19322
19407
|
}
|
|
19323
19408
|
|
|
19324
19409
|
// src/commands/prs/fixed.ts
|
|
@@ -26478,6 +26563,7 @@ function toSessionInfo({
|
|
|
26478
26563
|
serverOrigin,
|
|
26479
26564
|
assistArgs,
|
|
26480
26565
|
cwd,
|
|
26566
|
+
claudeSessionId,
|
|
26481
26567
|
restored,
|
|
26482
26568
|
error,
|
|
26483
26569
|
activity: activity2,
|
|
@@ -26508,6 +26594,7 @@ function toSessionInfo({
|
|
|
26508
26594
|
remoteOrigin: serverOrigin ?? originForCwd(cwd),
|
|
26509
26595
|
assistArgs,
|
|
26510
26596
|
cwd,
|
|
26597
|
+
claudeSessionId,
|
|
26511
26598
|
repoGroup: repoGroupForCwd(cwd),
|
|
26512
26599
|
restored,
|
|
26513
26600
|
error,
|
|
@@ -27155,13 +27242,6 @@ function greetClient(client, sessions, windowsProxy) {
|
|
|
27155
27242
|
void windowsProxy.discover();
|
|
27156
27243
|
}
|
|
27157
27244
|
|
|
27158
|
-
// src/commands/sessions/daemon/logSpawnedSession.ts
|
|
27159
|
-
function logSpawnedSession(session) {
|
|
27160
|
-
daemonLog(
|
|
27161
|
-
`session ${session.id} spawned: ${session.name} [${session.commandType}] ${session.cwd ?? ""}`
|
|
27162
|
-
);
|
|
27163
|
-
}
|
|
27164
|
-
|
|
27165
27245
|
// src/commands/sessions/daemon/decidePrPreview.ts
|
|
27166
27246
|
function decidePrPreview(sessions, waiters, notify2, d) {
|
|
27167
27247
|
const id = d.sessionId;
|
|
@@ -27249,361 +27329,93 @@ var PrPreviewCoordinator = class {
|
|
|
27249
27329
|
}
|
|
27250
27330
|
};
|
|
27251
27331
|
|
|
27252
|
-
// src/commands/sessions/daemon/
|
|
27253
|
-
function
|
|
27254
|
-
|
|
27255
|
-
|
|
27256
|
-
|
|
27257
|
-
`set-status for unknown session id=${id} status=${status3} (no live session; ignoring)`
|
|
27258
|
-
);
|
|
27259
|
-
return;
|
|
27260
|
-
}
|
|
27261
|
-
if (session.status === "stopped") {
|
|
27262
|
-
daemonLog(
|
|
27263
|
-
`set-status for stopped session id=${id} status=${status3} (held pending durable close; ignoring)`
|
|
27264
|
-
);
|
|
27265
|
-
return;
|
|
27266
|
-
}
|
|
27267
|
-
if (source === "permission") session.permissionActive = true;
|
|
27268
|
-
if (status3 === "running") session.permissionActive = false;
|
|
27269
|
-
onStatusChange(session, status3);
|
|
27270
|
-
}
|
|
27271
|
-
|
|
27272
|
-
// src/shared/db/recordPhaseCycleContext.ts
|
|
27273
|
-
import { sql as sql21 } from "drizzle-orm";
|
|
27274
|
-
async function recordPhaseCycleContext(db, itemId2, phaseIdx, window, resetsAt, pct) {
|
|
27275
|
-
if (pct <= 0) return;
|
|
27276
|
-
await db.insert(phaseCycleContext).values({ itemId: itemId2, phaseIdx, window, resetsAt, peakContextPct: pct }).onConflictDoUpdate({
|
|
27277
|
-
target: [
|
|
27278
|
-
phaseCycleContext.itemId,
|
|
27279
|
-
phaseCycleContext.phaseIdx,
|
|
27280
|
-
phaseCycleContext.window,
|
|
27281
|
-
phaseCycleContext.resetsAt
|
|
27282
|
-
],
|
|
27283
|
-
set: {
|
|
27284
|
-
peakContextPct: sql21`GREATEST(${phaseCycleContext.peakContextPct}, ${pct})`
|
|
27285
|
-
}
|
|
27286
|
-
});
|
|
27332
|
+
// src/commands/sessions/daemon/logSpawnedSession.ts
|
|
27333
|
+
function logSpawnedSession(session) {
|
|
27334
|
+
daemonLog(
|
|
27335
|
+
`session ${session.id} spawned: ${session.name} [${session.commandType}] ${session.cwd ?? ""}`
|
|
27336
|
+
);
|
|
27287
27337
|
}
|
|
27288
27338
|
|
|
27289
|
-
// src/
|
|
27290
|
-
|
|
27291
|
-
|
|
27292
|
-
|
|
27293
|
-
|
|
27294
|
-
|
|
27295
|
-
|
|
27296
|
-
|
|
27297
|
-
});
|
|
27339
|
+
// src/commands/sessions/daemon/emitSessionOutput.ts
|
|
27340
|
+
var MAX_SCROLLBACK = 256 * 1024;
|
|
27341
|
+
function emitSessionOutput(session, clients, data) {
|
|
27342
|
+
session.scrollback += data;
|
|
27343
|
+
if (session.scrollback.length > MAX_SCROLLBACK) {
|
|
27344
|
+
session.scrollback = session.scrollback.slice(-MAX_SCROLLBACK);
|
|
27345
|
+
}
|
|
27346
|
+
broadcast(clients, { type: "output", sessionId: session.id, data });
|
|
27298
27347
|
}
|
|
27299
27348
|
|
|
27300
|
-
// src/
|
|
27301
|
-
import {
|
|
27302
|
-
|
|
27303
|
-
const
|
|
27304
|
-
|
|
27305
|
-
|
|
27306
|
-
|
|
27307
|
-
return db.transaction(async (tx) => {
|
|
27308
|
-
const inserted = await tx.insert(phaseUsageMessages).values(unique.map((r) => ({ itemId: itemId2, phaseIdx, messageId: r.messageId }))).onConflictDoNothing().returning({ messageId: phaseUsageMessages.messageId });
|
|
27309
|
-
let tokensUp = 0;
|
|
27310
|
-
let tokensDown = 0;
|
|
27311
|
-
for (const { messageId } of inserted) {
|
|
27312
|
-
const r = byId.get(messageId);
|
|
27313
|
-
if (!r) continue;
|
|
27314
|
-
tokensUp += r.outputTokens;
|
|
27315
|
-
tokensDown += r.inputTokens;
|
|
27316
|
-
}
|
|
27317
|
-
if (tokensUp <= 0 && tokensDown <= 0) return { tokensUp: 0, tokensDown: 0 };
|
|
27318
|
-
await tx.insert(phaseUsage).values({ itemId: itemId2, phaseIdx, tokensUp, tokensDown }).onConflictDoUpdate({
|
|
27319
|
-
target: [phaseUsage.itemId, phaseUsage.phaseIdx],
|
|
27320
|
-
set: {
|
|
27321
|
-
tokensUp: sql23`${phaseUsage.tokensUp} + ${tokensUp}`,
|
|
27322
|
-
tokensDown: sql23`${phaseUsage.tokensDown} + ${tokensDown}`
|
|
27323
|
-
}
|
|
27324
|
-
});
|
|
27325
|
-
return { tokensUp, tokensDown };
|
|
27326
|
-
});
|
|
27349
|
+
// src/commands/sessions/daemon/exitReason.ts
|
|
27350
|
+
import { existsSync as existsSync64 } from "fs";
|
|
27351
|
+
function exitReason(session, exitCode) {
|
|
27352
|
+
const base = `process exited with code ${exitCode}`;
|
|
27353
|
+
if (session.cwd && !existsSync64(session.cwd))
|
|
27354
|
+
return `${base}: working directory ${session.cwd} no longer exists`;
|
|
27355
|
+
return base;
|
|
27327
27356
|
}
|
|
27328
27357
|
|
|
27329
|
-
// src/
|
|
27330
|
-
|
|
27331
|
-
|
|
27332
|
-
if (
|
|
27333
|
-
|
|
27334
|
-
|
|
27335
|
-
|
|
27336
|
-
}).where(
|
|
27337
|
-
and21(
|
|
27338
|
-
eq40(usagePeaks.window, window),
|
|
27339
|
-
eq40(usagePeaks.resetsAt, resetsAt),
|
|
27340
|
-
eq40(usagePeaks.segment, 0)
|
|
27341
|
-
)
|
|
27358
|
+
// src/commands/sessions/daemon/handleFailedResume.ts
|
|
27359
|
+
function handleFailedResume(session, exitCode, onStatusChange) {
|
|
27360
|
+
const failed2 = session.restored === true && exitCode !== 0 && session.scrollback.length === 0;
|
|
27361
|
+
if (!failed2) return false;
|
|
27362
|
+
session.error = `resume process exited with code ${exitCode} before producing any output`;
|
|
27363
|
+
daemonLog(
|
|
27364
|
+
`could not resume restored session "${session.name}" (id ${session.id}): ${session.error}`
|
|
27342
27365
|
);
|
|
27366
|
+
onStatusChange(session, "error", exitCode);
|
|
27367
|
+
return true;
|
|
27343
27368
|
}
|
|
27344
27369
|
|
|
27345
|
-
// src/commands/sessions/
|
|
27346
|
-
import
|
|
27347
|
-
|
|
27348
|
-
|
|
27349
|
-
|
|
27350
|
-
|
|
27351
|
-
|
|
27352
|
-
|
|
27353
|
-
|
|
27354
|
-
|
|
27355
|
-
|
|
27356
|
-
|
|
27357
|
-
if (entry.type !== "assistant") continue;
|
|
27358
|
-
const id = entry.message?.id;
|
|
27359
|
-
const usage = entry.message?.usage;
|
|
27360
|
-
if (!id || !usage) continue;
|
|
27361
|
-
byId.set(id, {
|
|
27362
|
-
messageId: id,
|
|
27363
|
-
inputTokens: usage.input_tokens ?? 0,
|
|
27364
|
-
outputTokens: usage.output_tokens ?? 0
|
|
27365
|
-
});
|
|
27370
|
+
// src/commands/sessions/daemon/watchActivity.ts
|
|
27371
|
+
import { existsSync as existsSync65, mkdirSync as mkdirSync26, watch as watch2 } from "fs";
|
|
27372
|
+
import { dirname as dirname31 } from "path";
|
|
27373
|
+
|
|
27374
|
+
// src/commands/sessions/daemon/applyReviewPause.ts
|
|
27375
|
+
function applyReviewPause(session, activity2) {
|
|
27376
|
+
const inReview = activity2.kind === "backlog" && activity2.phase !== void 0 && activity2.phase === activity2.totalPhases;
|
|
27377
|
+
if (inReview && !session.reviewStarted) {
|
|
27378
|
+
session.autoAdvance = false;
|
|
27379
|
+
session.reviewStarted = true;
|
|
27380
|
+
} else if (!inReview && session.reviewStarted) {
|
|
27381
|
+
session.reviewStarted = false;
|
|
27366
27382
|
}
|
|
27367
|
-
return [...byId.values()];
|
|
27368
27383
|
}
|
|
27369
|
-
|
|
27370
|
-
|
|
27371
|
-
|
|
27384
|
+
|
|
27385
|
+
// src/shared/db/getPhaseActiveMs.ts
|
|
27386
|
+
import { and as and22, eq as eq42 } from "drizzle-orm";
|
|
27387
|
+
async function getPhaseActiveMs(db, itemId2, phaseIdx) {
|
|
27388
|
+
const [row] = await db.select({ activeMs: phaseUsage.activeMs }).from(phaseUsage).where(
|
|
27389
|
+
and22(eq42(phaseUsage.itemId, itemId2), eq42(phaseUsage.phaseIdx, phaseIdx))
|
|
27390
|
+
);
|
|
27391
|
+
return row?.activeMs ?? 0;
|
|
27372
27392
|
}
|
|
27373
27393
|
|
|
27374
|
-
// src/commands/sessions/daemon/
|
|
27375
|
-
async function
|
|
27394
|
+
// src/commands/sessions/daemon/loadPhaseActiveMs.ts
|
|
27395
|
+
async function loadPhaseActiveMs(itemId2, phaseIdx) {
|
|
27376
27396
|
try {
|
|
27377
|
-
if (!process.env.ASSIST_DATABASE_URL && !loadConfig().database.url)
|
|
27378
|
-
|
|
27379
|
-
|
|
27380
|
-
await recordPhasePeakContext(db, itemId2, phaseIdx, usedPct);
|
|
27381
|
-
for (const { window, resetsAt } of windows) {
|
|
27382
|
-
await recordPhaseCycleContext(
|
|
27383
|
-
db,
|
|
27384
|
-
itemId2,
|
|
27385
|
-
phaseIdx,
|
|
27386
|
-
window,
|
|
27387
|
-
resetsAt,
|
|
27388
|
-
usedPct
|
|
27389
|
-
);
|
|
27390
|
-
}
|
|
27391
|
-
}
|
|
27392
|
-
const responses = await readTranscriptUsage(transcriptPath);
|
|
27393
|
-
if (responses.length === 0) return;
|
|
27394
|
-
const { tokensUp, tokensDown } = await recordPhaseTranscriptUsage(
|
|
27395
|
-
db,
|
|
27396
|
-
itemId2,
|
|
27397
|
-
phaseIdx,
|
|
27398
|
-
responses
|
|
27399
|
-
);
|
|
27400
|
-
for (const { window, resetsAt } of windows) {
|
|
27401
|
-
await recordWindowTokens(db, window, resetsAt, tokensUp, tokensDown);
|
|
27402
|
-
}
|
|
27397
|
+
if (!process.env.ASSIST_DATABASE_URL && !loadConfig().database.url)
|
|
27398
|
+
return null;
|
|
27399
|
+
return await getPhaseActiveMs(await getDb(), itemId2, phaseIdx);
|
|
27403
27400
|
} catch (error) {
|
|
27404
|
-
daemonLog(`phase-
|
|
27401
|
+
daemonLog(`phase-active-ms load failed: ${String(error)}`);
|
|
27402
|
+
return null;
|
|
27405
27403
|
}
|
|
27406
27404
|
}
|
|
27407
27405
|
|
|
27408
|
-
// src/commands/sessions/daemon/
|
|
27409
|
-
function
|
|
27410
|
-
|
|
27411
|
-
|
|
27412
|
-
|
|
27413
|
-
|
|
27414
|
-
|
|
27415
|
-
|
|
27416
|
-
|
|
27417
|
-
|
|
27418
|
-
|
|
27419
|
-
|
|
27420
|
-
|
|
27421
|
-
);
|
|
27422
|
-
void flushPhaseActiveMs(session);
|
|
27423
|
-
return true;
|
|
27424
|
-
}
|
|
27425
|
-
return false;
|
|
27426
|
-
}
|
|
27427
|
-
|
|
27428
|
-
// src/commands/sessions/daemon/applyUsageRecord.ts
|
|
27429
|
-
function applyUsageRecord(sessions, windows, notify2, claudeSessionId, transcriptPath, usedPct) {
|
|
27430
|
-
if (recordSessionUsage(
|
|
27431
|
-
sessions,
|
|
27432
|
-
claudeSessionId,
|
|
27433
|
-
transcriptPath,
|
|
27434
|
-
usedPct,
|
|
27435
|
-
windows
|
|
27436
|
-
))
|
|
27437
|
-
notify2();
|
|
27438
|
-
}
|
|
27439
|
-
|
|
27440
|
-
// src/commands/sessions/daemon/shouldAutoDismiss.ts
|
|
27441
|
-
function shouldAutoDismiss(session, exitCode) {
|
|
27442
|
-
if (session.status !== "done" || exitCode !== 0) {
|
|
27443
|
-
return false;
|
|
27444
|
-
}
|
|
27445
|
-
if (session.reviewStarted) {
|
|
27446
|
-
return session.autoAdvance === true;
|
|
27447
|
-
}
|
|
27448
|
-
const args = session.assistArgs;
|
|
27449
|
-
if (args === void 0) return false;
|
|
27450
|
-
if (args[0] === "update") return true;
|
|
27451
|
-
return args.includes("--once") && args[0] !== "next";
|
|
27452
|
-
}
|
|
27453
|
-
|
|
27454
|
-
// src/commands/sessions/daemon/shouldAutoRun.ts
|
|
27455
|
-
function shouldAutoRun(session, status3 = session.status) {
|
|
27456
|
-
if (!session.autoRun) return { run: false, reason: null };
|
|
27457
|
-
if (status3 !== "done")
|
|
27458
|
-
return {
|
|
27459
|
-
run: false,
|
|
27460
|
-
reason: `session status is "${status3}", not "done"`
|
|
27461
|
-
};
|
|
27462
|
-
if (session.commandType !== "assist")
|
|
27463
|
-
return {
|
|
27464
|
-
run: false,
|
|
27465
|
-
reason: `command type is "${session.commandType}", not "assist"`
|
|
27466
|
-
};
|
|
27467
|
-
const cmd = session.assistArgs?.[0];
|
|
27468
|
-
if (cmd !== "draft" && cmd !== "bug" && cmd !== "refine")
|
|
27469
|
-
return {
|
|
27470
|
-
run: false,
|
|
27471
|
-
reason: `command "${cmd ?? "(none)"}" is not draft/bug/refine`
|
|
27472
|
-
};
|
|
27473
|
-
const itemId2 = session.activity?.itemId;
|
|
27474
|
-
if (itemId2 == null)
|
|
27475
|
-
return {
|
|
27476
|
-
run: false,
|
|
27477
|
-
reason: "no itemId in activity (draft did not create an item)"
|
|
27478
|
-
};
|
|
27479
|
-
return { run: true, itemId: itemId2 };
|
|
27480
|
-
}
|
|
27481
|
-
|
|
27482
|
-
// src/commands/sessions/daemon/applyStatusChange.ts
|
|
27483
|
-
function applyStatusChange(session, status3, exitCode, dismiss, notify2, reuseForRun) {
|
|
27484
|
-
if (session.status === status3) return;
|
|
27485
|
-
const deps2 = { dismiss, notify: notify2, reuseForRun };
|
|
27486
|
-
const chainsToNextRun = shouldAutoRun(session, status3).run;
|
|
27487
|
-
if (status3 === "done" && session.worktree && !chainsToNextRun) {
|
|
27488
|
-
session.closing = true;
|
|
27489
|
-
daemonLog(
|
|
27490
|
-
`session ${session.id} closing: checking durability of ${session.worktree.path} before reap`
|
|
27491
|
-
);
|
|
27492
|
-
notify2();
|
|
27493
|
-
void resolveCloseDurability(
|
|
27494
|
-
session,
|
|
27495
|
-
() => finishStatusChange(session, "done", exitCode, deps2),
|
|
27496
|
-
notify2
|
|
27497
|
-
);
|
|
27498
|
-
return;
|
|
27499
|
-
}
|
|
27500
|
-
finishStatusChange(session, status3, exitCode, deps2);
|
|
27501
|
-
}
|
|
27502
|
-
function finishStatusChange(session, status3, exitCode, deps2) {
|
|
27503
|
-
daemonLog(`session ${session.id} status: ${session.status} -> ${status3}`);
|
|
27504
|
-
void flushPhaseActiveMs(session);
|
|
27505
|
-
setStatus2(session, status3);
|
|
27506
|
-
if (status3 !== "stopped") session.undurable = void 0;
|
|
27507
|
-
const autoRun = shouldAutoRun(session);
|
|
27508
|
-
if (autoRun.run) {
|
|
27509
|
-
deps2.reuseForRun(session, autoRun.itemId);
|
|
27510
|
-
deps2.notify();
|
|
27511
|
-
return;
|
|
27512
|
-
}
|
|
27513
|
-
if (status3 === "done" && autoRun.reason != null) {
|
|
27514
|
-
daemonLog(
|
|
27515
|
-
`session ${session.id} auto-run enabled but skipped (exit ${exitCode ?? "none"}): ${autoRun.reason}`
|
|
27516
|
-
);
|
|
27517
|
-
}
|
|
27518
|
-
if (shouldAutoDismiss(session, exitCode)) deps2.dismiss(session.id);
|
|
27519
|
-
else deps2.notify();
|
|
27520
|
-
}
|
|
27521
|
-
|
|
27522
|
-
// src/commands/sessions/daemon/makeStatusChangeHandler.ts
|
|
27523
|
-
function makeStatusChangeHandler(dismiss, notify2, reuseForRun) {
|
|
27524
|
-
return (s, status3, exitCode) => applyStatusChange(s, status3, exitCode, dismiss, notify2, reuseForRun);
|
|
27525
|
-
}
|
|
27526
|
-
|
|
27527
|
-
// src/commands/sessions/daemon/emitSessionOutput.ts
|
|
27528
|
-
var MAX_SCROLLBACK = 256 * 1024;
|
|
27529
|
-
function emitSessionOutput(session, clients, data) {
|
|
27530
|
-
session.scrollback += data;
|
|
27531
|
-
if (session.scrollback.length > MAX_SCROLLBACK) {
|
|
27532
|
-
session.scrollback = session.scrollback.slice(-MAX_SCROLLBACK);
|
|
27533
|
-
}
|
|
27534
|
-
broadcast(clients, { type: "output", sessionId: session.id, data });
|
|
27535
|
-
}
|
|
27536
|
-
|
|
27537
|
-
// src/commands/sessions/daemon/exitReason.ts
|
|
27538
|
-
import { existsSync as existsSync64 } from "fs";
|
|
27539
|
-
function exitReason(session, exitCode) {
|
|
27540
|
-
const base = `process exited with code ${exitCode}`;
|
|
27541
|
-
if (session.cwd && !existsSync64(session.cwd))
|
|
27542
|
-
return `${base}: working directory ${session.cwd} no longer exists`;
|
|
27543
|
-
return base;
|
|
27544
|
-
}
|
|
27545
|
-
|
|
27546
|
-
// src/commands/sessions/daemon/handleFailedResume.ts
|
|
27547
|
-
function handleFailedResume(session, exitCode, onStatusChange) {
|
|
27548
|
-
const failed2 = session.restored === true && exitCode !== 0 && session.scrollback.length === 0;
|
|
27549
|
-
if (!failed2) return false;
|
|
27550
|
-
session.error = `resume process exited with code ${exitCode} before producing any output`;
|
|
27551
|
-
daemonLog(
|
|
27552
|
-
`could not resume restored session "${session.name}" (id ${session.id}): ${session.error}`
|
|
27553
|
-
);
|
|
27554
|
-
onStatusChange(session, "error", exitCode);
|
|
27555
|
-
return true;
|
|
27556
|
-
}
|
|
27557
|
-
|
|
27558
|
-
// src/commands/sessions/daemon/watchActivity.ts
|
|
27559
|
-
import { existsSync as existsSync65, mkdirSync as mkdirSync26, watch as watch2 } from "fs";
|
|
27560
|
-
import { dirname as dirname31 } from "path";
|
|
27561
|
-
|
|
27562
|
-
// src/commands/sessions/daemon/applyReviewPause.ts
|
|
27563
|
-
function applyReviewPause(session, activity2) {
|
|
27564
|
-
const inReview = activity2.kind === "backlog" && activity2.phase !== void 0 && activity2.phase === activity2.totalPhases;
|
|
27565
|
-
if (inReview && !session.reviewStarted) {
|
|
27566
|
-
session.autoAdvance = false;
|
|
27567
|
-
session.reviewStarted = true;
|
|
27568
|
-
} else if (!inReview && session.reviewStarted) {
|
|
27569
|
-
session.reviewStarted = false;
|
|
27570
|
-
}
|
|
27571
|
-
}
|
|
27572
|
-
|
|
27573
|
-
// src/shared/db/getPhaseActiveMs.ts
|
|
27574
|
-
import { and as and22, eq as eq41 } from "drizzle-orm";
|
|
27575
|
-
async function getPhaseActiveMs(db, itemId2, phaseIdx) {
|
|
27576
|
-
const [row] = await db.select({ activeMs: phaseUsage.activeMs }).from(phaseUsage).where(
|
|
27577
|
-
and22(eq41(phaseUsage.itemId, itemId2), eq41(phaseUsage.phaseIdx, phaseIdx))
|
|
27578
|
-
);
|
|
27579
|
-
return row?.activeMs ?? 0;
|
|
27580
|
-
}
|
|
27581
|
-
|
|
27582
|
-
// src/commands/sessions/daemon/loadPhaseActiveMs.ts
|
|
27583
|
-
async function loadPhaseActiveMs(itemId2, phaseIdx) {
|
|
27584
|
-
try {
|
|
27585
|
-
if (!process.env.ASSIST_DATABASE_URL && !loadConfig().database.url)
|
|
27586
|
-
return null;
|
|
27587
|
-
return await getPhaseActiveMs(await getDb(), itemId2, phaseIdx);
|
|
27588
|
-
} catch (error) {
|
|
27589
|
-
daemonLog(`phase-active-ms load failed: ${String(error)}`);
|
|
27590
|
-
return null;
|
|
27591
|
-
}
|
|
27592
|
-
}
|
|
27593
|
-
|
|
27594
|
-
// src/commands/sessions/daemon/seedRunningMsFromUsage.ts
|
|
27595
|
-
async function seedRunningMsFromUsage(session, notify2) {
|
|
27596
|
-
if (session.usageSeeded) return;
|
|
27597
|
-
const phase = sessionBacklogPhase(session);
|
|
27598
|
-
if (!phase) return;
|
|
27599
|
-
session.usageSeeded = true;
|
|
27600
|
-
const priorMs = await loadPhaseActiveMs(phase.itemId, phase.phaseIdx);
|
|
27601
|
-
if (priorMs == null || priorMs <= session.runningMs) return;
|
|
27602
|
-
session.runningMs = priorMs;
|
|
27603
|
-
daemonLog(
|
|
27604
|
-
`session ${session.id} seeded runningMs=${priorMs} from item ${phase.itemId} phase ${phase.phaseIdx} recorded usage`
|
|
27605
|
-
);
|
|
27606
|
-
notify2();
|
|
27406
|
+
// src/commands/sessions/daemon/seedRunningMsFromUsage.ts
|
|
27407
|
+
async function seedRunningMsFromUsage(session, notify2) {
|
|
27408
|
+
if (session.usageSeeded) return;
|
|
27409
|
+
const phase = sessionBacklogPhase(session);
|
|
27410
|
+
if (!phase) return;
|
|
27411
|
+
session.usageSeeded = true;
|
|
27412
|
+
const priorMs = await loadPhaseActiveMs(phase.itemId, phase.phaseIdx);
|
|
27413
|
+
if (priorMs == null || priorMs <= session.runningMs) return;
|
|
27414
|
+
session.runningMs = priorMs;
|
|
27415
|
+
daemonLog(
|
|
27416
|
+
`session ${session.id} seeded runningMs=${priorMs} from item ${phase.itemId} phase ${phase.phaseIdx} recorded usage`
|
|
27417
|
+
);
|
|
27418
|
+
notify2();
|
|
27607
27419
|
}
|
|
27608
27420
|
|
|
27609
27421
|
// src/commands/sessions/daemon/watchActivity.ts
|
|
@@ -27721,163 +27533,53 @@ function reportSilentFailure(session, clients) {
|
|
|
27721
27533
|
);
|
|
27722
27534
|
}
|
|
27723
27535
|
|
|
27724
|
-
// src/commands/sessions/daemon/
|
|
27725
|
-
|
|
27726
|
-
session.gitWatcher?.close();
|
|
27727
|
-
session.gitWatcher = void 0;
|
|
27728
|
-
session.undurable = void 0;
|
|
27729
|
-
session.pendingDismiss = void 0;
|
|
27730
|
-
session.closing = void 0;
|
|
27731
|
-
session.scrollback = "";
|
|
27732
|
-
session.startedAt = Date.now();
|
|
27733
|
-
session.runningMs = 0;
|
|
27734
|
-
session.runningSince = null;
|
|
27735
|
-
setStatus2(session, status3);
|
|
27736
|
-
session.restored = void 0;
|
|
27737
|
-
session.pty = respawn();
|
|
27738
|
-
if (session.cols && session.rows)
|
|
27739
|
-
try {
|
|
27740
|
-
session.pty?.resize(session.cols, session.rows);
|
|
27741
|
-
} catch {
|
|
27742
|
-
}
|
|
27743
|
-
broadcast(clients, { type: "clear", sessionId: session.id });
|
|
27744
|
-
wirePtyEvents(session, clients, onStatusChange);
|
|
27745
|
-
}
|
|
27746
|
-
|
|
27747
|
-
// src/commands/sessions/daemon/respawnPlan.ts
|
|
27748
|
-
import { randomUUID as randomUUID7 } from "crypto";
|
|
27536
|
+
// src/commands/sessions/daemon/watchTranscript.ts
|
|
27537
|
+
import { existsSync as existsSync67, watch as watch3 } from "fs";
|
|
27749
27538
|
|
|
27750
|
-
// src/commands/sessions/
|
|
27751
|
-
|
|
27752
|
-
|
|
27753
|
-
|
|
27539
|
+
// src/commands/sessions/shared/findTranscriptPathSync.ts
|
|
27540
|
+
import * as fs36 from "fs";
|
|
27541
|
+
import * as os4 from "os";
|
|
27542
|
+
import * as path60 from "path";
|
|
27543
|
+
function projectDirForCwd(cwd) {
|
|
27544
|
+
return path60.join(
|
|
27545
|
+
os4.homedir(),
|
|
27546
|
+
".claude",
|
|
27547
|
+
"projects",
|
|
27548
|
+
cwd.replace(/[^a-zA-Z0-9]/g, "-")
|
|
27549
|
+
);
|
|
27754
27550
|
}
|
|
27755
|
-
|
|
27756
|
-
|
|
27757
|
-
|
|
27758
|
-
|
|
27759
|
-
|
|
27760
|
-
|
|
27761
|
-
|
|
27762
|
-
|
|
27763
|
-
|
|
27764
|
-
|
|
27765
|
-
|
|
27766
|
-
}),
|
|
27767
|
-
status: "waiting"
|
|
27768
|
-
};
|
|
27769
|
-
if (initialPrompt) return freshClaudePlan(session, initialPrompt, cwd);
|
|
27770
|
-
if (cwd && session.harness !== "pi")
|
|
27771
|
-
return freshClaudePlan(session, void 0, cwd);
|
|
27551
|
+
function transcriptPathFor(cwd, claudeSessionId) {
|
|
27552
|
+
return path60.join(projectDirForCwd(cwd), `${claudeSessionId}.jsonl`);
|
|
27553
|
+
}
|
|
27554
|
+
function findTranscriptPathSync(cwd, claudeSessionId) {
|
|
27555
|
+
const direct = transcriptPathFor(cwd, claudeSessionId);
|
|
27556
|
+
if (fs36.existsSync(direct)) return direct;
|
|
27557
|
+
const dir = projectDirForCwd(cwd);
|
|
27558
|
+
let files;
|
|
27559
|
+
try {
|
|
27560
|
+
files = fs36.readdirSync(dir);
|
|
27561
|
+
} catch {
|
|
27772
27562
|
return null;
|
|
27773
27563
|
}
|
|
27774
|
-
|
|
27775
|
-
|
|
27776
|
-
|
|
27777
|
-
|
|
27778
|
-
assistResumeArgs({ assistArgs, claudeSessionId }),
|
|
27779
|
-
cwd,
|
|
27780
|
-
session.id,
|
|
27781
|
-
idle ? { ASSIST_RESUME_IDLE: "1" } : void 0
|
|
27782
|
-
),
|
|
27783
|
-
status: idle ? "waiting" : "running"
|
|
27784
|
-
};
|
|
27564
|
+
for (const file of files) {
|
|
27565
|
+
if (!file.endsWith(".jsonl")) continue;
|
|
27566
|
+
if (headContainsSessionId(path60.join(dir, file), claudeSessionId))
|
|
27567
|
+
return path60.join(dir, file);
|
|
27785
27568
|
}
|
|
27786
27569
|
return null;
|
|
27787
27570
|
}
|
|
27788
|
-
function
|
|
27789
|
-
|
|
27790
|
-
|
|
27791
|
-
|
|
27792
|
-
|
|
27793
|
-
|
|
27794
|
-
|
|
27795
|
-
|
|
27796
|
-
|
|
27797
|
-
|
|
27798
|
-
|
|
27799
|
-
},
|
|
27800
|
-
status: prompt ? "running" : "waiting"
|
|
27801
|
-
};
|
|
27802
|
-
}
|
|
27803
|
-
|
|
27804
|
-
// src/commands/sessions/daemon/restartSession.ts
|
|
27805
|
-
function restartSession(session, clients, onStatusChange) {
|
|
27806
|
-
const plan2 = respawnPlan(session);
|
|
27807
|
-
if (!plan2) return false;
|
|
27808
|
-
const via = session.claudeSessionId ? `resume ${session.claudeSessionId}` : "fresh";
|
|
27809
|
-
const respawn = () => {
|
|
27810
|
-
respawnSession(session, plan2.spawn, plan2.status, clients, onStatusChange);
|
|
27811
|
-
daemonLog(`session ${session.id} restarted (${via}): ${session.name}`);
|
|
27812
|
-
};
|
|
27813
|
-
const pty2 = session.pty;
|
|
27814
|
-
const alive = session.status === "running" || session.status === "waiting";
|
|
27815
|
-
if (pty2 && alive) {
|
|
27816
|
-
daemonLog(
|
|
27817
|
-
`session ${session.id} restart requested: killing running process, will resume on exit`
|
|
27818
|
-
);
|
|
27819
|
-
session.pendingRestart = respawn;
|
|
27820
|
-
killPtyTree(pty2);
|
|
27821
|
-
} else {
|
|
27822
|
-
respawn();
|
|
27823
|
-
}
|
|
27824
|
-
return true;
|
|
27825
|
-
}
|
|
27826
|
-
|
|
27827
|
-
// src/commands/sessions/daemon/restartManagedSession.ts
|
|
27828
|
-
function restartManagedSession(sessions, id, clients, onStatusChange) {
|
|
27829
|
-
const s = sessions.get(id);
|
|
27830
|
-
if (!s) {
|
|
27831
|
-
daemonLog(
|
|
27832
|
-
`restart requested for unknown session id=${id} (no live session; ignoring)`
|
|
27833
|
-
);
|
|
27834
|
-
return {
|
|
27835
|
-
ok: false,
|
|
27836
|
-
reason: `Session ${id} is no longer tracked and can't be restarted.`
|
|
27837
|
-
};
|
|
27838
|
-
}
|
|
27839
|
-
daemonLog(`restart requested: id=${id} status=${s.status} (${s.name})`);
|
|
27840
|
-
if (!restartSession(s, clients, onStatusChange)) {
|
|
27841
|
-
daemonLog(`restart for session ${id} did nothing: no respawn plan`);
|
|
27842
|
-
return {
|
|
27843
|
-
ok: false,
|
|
27844
|
-
reason: `Session ${s.name} can't be restarted (no resumable command).`
|
|
27845
|
-
};
|
|
27571
|
+
function headContainsSessionId(filePath, claudeSessionId) {
|
|
27572
|
+
let fd;
|
|
27573
|
+
try {
|
|
27574
|
+
fd = fs36.openSync(filePath, "r");
|
|
27575
|
+
const buf = Buffer.alloc(16384);
|
|
27576
|
+
const bytesRead = fs36.readSync(fd, buf, 0, buf.length, 0);
|
|
27577
|
+
return buf.toString("utf8", 0, bytesRead).includes(claudeSessionId);
|
|
27578
|
+
} catch {
|
|
27579
|
+
return false;
|
|
27580
|
+
} finally {
|
|
27581
|
+
if (fd !== void 0) fs36.closeSync(fd);
|
|
27846
27582
|
}
|
|
27847
|
-
return { ok: true };
|
|
27848
|
-
}
|
|
27849
|
-
|
|
27850
|
-
// src/commands/sessions/daemon/restoreBase.ts
|
|
27851
|
-
function restoreBase(id, persisted) {
|
|
27852
|
-
return {
|
|
27853
|
-
id,
|
|
27854
|
-
name: persisted.name,
|
|
27855
|
-
title: persisted.title,
|
|
27856
|
-
subtitle: persisted.subtitle,
|
|
27857
|
-
commandType: persisted.commandType,
|
|
27858
|
-
harness: persisted.harness,
|
|
27859
|
-
scrollback: "",
|
|
27860
|
-
cwd: persisted.cwd,
|
|
27861
|
-
assistArgs: persisted.assistArgs,
|
|
27862
|
-
initialPrompt: persisted.initialPrompt,
|
|
27863
|
-
starred: persisted.starred
|
|
27864
|
-
};
|
|
27865
|
-
}
|
|
27866
|
-
|
|
27867
|
-
// src/commands/sessions/daemon/errorSession.ts
|
|
27868
|
-
function errorSession(id, persisted, error) {
|
|
27869
|
-
return {
|
|
27870
|
-
...restoreBase(id, persisted),
|
|
27871
|
-
status: "error",
|
|
27872
|
-
startedAt: persisted.startedAt,
|
|
27873
|
-
runningMs: persisted.runningMs ?? 0,
|
|
27874
|
-
runningSince: null,
|
|
27875
|
-
pty: null,
|
|
27876
|
-
runName: persisted.runName,
|
|
27877
|
-
runArgs: persisted.runArgs,
|
|
27878
|
-
error,
|
|
27879
|
-
restored: false
|
|
27880
|
-
};
|
|
27881
27583
|
}
|
|
27882
27584
|
|
|
27883
27585
|
// src/commands/sessions/shared/normalizeUser.ts
|
|
@@ -27994,94 +27696,579 @@ function parseTailEntries(raw) {
|
|
|
27994
27696
|
}
|
|
27995
27697
|
return entries;
|
|
27996
27698
|
}
|
|
27997
|
-
function dropPartialFirstLine(raw, sliced) {
|
|
27998
|
-
if (!sliced) return raw;
|
|
27999
|
-
const newline = raw.indexOf("\n");
|
|
28000
|
-
return newline === -1 ? "" : raw.slice(newline + 1);
|
|
27699
|
+
function dropPartialFirstLine(raw, sliced) {
|
|
27700
|
+
if (!sliced) return raw;
|
|
27701
|
+
const newline = raw.indexOf("\n");
|
|
27702
|
+
return newline === -1 ? "" : raw.slice(newline + 1);
|
|
27703
|
+
}
|
|
27704
|
+
async function readTranscriptTail(filePath, maxBytes = DEFAULT_MAX_BYTES) {
|
|
27705
|
+
let handle;
|
|
27706
|
+
try {
|
|
27707
|
+
handle = await fs37.promises.open(filePath, "r");
|
|
27708
|
+
const { size } = await handle.stat();
|
|
27709
|
+
const start3 = Math.max(0, size - maxBytes);
|
|
27710
|
+
const length = size - start3;
|
|
27711
|
+
if (length === 0) return [];
|
|
27712
|
+
const buf = Buffer.alloc(length);
|
|
27713
|
+
await handle.read(buf, 0, length, start3);
|
|
27714
|
+
return parseTailEntries(
|
|
27715
|
+
dropPartialFirstLine(buf.toString("utf8"), start3 > 0)
|
|
27716
|
+
);
|
|
27717
|
+
} catch {
|
|
27718
|
+
return [];
|
|
27719
|
+
} finally {
|
|
27720
|
+
await handle?.close();
|
|
27721
|
+
}
|
|
27722
|
+
}
|
|
27723
|
+
function readTranscriptTailSync(filePath, maxBytes = DEFAULT_MAX_BYTES) {
|
|
27724
|
+
let fd;
|
|
27725
|
+
try {
|
|
27726
|
+
fd = fs37.openSync(filePath, "r");
|
|
27727
|
+
const { size } = fs37.fstatSync(fd);
|
|
27728
|
+
const start3 = Math.max(0, size - maxBytes);
|
|
27729
|
+
const length = size - start3;
|
|
27730
|
+
if (length === 0) return [];
|
|
27731
|
+
const buf = Buffer.alloc(length);
|
|
27732
|
+
fs37.readSync(fd, buf, 0, length, start3);
|
|
27733
|
+
return parseTailEntries(
|
|
27734
|
+
dropPartialFirstLine(buf.toString("utf8"), start3 > 0)
|
|
27735
|
+
);
|
|
27736
|
+
} catch {
|
|
27737
|
+
return [];
|
|
27738
|
+
} finally {
|
|
27739
|
+
if (fd !== void 0) fs37.closeSync(fd);
|
|
27740
|
+
}
|
|
27741
|
+
}
|
|
27742
|
+
|
|
27743
|
+
// src/commands/sessions/daemon/reconcileTranscriptStatus.ts
|
|
27744
|
+
async function resolveTranscriptPath(session) {
|
|
27745
|
+
if (session.transcriptPath) return session.transcriptPath;
|
|
27746
|
+
if (!session.claudeSessionId) return null;
|
|
27747
|
+
const direct = session.cwd ? findTranscriptPathSync(session.cwd, session.claudeSessionId) : null;
|
|
27748
|
+
const filePath = direct ?? await findSessionJsonlPath(session.claudeSessionId);
|
|
27749
|
+
if (filePath) session.transcriptPath = filePath;
|
|
27750
|
+
return filePath;
|
|
27751
|
+
}
|
|
27752
|
+
async function reconcileTranscriptStatus(session, onStatusChange) {
|
|
27753
|
+
if (session.status === "done" || session.status === "error" || session.status === "stopped")
|
|
27754
|
+
return;
|
|
27755
|
+
const filePath = await resolveTranscriptPath(session);
|
|
27756
|
+
if (!filePath) return;
|
|
27757
|
+
const entries = await readTranscriptTail(filePath);
|
|
27758
|
+
const derived = deriveTranscriptStatus(entries, {
|
|
27759
|
+
permissionActive: session.permissionActive
|
|
27760
|
+
});
|
|
27761
|
+
if (!derived) return;
|
|
27762
|
+
if (derived === "running") session.permissionActive = false;
|
|
27763
|
+
if (derived === session.status) return;
|
|
27764
|
+
daemonLog(
|
|
27765
|
+
`session ${session.id} transcript reconcile: ${session.status} -> ${derived}`
|
|
27766
|
+
);
|
|
27767
|
+
onStatusChange(session, derived);
|
|
27768
|
+
}
|
|
27769
|
+
|
|
27770
|
+
// src/commands/sessions/daemon/watchTranscript.ts
|
|
27771
|
+
var DEBOUNCE_MS3 = 100;
|
|
27772
|
+
function watchTranscript(session, onStatusChange) {
|
|
27773
|
+
if (!session.cwd || !session.claudeSessionId) return;
|
|
27774
|
+
if (session.watchedTranscriptId === session.claudeSessionId) return;
|
|
27775
|
+
const dir = projectDirForCwd(session.cwd);
|
|
27776
|
+
if (!existsSync67(dir)) return;
|
|
27777
|
+
if (session.transcriptWatcher) {
|
|
27778
|
+
session.transcriptWatcher.close();
|
|
27779
|
+
session.transcriptWatcher = void 0;
|
|
27780
|
+
}
|
|
27781
|
+
session.transcriptPath = void 0;
|
|
27782
|
+
const fileName = `${session.claudeSessionId}.jsonl`;
|
|
27783
|
+
let timer = null;
|
|
27784
|
+
const run4 = () => {
|
|
27785
|
+
timer = null;
|
|
27786
|
+
void reconcileTranscriptStatus(session, onStatusChange);
|
|
27787
|
+
};
|
|
27788
|
+
try {
|
|
27789
|
+
session.transcriptWatcher = watch3(dir, (_event, changed) => {
|
|
27790
|
+
if (changed && changed !== fileName) return;
|
|
27791
|
+
if (timer) clearTimeout(timer);
|
|
27792
|
+
timer = setTimeout(run4, DEBOUNCE_MS3);
|
|
27793
|
+
});
|
|
27794
|
+
} catch (error) {
|
|
27795
|
+
daemonLog(
|
|
27796
|
+
`session ${session.id} transcript watch failed: ${error instanceof Error ? error.message : String(error)}`
|
|
27797
|
+
);
|
|
27798
|
+
return;
|
|
27799
|
+
}
|
|
27800
|
+
session.watchedTranscriptId = session.claudeSessionId;
|
|
27801
|
+
daemonLog(`session ${session.id} watching transcript ${fileName}`);
|
|
27802
|
+
run4();
|
|
27803
|
+
}
|
|
27804
|
+
|
|
27805
|
+
// src/commands/sessions/daemon/wireSessionWatchers.ts
|
|
27806
|
+
function wireSessionWatchers(session, notify2, onStatusChange) {
|
|
27807
|
+
const wireTranscript = (s) => watchTranscript(s, onStatusChange);
|
|
27808
|
+
watchActivity(session, notify2, wireTranscript);
|
|
27809
|
+
wireTranscript(session);
|
|
27810
|
+
}
|
|
27811
|
+
|
|
27812
|
+
// src/commands/sessions/daemon/registerSpawnedSession.ts
|
|
27813
|
+
function registerSpawnedSession(session, sessions, clients, onStatusChange, notify2) {
|
|
27814
|
+
sessions.set(session.id, session);
|
|
27815
|
+
wirePtyEvents(session, clients, onStatusChange);
|
|
27816
|
+
notify2();
|
|
27817
|
+
wireSessionWatchers(session, notify2, onStatusChange);
|
|
27818
|
+
logSpawnedSession(session);
|
|
27819
|
+
return session.id;
|
|
27820
|
+
}
|
|
27821
|
+
|
|
27822
|
+
// src/commands/sessions/daemon/applySetStatus.ts
|
|
27823
|
+
function applySetStatus(sessions, id, status3, source, onStatusChange) {
|
|
27824
|
+
const session = sessions.get(id);
|
|
27825
|
+
if (!session) {
|
|
27826
|
+
daemonLog(
|
|
27827
|
+
`set-status for unknown session id=${id} status=${status3} (no live session; ignoring)`
|
|
27828
|
+
);
|
|
27829
|
+
return;
|
|
27830
|
+
}
|
|
27831
|
+
if (session.status === "stopped") {
|
|
27832
|
+
daemonLog(
|
|
27833
|
+
`set-status for stopped session id=${id} status=${status3} (held pending durable close; ignoring)`
|
|
27834
|
+
);
|
|
27835
|
+
return;
|
|
27836
|
+
}
|
|
27837
|
+
if (source === "permission") session.permissionActive = true;
|
|
27838
|
+
if (status3 === "running") session.permissionActive = false;
|
|
27839
|
+
onStatusChange(session, status3);
|
|
27840
|
+
}
|
|
27841
|
+
|
|
27842
|
+
// src/shared/db/recordPhaseCycleContext.ts
|
|
27843
|
+
import { sql as sql21 } from "drizzle-orm";
|
|
27844
|
+
async function recordPhaseCycleContext(db, itemId2, phaseIdx, window, resetsAt, pct) {
|
|
27845
|
+
if (pct <= 0) return;
|
|
27846
|
+
await db.insert(phaseCycleContext).values({ itemId: itemId2, phaseIdx, window, resetsAt, peakContextPct: pct }).onConflictDoUpdate({
|
|
27847
|
+
target: [
|
|
27848
|
+
phaseCycleContext.itemId,
|
|
27849
|
+
phaseCycleContext.phaseIdx,
|
|
27850
|
+
phaseCycleContext.window,
|
|
27851
|
+
phaseCycleContext.resetsAt
|
|
27852
|
+
],
|
|
27853
|
+
set: {
|
|
27854
|
+
peakContextPct: sql21`GREATEST(${phaseCycleContext.peakContextPct}, ${pct})`
|
|
27855
|
+
}
|
|
27856
|
+
});
|
|
27857
|
+
}
|
|
27858
|
+
|
|
27859
|
+
// src/shared/db/recordPhasePeakContext.ts
|
|
27860
|
+
import { sql as sql22 } from "drizzle-orm";
|
|
27861
|
+
async function recordPhasePeakContext(db, itemId2, phaseIdx, pct) {
|
|
27862
|
+
await db.insert(phaseUsage).values({ itemId: itemId2, phaseIdx, peakContextPct: pct }).onConflictDoUpdate({
|
|
27863
|
+
target: [phaseUsage.itemId, phaseUsage.phaseIdx],
|
|
27864
|
+
set: {
|
|
27865
|
+
peakContextPct: sql22`GREATEST(${phaseUsage.peakContextPct}, ${pct})`
|
|
27866
|
+
}
|
|
27867
|
+
});
|
|
27868
|
+
}
|
|
27869
|
+
|
|
27870
|
+
// src/shared/db/recordPhaseTranscriptUsage.ts
|
|
27871
|
+
import { sql as sql23 } from "drizzle-orm";
|
|
27872
|
+
async function recordPhaseTranscriptUsage(db, itemId2, phaseIdx, responses) {
|
|
27873
|
+
const byId = /* @__PURE__ */ new Map();
|
|
27874
|
+
for (const r of responses) byId.set(r.messageId, r);
|
|
27875
|
+
const unique = [...byId.values()];
|
|
27876
|
+
if (unique.length === 0) return { tokensUp: 0, tokensDown: 0 };
|
|
27877
|
+
return db.transaction(async (tx) => {
|
|
27878
|
+
const inserted = await tx.insert(phaseUsageMessages).values(unique.map((r) => ({ itemId: itemId2, phaseIdx, messageId: r.messageId }))).onConflictDoNothing().returning({ messageId: phaseUsageMessages.messageId });
|
|
27879
|
+
let tokensUp = 0;
|
|
27880
|
+
let tokensDown = 0;
|
|
27881
|
+
for (const { messageId } of inserted) {
|
|
27882
|
+
const r = byId.get(messageId);
|
|
27883
|
+
if (!r) continue;
|
|
27884
|
+
tokensUp += r.outputTokens;
|
|
27885
|
+
tokensDown += r.inputTokens;
|
|
27886
|
+
}
|
|
27887
|
+
if (tokensUp <= 0 && tokensDown <= 0) return { tokensUp: 0, tokensDown: 0 };
|
|
27888
|
+
await tx.insert(phaseUsage).values({ itemId: itemId2, phaseIdx, tokensUp, tokensDown }).onConflictDoUpdate({
|
|
27889
|
+
target: [phaseUsage.itemId, phaseUsage.phaseIdx],
|
|
27890
|
+
set: {
|
|
27891
|
+
tokensUp: sql23`${phaseUsage.tokensUp} + ${tokensUp}`,
|
|
27892
|
+
tokensDown: sql23`${phaseUsage.tokensDown} + ${tokensDown}`
|
|
27893
|
+
}
|
|
27894
|
+
});
|
|
27895
|
+
return { tokensUp, tokensDown };
|
|
27896
|
+
});
|
|
27897
|
+
}
|
|
27898
|
+
|
|
27899
|
+
// src/shared/db/recordWindowTokens.ts
|
|
27900
|
+
import { and as and23, eq as eq43, sql as sql24 } from "drizzle-orm";
|
|
27901
|
+
async function recordWindowTokens(db, window, resetsAt, tokensUp, tokensDown) {
|
|
27902
|
+
if (tokensUp <= 0 && tokensDown <= 0) return;
|
|
27903
|
+
await db.update(usagePeaks).set({
|
|
27904
|
+
tokensUp: sql24`${usagePeaks.tokensUp} + ${tokensUp}`,
|
|
27905
|
+
tokensDown: sql24`${usagePeaks.tokensDown} + ${tokensDown}`
|
|
27906
|
+
}).where(
|
|
27907
|
+
and23(
|
|
27908
|
+
eq43(usagePeaks.window, window),
|
|
27909
|
+
eq43(usagePeaks.resetsAt, resetsAt),
|
|
27910
|
+
eq43(usagePeaks.segment, 0)
|
|
27911
|
+
)
|
|
27912
|
+
);
|
|
27913
|
+
}
|
|
27914
|
+
|
|
27915
|
+
// src/commands/sessions/shared/transcriptUsage.ts
|
|
27916
|
+
import * as fs38 from "fs";
|
|
27917
|
+
function transcriptUsage(lines) {
|
|
27918
|
+
const byId = /* @__PURE__ */ new Map();
|
|
27919
|
+
for (const line of lines) {
|
|
27920
|
+
if (!line.trim()) continue;
|
|
27921
|
+
let entry;
|
|
27922
|
+
try {
|
|
27923
|
+
entry = JSON.parse(line);
|
|
27924
|
+
} catch {
|
|
27925
|
+
continue;
|
|
27926
|
+
}
|
|
27927
|
+
if (entry.type !== "assistant") continue;
|
|
27928
|
+
const id = entry.message?.id;
|
|
27929
|
+
const usage = entry.message?.usage;
|
|
27930
|
+
if (!id || !usage) continue;
|
|
27931
|
+
byId.set(id, {
|
|
27932
|
+
messageId: id,
|
|
27933
|
+
inputTokens: usage.input_tokens ?? 0,
|
|
27934
|
+
outputTokens: usage.output_tokens ?? 0
|
|
27935
|
+
});
|
|
27936
|
+
}
|
|
27937
|
+
return [...byId.values()];
|
|
27938
|
+
}
|
|
27939
|
+
async function readTranscriptUsage(transcriptPath) {
|
|
27940
|
+
const content = await fs38.promises.readFile(transcriptPath, "utf8");
|
|
27941
|
+
return transcriptUsage(content.split("\n"));
|
|
27942
|
+
}
|
|
27943
|
+
|
|
27944
|
+
// src/commands/sessions/daemon/persistPhaseTokens.ts
|
|
27945
|
+
async function persistPhaseTokens(itemId2, phaseIdx, transcriptPath, usedPct, windows) {
|
|
27946
|
+
try {
|
|
27947
|
+
if (!process.env.ASSIST_DATABASE_URL && !loadConfig().database.url) return;
|
|
27948
|
+
const db = await getDb();
|
|
27949
|
+
if (usedPct !== void 0) {
|
|
27950
|
+
await recordPhasePeakContext(db, itemId2, phaseIdx, usedPct);
|
|
27951
|
+
for (const { window, resetsAt } of windows) {
|
|
27952
|
+
await recordPhaseCycleContext(
|
|
27953
|
+
db,
|
|
27954
|
+
itemId2,
|
|
27955
|
+
phaseIdx,
|
|
27956
|
+
window,
|
|
27957
|
+
resetsAt,
|
|
27958
|
+
usedPct
|
|
27959
|
+
);
|
|
27960
|
+
}
|
|
27961
|
+
}
|
|
27962
|
+
const responses = await readTranscriptUsage(transcriptPath);
|
|
27963
|
+
if (responses.length === 0) return;
|
|
27964
|
+
const { tokensUp, tokensDown } = await recordPhaseTranscriptUsage(
|
|
27965
|
+
db,
|
|
27966
|
+
itemId2,
|
|
27967
|
+
phaseIdx,
|
|
27968
|
+
responses
|
|
27969
|
+
);
|
|
27970
|
+
for (const { window, resetsAt } of windows) {
|
|
27971
|
+
await recordWindowTokens(db, window, resetsAt, tokensUp, tokensDown);
|
|
27972
|
+
}
|
|
27973
|
+
} catch (error) {
|
|
27974
|
+
daemonLog(`phase-tokens persist failed: ${String(error)}`);
|
|
27975
|
+
}
|
|
27976
|
+
}
|
|
27977
|
+
|
|
27978
|
+
// src/commands/sessions/daemon/recordSessionUsage.ts
|
|
27979
|
+
function recordSessionUsage(sessions, claudeSessionId, transcriptPath, usedPct, windows) {
|
|
27980
|
+
for (const session of sessions) {
|
|
27981
|
+
if (session.claudeSessionId !== claudeSessionId) continue;
|
|
27982
|
+
session.usedPct = usedPct;
|
|
27983
|
+
const phase = sessionBacklogPhase(session);
|
|
27984
|
+
if (phase && transcriptPath)
|
|
27985
|
+
void persistPhaseTokens(
|
|
27986
|
+
phase.itemId,
|
|
27987
|
+
phase.phaseIdx,
|
|
27988
|
+
transcriptPath,
|
|
27989
|
+
usedPct,
|
|
27990
|
+
windows
|
|
27991
|
+
);
|
|
27992
|
+
void flushPhaseActiveMs(session);
|
|
27993
|
+
return true;
|
|
27994
|
+
}
|
|
27995
|
+
return false;
|
|
27996
|
+
}
|
|
27997
|
+
|
|
27998
|
+
// src/commands/sessions/daemon/applyUsageRecord.ts
|
|
27999
|
+
function applyUsageRecord(sessions, windows, notify2, claudeSessionId, transcriptPath, usedPct) {
|
|
28000
|
+
if (recordSessionUsage(
|
|
28001
|
+
sessions,
|
|
28002
|
+
claudeSessionId,
|
|
28003
|
+
transcriptPath,
|
|
28004
|
+
usedPct,
|
|
28005
|
+
windows
|
|
28006
|
+
))
|
|
28007
|
+
notify2();
|
|
28008
|
+
}
|
|
28009
|
+
|
|
28010
|
+
// src/commands/sessions/daemon/shouldAutoRun.ts
|
|
28011
|
+
function shouldAutoRun(session, status3 = session.status) {
|
|
28012
|
+
if (!session.autoRun) return { run: false, reason: null };
|
|
28013
|
+
if (status3 !== "done")
|
|
28014
|
+
return {
|
|
28015
|
+
run: false,
|
|
28016
|
+
reason: `session status is "${status3}", not "done"`
|
|
28017
|
+
};
|
|
28018
|
+
if (session.commandType !== "assist")
|
|
28019
|
+
return {
|
|
28020
|
+
run: false,
|
|
28021
|
+
reason: `command type is "${session.commandType}", not "assist"`
|
|
28022
|
+
};
|
|
28023
|
+
const cmd = session.assistArgs?.[0];
|
|
28024
|
+
if (cmd !== "draft" && cmd !== "bug" && cmd !== "refine")
|
|
28025
|
+
return {
|
|
28026
|
+
run: false,
|
|
28027
|
+
reason: `command "${cmd ?? "(none)"}" is not draft/bug/refine`
|
|
28028
|
+
};
|
|
28029
|
+
const itemId2 = session.activity?.itemId;
|
|
28030
|
+
if (itemId2 == null)
|
|
28031
|
+
return {
|
|
28032
|
+
run: false,
|
|
28033
|
+
reason: "no itemId in activity (draft did not create an item)"
|
|
28034
|
+
};
|
|
28035
|
+
return { run: true, itemId: itemId2 };
|
|
28036
|
+
}
|
|
28037
|
+
|
|
28038
|
+
// src/commands/sessions/daemon/endsStreamLife.ts
|
|
28039
|
+
function endsStreamLife(session, status3, sharedWithOtherAgents) {
|
|
28040
|
+
if (status3 !== "done" || !session.worktree) return false;
|
|
28041
|
+
if (shouldAutoRun(session, status3).run) return false;
|
|
28042
|
+
if (!sharedWithOtherAgents(session)) return true;
|
|
28043
|
+
daemonLog(
|
|
28044
|
+
`session ${session.id} done in ${session.worktree.path}: workspace kept, other agents are still working there`
|
|
28045
|
+
);
|
|
28046
|
+
return false;
|
|
28047
|
+
}
|
|
28048
|
+
|
|
28049
|
+
// src/commands/sessions/daemon/shouldAutoDismiss.ts
|
|
28050
|
+
function shouldAutoDismiss(session, exitCode) {
|
|
28051
|
+
if (session.status !== "done" || exitCode !== 0) {
|
|
28052
|
+
return false;
|
|
28053
|
+
}
|
|
28054
|
+
if (session.reviewStarted) {
|
|
28055
|
+
return session.autoAdvance === true;
|
|
28056
|
+
}
|
|
28057
|
+
const args = session.assistArgs;
|
|
28058
|
+
if (args === void 0) return false;
|
|
28059
|
+
if (args[0] === "update") return true;
|
|
28060
|
+
return args.includes("--once") && args[0] !== "next";
|
|
28061
|
+
}
|
|
28062
|
+
|
|
28063
|
+
// src/commands/sessions/daemon/applyStatusChange.ts
|
|
28064
|
+
function applyStatusChange(session, status3, exitCode, dismiss, notify2, reuseForRun, sharedWithOtherAgents = () => false) {
|
|
28065
|
+
if (session.status === status3) return;
|
|
28066
|
+
const deps2 = { dismiss, notify: notify2, reuseForRun };
|
|
28067
|
+
if (endsStreamLife(session, status3, sharedWithOtherAgents)) {
|
|
28068
|
+
session.closing = true;
|
|
28069
|
+
daemonLog(
|
|
28070
|
+
`session ${session.id} closing: checking durability of ${session.worktree?.path} before reap`
|
|
28071
|
+
);
|
|
28072
|
+
notify2();
|
|
28073
|
+
void resolveCloseDurability(
|
|
28074
|
+
session,
|
|
28075
|
+
() => finishStatusChange(session, "done", exitCode, deps2),
|
|
28076
|
+
notify2
|
|
28077
|
+
);
|
|
28078
|
+
return;
|
|
28079
|
+
}
|
|
28080
|
+
finishStatusChange(session, status3, exitCode, deps2);
|
|
28081
|
+
}
|
|
28082
|
+
function finishStatusChange(session, status3, exitCode, deps2) {
|
|
28083
|
+
daemonLog(`session ${session.id} status: ${session.status} -> ${status3}`);
|
|
28084
|
+
void flushPhaseActiveMs(session);
|
|
28085
|
+
setStatus2(session, status3);
|
|
28086
|
+
if (status3 !== "stopped") session.undurable = void 0;
|
|
28087
|
+
const autoRun = shouldAutoRun(session);
|
|
28088
|
+
if (autoRun.run) {
|
|
28089
|
+
deps2.reuseForRun(session, autoRun.itemId);
|
|
28090
|
+
deps2.notify();
|
|
28091
|
+
return;
|
|
28092
|
+
}
|
|
28093
|
+
if (status3 === "done" && autoRun.reason != null) {
|
|
28094
|
+
daemonLog(
|
|
28095
|
+
`session ${session.id} auto-run enabled but skipped (exit ${exitCode ?? "none"}): ${autoRun.reason}`
|
|
28096
|
+
);
|
|
28097
|
+
}
|
|
28098
|
+
if (shouldAutoDismiss(session, exitCode)) deps2.dismiss(session.id);
|
|
28099
|
+
else deps2.notify();
|
|
28100
|
+
}
|
|
28101
|
+
|
|
28102
|
+
// src/commands/sessions/daemon/makeStatusChangeHandler.ts
|
|
28103
|
+
function makeStatusChangeHandler(sessions, dismiss, notify2, reuseForRun) {
|
|
28104
|
+
return (s, status3, exitCode) => applyStatusChange(
|
|
28105
|
+
s,
|
|
28106
|
+
status3,
|
|
28107
|
+
exitCode,
|
|
28108
|
+
dismiss,
|
|
28109
|
+
notify2,
|
|
28110
|
+
reuseForRun,
|
|
28111
|
+
(session) => otherTreeHolders(sessions, session).length > 0
|
|
28112
|
+
);
|
|
28113
|
+
}
|
|
28114
|
+
|
|
28115
|
+
// src/commands/sessions/daemon/respawnSession.ts
|
|
28116
|
+
function respawnSession(session, respawn, status3, clients, onStatusChange) {
|
|
28117
|
+
session.gitWatcher?.close();
|
|
28118
|
+
session.gitWatcher = void 0;
|
|
28119
|
+
session.undurable = void 0;
|
|
28120
|
+
session.pendingDismiss = void 0;
|
|
28121
|
+
session.closing = void 0;
|
|
28122
|
+
session.scrollback = "";
|
|
28123
|
+
session.startedAt = Date.now();
|
|
28124
|
+
session.runningMs = 0;
|
|
28125
|
+
session.runningSince = null;
|
|
28126
|
+
setStatus2(session, status3);
|
|
28127
|
+
session.restored = void 0;
|
|
28128
|
+
session.pty = respawn();
|
|
28129
|
+
if (session.cols && session.rows)
|
|
28130
|
+
try {
|
|
28131
|
+
session.pty?.resize(session.cols, session.rows);
|
|
28132
|
+
} catch {
|
|
28133
|
+
}
|
|
28134
|
+
broadcast(clients, { type: "clear", sessionId: session.id });
|
|
28135
|
+
wirePtyEvents(session, clients, onStatusChange);
|
|
28136
|
+
}
|
|
28137
|
+
|
|
28138
|
+
// src/commands/sessions/daemon/respawnPlan.ts
|
|
28139
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
28140
|
+
|
|
28141
|
+
// src/commands/sessions/daemon/assistResumeArgs.ts
|
|
28142
|
+
function assistResumeArgs(session) {
|
|
28143
|
+
const args = ["assist", ...session.assistArgs];
|
|
28144
|
+
return session.claudeSessionId ? [...args, "--resume-session", session.claudeSessionId] : args;
|
|
28145
|
+
}
|
|
28146
|
+
|
|
28147
|
+
// src/commands/sessions/daemon/respawnPlan.ts
|
|
28148
|
+
function respawnPlan(session) {
|
|
28149
|
+
const { commandType, claudeSessionId, cwd, assistArgs, initialPrompt } = session;
|
|
28150
|
+
if (commandType === "claude") {
|
|
28151
|
+
if (claudeSessionId)
|
|
28152
|
+
return {
|
|
28153
|
+
spawn: () => spawnClaude2({
|
|
28154
|
+
resumeSessionId: claudeSessionId,
|
|
28155
|
+
cwd,
|
|
28156
|
+
sessionId: session.id
|
|
28157
|
+
}),
|
|
28158
|
+
status: "waiting"
|
|
28159
|
+
};
|
|
28160
|
+
if (initialPrompt) return freshClaudePlan(session, initialPrompt, cwd);
|
|
28161
|
+
if (cwd && session.harness !== "pi")
|
|
28162
|
+
return freshClaudePlan(session, void 0, cwd);
|
|
28163
|
+
return null;
|
|
28164
|
+
}
|
|
28165
|
+
if (commandType === "assist" && assistArgs) {
|
|
28166
|
+
const idle = session.status === "waiting";
|
|
28167
|
+
return {
|
|
28168
|
+
spawn: () => spawnPty(
|
|
28169
|
+
assistResumeArgs({ assistArgs, claudeSessionId }),
|
|
28170
|
+
cwd,
|
|
28171
|
+
session.id,
|
|
28172
|
+
idle ? { ASSIST_RESUME_IDLE: "1" } : void 0
|
|
28173
|
+
),
|
|
28174
|
+
status: idle ? "waiting" : "running"
|
|
28175
|
+
};
|
|
28176
|
+
}
|
|
28177
|
+
return null;
|
|
28178
|
+
}
|
|
28179
|
+
function freshClaudePlan(session, prompt, cwd) {
|
|
28180
|
+
const claudeSessionId = randomUUID7();
|
|
28181
|
+
return {
|
|
28182
|
+
spawn: () => {
|
|
28183
|
+
session.claudeSessionId = claudeSessionId;
|
|
28184
|
+
return spawnClaude2({
|
|
28185
|
+
prompt,
|
|
28186
|
+
cwd,
|
|
28187
|
+
sessionId: session.id,
|
|
28188
|
+
claudeSessionId
|
|
28189
|
+
});
|
|
28190
|
+
},
|
|
28191
|
+
status: prompt ? "running" : "waiting"
|
|
28192
|
+
};
|
|
28001
28193
|
}
|
|
28002
|
-
|
|
28003
|
-
|
|
28004
|
-
|
|
28005
|
-
|
|
28006
|
-
|
|
28007
|
-
|
|
28008
|
-
|
|
28009
|
-
|
|
28010
|
-
|
|
28011
|
-
|
|
28012
|
-
|
|
28013
|
-
|
|
28194
|
+
|
|
28195
|
+
// src/commands/sessions/daemon/restartSession.ts
|
|
28196
|
+
function restartSession(session, clients, onStatusChange) {
|
|
28197
|
+
const plan2 = respawnPlan(session);
|
|
28198
|
+
if (!plan2) return false;
|
|
28199
|
+
const via = session.claudeSessionId ? `resume ${session.claudeSessionId}` : "fresh";
|
|
28200
|
+
const respawn = () => {
|
|
28201
|
+
respawnSession(session, plan2.spawn, plan2.status, clients, onStatusChange);
|
|
28202
|
+
daemonLog(`session ${session.id} restarted (${via}): ${session.name}`);
|
|
28203
|
+
};
|
|
28204
|
+
const pty2 = session.pty;
|
|
28205
|
+
const alive = session.status === "running" || session.status === "waiting";
|
|
28206
|
+
if (pty2 && alive) {
|
|
28207
|
+
daemonLog(
|
|
28208
|
+
`session ${session.id} restart requested: killing running process, will resume on exit`
|
|
28014
28209
|
);
|
|
28015
|
-
|
|
28016
|
-
|
|
28017
|
-
}
|
|
28018
|
-
|
|
28210
|
+
session.pendingRestart = respawn;
|
|
28211
|
+
killPtyTree(pty2);
|
|
28212
|
+
} else {
|
|
28213
|
+
respawn();
|
|
28019
28214
|
}
|
|
28215
|
+
return true;
|
|
28020
28216
|
}
|
|
28021
|
-
|
|
28022
|
-
|
|
28023
|
-
|
|
28024
|
-
|
|
28025
|
-
|
|
28026
|
-
|
|
28027
|
-
|
|
28028
|
-
if (length === 0) return [];
|
|
28029
|
-
const buf = Buffer.alloc(length);
|
|
28030
|
-
fs37.readSync(fd, buf, 0, length, start3);
|
|
28031
|
-
return parseTailEntries(
|
|
28032
|
-
dropPartialFirstLine(buf.toString("utf8"), start3 > 0)
|
|
28217
|
+
|
|
28218
|
+
// src/commands/sessions/daemon/restartManagedSession.ts
|
|
28219
|
+
function restartManagedSession(sessions, id, clients, onStatusChange) {
|
|
28220
|
+
const s = sessions.get(id);
|
|
28221
|
+
if (!s) {
|
|
28222
|
+
daemonLog(
|
|
28223
|
+
`restart requested for unknown session id=${id} (no live session; ignoring)`
|
|
28033
28224
|
);
|
|
28034
|
-
|
|
28035
|
-
|
|
28036
|
-
|
|
28037
|
-
|
|
28225
|
+
return {
|
|
28226
|
+
ok: false,
|
|
28227
|
+
reason: `Session ${id} is no longer tracked and can't be restarted.`
|
|
28228
|
+
};
|
|
28229
|
+
}
|
|
28230
|
+
daemonLog(`restart requested: id=${id} status=${s.status} (${s.name})`);
|
|
28231
|
+
if (!restartSession(s, clients, onStatusChange)) {
|
|
28232
|
+
daemonLog(`restart for session ${id} did nothing: no respawn plan`);
|
|
28233
|
+
return {
|
|
28234
|
+
ok: false,
|
|
28235
|
+
reason: `Session ${s.name} can't be restarted (no resumable command).`
|
|
28236
|
+
};
|
|
28038
28237
|
}
|
|
28238
|
+
return { ok: true };
|
|
28039
28239
|
}
|
|
28040
28240
|
|
|
28041
|
-
// src/commands/sessions/
|
|
28042
|
-
|
|
28043
|
-
|
|
28044
|
-
|
|
28045
|
-
|
|
28046
|
-
|
|
28047
|
-
|
|
28048
|
-
|
|
28049
|
-
|
|
28050
|
-
|
|
28051
|
-
|
|
28052
|
-
|
|
28053
|
-
|
|
28054
|
-
|
|
28055
|
-
}
|
|
28056
|
-
function findTranscriptPathSync(cwd, claudeSessionId) {
|
|
28057
|
-
const direct = transcriptPathFor(cwd, claudeSessionId);
|
|
28058
|
-
if (fs38.existsSync(direct)) return direct;
|
|
28059
|
-
const dir = projectDirForCwd(cwd);
|
|
28060
|
-
let files;
|
|
28061
|
-
try {
|
|
28062
|
-
files = fs38.readdirSync(dir);
|
|
28063
|
-
} catch {
|
|
28064
|
-
return null;
|
|
28065
|
-
}
|
|
28066
|
-
for (const file of files) {
|
|
28067
|
-
if (!file.endsWith(".jsonl")) continue;
|
|
28068
|
-
if (headContainsSessionId(path60.join(dir, file), claudeSessionId))
|
|
28069
|
-
return path60.join(dir, file);
|
|
28070
|
-
}
|
|
28071
|
-
return null;
|
|
28241
|
+
// src/commands/sessions/daemon/restoreBase.ts
|
|
28242
|
+
function restoreBase(id, persisted) {
|
|
28243
|
+
return {
|
|
28244
|
+
id,
|
|
28245
|
+
name: persisted.name,
|
|
28246
|
+
title: persisted.title,
|
|
28247
|
+
subtitle: persisted.subtitle,
|
|
28248
|
+
commandType: persisted.commandType,
|
|
28249
|
+
harness: persisted.harness,
|
|
28250
|
+
scrollback: "",
|
|
28251
|
+
cwd: persisted.cwd,
|
|
28252
|
+
assistArgs: persisted.assistArgs,
|
|
28253
|
+
initialPrompt: persisted.initialPrompt,
|
|
28254
|
+
starred: persisted.starred
|
|
28255
|
+
};
|
|
28072
28256
|
}
|
|
28073
|
-
|
|
28074
|
-
|
|
28075
|
-
|
|
28076
|
-
|
|
28077
|
-
|
|
28078
|
-
|
|
28079
|
-
|
|
28080
|
-
|
|
28081
|
-
|
|
28082
|
-
|
|
28083
|
-
|
|
28084
|
-
|
|
28257
|
+
|
|
28258
|
+
// src/commands/sessions/daemon/errorSession.ts
|
|
28259
|
+
function errorSession(id, persisted, error) {
|
|
28260
|
+
return {
|
|
28261
|
+
...restoreBase(id, persisted),
|
|
28262
|
+
status: "error",
|
|
28263
|
+
startedAt: persisted.startedAt,
|
|
28264
|
+
runningMs: persisted.runningMs ?? 0,
|
|
28265
|
+
runningSince: null,
|
|
28266
|
+
pty: null,
|
|
28267
|
+
runName: persisted.runName,
|
|
28268
|
+
runArgs: persisted.runArgs,
|
|
28269
|
+
error,
|
|
28270
|
+
restored: false
|
|
28271
|
+
};
|
|
28085
28272
|
}
|
|
28086
28273
|
|
|
28087
28274
|
// src/commands/sessions/daemon/deriveRestoreStatus.ts
|
|
@@ -28297,7 +28484,7 @@ function rearmStoppedSessions(sessions, notify2) {
|
|
|
28297
28484
|
}
|
|
28298
28485
|
|
|
28299
28486
|
// src/commands/sessions/daemon/worktree/reconcileWorktreesOnRestore.ts
|
|
28300
|
-
import { existsSync as
|
|
28487
|
+
import { existsSync as existsSync69 } from "fs";
|
|
28301
28488
|
import { basename as basename18 } from "path";
|
|
28302
28489
|
|
|
28303
28490
|
// src/commands/sessions/daemon/worktree/accountedTrees.ts
|
|
@@ -28387,9 +28574,9 @@ function capped(lines) {
|
|
|
28387
28574
|
}
|
|
28388
28575
|
|
|
28389
28576
|
// src/commands/sessions/daemon/worktree/reclaimVanishedWorktrees.ts
|
|
28390
|
-
import { existsSync as
|
|
28577
|
+
import { existsSync as existsSync68 } from "fs";
|
|
28391
28578
|
async function reclaimVanishedWorktrees(clone, paths) {
|
|
28392
|
-
if (!
|
|
28579
|
+
if (!existsSync68(clone)) {
|
|
28393
28580
|
for (const { path: path71 } of paths) forgetWorktree(path71);
|
|
28394
28581
|
daemonLog(
|
|
28395
28582
|
`clone ${clone} is gone; forgot ${paths.length} worktree record(s) it owned`
|
|
@@ -28482,7 +28669,7 @@ async function recoverOrphanedWorktrees(sessions, spawnWith, notify2) {
|
|
|
28482
28669
|
const vanished = /* @__PURE__ */ new Map();
|
|
28483
28670
|
for (const { path: path71, clone } of readWorktreeRegistry()) {
|
|
28484
28671
|
if (accounted.has(path71)) continue;
|
|
28485
|
-
if (!
|
|
28672
|
+
if (!existsSync69(path71)) {
|
|
28486
28673
|
vanished.set(clone, [
|
|
28487
28674
|
...vanished.get(clone) ?? [],
|
|
28488
28675
|
{ path: path71, branch: basename18(path71) }
|
|
@@ -28650,86 +28837,23 @@ function startHeldSession(session, sessions, clients, onStatusChange, notify2) {
|
|
|
28650
28837
|
notify2();
|
|
28651
28838
|
}
|
|
28652
28839
|
|
|
28840
|
+
// src/commands/sessions/daemon/startHeldInTree.ts
|
|
28841
|
+
function startHeldInTree(seeded, sessions, clients, onStatusChange, notify2) {
|
|
28842
|
+
const start3 = (session) => startHeldSession(session, sessions, clients, onStatusChange, notify2);
|
|
28843
|
+
start3(seeded);
|
|
28844
|
+
for (const session of sessions.values())
|
|
28845
|
+
if (session !== seeded && session.pendingStart && session.cwd === seeded.cwd)
|
|
28846
|
+
start3(session);
|
|
28847
|
+
}
|
|
28848
|
+
|
|
28653
28849
|
// src/commands/sessions/daemon/treeSpawnContext.ts
|
|
28654
28850
|
function treeSpawnContext(sessions, spawnWith, notify2, clients, onStatusChange) {
|
|
28655
28851
|
return {
|
|
28656
28852
|
sessions,
|
|
28657
28853
|
spawnWith,
|
|
28658
28854
|
notify: notify2,
|
|
28659
|
-
startHeld: (session) =>
|
|
28660
|
-
};
|
|
28661
|
-
}
|
|
28662
|
-
|
|
28663
|
-
// src/commands/sessions/daemon/watchTranscript.ts
|
|
28664
|
-
import { existsSync as existsSync69, watch as watch3 } from "fs";
|
|
28665
|
-
|
|
28666
|
-
// src/commands/sessions/daemon/reconcileTranscriptStatus.ts
|
|
28667
|
-
async function resolveTranscriptPath(session) {
|
|
28668
|
-
if (session.transcriptPath) return session.transcriptPath;
|
|
28669
|
-
if (!session.claudeSessionId) return null;
|
|
28670
|
-
const direct = session.cwd ? findTranscriptPathSync(session.cwd, session.claudeSessionId) : null;
|
|
28671
|
-
const filePath = direct ?? await findSessionJsonlPath(session.claudeSessionId);
|
|
28672
|
-
if (filePath) session.transcriptPath = filePath;
|
|
28673
|
-
return filePath;
|
|
28674
|
-
}
|
|
28675
|
-
async function reconcileTranscriptStatus(session, onStatusChange) {
|
|
28676
|
-
if (session.status === "done" || session.status === "error" || session.status === "stopped")
|
|
28677
|
-
return;
|
|
28678
|
-
const filePath = await resolveTranscriptPath(session);
|
|
28679
|
-
if (!filePath) return;
|
|
28680
|
-
const entries = await readTranscriptTail(filePath);
|
|
28681
|
-
const derived = deriveTranscriptStatus(entries, {
|
|
28682
|
-
permissionActive: session.permissionActive
|
|
28683
|
-
});
|
|
28684
|
-
if (!derived) return;
|
|
28685
|
-
if (derived === "running") session.permissionActive = false;
|
|
28686
|
-
if (derived === session.status) return;
|
|
28687
|
-
daemonLog(
|
|
28688
|
-
`session ${session.id} transcript reconcile: ${session.status} -> ${derived}`
|
|
28689
|
-
);
|
|
28690
|
-
onStatusChange(session, derived);
|
|
28691
|
-
}
|
|
28692
|
-
|
|
28693
|
-
// src/commands/sessions/daemon/watchTranscript.ts
|
|
28694
|
-
var DEBOUNCE_MS3 = 100;
|
|
28695
|
-
function watchTranscript(session, onStatusChange) {
|
|
28696
|
-
if (!session.cwd || !session.claudeSessionId) return;
|
|
28697
|
-
if (session.watchedTranscriptId === session.claudeSessionId) return;
|
|
28698
|
-
const dir = projectDirForCwd(session.cwd);
|
|
28699
|
-
if (!existsSync69(dir)) return;
|
|
28700
|
-
if (session.transcriptWatcher) {
|
|
28701
|
-
session.transcriptWatcher.close();
|
|
28702
|
-
session.transcriptWatcher = void 0;
|
|
28703
|
-
}
|
|
28704
|
-
session.transcriptPath = void 0;
|
|
28705
|
-
const fileName = `${session.claudeSessionId}.jsonl`;
|
|
28706
|
-
let timer = null;
|
|
28707
|
-
const run4 = () => {
|
|
28708
|
-
timer = null;
|
|
28709
|
-
void reconcileTranscriptStatus(session, onStatusChange);
|
|
28855
|
+
startHeld: (session) => startHeldInTree(session, sessions, clients, onStatusChange, notify2)
|
|
28710
28856
|
};
|
|
28711
|
-
try {
|
|
28712
|
-
session.transcriptWatcher = watch3(dir, (_event, changed) => {
|
|
28713
|
-
if (changed && changed !== fileName) return;
|
|
28714
|
-
if (timer) clearTimeout(timer);
|
|
28715
|
-
timer = setTimeout(run4, DEBOUNCE_MS3);
|
|
28716
|
-
});
|
|
28717
|
-
} catch (error) {
|
|
28718
|
-
daemonLog(
|
|
28719
|
-
`session ${session.id} transcript watch failed: ${error instanceof Error ? error.message : String(error)}`
|
|
28720
|
-
);
|
|
28721
|
-
return;
|
|
28722
|
-
}
|
|
28723
|
-
session.watchedTranscriptId = session.claudeSessionId;
|
|
28724
|
-
daemonLog(`session ${session.id} watching transcript ${fileName}`);
|
|
28725
|
-
run4();
|
|
28726
|
-
}
|
|
28727
|
-
|
|
28728
|
-
// src/commands/sessions/daemon/wireSessionWatchers.ts
|
|
28729
|
-
function wireSessionWatchers(session, notify2, onStatusChange) {
|
|
28730
|
-
const wireTranscript = (s) => watchTranscript(s, onStatusChange);
|
|
28731
|
-
watchActivity(session, notify2, wireTranscript);
|
|
28732
|
-
wireTranscript(session);
|
|
28733
28857
|
}
|
|
28734
28858
|
|
|
28735
28859
|
// src/commands/sessions/daemon/connectToWindowsDaemon.ts
|
|
@@ -28934,8 +29058,11 @@ async function forwardWindowsCreate(conn, state, client, data) {
|
|
|
28934
29058
|
}
|
|
28935
29059
|
}
|
|
28936
29060
|
function stripOutboundSessionId(data) {
|
|
28937
|
-
|
|
28938
|
-
|
|
29061
|
+
const stripped = { ...data };
|
|
29062
|
+
for (const key of ["sessionId", "joinSessionId"])
|
|
29063
|
+
if (typeof stripped[key] === "string")
|
|
29064
|
+
stripped[key] = stripWindowsSessionId(stripped[key]);
|
|
29065
|
+
return stripped;
|
|
28939
29066
|
}
|
|
28940
29067
|
|
|
28941
29068
|
// src/commands/sessions/daemon/WindowsProxyState.ts
|
|
@@ -29388,6 +29515,46 @@ var WindowsProxy = class {
|
|
|
29388
29515
|
}
|
|
29389
29516
|
};
|
|
29390
29517
|
|
|
29518
|
+
// src/commands/sessions/daemon/worktree/joinableStream.ts
|
|
29519
|
+
function joinableStream(sessions, targetId) {
|
|
29520
|
+
const target = sessions.get(targetId);
|
|
29521
|
+
if (!target) return { reason: "no such session" };
|
|
29522
|
+
if (target.commandType === "run")
|
|
29523
|
+
return { reason: "a server run has no agent stream" };
|
|
29524
|
+
if (target.closing === true) return { reason: "the session is closing" };
|
|
29525
|
+
if (target.status !== "running" && target.status !== "waiting")
|
|
29526
|
+
return { reason: `the session is ${target.status}` };
|
|
29527
|
+
if (!target.cwd) return { reason: "the session has no working directory" };
|
|
29528
|
+
return { session: target };
|
|
29529
|
+
}
|
|
29530
|
+
|
|
29531
|
+
// src/commands/sessions/daemon/worktree/spawnIntoStream.ts
|
|
29532
|
+
function spawnIntoStream(ctx, target, prompt, harness) {
|
|
29533
|
+
const holdUntilSeeded = target.pendingStart !== void 0;
|
|
29534
|
+
const id = ctx.spawnWith(
|
|
29535
|
+
(sid) => createSession(sid, prompt, target.cwd, false, harness, holdUntilSeeded)
|
|
29536
|
+
);
|
|
29537
|
+
const joined = ctx.sessions.get(id);
|
|
29538
|
+
if (joined && target.worktree) joined.worktree = { ...target.worktree };
|
|
29539
|
+
daemonLog(
|
|
29540
|
+
`session ${id} added to the stream of session ${target.id} in ${target.cwd}: no workspace allocated${holdUntilSeeded ? ", held until that workspace finishes seeding" : ""}`
|
|
29541
|
+
);
|
|
29542
|
+
ctx.notify();
|
|
29543
|
+
return id;
|
|
29544
|
+
}
|
|
29545
|
+
|
|
29546
|
+
// src/commands/sessions/daemon/worktree/addAgentToStream.ts
|
|
29547
|
+
function addAgentToStream(ctx, targetId, prompt, harness) {
|
|
29548
|
+
const joinable = joinableStream(ctx.sessions, targetId);
|
|
29549
|
+
if ("reason" in joinable) {
|
|
29550
|
+
daemonLog(
|
|
29551
|
+
`create: cannot add an agent to session ${targetId}: ${joinable.reason}`
|
|
29552
|
+
);
|
|
29553
|
+
return void 0;
|
|
29554
|
+
}
|
|
29555
|
+
return spawnIntoStream(ctx, joinable.session, prompt, harness);
|
|
29556
|
+
}
|
|
29557
|
+
|
|
29391
29558
|
// src/commands/sessions/daemon/createAssistSession.ts
|
|
29392
29559
|
function createAssistSession(id, assistArgs, cwd, meta, holdPty) {
|
|
29393
29560
|
const startedAt = Date.now();
|
|
@@ -29595,13 +29762,13 @@ var SessionManager = class {
|
|
|
29595
29762
|
return restoreAllSessions(this.spawnWith, this.sessions, this.notify);
|
|
29596
29763
|
}
|
|
29597
29764
|
drain = () => drainSessions(this.sessions, this.notify);
|
|
29598
|
-
|
|
29599
|
-
this.
|
|
29600
|
-
|
|
29601
|
-
|
|
29602
|
-
|
|
29603
|
-
|
|
29604
|
-
|
|
29765
|
+
spawnWith = (create) => registerSpawnedSession(
|
|
29766
|
+
create(sessionLimits.nextId(this.sessions.size, this.idCounter)),
|
|
29767
|
+
this.sessions,
|
|
29768
|
+
this.clients,
|
|
29769
|
+
this.onStatusChange,
|
|
29770
|
+
this.notify
|
|
29771
|
+
);
|
|
29605
29772
|
treeCtx() {
|
|
29606
29773
|
return treeSpawnContext(
|
|
29607
29774
|
this.sessions,
|
|
@@ -29614,6 +29781,9 @@ var SessionManager = class {
|
|
|
29614
29781
|
spawn(prompt, cwd, design, harness) {
|
|
29615
29782
|
return spawnInTree(this.treeCtx(), prompt, cwd, design, harness);
|
|
29616
29783
|
}
|
|
29784
|
+
addAgent(targetId, prompt, harness) {
|
|
29785
|
+
return addAgentToStream(this.treeCtx(), targetId, prompt, harness);
|
|
29786
|
+
}
|
|
29617
29787
|
spawnRun(runName, runArgs, cwd, meta) {
|
|
29618
29788
|
return this.spawnWith(
|
|
29619
29789
|
(id) => createRunSession(id, runName, runArgs, cwd, meta)
|
|
@@ -29629,15 +29799,11 @@ var SessionManager = class {
|
|
|
29629
29799
|
return resumeInTree(this.treeCtx(), sessionId, cwd, name);
|
|
29630
29800
|
}
|
|
29631
29801
|
onStatusChange = makeStatusChangeHandler(
|
|
29802
|
+
this.sessions,
|
|
29632
29803
|
(id) => this.dismissSession(id),
|
|
29633
29804
|
() => this.notify(),
|
|
29634
29805
|
(session, itemId2) => reuseSessionForRun(session, itemId2, this.clients, this.onStatusChange)
|
|
29635
29806
|
);
|
|
29636
|
-
wire(session) {
|
|
29637
|
-
this.sessions.set(session.id, session);
|
|
29638
|
-
wirePtyEvents(session, this.clients, this.onStatusChange);
|
|
29639
|
-
this.notify();
|
|
29640
|
-
}
|
|
29641
29807
|
writeToSession(id, data) {
|
|
29642
29808
|
writeToSession(this.sessions, id, data, this.onStatusChange);
|
|
29643
29809
|
}
|
|
@@ -29883,16 +30049,20 @@ var lifecycleHandlers = {
|
|
|
29883
30049
|
function spawnCreate(m, d) {
|
|
29884
30050
|
const design = d.design === true;
|
|
29885
30051
|
const harness = d.harness;
|
|
30052
|
+
const prompt = d.prompt;
|
|
29886
30053
|
if (design)
|
|
29887
30054
|
daemonLog(`create: design session (cwd=${d.cwd ?? ""})`);
|
|
29888
30055
|
if (harness && harness !== "claude")
|
|
29889
30056
|
daemonLog(`create: ${harness} session (cwd=${d.cwd ?? ""})`);
|
|
29890
|
-
|
|
29891
|
-
|
|
29892
|
-
|
|
29893
|
-
|
|
29894
|
-
|
|
29895
|
-
|
|
30057
|
+
const joinSessionId = design ? void 0 : d.joinSessionId;
|
|
30058
|
+
if (joinSessionId) {
|
|
30059
|
+
const joined = m.addAgent(joinSessionId, prompt, harness);
|
|
30060
|
+
if (joined) return joined;
|
|
30061
|
+
daemonLog(
|
|
30062
|
+
`create: falling back to a fresh isolated session (cwd=${d.cwd ?? ""})`
|
|
30063
|
+
);
|
|
30064
|
+
}
|
|
30065
|
+
return m.spawn(prompt, d.cwd, design, harness);
|
|
29896
30066
|
}
|
|
29897
30067
|
|
|
29898
30068
|
// src/commands/sessions/daemon/messageHandlers.ts
|
|
@@ -30195,7 +30365,7 @@ var sessionsConfigHelp = [
|
|
|
30195
30365
|
{
|
|
30196
30366
|
key: "sessions.includeCommittedChanges",
|
|
30197
30367
|
setter: "assist config set sessions.includeCommittedChanges true -g",
|
|
30198
|
-
note: "session card counts and /diff span the whole
|
|
30368
|
+
note: "session card counts and /diff span the whole session (its first commit's parent, else merge-base with the default branch) instead of just uncommitted work (default off)"
|
|
30199
30369
|
},
|
|
30200
30370
|
{
|
|
30201
30371
|
key: "worktree.enabled",
|