@norman-else/dsh-claude 0.1.44 → 0.1.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/lib/bin.mjs +1 -1
- package/lib/client.d.ts +3 -1
- package/lib/client.js +603 -245
- package/lib/client.js.map +1 -1
- package/lib/index.mjs +144 -26
- package/lib/index.mjs.map +1 -1
- package/lib/{preset-installer-JUktnfwS.mjs → preset-installer-CuosP3sA.mjs} +2 -2
- package/lib/{preset-installer-JUktnfwS.mjs.map → preset-installer-CuosP3sA.mjs.map} +1 -1
- package/package.json +97 -95
package/lib/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { A as CLAUDE_REPOSITORY_FEEDBACK_PATH, B as DEFAULT_CLAUDE_RENDER_MODE, C as CLAUDE_PROJECTION_PATH, D as CLAUDE_PROSE_MODES, E as CLAUDE_PROMPT_REFINE_PATH, F as CLAUDE_REWIND_PATH, G as isClaudeRenderMode, H as TASK_TOOL_NAMES, I as CLAUDE_UPDATE_CHECK_PATH, L as CLAUDE_UPDATE_PATH, M as CLAUDE_REPOSITORY_SETUP_PATH, N as CLAUDE_REPOSITORY_STATUS_PATH, O as CLAUDE_RENDER_MODES, P as CLAUDE_REVIEW_COMMENT_PATH, R as CLAUDE_USAGE_PATH, S as CLAUDE_PLAN_FEEDBACK_PATH, T as CLAUDE_PROMPT_NAME_PATH, U as isClaudeAlertMode, W as isClaudeProseMode, _ as CLAUDE_CODE_PROVIDER_IDS, a as latestClaudeTasks, b as CLAUDE_GLOBAL_SETTINGS_PATH, c as normalizeTasksEvent, d as CLAUDE_ACTIVITY_EVENT, f as CLAUDE_ALERT_MODES, g as CLAUDE_CODE_PROVIDER, h as CLAUDE_CODE_PRESET_ID, i as latestClaudeSessionBinding, j as CLAUDE_REPOSITORY_FILE_PATH, k as CLAUDE_REPOSITORY_ACTION_PATH, l as redactText, m as CLAUDE_CLIENT_DIAGNOSTICS_PATH, n as currentClaudeActivityCursor, o as normalizeActivity, p as CLAUDE_ASK_PATH, r as latestClaudeContextUsage, s as normalizeContextUsage, t as boundText, u as safeDetail, v as CLAUDE_DOCTOR_PATH, w as CLAUDE_PROMPTS_PATH, x as CLAUDE_JIRA_PATH, y as CLAUDE_EDITOR_OPEN_PATH, z as DEFAULT_CLAUDE_PROSE_MODE } from "./events-oovRTmX7.mjs";
|
|
2
2
|
import { a as projectClaudeCommands, i as CLAUDE_COMMANDS_SERVICE, r as dynamicPresenterDefinition, t as CLAUDE_PRESENTER_NAMES } from "./presenters-BVWj7u0a.mjs";
|
|
3
|
-
import { a as resolveClaudeExecutable, n as ensureManagedPreset, o as runClaudeDoctor, t as ManagedPresetConflictError } from "./preset-installer-
|
|
3
|
+
import { a as resolveClaudeExecutable, n as ensureManagedPreset, o as runClaudeDoctor, t as ManagedPresetConflictError } from "./preset-installer-CuosP3sA.mjs";
|
|
4
4
|
import z from "@deepseek-ai/schemastery";
|
|
5
5
|
import { createHash, randomUUID } from "node:crypto";
|
|
6
6
|
import { chmod, mkdir, opendir, readFile, readdir, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
@@ -846,7 +846,7 @@ function createPermissionBridge(approval, activeContext, userQuestion, planFeedb
|
|
|
846
846
|
...plan === void 0 ? {} : { text: plan }
|
|
847
847
|
});
|
|
848
848
|
const userDecides = plan !== void 0;
|
|
849
|
-
silenced = userDecides && approvalPolicyOf(session.
|
|
849
|
+
silenced = userDecides && approvalPolicyOf(session.snapshotEvents()) === SILENT_POLICY;
|
|
850
850
|
if (silenced) session.append("approval/policy", { policy: ASKING_POLICY });
|
|
851
851
|
const alreadyFullAccess = !userDecides && await active.hasFullAccess?.() === true;
|
|
852
852
|
const revision = new AbortController();
|
|
@@ -1815,6 +1815,10 @@ const MAX_OUTPUT_BYTES$5 = 65536;
|
|
|
1815
1815
|
* status probes' five seconds, and this sits in front of every turn. */
|
|
1816
1816
|
const GIT_TIMEOUT_MS$4 = 3e4;
|
|
1817
1817
|
const OBJECT_NAME = /^[0-9a-f]{40}$|^[0-9a-f]{64}$/u;
|
|
1818
|
+
/** A snapshot holds the bytes on disk, not git's line-ending translation of
|
|
1819
|
+
* them. Under `core.autocrlf=true` a plain `add` would store LF and the
|
|
1820
|
+
* restore would write CRLF, so a rewind on Windows would flip every LF file. */
|
|
1821
|
+
const GIT_OPTIONS = ["-c", "core.autocrlf=false"];
|
|
1818
1822
|
async function collect$4(handle) {
|
|
1819
1823
|
return {
|
|
1820
1824
|
exitCode: (await handle.done).exitCode,
|
|
@@ -1823,7 +1827,11 @@ async function collect$4(handle) {
|
|
|
1823
1827
|
}
|
|
1824
1828
|
async function run$1(runtime, git, args, cwd, env = {}) {
|
|
1825
1829
|
return collect$4(runtime.spawn({
|
|
1826
|
-
argv: [
|
|
1830
|
+
argv: [
|
|
1831
|
+
git,
|
|
1832
|
+
...GIT_OPTIONS,
|
|
1833
|
+
...args
|
|
1834
|
+
],
|
|
1827
1835
|
cwd,
|
|
1828
1836
|
stdio: {
|
|
1829
1837
|
stdin: "ignore",
|
|
@@ -2283,7 +2291,7 @@ var ClaudeSupervisor = class {
|
|
|
2283
2291
|
} else await this.#syncPermissionMode(entry);
|
|
2284
2292
|
await throwIfUnavailable();
|
|
2285
2293
|
const promptUuid = randomUUID();
|
|
2286
|
-
const cursor = currentClaudeActivityCursor(request.agent.session.
|
|
2294
|
+
const cursor = currentClaudeActivityCursor(request.agent.session.snapshotEvents());
|
|
2287
2295
|
const projection = await this.#sidecar.read(sessionId);
|
|
2288
2296
|
await throwIfUnavailable();
|
|
2289
2297
|
cursor.nextOrdinal = projection.activities.reduce((next, activity) => activity.turn === cursor.turn && activity.step === cursor.step ? Math.max(next, activity.ordinal + 1) : next, 0);
|
|
@@ -2436,7 +2444,7 @@ var ClaudeSupervisor = class {
|
|
|
2436
2444
|
}
|
|
2437
2445
|
}
|
|
2438
2446
|
async #syncPermissionMode(entry) {
|
|
2439
|
-
const mode = claudePermissionMode(entry.ownerAgent.session.
|
|
2447
|
+
const mode = claudePermissionMode(entry.ownerAgent.session.snapshotEvents());
|
|
2440
2448
|
if (mode === entry.permissionMode) return;
|
|
2441
2449
|
await this.#control(entry, entry.query.setPermissionMode(mode), "Claude Code permission mode switch");
|
|
2442
2450
|
entry.permissionMode = mode;
|
|
@@ -2530,13 +2538,13 @@ var ClaudeSupervisor = class {
|
|
|
2530
2538
|
const cwd = agent.session.header.cwd ?? process.cwd();
|
|
2531
2539
|
const input = new AsyncQueue();
|
|
2532
2540
|
const lifetime = new AbortController();
|
|
2533
|
-
const projection = await this.#sidecar.importLegacy(sessionId, agent.session.
|
|
2541
|
+
const projection = await this.#sidecar.importLegacy(sessionId, agent.session.snapshotEvents());
|
|
2534
2542
|
if (signalAborted(signal) || signalAborted(cancellationSignal)) throw abortFailure();
|
|
2535
2543
|
const binding = projection.binding;
|
|
2536
2544
|
const pendingRewind = projection.rewind?.pending;
|
|
2537
2545
|
const forkAt = pendingRewind !== void 0 && "resumeAt" in pendingRewind ? pendingRewind.resumeAt : void 0;
|
|
2538
2546
|
const startFresh = pendingRewind !== void 0 && "fresh" in pendingRewind;
|
|
2539
|
-
const permissionMode = claudePermissionMode(agent.session.
|
|
2547
|
+
const permissionMode = claudePermissionMode(agent.session.snapshotEvents());
|
|
2540
2548
|
const entry = {
|
|
2541
2549
|
sessionId,
|
|
2542
2550
|
ownerAgent: agent,
|
|
@@ -2788,7 +2796,7 @@ var ClaudeSupervisor = class {
|
|
|
2788
2796
|
case "unknown":
|
|
2789
2797
|
await this.#appendActivity(active, {
|
|
2790
2798
|
kind: message.kind === "status" ? "status" : "warning",
|
|
2791
|
-
phase: "
|
|
2799
|
+
phase: "completed",
|
|
2792
2800
|
title: message.title,
|
|
2793
2801
|
..."summary" in message ? { summary: message.summary } : {},
|
|
2794
2802
|
..."detail" in message ? { detail: message.detail } : {}
|
|
@@ -4236,6 +4244,7 @@ function envelope(projection, meta) {
|
|
|
4236
4244
|
...projection.contextUsage === void 0 ? {} : { contextUsage: projection.contextUsage },
|
|
4237
4245
|
...projection.tasks === void 0 ? {} : { tasks: projection.tasks },
|
|
4238
4246
|
...meta.repository === void 0 ? {} : { repository: meta.repository },
|
|
4247
|
+
...meta.repositories === void 0 ? {} : { repositories: meta.repositories },
|
|
4239
4248
|
reviewComments: meta.reviewComments,
|
|
4240
4249
|
...projection.rewind === void 0 ? {} : { rewind: { ranges: projection.rewind.ranges } }
|
|
4241
4250
|
};
|
|
@@ -4251,7 +4260,7 @@ function envelope(projection, meta) {
|
|
|
4251
4260
|
* session spent it in proportion to how many Claude sessions existed — which
|
|
4252
4261
|
* is what left the settings panel unable to get a connection at all. One
|
|
4253
4262
|
* carrier is one connection, whatever the session count. */
|
|
4254
|
-
function registerClaudeProjectionRoute(ctx, sidecar, ownsSession, commandsForSession = () => [], repositoryForSession = async () => void 0, reviewCommentsForSession = () => []) {
|
|
4263
|
+
function registerClaudeProjectionRoute(ctx, sidecar, ownsSession, commandsForSession = () => [], repositoryForSession = async () => void 0, reviewCommentsForSession = () => [], extraRepositoriesForSession = async () => []) {
|
|
4255
4264
|
const info = (message) => {
|
|
4256
4265
|
ctx.logger?.info?.(message);
|
|
4257
4266
|
};
|
|
@@ -4264,12 +4273,14 @@ function registerClaudeProjectionRoute(ctx, sidecar, ownsSession, commandsForSes
|
|
|
4264
4273
|
reviewComments: owned ? reviewCommentsForSession(sessionId) : []
|
|
4265
4274
|
};
|
|
4266
4275
|
};
|
|
4267
|
-
const assembleMeta = async (sessionId) => {
|
|
4276
|
+
const assembleMeta = async (sessionId, activities) => {
|
|
4268
4277
|
const meta = localMeta(sessionId);
|
|
4269
|
-
|
|
4278
|
+
if (!meta.owned) return meta;
|
|
4279
|
+
const [repository, repositories] = await Promise.all([repositoryForSession(sessionId), extraRepositoriesForSession(sessionId, activities ?? (await sidecar.read(sessionId)).activities)]);
|
|
4270
4280
|
return {
|
|
4271
4281
|
...meta,
|
|
4272
|
-
...repository === void 0 ? {} : { repository }
|
|
4282
|
+
...repository === void 0 ? {} : { repository },
|
|
4283
|
+
...repositories.length === 0 ? {} : { repositories }
|
|
4273
4284
|
};
|
|
4274
4285
|
};
|
|
4275
4286
|
const streamMulti = async (res, io, sessionIds) => {
|
|
@@ -4301,6 +4312,7 @@ function registerClaudeProjectionRoute(ctx, sidecar, ownsSession, commandsForSes
|
|
|
4301
4312
|
owned: meta.owned,
|
|
4302
4313
|
commands: meta.commands,
|
|
4303
4314
|
...meta.repository === void 0 ? {} : { repository: meta.repository },
|
|
4315
|
+
...meta.repositories === void 0 ? {} : { repositories: meta.repositories },
|
|
4304
4316
|
reviewComments: meta.reviewComments
|
|
4305
4317
|
});
|
|
4306
4318
|
};
|
|
@@ -4314,7 +4326,7 @@ function registerClaudeProjectionRoute(ctx, sidecar, ownsSession, commandsForSes
|
|
|
4314
4326
|
seq: sidecar.sequence(sessionId),
|
|
4315
4327
|
...envelope(projection, meta)
|
|
4316
4328
|
});
|
|
4317
|
-
const probed = await assembleMeta(sessionId);
|
|
4329
|
+
const probed = await assembleMeta(sessionId, projection.activities);
|
|
4318
4330
|
if (closed || JSON.stringify(probed) === JSON.stringify(metas.get(sessionId))) return;
|
|
4319
4331
|
writeMeta(sessionId, probed);
|
|
4320
4332
|
};
|
|
@@ -4427,7 +4439,8 @@ function registerClaudeProjectionRoute(ctx, sidecar, ownsSession, commandsForSes
|
|
|
4427
4439
|
if (target.kind === "multi") return await streamMulti(res, io, target.sessionIds);
|
|
4428
4440
|
info(`dsh-claude: projection poll for ${target.sessionId.slice(0, 64)}`);
|
|
4429
4441
|
try {
|
|
4430
|
-
const
|
|
4442
|
+
const projection = await sidecar.read(target.sessionId);
|
|
4443
|
+
const body = envelope(projection, await assembleMeta(target.sessionId, projection.activities));
|
|
4431
4444
|
res.writeHead(200, {
|
|
4432
4445
|
"content-type": "application/json; charset=utf-8",
|
|
4433
4446
|
"cache-control": "no-store",
|
|
@@ -4727,6 +4740,7 @@ var RepositoryStatusService = class {
|
|
|
4727
4740
|
#lastReady = /* @__PURE__ */ new Map();
|
|
4728
4741
|
#gitExecutable;
|
|
4729
4742
|
#ghExecutable;
|
|
4743
|
+
#roots = /* @__PURE__ */ new Map();
|
|
4730
4744
|
constructor(runtime, cacheTtlMs = CACHE_TTL_MS) {
|
|
4731
4745
|
this.#runtime = runtime;
|
|
4732
4746
|
this.#cacheTtlMs = cacheTtlMs;
|
|
@@ -4749,7 +4763,27 @@ var RepositoryStatusService = class {
|
|
|
4749
4763
|
this.#cache.delete(cwd);
|
|
4750
4764
|
this.#lastReady.delete(cwd);
|
|
4751
4765
|
}
|
|
4766
|
+
/** The repository holding `directory`, or undefined outside any. A root
|
|
4767
|
+
* does not move, so a settled answer is kept for the service's lifetime;
|
|
4768
|
+
* a probe that could not run is not an answer and is asked again. */
|
|
4769
|
+
rootOf(directory) {
|
|
4770
|
+
const known = this.#roots.get(directory);
|
|
4771
|
+
if (known !== void 0) return known;
|
|
4772
|
+
const value = (async () => {
|
|
4773
|
+
const top = await run(this.#runtime, await this.#git(), [
|
|
4774
|
+
"rev-parse",
|
|
4775
|
+
"--path-format=absolute",
|
|
4776
|
+
"--show-toplevel"
|
|
4777
|
+
], directory, GIT_TIMEOUT_MS$3);
|
|
4778
|
+
return top.exitCode === 0 ? resolve(top.stdout.trim()) : void 0;
|
|
4779
|
+
})().catch(() => {
|
|
4780
|
+
this.#roots.delete(directory);
|
|
4781
|
+
});
|
|
4782
|
+
this.#roots.set(directory, value);
|
|
4783
|
+
return value;
|
|
4784
|
+
}
|
|
4752
4785
|
dispose() {
|
|
4786
|
+
this.#roots.clear();
|
|
4753
4787
|
this.#cache.clear();
|
|
4754
4788
|
this.#lastReady.clear();
|
|
4755
4789
|
}
|
|
@@ -5833,7 +5867,8 @@ var RepositoryActionService = class {
|
|
|
5833
5867
|
const merged = await this.#run(gh, [
|
|
5834
5868
|
"pr",
|
|
5835
5869
|
"merge",
|
|
5836
|
-
`--${method}
|
|
5870
|
+
`--${method}`,
|
|
5871
|
+
...request.admin === true ? ["--admin"] : []
|
|
5837
5872
|
], before.root, REMOTE_TIMEOUT_MS);
|
|
5838
5873
|
if (merged.exitCode !== 0 || merged.lossy) {
|
|
5839
5874
|
const reason = merged.stderr.split(/\r?\n/u).map((line) => line.trim()).filter((line) => line.length > 0).at(-1);
|
|
@@ -6274,7 +6309,7 @@ async function streamSetup(res, service, input) {
|
|
|
6274
6309
|
* The prefix is registered as a stream because the setup POST holds its
|
|
6275
6310
|
* connection open for the whole worktree build; the short sibling paths ride
|
|
6276
6311
|
* the same registration and answer with `json` before releasing it. */
|
|
6277
|
-
function registerRepositorySetupRoute(ctx, service, sweep) {
|
|
6312
|
+
function registerRepositorySetupRoute(ctx, service, sweep, cleaned) {
|
|
6278
6313
|
registerPluginRoute(ctx, {
|
|
6279
6314
|
mode: "stream",
|
|
6280
6315
|
kind: "prefix",
|
|
@@ -6306,7 +6341,10 @@ function registerRepositorySetupRoute(ctx, service, sweep) {
|
|
|
6306
6341
|
if (pathname === `/plugins/dsh-claude/repository/setup/cleanup`) {
|
|
6307
6342
|
if (io.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
6308
6343
|
const input = await readJson$6(io);
|
|
6309
|
-
|
|
6344
|
+
const path = string$2(input, "path");
|
|
6345
|
+
const result = await service.cleanupMerged(path, string$2(input, "baseBranch"));
|
|
6346
|
+
cleaned?.(path);
|
|
6347
|
+
return json(res, 200, result);
|
|
6310
6348
|
}
|
|
6311
6349
|
if (pathname === `/plugins/dsh-claude/repository/setup/sweep`) {
|
|
6312
6350
|
if (io.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
@@ -6335,6 +6373,7 @@ function registerRepositorySetupRoute(ctx, service, sweep) {
|
|
|
6335
6373
|
//#region src/repository-action-routes.ts
|
|
6336
6374
|
const MAX_BODY_BYTES$6 = 16384;
|
|
6337
6375
|
const MAX_SESSION_ID_CHARS$6 = 1024;
|
|
6376
|
+
const MAX_ROOT_CHARS = 4096;
|
|
6338
6377
|
const ACTIONS = /* @__PURE__ */ new Set([
|
|
6339
6378
|
"commit",
|
|
6340
6379
|
"commit-push",
|
|
@@ -6373,6 +6412,10 @@ function sessionId$1(url) {
|
|
|
6373
6412
|
if (value === null || value.length === 0 || value.length > MAX_SESSION_ID_CHARS$6) throw new RepositoryActionError("invalid-session", "The session is invalid.");
|
|
6374
6413
|
return value;
|
|
6375
6414
|
}
|
|
6415
|
+
function requestedRoot(url) {
|
|
6416
|
+
const value = url.searchParams.get("root");
|
|
6417
|
+
return value === null || value.length === 0 || value.length > MAX_ROOT_CHARS ? void 0 : value;
|
|
6418
|
+
}
|
|
6376
6419
|
function string$1(input, key) {
|
|
6377
6420
|
const value = input[key];
|
|
6378
6421
|
if (typeof value !== "string") throw new RepositoryActionError("invalid-request", `The ${key} field is required.`);
|
|
@@ -6401,6 +6444,9 @@ function actionRequest(input) {
|
|
|
6401
6444
|
...input.push === void 0 ? {} : typeof input.push === "boolean" ? { push: input.push } : (() => {
|
|
6402
6445
|
throw new RepositoryActionError("invalid-request", "The push field must be a boolean.");
|
|
6403
6446
|
})(),
|
|
6447
|
+
...input.admin === void 0 ? {} : typeof input.admin === "boolean" ? { admin: input.admin } : (() => {
|
|
6448
|
+
throw new RepositoryActionError("invalid-request", "The admin field must be a boolean.");
|
|
6449
|
+
})(),
|
|
6404
6450
|
...input.mergeMethod === void 0 ? {} : input.mergeMethod === "merge" || input.mergeMethod === "squash" || input.mergeMethod === "rebase" ? { mergeMethod: input.mergeMethod } : (() => {
|
|
6405
6451
|
throw new RepositoryActionError("invalid-request", "The mergeMethod field is invalid.");
|
|
6406
6452
|
})()
|
|
@@ -6420,7 +6466,7 @@ function registerRepositoryActionRoute(ctx, service, cwdForSession) {
|
|
|
6420
6466
|
handler: async (io) => {
|
|
6421
6467
|
const url = io.url;
|
|
6422
6468
|
try {
|
|
6423
|
-
const cwd = cwdForSession(sessionId$1(url));
|
|
6469
|
+
const cwd = cwdForSession(sessionId$1(url), requestedRoot(url));
|
|
6424
6470
|
if (cwd === void 0) throw new RepositoryActionError("session-unavailable", "The Claude session is unavailable.");
|
|
6425
6471
|
if (url.pathname === `/plugins/dsh-claude/repository/action/preview`) {
|
|
6426
6472
|
if (io.method !== "GET") return {
|
|
@@ -6680,10 +6726,13 @@ const PROMPT_NAME = /^[\p{L}\p{N}][\p{L}\p{M}\p{N} ._()\[\]-]{0,127}$/u;
|
|
|
6680
6726
|
function claudePromptsDir() {
|
|
6681
6727
|
return join(homedir(), ".claude", "prompts");
|
|
6682
6728
|
}
|
|
6683
|
-
/** `~/.claude/prompts/x.md` rather than the absolute path it expands to.
|
|
6729
|
+
/** `~/.claude/prompts/x.md` rather than the absolute path it expands to.
|
|
6730
|
+
* Windows joins with backslashes; the display form is the same on every OS. */
|
|
6684
6731
|
function displayPath(file) {
|
|
6685
6732
|
const home = homedir();
|
|
6686
|
-
|
|
6733
|
+
const separator = file.charAt(home.length);
|
|
6734
|
+
if (!file.startsWith(home) || separator !== "/" && separator !== "\\") return file;
|
|
6735
|
+
return `~${file.slice(home.length).replaceAll("\\", "/")}`;
|
|
6687
6736
|
}
|
|
6688
6737
|
/** The menu's second row: the first non-empty line, collapsed and bounded. */
|
|
6689
6738
|
function summarize(body) {
|
|
@@ -7456,7 +7505,7 @@ function registerPullRequestFeedbackRoute(ctx, service, cwdForSession) {
|
|
|
7456
7505
|
const reads = io.method === "GET";
|
|
7457
7506
|
const writes = io.method === "POST";
|
|
7458
7507
|
try {
|
|
7459
|
-
const cwd = cwdForSession(sessionId(url));
|
|
7508
|
+
const cwd = cwdForSession(sessionId(url), url.searchParams.get("root") ?? void 0);
|
|
7460
7509
|
if (cwd === void 0) throw new PullRequestFeedbackError("session-unavailable", "The Claude session is unavailable.");
|
|
7461
7510
|
if (url.pathname === `/plugins/dsh-claude/repository/feedback/comments`) {
|
|
7462
7511
|
if (!reads) return {
|
|
@@ -8595,6 +8644,60 @@ function registerClaudeRewindRoute(ctx, sidecar, access) {
|
|
|
8595
8644
|
});
|
|
8596
8645
|
}
|
|
8597
8646
|
//#endregion
|
|
8647
|
+
//#region src/touched-repositories.ts
|
|
8648
|
+
/** Which other repositories a session has written into.
|
|
8649
|
+
*
|
|
8650
|
+
* A session is pinned to one checkout, but Claude writes wherever it is
|
|
8651
|
+
* pointed: a monorepo sibling, a dependency checked out next door. Those
|
|
8652
|
+
* edits never showed up anywhere -- no diff, no pull request -- because every
|
|
8653
|
+
* repository read keyed off the session's own cwd. The file tools' arguments
|
|
8654
|
+
* are already on the activity log, so the extra roots are derived from there
|
|
8655
|
+
* rather than tracked as new state. */
|
|
8656
|
+
const FILE_TOOLS = /* @__PURE__ */ new Set([
|
|
8657
|
+
"Edit",
|
|
8658
|
+
"MultiEdit",
|
|
8659
|
+
"Write",
|
|
8660
|
+
"NotebookEdit"
|
|
8661
|
+
]);
|
|
8662
|
+
/** The activity detail is a redacted JSON string that may be cut short, so
|
|
8663
|
+
* this matches the one key rather than parsing the document. */
|
|
8664
|
+
const PATH_KEY = /"(?:file_path|notebook_path)"\s*:\s*"((?:[^"\\]|\\.)*)"/u;
|
|
8665
|
+
/** Absolute paths Claude wrote through its file tools, first-seen order. */
|
|
8666
|
+
function touchedFilePaths(activities) {
|
|
8667
|
+
const paths = /* @__PURE__ */ new Set();
|
|
8668
|
+
for (const activity of activities) {
|
|
8669
|
+
if (activity.kind !== "tool-call" && activity.kind !== "subagent" || activity.toolName === void 0 || !FILE_TOOLS.has(activity.toolName) || activity.detail === void 0) continue;
|
|
8670
|
+
const escaped = PATH_KEY.exec(activity.detail)?.[1];
|
|
8671
|
+
if (escaped === void 0) continue;
|
|
8672
|
+
try {
|
|
8673
|
+
const path = JSON.parse(`"${escaped}"`);
|
|
8674
|
+
if (isAbsolute(path)) paths.add(path);
|
|
8675
|
+
} catch {}
|
|
8676
|
+
}
|
|
8677
|
+
return [...paths];
|
|
8678
|
+
}
|
|
8679
|
+
/** Repository roots behind the touched paths, minus the session's own, in
|
|
8680
|
+
* first-seen order. `rootOf` answers undefined outside any repository. */
|
|
8681
|
+
async function touchedRepositoryRoots(paths, sessionRoot, rootOf, max) {
|
|
8682
|
+
const roots = [];
|
|
8683
|
+
const directories = new Set(paths.map((path) => dirname(path)));
|
|
8684
|
+
for (const directory of directories) {
|
|
8685
|
+
const root = await rootOf(directory);
|
|
8686
|
+
if (root === void 0 || root === sessionRoot || roots.includes(root)) continue;
|
|
8687
|
+
roots.push(root);
|
|
8688
|
+
if (roots.length >= max) break;
|
|
8689
|
+
}
|
|
8690
|
+
return roots;
|
|
8691
|
+
}
|
|
8692
|
+
/** Whether a linked checkout still has anything to show. A cleaned-up
|
|
8693
|
+
* worktree is gone, and a checkout cleaned up in place is back on base with
|
|
8694
|
+
* nothing pending -- either way its bar comes down. A clean checkout with an
|
|
8695
|
+
* open or merged pull request, or unpushed work, stays. */
|
|
8696
|
+
function linkedRepositoryShown(status) {
|
|
8697
|
+
if (status.status !== "ready") return false;
|
|
8698
|
+
return status.dirty === true || status.upstream === false || (status.ahead ?? 0) > 0 || status.pullRequest !== void 0;
|
|
8699
|
+
}
|
|
8700
|
+
//#endregion
|
|
8598
8701
|
//#region src/update-routes.ts
|
|
8599
8702
|
const PLUGIN_PACKAGE_NAME = "@norman-else/dsh-claude";
|
|
8600
8703
|
const MAX_MANIFEST_BYTES = 262144;
|
|
@@ -9303,6 +9406,8 @@ const CLAUDE_SCOPE_UNAVAILABLE_MESSAGE = "agent command scope unavailable (prese
|
|
|
9303
9406
|
const CATALOG_RETRY_MS = 5e3;
|
|
9304
9407
|
const SCOPE_RETRY_MS = 500;
|
|
9305
9408
|
const MAX_CATALOG_RETRIES = 3;
|
|
9409
|
+
/** Bounded: each extra checkout costs a git chain and a `gh pr view` per sweep. */
|
|
9410
|
+
const MAX_EXTRA_REPOSITORIES = 8;
|
|
9306
9411
|
const MAX_SCOPE_RETRIES = 24;
|
|
9307
9412
|
function mountClaudeMetadata(ctx, supervisor, agent, model, sidecar, publishCommands = () => {}, resolveCommands = () => ctx.agentPresets.serviceFor(agent, CLAUDE_COMMANDS_SERVICE)) {
|
|
9308
9413
|
if (ctx.agentPresets.composedPreset(agent.ctx) !== "claude") return void 0;
|
|
@@ -9553,15 +9658,28 @@ async function apply(ctx, config) {
|
|
|
9553
9658
|
supervisor.limitsChanged();
|
|
9554
9659
|
}
|
|
9555
9660
|
});
|
|
9556
|
-
registerRepositorySetupRoute(webCtx, repositorySetup, () => sweepWorktrees?.());
|
|
9661
|
+
registerRepositorySetupRoute(webCtx, repositorySetup, () => sweepWorktrees?.(), (path) => repositoryStatus.invalidate(path));
|
|
9557
9662
|
registerRepositoryStatusRoute(webCtx, repositoryStatus);
|
|
9558
9663
|
registerRepositoryFileRoute(webCtx, repositoryStatus);
|
|
9559
9664
|
registerJiraRoute(webCtx, new JiraService());
|
|
9560
9665
|
const repositoryActions = new RepositoryActionService(webCtx.subprocess, supervisorConfig.executablePath, (cwd) => repositoryStatus.invalidate(cwd));
|
|
9561
|
-
|
|
9666
|
+
/** Roots the latest projection probe vouched for, per session: the only
|
|
9667
|
+
* checkouts a route may act on besides the session's own. */
|
|
9668
|
+
const extraRoots = /* @__PURE__ */ new Map();
|
|
9669
|
+
const cwdForClaudeSession = (sessionId, root) => {
|
|
9562
9670
|
const agent = webCtx.agents.get(sessionId);
|
|
9563
9671
|
if (agent === void 0 || webCtx.agentPresets.composedPreset(agent.ctx) !== "claude") return void 0;
|
|
9564
|
-
return agent.session.header.cwd;
|
|
9672
|
+
if (root === void 0) return agent.session.header.cwd;
|
|
9673
|
+
return extraRoots.get(sessionId)?.includes(root) === true ? root : void 0;
|
|
9674
|
+
};
|
|
9675
|
+
const extraRepositoriesForClaudeSession = async (sessionId, activities) => {
|
|
9676
|
+
const cwd = cwdForClaudeSession(sessionId);
|
|
9677
|
+
if (cwd === void 0) return [];
|
|
9678
|
+
const own = await repositoryStatus.rootOf(cwd);
|
|
9679
|
+
const roots = await touchedRepositoryRoots(touchedFilePaths(activities), own ?? cwd, (directory) => repositoryStatus.rootOf(directory), MAX_EXTRA_REPOSITORIES);
|
|
9680
|
+
const statuses = (await Promise.all(roots.map((root) => repositoryStatus.inspect(root)))).filter(linkedRepositoryShown);
|
|
9681
|
+
extraRoots.set(sessionId, statuses.map((status) => status.root ?? status.cwd));
|
|
9682
|
+
return statuses;
|
|
9565
9683
|
};
|
|
9566
9684
|
registerRepositoryActionRoute(webCtx, repositoryActions, cwdForClaudeSession);
|
|
9567
9685
|
registerEditorOpenRoute(webCtx, new EditorOpenService(webCtx.subprocess), cwdForClaudeSession);
|
|
@@ -9586,7 +9704,7 @@ async function apply(ctx, config) {
|
|
|
9586
9704
|
registerClaudeRewindRoute(webCtx, sidecar, {
|
|
9587
9705
|
eventsFor: (sessionId) => {
|
|
9588
9706
|
const agent = webCtx.agents.get(sessionId);
|
|
9589
|
-
return agent === void 0 || webCtx.agentPresets.composedPreset(agent.ctx) !== "claude" ? void 0 : agent.session.
|
|
9707
|
+
return agent === void 0 || webCtx.agentPresets.composedPreset(agent.ctx) !== "claude" ? void 0 : agent.session.snapshotEvents();
|
|
9590
9708
|
},
|
|
9591
9709
|
busy: (sessionId) => supervisor.snapshots().some((item) => item.sessionId === sessionId && (item.state === "running" || item.state === "interrupting")),
|
|
9592
9710
|
reset: (sessionId) => supervisor.disposeSession(sessionId),
|
|
@@ -9601,7 +9719,7 @@ async function apply(ctx, config) {
|
|
|
9601
9719
|
if (agent === void 0 || webCtx.agentPresets.composedPreset(agent.ctx) !== "claude") return void 0;
|
|
9602
9720
|
const cwd = agent.session.header.cwd;
|
|
9603
9721
|
return cwd === void 0 ? void 0 : repositoryStatus.inspect(cwd);
|
|
9604
|
-
}, (sessionId) => reviewComments.list(sessionId));
|
|
9722
|
+
}, (sessionId) => reviewComments.list(sessionId), extraRepositoriesForClaudeSession);
|
|
9605
9723
|
});
|
|
9606
9724
|
}
|
|
9607
9725
|
//#endregion
|