@norman-else/dsh-claude 0.1.32 → 0.1.33
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/INSTALL.md +1 -1
- package/README.md +1 -1
- package/lib/bin.mjs +1 -1
- package/lib/client.d.ts +25 -2
- package/lib/client.js +1299 -306
- package/lib/client.js.map +1 -1
- package/lib/{command-bridge-tqsu15hR.mjs → command-bridge-DXI6nWhB.mjs} +2 -2
- package/lib/{command-bridge-tqsu15hR.mjs.map → command-bridge-DXI6nWhB.mjs.map} +1 -1
- package/lib/{events-CSDcXsWE.mjs → events-DeSV0S1-.mjs} +3 -2
- package/lib/events-DeSV0S1-.mjs.map +1 -0
- package/lib/index.mjs +415 -101
- package/lib/index.mjs.map +1 -1
- package/lib/{preset-installer-CVdbU87E.mjs → preset-installer-DbmlhBXI.mjs} +9 -11
- package/lib/preset-installer-DbmlhBXI.mjs.map +1 -0
- package/lib/preset-route.mjs +2 -2
- package/package.json +188 -184
- package/lib/events-CSDcXsWE.mjs.map +0 -1
- package/lib/preset-installer-CVdbU87E.mjs.map +0 -1
package/lib/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { A as
|
|
2
|
-
import { n as projectClaudeCommands, t as CLAUDE_COMMANDS_SERVICE } from "./command-bridge-
|
|
3
|
-
import { a as resolveClaudeExecutable, n as ensureManagedPreset, o as runClaudeDoctor, t as ManagedPresetConflictError } from "./preset-installer-
|
|
1
|
+
import { A as CLAUDE_UPDATE_PATH, C as CLAUDE_REPOSITORY_FEEDBACK_PATH, D as CLAUDE_REVIEW_COMMENT_PATH, E as CLAUDE_REPOSITORY_STATUS_PATH, N as TASK_TOOL_NAMES, O as CLAUDE_REWIND_PATH, S as CLAUDE_REPOSITORY_ACTION_PATH, T as CLAUDE_REPOSITORY_SETUP_PATH, _ as CLAUDE_DOCTOR_PATH, a as latestClaudeTasks, b as CLAUDE_JIRA_PATH, c as normalizeTasksEvent, d as CLAUDE_ACTIVITY_EVENT, f as CLAUDE_ASK_PATH, g as CLAUDE_CODE_PROVIDER_IDS, h as CLAUDE_CODE_PROVIDER, i as latestClaudeSessionBinding, j as CLAUDE_USAGE_PATH, k as CLAUDE_UPDATE_CHECK_PATH, l as redactText, m as CLAUDE_CODE_PRESET_ID, n as currentClaudeActivityCursor, o as normalizeActivity, p as CLAUDE_CLIENT_DIAGNOSTICS_PATH, r as latestClaudeContextUsage, s as normalizeContextUsage, t as boundText, u as safeDetail, v as CLAUDE_EDITOR_OPEN_PATH, w as CLAUDE_REPOSITORY_FILE_PATH, x as CLAUDE_PROJECTION_PATH, y as CLAUDE_GLOBAL_SETTINGS_PATH } from "./events-DeSV0S1-.mjs";
|
|
2
|
+
import { n as projectClaudeCommands, t as CLAUDE_COMMANDS_SERVICE } from "./command-bridge-DXI6nWhB.mjs";
|
|
3
|
+
import { a as resolveClaudeExecutable, n as ensureManagedPreset, o as runClaudeDoctor, t as ManagedPresetConflictError } from "./preset-installer-DbmlhBXI.mjs";
|
|
4
4
|
import z from "@deepseek-ai/schemastery";
|
|
5
5
|
import { createHash, randomUUID } from "node:crypto";
|
|
6
6
|
import { chmod, mkdir, opendir, readFile, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
@@ -87,7 +87,7 @@ function emptyProjection() {
|
|
|
87
87
|
activities: []
|
|
88
88
|
};
|
|
89
89
|
}
|
|
90
|
-
function record$
|
|
90
|
+
function record$13(value) {
|
|
91
91
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
92
92
|
}
|
|
93
93
|
function finiteInteger(value) {
|
|
@@ -97,7 +97,7 @@ function string$4(value, max) {
|
|
|
97
97
|
return typeof value === "string" && value.length > 0 && value.length <= max;
|
|
98
98
|
}
|
|
99
99
|
function binding(value) {
|
|
100
|
-
const input = record$
|
|
100
|
+
const input = record$13(value);
|
|
101
101
|
if (input === void 0 || !string$4(input.claudeSessionId, 512) || !string$4(input.sdkVersion, 128) || !string$4(input.cwd, 4096) || input.cliVersion !== void 0 && !string$4(input.cliVersion, 128)) return void 0;
|
|
102
102
|
return {
|
|
103
103
|
claudeSessionId: input.claudeSessionId,
|
|
@@ -128,21 +128,21 @@ const ACTIVITY_PHASES = /* @__PURE__ */ new Set([
|
|
|
128
128
|
"failed"
|
|
129
129
|
]);
|
|
130
130
|
function activity(value) {
|
|
131
|
-
const input = record$
|
|
131
|
+
const input = record$13(value);
|
|
132
132
|
if (input === void 0 || !finiteInteger(input.turn) || !finiteInteger(input.step) || !finiteInteger(input.ordinal) || typeof input.kind !== "string" || !ACTIVITY_KINDS.has(input.kind) || input.phase !== void 0 && (typeof input.phase !== "string" || !ACTIVITY_PHASES.has(input.phase))) return void 0;
|
|
133
133
|
return normalizeActivity(input);
|
|
134
134
|
}
|
|
135
135
|
function contextUsage(value) {
|
|
136
|
-
const input = record$
|
|
136
|
+
const input = record$13(value);
|
|
137
137
|
if (input === void 0 || !Array.isArray(input.categories)) return void 0;
|
|
138
138
|
return normalizeContextUsage(input);
|
|
139
139
|
}
|
|
140
140
|
function rewind(value) {
|
|
141
|
-
const input = record$
|
|
141
|
+
const input = record$13(value);
|
|
142
142
|
if (input === void 0 || !Array.isArray(input.ranges) || input.ranges.length > 200 || !Array.isArray(input.anchors) || input.anchors.length > 2e3) return void 0;
|
|
143
143
|
const ranges = [];
|
|
144
144
|
for (const item of input.ranges) {
|
|
145
|
-
const range = record$
|
|
145
|
+
const range = record$13(item);
|
|
146
146
|
if (range === void 0 || !finiteInteger(range.start) || !finiteInteger(range.end) || range.end < range.start) return void 0;
|
|
147
147
|
ranges.push({
|
|
148
148
|
start: range.start,
|
|
@@ -151,14 +151,14 @@ function rewind(value) {
|
|
|
151
151
|
}
|
|
152
152
|
const anchors = [];
|
|
153
153
|
for (const item of input.anchors) {
|
|
154
|
-
const anchor = record$
|
|
154
|
+
const anchor = record$13(item);
|
|
155
155
|
if (anchor === void 0 || !finiteInteger(anchor.turn) || !string$4(anchor.uuid, 128)) return void 0;
|
|
156
156
|
anchors.push({
|
|
157
157
|
turn: anchor.turn,
|
|
158
158
|
uuid: anchor.uuid
|
|
159
159
|
});
|
|
160
160
|
}
|
|
161
|
-
const pending = record$
|
|
161
|
+
const pending = record$13(input.pending);
|
|
162
162
|
if (input.pending !== void 0 && pending === void 0) return void 0;
|
|
163
163
|
if (pending === void 0) return {
|
|
164
164
|
ranges,
|
|
@@ -177,12 +177,12 @@ function rewind(value) {
|
|
|
177
177
|
};
|
|
178
178
|
}
|
|
179
179
|
function tasks(value) {
|
|
180
|
-
const input = record$
|
|
180
|
+
const input = record$13(value);
|
|
181
181
|
if (input === void 0 || !Array.isArray(input.tasks)) return void 0;
|
|
182
182
|
return normalizeTasksEvent(input.tasks);
|
|
183
183
|
}
|
|
184
184
|
function parseClaudeSidecar(value) {
|
|
185
|
-
const input = record$
|
|
185
|
+
const input = record$13(value);
|
|
186
186
|
if (input === void 0 || input.schemaVersion !== SIDECAR_SCHEMA_VERSION || !finiteInteger(input.revision) || !Array.isArray(input.activities) || input.activities.length > MAX_ACTIVITIES) throw new Error("dsh-claude: invalid sidecar document");
|
|
187
187
|
const activities = input.activities.map(activity);
|
|
188
188
|
if (activities.some((item) => item === void 0)) throw new Error("dsh-claude: invalid sidecar activity");
|
|
@@ -786,7 +786,7 @@ function createUserQuestionBridge(userQuestions, activeContext) {
|
|
|
786
786
|
}
|
|
787
787
|
//#endregion
|
|
788
788
|
//#region src/sdk-messages.ts
|
|
789
|
-
function record$
|
|
789
|
+
function record$12(value) {
|
|
790
790
|
return value !== null && typeof value === "object" ? value : void 0;
|
|
791
791
|
}
|
|
792
792
|
function string$3(value) {
|
|
@@ -818,12 +818,12 @@ function hasUsageCounts(usage) {
|
|
|
818
818
|
return usage.inputTokens !== void 0 || usage.outputTokens !== void 0 || usage.cacheReadTokens !== void 0 || usage.cacheCreationTokens !== void 0;
|
|
819
819
|
}
|
|
820
820
|
function resultUsage(message) {
|
|
821
|
-
const normalized = usageOf(record$
|
|
821
|
+
const normalized = usageOf(record$12(message.usage));
|
|
822
822
|
if (typeof message.total_cost_usd === "number") normalized.cumulativeCostUsd = message.total_cost_usd;
|
|
823
823
|
return normalized;
|
|
824
824
|
}
|
|
825
825
|
function normalizeAssistant(message) {
|
|
826
|
-
const envelope = record$
|
|
826
|
+
const envelope = record$12(message.message);
|
|
827
827
|
const content = envelope?.content;
|
|
828
828
|
if (!Array.isArray(content)) return [{
|
|
829
829
|
kind: "protocol-error",
|
|
@@ -833,7 +833,7 @@ function normalizeAssistant(message) {
|
|
|
833
833
|
const parentToolUseId = string$3(message.parent_tool_use_id);
|
|
834
834
|
const normalized = [];
|
|
835
835
|
for (const item of content) {
|
|
836
|
-
const block = record$
|
|
836
|
+
const block = record$12(item);
|
|
837
837
|
if (block === void 0) continue;
|
|
838
838
|
if (block.type === "text") {
|
|
839
839
|
const text = string$3(block.text);
|
|
@@ -862,7 +862,7 @@ function normalizeAssistant(message) {
|
|
|
862
862
|
});
|
|
863
863
|
}
|
|
864
864
|
}
|
|
865
|
-
const usage = usageOf(record$
|
|
865
|
+
const usage = usageOf(record$12(envelope?.usage));
|
|
866
866
|
if (hasUsageCounts(usage)) normalized.push({
|
|
867
867
|
kind: "request-usage",
|
|
868
868
|
usage,
|
|
@@ -872,7 +872,7 @@ function normalizeAssistant(message) {
|
|
|
872
872
|
}
|
|
873
873
|
function normalizeUser(message) {
|
|
874
874
|
if (message.isReplay === true) return [];
|
|
875
|
-
const content = record$
|
|
875
|
+
const content = record$12(message.message)?.content;
|
|
876
876
|
if (typeof content === "string") return [];
|
|
877
877
|
if (!Array.isArray(content)) return [{
|
|
878
878
|
kind: "protocol-error",
|
|
@@ -882,7 +882,7 @@ function normalizeUser(message) {
|
|
|
882
882
|
const parentToolUseId = string$3(message.parent_tool_use_id);
|
|
883
883
|
const normalized = [];
|
|
884
884
|
for (const item of content) {
|
|
885
|
-
const block = record$
|
|
885
|
+
const block = record$12(item);
|
|
886
886
|
if (block?.type !== "tool_result") continue;
|
|
887
887
|
const toolUseId = string$3(block.tool_use_id);
|
|
888
888
|
if (toolUseId === void 0) continue;
|
|
@@ -963,7 +963,7 @@ function normalizeSystem(message) {
|
|
|
963
963
|
const summary = string$3(message.summary);
|
|
964
964
|
const subagentType = string$3(message.subagent_type);
|
|
965
965
|
const lastToolName = string$3(message.last_tool_name);
|
|
966
|
-
const usage = taskUsageOf(record$
|
|
966
|
+
const usage = taskUsageOf(record$12(message.usage));
|
|
967
967
|
return [{
|
|
968
968
|
kind: "subagent",
|
|
969
969
|
title: summary ?? description ?? "Claude subagent update",
|
|
@@ -979,7 +979,7 @@ function normalizeSystem(message) {
|
|
|
979
979
|
}];
|
|
980
980
|
}
|
|
981
981
|
if (subtype === "task_updated") {
|
|
982
|
-
const patch = record$
|
|
982
|
+
const patch = record$12(message.patch);
|
|
983
983
|
const status = string$3(patch?.status);
|
|
984
984
|
const taskId = string$3(message.task_id);
|
|
985
985
|
const description = string$3(patch?.description);
|
|
@@ -1002,7 +1002,7 @@ function normalizeSystem(message) {
|
|
|
1002
1002
|
const taskId = string$3(message.task_id);
|
|
1003
1003
|
const summary = string$3(message.summary);
|
|
1004
1004
|
const taskStatus = failed ? "failed" : stopped ? "stopped" : "completed";
|
|
1005
|
-
const usage = taskUsageOf(record$
|
|
1005
|
+
const usage = taskUsageOf(record$12(message.usage));
|
|
1006
1006
|
return [{
|
|
1007
1007
|
kind: "subagent",
|
|
1008
1008
|
title: summary ?? taskId ?? "Claude subagent finished",
|
|
@@ -1017,7 +1017,7 @@ function normalizeSystem(message) {
|
|
|
1017
1017
|
if (subtype === "background_tasks_changed") return [{
|
|
1018
1018
|
kind: "background-tasks",
|
|
1019
1019
|
tasks: (Array.isArray(message.tasks) ? message.tasks : []).flatMap((item) => {
|
|
1020
|
-
const entry = record$
|
|
1020
|
+
const entry = record$12(item);
|
|
1021
1021
|
const taskId = string$3(entry?.task_id);
|
|
1022
1022
|
const description = string$3(entry?.description);
|
|
1023
1023
|
const taskType = string$3(entry?.task_type);
|
|
@@ -1035,7 +1035,7 @@ function normalizeSystem(message) {
|
|
|
1035
1035
|
detail: message
|
|
1036
1036
|
}];
|
|
1037
1037
|
if (subtype === "compact_boundary") {
|
|
1038
|
-
const metadata = record$
|
|
1038
|
+
const metadata = record$12(message.compact_metadata);
|
|
1039
1039
|
const trigger = metadata?.trigger === "auto" || metadata?.trigger === "manual" ? metadata.trigger : void 0;
|
|
1040
1040
|
const preTokens = finiteNumber(metadata?.pre_tokens);
|
|
1041
1041
|
const postTokens = finiteNumber(metadata?.post_tokens);
|
|
@@ -1074,10 +1074,10 @@ const RESULT_ERROR_SUBTYPES = /* @__PURE__ */ new Set([
|
|
|
1074
1074
|
function normalizeSdkMessage(message) {
|
|
1075
1075
|
const value = message;
|
|
1076
1076
|
if (value.type === "stream_event") {
|
|
1077
|
-
const event = record$
|
|
1077
|
+
const event = record$12(value.event);
|
|
1078
1078
|
const parentToolUseId = string$3(value.parent_tool_use_id);
|
|
1079
1079
|
if (event?.type === "content_block_delta") {
|
|
1080
|
-
const delta = record$
|
|
1080
|
+
const delta = record$12(event.delta);
|
|
1081
1081
|
if (delta?.type === "text_delta") {
|
|
1082
1082
|
const text = string$3(delta.text);
|
|
1083
1083
|
return text === void 0 ? [] : [{
|
|
@@ -1112,7 +1112,7 @@ function normalizeSdkMessage(message) {
|
|
|
1112
1112
|
const errors = Array.isArray(value.errors) ? value.errors.filter((item) => typeof item === "string") : void 0;
|
|
1113
1113
|
const terminalReason = string$3(value.terminal_reason);
|
|
1114
1114
|
const userMessageUuid = string$3(value.user_message_uuid);
|
|
1115
|
-
const permissionDenials = Array.isArray(value.permission_denials) ? value.permission_denials.map((item) => record$
|
|
1115
|
+
const permissionDenials = Array.isArray(value.permission_denials) ? value.permission_denials.map((item) => record$12(item)).filter((item) => item !== void 0).map((item) => {
|
|
1116
1116
|
const toolName = string$3(item.tool_name);
|
|
1117
1117
|
const toolUseId = string$3(item.tool_use_id);
|
|
1118
1118
|
return toolName === void 0 || toolUseId === void 0 ? void 0 : {
|
|
@@ -1138,7 +1138,7 @@ function normalizeSdkMessage(message) {
|
|
|
1138
1138
|
detail: value.error ?? value.output
|
|
1139
1139
|
}];
|
|
1140
1140
|
if (value.type === "rate_limit_event") {
|
|
1141
|
-
const status = string$3(record$
|
|
1141
|
+
const status = string$3(record$12(value.rate_limit_info)?.status);
|
|
1142
1142
|
return [{
|
|
1143
1143
|
kind: "status",
|
|
1144
1144
|
title: status !== void 0 && status !== "allowed" ? "Claude rate limit is blocking requests" : "Claude rate limit status changed",
|
|
@@ -1177,7 +1177,7 @@ const FIXED_WINDOWS = [
|
|
|
1177
1177
|
"seven_day_opus",
|
|
1178
1178
|
"seven_day_sonnet"
|
|
1179
1179
|
];
|
|
1180
|
-
function record$
|
|
1180
|
+
function record$11(value) {
|
|
1181
1181
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
1182
1182
|
}
|
|
1183
1183
|
/** Utilization is documented as 0-100; clamp so a server glitch cannot render
|
|
@@ -1189,7 +1189,7 @@ function resetsAt(value) {
|
|
|
1189
1189
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
1190
1190
|
}
|
|
1191
1191
|
function window(id, source, label) {
|
|
1192
|
-
const entry = record$
|
|
1192
|
+
const entry = record$11(source);
|
|
1193
1193
|
if (entry === void 0) return void 0;
|
|
1194
1194
|
const used = utilization(entry.utilization);
|
|
1195
1195
|
const reset = resetsAt(entry.resets_at);
|
|
@@ -1203,9 +1203,9 @@ function window(id, source, label) {
|
|
|
1203
1203
|
}
|
|
1204
1204
|
/** Project the SDK's `/usage` response onto the windows the settings card shows. */
|
|
1205
1205
|
function normalizePlanUsage(value, fetchedAt) {
|
|
1206
|
-
const response = record$
|
|
1206
|
+
const response = record$11(value);
|
|
1207
1207
|
const subscription = typeof response?.subscription_type === "string" ? response.subscription_type : void 0;
|
|
1208
|
-
const limits = record$
|
|
1208
|
+
const limits = record$11(response?.rate_limits);
|
|
1209
1209
|
if (response?.rate_limits_available !== true || limits === void 0) return {
|
|
1210
1210
|
available: false,
|
|
1211
1211
|
...subscription === void 0 ? {} : { subscription },
|
|
@@ -1213,7 +1213,7 @@ function normalizePlanUsage(value, fetchedAt) {
|
|
|
1213
1213
|
fetchedAt
|
|
1214
1214
|
};
|
|
1215
1215
|
const windows = [...FIXED_WINDOWS.map((id) => window(id, limits[id])), ...(Array.isArray(limits.model_scoped) ? limits.model_scoped : []).map((entry, index) => {
|
|
1216
|
-
const name = record$
|
|
1216
|
+
const name = record$11(entry)?.display_name;
|
|
1217
1217
|
return window(`model:${typeof name === "string" ? name : index}`, entry, typeof name === "string" ? name : void 0);
|
|
1218
1218
|
})].filter((entry) => entry !== void 0);
|
|
1219
1219
|
return {
|
|
@@ -2085,7 +2085,7 @@ var ClaudeSupervisor = class {
|
|
|
2085
2085
|
entry.taskSnapshotAt = Date.now();
|
|
2086
2086
|
await this.#sidecar.writeTasks(entry.sessionId, [...entry.tasks.values()]).catch(() => void 0);
|
|
2087
2087
|
}
|
|
2088
|
-
/** What DSH is told about token usage.
|
|
2088
|
+
/** What DSH is told about token usage: newest call's prompt, whole turn's output.
|
|
2089
2089
|
*
|
|
2090
2090
|
* `TokenUsage` is documented as "token accounting for ONE model call", and
|
|
2091
2091
|
* DSH's token meter divides `uncachedInput + cacheRead + cacheWrite` by the
|
|
@@ -2095,10 +2095,20 @@ var ClaudeSupervisor = class {
|
|
|
2095
2095
|
* times, which sums past the window without the conversation ever growing.
|
|
2096
2096
|
* The newest single call answers "how big is this conversation now".
|
|
2097
2097
|
*
|
|
2098
|
-
*
|
|
2099
|
-
*
|
|
2098
|
+
* Output is deliberately excluded from that pressure sum, so the same
|
|
2099
|
+
* argument never applied to it — and taking it from the newest call reported
|
|
2100
|
+
* whatever the wrap-up message happened to cost, which is a couple of tokens
|
|
2101
|
+
* after a turn that wrote thousands. The turn total is the honest figure.
|
|
2102
|
+
*
|
|
2103
|
+
* The sidecar activity keeps the whole turn total for both — that is the
|
|
2104
|
+
* audit and cost record, and nothing divides it by a window. */
|
|
2100
2105
|
#reportedUsage(active, result) {
|
|
2101
|
-
|
|
2106
|
+
const prompt = active.requestUsage;
|
|
2107
|
+
if (prompt === void 0) return result.usage;
|
|
2108
|
+
return {
|
|
2109
|
+
...prompt,
|
|
2110
|
+
...result.usage.outputTokens === void 0 ? {} : { outputTokens: result.usage.outputTokens }
|
|
2111
|
+
};
|
|
2102
2112
|
}
|
|
2103
2113
|
async #completeProgressSegment(active, result, recordUsage = true) {
|
|
2104
2114
|
if (recordUsage && (result.usage.inputTokens !== void 0 || result.usage.outputTokens !== void 0 || result.usage.cumulativeCostUsd !== void 0)) {
|
|
@@ -3148,14 +3158,14 @@ function parseGitHubRemote(value) {
|
|
|
3148
3158
|
if (match?.[1] === void 0 || match[2] === void 0) return void 0;
|
|
3149
3159
|
return `${match[1]}/${match[2]}`;
|
|
3150
3160
|
}
|
|
3151
|
-
function record$
|
|
3161
|
+
function record$10(value) {
|
|
3152
3162
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
3153
3163
|
}
|
|
3154
3164
|
function aggregateChecks(value) {
|
|
3155
3165
|
if (!Array.isArray(value) || value.length === 0) return "none";
|
|
3156
3166
|
let pending = false;
|
|
3157
3167
|
for (const item of value) {
|
|
3158
|
-
const check = record$
|
|
3168
|
+
const check = record$10(item);
|
|
3159
3169
|
if (check === void 0) continue;
|
|
3160
3170
|
const conclusion = typeof check.conclusion === "string" ? check.conclusion.toUpperCase() : void 0;
|
|
3161
3171
|
const status = typeof check.status === "string" ? check.status.toUpperCase() : void 0;
|
|
@@ -3179,7 +3189,7 @@ function reviewState(value) {
|
|
|
3179
3189
|
return "none";
|
|
3180
3190
|
}
|
|
3181
3191
|
function parsePullRequest(value) {
|
|
3182
|
-
const input = record$
|
|
3192
|
+
const input = record$10(value);
|
|
3183
3193
|
if (input === void 0 || !Number.isSafeInteger(input.number) || Number(input.number) <= 0 || typeof input.title !== "string" || typeof input.url !== "string") return void 0;
|
|
3184
3194
|
let url;
|
|
3185
3195
|
try {
|
|
@@ -3200,7 +3210,7 @@ function parsePullRequest(value) {
|
|
|
3200
3210
|
review: reviewState(input.reviewDecision),
|
|
3201
3211
|
checks: aggregateChecks(input.statusCheckRollup),
|
|
3202
3212
|
...typeof input.mergeStateStatus === "string" ? { mergeState: bounded(input.mergeStateStatus) } : {},
|
|
3203
|
-
...typeof record$
|
|
3213
|
+
...typeof record$10(input.author)?.login === "string" ? { author: bounded(String(record$10(input.author)?.login)) } : {},
|
|
3204
3214
|
...typeof input.createdAt === "string" && Number.isFinite(Date.parse(input.createdAt)) ? { createdAt: new Date(input.createdAt).toISOString() } : {},
|
|
3205
3215
|
...typeof input.mergedAt === "string" && Number.isFinite(Date.parse(input.mergedAt)) ? { mergedAt: new Date(input.mergedAt).toISOString() } : {},
|
|
3206
3216
|
...typeof input.baseRefName === "string" && bounded(input.baseRefName).length > 0 ? { baseBranch: bounded(input.baseRefName) } : {}
|
|
@@ -3981,6 +3991,24 @@ const MAX_UNPUSHED_COMMITS = 20;
|
|
|
3981
3991
|
const GIT_TIMEOUT_MS$1 = 15e3;
|
|
3982
3992
|
const REMOTE_TIMEOUT_MS = 6e4;
|
|
3983
3993
|
const GENERATE_TIMEOUT_MS = 6e4;
|
|
3994
|
+
/** One cold `claude -p` per generation, so everything the subject line cannot
|
|
3995
|
+
* use is cost: MCP servers (which `ask` already skips for the same reason,
|
|
3996
|
+
* and which stall for as long as an unreachable one takes to give up) and the
|
|
3997
|
+
* user's own hooks and settings. Project settings stay: a repository's commit
|
|
3998
|
+
* conventions belong in the message. `--tools ''` keeps `--output-format`
|
|
3999
|
+
* between it and the prompt -- both flags are variadic. */
|
|
4000
|
+
const GENERATE_ARGUMENTS = [
|
|
4001
|
+
"-p",
|
|
4002
|
+
"--strict-mcp-config",
|
|
4003
|
+
"--mcp-config",
|
|
4004
|
+
"{\"mcpServers\":{}}",
|
|
4005
|
+
"--setting-sources",
|
|
4006
|
+
"project,local",
|
|
4007
|
+
"--tools",
|
|
4008
|
+
"",
|
|
4009
|
+
"--output-format",
|
|
4010
|
+
"text"
|
|
4011
|
+
];
|
|
3984
4012
|
var RepositoryActionError = class extends Error {
|
|
3985
4013
|
code;
|
|
3986
4014
|
commit;
|
|
@@ -4084,14 +4112,7 @@ var RepositoryActionService = class {
|
|
|
4084
4112
|
`Diff:\n${preview.patch.slice(0, 24576)}`
|
|
4085
4113
|
].join("\n");
|
|
4086
4114
|
try {
|
|
4087
|
-
const result = await this.#run(this.#claudeExecutable,
|
|
4088
|
-
"-p",
|
|
4089
|
-
"--tools",
|
|
4090
|
-
"",
|
|
4091
|
-
"--output-format",
|
|
4092
|
-
"text",
|
|
4093
|
-
prompt
|
|
4094
|
-
], preview.root, GENERATE_TIMEOUT_MS);
|
|
4115
|
+
const result = await this.#run(this.#claudeExecutable, GENERATE_ARGUMENTS.concat(prompt), preview.root, GENERATE_TIMEOUT_MS);
|
|
4095
4116
|
return result.exitCode === 0 && !result.lossy ? normalizedGeneratedMessage(result.stdout, fallback) : fallback;
|
|
4096
4117
|
} catch {
|
|
4097
4118
|
return fallback;
|
|
@@ -4420,20 +4441,20 @@ var RepositoryActionService = class {
|
|
|
4420
4441
|
};
|
|
4421
4442
|
//#endregion
|
|
4422
4443
|
//#region src/repository-setup-routes.ts
|
|
4423
|
-
const MAX_BODY_BYTES$
|
|
4424
|
-
function record$
|
|
4444
|
+
const MAX_BODY_BYTES$6 = 16384;
|
|
4445
|
+
function record$9(value) {
|
|
4425
4446
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
4426
4447
|
}
|
|
4427
|
-
async function readJson$
|
|
4448
|
+
async function readJson$6(req) {
|
|
4428
4449
|
const chunks = [];
|
|
4429
4450
|
let size = 0;
|
|
4430
4451
|
for await (const chunk of req) {
|
|
4431
4452
|
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
4432
4453
|
size += buffer.length;
|
|
4433
|
-
if (size > MAX_BODY_BYTES$
|
|
4454
|
+
if (size > MAX_BODY_BYTES$6) throw new RepositorySetupError("body-too-large", "The request body is too large.");
|
|
4434
4455
|
chunks.push(buffer);
|
|
4435
4456
|
}
|
|
4436
|
-
const value = record$
|
|
4457
|
+
const value = record$9(JSON.parse(Buffer.concat(chunks).toString("utf8")));
|
|
4437
4458
|
if (value === void 0) throw new RepositorySetupError("invalid-request", "The request body is invalid.");
|
|
4438
4459
|
return value;
|
|
4439
4460
|
}
|
|
@@ -4495,7 +4516,7 @@ function registerRepositorySetupRoute(ctx, service) {
|
|
|
4495
4516
|
try {
|
|
4496
4517
|
if (pathname === `/plugins/dsh-claude/repository/setup/branches/refresh`) {
|
|
4497
4518
|
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
4498
|
-
const input = await readJson$
|
|
4519
|
+
const input = await readJson$6(req);
|
|
4499
4520
|
return json(res, 200, await service.refreshBranches(string$2(input, "cwd")));
|
|
4500
4521
|
}
|
|
4501
4522
|
if (pathname === `/plugins/dsh-claude/repository/setup/branches`) {
|
|
@@ -4506,19 +4527,19 @@ function registerRepositorySetupRoute(ctx, service) {
|
|
|
4506
4527
|
}
|
|
4507
4528
|
if (pathname === "/plugins/dsh-claude/repository/setup") {
|
|
4508
4529
|
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
4509
|
-
const input = await readJson$
|
|
4530
|
+
const input = await readJson$6(req);
|
|
4510
4531
|
if (typeof input.worktree !== "boolean") throw new RepositorySetupError("invalid-request", "The worktree field is required.");
|
|
4511
4532
|
await streamSetup(res, service, input);
|
|
4512
4533
|
return;
|
|
4513
4534
|
}
|
|
4514
4535
|
if (pathname === `/plugins/dsh-claude/repository/setup/cleanup`) {
|
|
4515
4536
|
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
4516
|
-
const input = await readJson$
|
|
4537
|
+
const input = await readJson$6(req);
|
|
4517
4538
|
return json(res, 200, await service.cleanupMerged(string$2(input, "path"), string$2(input, "baseBranch")));
|
|
4518
4539
|
}
|
|
4519
4540
|
if (pathname === `/plugins/dsh-claude/repository/setup/bind`) {
|
|
4520
4541
|
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
4521
|
-
const input = await readJson$
|
|
4542
|
+
const input = await readJson$6(req);
|
|
4522
4543
|
await service.bindLease(string$2(input, "leaseId"), string$2(input, "sessionId"));
|
|
4523
4544
|
return json(res, 200, { ok: true });
|
|
4524
4545
|
}
|
|
@@ -4536,7 +4557,7 @@ function registerRepositorySetupRoute(ctx, service) {
|
|
|
4536
4557
|
}
|
|
4537
4558
|
//#endregion
|
|
4538
4559
|
//#region src/repository-action-routes.ts
|
|
4539
|
-
const MAX_BODY_BYTES$
|
|
4560
|
+
const MAX_BODY_BYTES$5 = 16384;
|
|
4540
4561
|
const MAX_SESSION_ID_CHARS$5 = 1024;
|
|
4541
4562
|
const ACTIONS = /* @__PURE__ */ new Set([
|
|
4542
4563
|
"commit",
|
|
@@ -4546,19 +4567,19 @@ const ACTIONS = /* @__PURE__ */ new Set([
|
|
|
4546
4567
|
"merge-pr",
|
|
4547
4568
|
"update-branch"
|
|
4548
4569
|
]);
|
|
4549
|
-
function record$
|
|
4570
|
+
function record$8(value) {
|
|
4550
4571
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
4551
4572
|
}
|
|
4552
|
-
async function readJson$
|
|
4573
|
+
async function readJson$5(req) {
|
|
4553
4574
|
const chunks = [];
|
|
4554
4575
|
let size = 0;
|
|
4555
4576
|
for await (const chunk of req) {
|
|
4556
4577
|
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
4557
4578
|
size += buffer.length;
|
|
4558
|
-
if (size > MAX_BODY_BYTES$
|
|
4579
|
+
if (size > MAX_BODY_BYTES$5) throw new RepositoryActionError("body-too-large", "The request body is too large.");
|
|
4559
4580
|
chunks.push(buffer);
|
|
4560
4581
|
}
|
|
4561
|
-
const value = record$
|
|
4582
|
+
const value = record$8(JSON.parse(Buffer.concat(chunks).toString("utf8")));
|
|
4562
4583
|
if (value === void 0) throw new RepositoryActionError("invalid-request", "The request body is invalid.");
|
|
4563
4584
|
return value;
|
|
4564
4585
|
}
|
|
@@ -4613,12 +4634,12 @@ function registerRepositoryActionRoute(ctx, service, cwdForSession) {
|
|
|
4613
4634
|
}
|
|
4614
4635
|
if (url.pathname === `/plugins/dsh-claude/repository/action/message`) {
|
|
4615
4636
|
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
4616
|
-
const input = await readJson$
|
|
4637
|
+
const input = await readJson$5(req);
|
|
4617
4638
|
return json(res, 200, { message: await service.generateMessage(cwd, string$1(input, "fingerprint")) });
|
|
4618
4639
|
}
|
|
4619
4640
|
if (url.pathname === "/plugins/dsh-claude/repository/action") {
|
|
4620
4641
|
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
4621
|
-
return json(res, 200, await service.execute(cwd, actionRequest(await readJson$
|
|
4642
|
+
return json(res, 200, await service.execute(cwd, actionRequest(await readJson$5(req))));
|
|
4622
4643
|
}
|
|
4623
4644
|
return json(res, 404, { error: "not found" });
|
|
4624
4645
|
} catch (error) {
|
|
@@ -4818,6 +4839,9 @@ const MAX_OUTPUT_BYTES = 524288;
|
|
|
4818
4839
|
const GIT_TIMEOUT_MS = 1e4;
|
|
4819
4840
|
const GH_TIMEOUT_MS = 6e4;
|
|
4820
4841
|
const MAX_COMMENTS = 100;
|
|
4842
|
+
const MAX_THREADS = 100;
|
|
4843
|
+
const MAX_MENTIONABLE_USERS = 20;
|
|
4844
|
+
const MAX_REPLY_CHARS$1 = 2e3;
|
|
4821
4845
|
const MAX_COMMENT_CHARS = 4096;
|
|
4822
4846
|
const MAX_FAILING_CHECKS = 3;
|
|
4823
4847
|
const MAX_LOG_CHARS = 8192;
|
|
@@ -4829,6 +4853,31 @@ var PullRequestFeedbackError = class extends Error {
|
|
|
4829
4853
|
this.code = code;
|
|
4830
4854
|
}
|
|
4831
4855
|
};
|
|
4856
|
+
/** Threads carry the anchor; comments carry the prose. `isOutdated` marks a
|
|
4857
|
+
* thread whose lines the branch has since moved past. */
|
|
4858
|
+
const REVIEW_THREADS_QUERY = `query($owner:String!,$name:String!,$number:Int!){
|
|
4859
|
+
repository(owner:$owner,name:$name){
|
|
4860
|
+
pullRequest(number:$number){
|
|
4861
|
+
reviewThreads(first:100){
|
|
4862
|
+
nodes{
|
|
4863
|
+
id isResolved isOutdated path line originalLine diffSide
|
|
4864
|
+
comments(first:50){ nodes{ databaseId body url createdAt author{ __typename login avatarUrl } } }
|
|
4865
|
+
}
|
|
4866
|
+
}
|
|
4867
|
+
}
|
|
4868
|
+
}
|
|
4869
|
+
}`;
|
|
4870
|
+
const RESOLVE_MUTATION = `mutation($threadId:ID!){
|
|
4871
|
+
resolveReviewThread(input:{threadId:$threadId}){ thread{ isResolved } }
|
|
4872
|
+
}`;
|
|
4873
|
+
const UNRESOLVE_MUTATION = `mutation($threadId:ID!){
|
|
4874
|
+
unresolveReviewThread(input:{threadId:$threadId}){ thread{ isResolved } }
|
|
4875
|
+
}`;
|
|
4876
|
+
const MENTIONABLE_USERS_QUERY = `query($owner:String!,$name:String!,$q:String!){
|
|
4877
|
+
repository(owner:$owner,name:$name){
|
|
4878
|
+
mentionableUsers(first:20,query:$q){ nodes{ login avatarUrl } }
|
|
4879
|
+
}
|
|
4880
|
+
}`;
|
|
4832
4881
|
async function collect(handle) {
|
|
4833
4882
|
const outcome = await handle.done;
|
|
4834
4883
|
const stdout = handle.collected.stdout?.readFrom(0);
|
|
@@ -4838,34 +4887,100 @@ async function collect(handle) {
|
|
|
4838
4887
|
lossy: stdout?.lossy === true
|
|
4839
4888
|
};
|
|
4840
4889
|
}
|
|
4841
|
-
function record$
|
|
4890
|
+
function record$7(value) {
|
|
4842
4891
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
4843
4892
|
}
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4893
|
+
/** Read `repository.pullRequest.reviewThreads.nodes` out of a GraphQL response.
|
|
4894
|
+
* A payload carrying `errors` instead of data yields nothing rather than
|
|
4895
|
+
* throwing: the caller distinguishes "no threads" from "call failed" by the
|
|
4896
|
+
* process exit code. */
|
|
4897
|
+
function parseReviewThreads(value) {
|
|
4898
|
+
const nodes = record$7(record$7(record$7(record$7(record$7(value)?.data)?.repository)?.pullRequest)?.reviewThreads)?.nodes;
|
|
4899
|
+
if (!Array.isArray(nodes)) return [];
|
|
4900
|
+
const threads = [];
|
|
4901
|
+
let total = 0;
|
|
4902
|
+
for (const item of nodes) {
|
|
4903
|
+
const input = record$7(item);
|
|
4904
|
+
if (input === void 0 || typeof input.id !== "string" || input.id.length === 0) continue;
|
|
4851
4905
|
const path = typeof input.path === "string" ? input.path : "";
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
const
|
|
4855
|
-
const
|
|
4856
|
-
comments.push({
|
|
4857
|
-
id: Number(input.id),
|
|
4906
|
+
if (path.length === 0) continue;
|
|
4907
|
+
const line = Number.isSafeInteger(input.line) ? Number(input.line) : Number.isSafeInteger(input.originalLine) ? Number(input.originalLine) : void 0;
|
|
4908
|
+
const side = input.diffSide === "LEFT" ? "old" : "new";
|
|
4909
|
+
const anchor = {
|
|
4858
4910
|
path,
|
|
4859
4911
|
...line === void 0 ? {} : { line },
|
|
4860
|
-
side
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4912
|
+
side
|
|
4913
|
+
};
|
|
4914
|
+
const comments = [];
|
|
4915
|
+
const commentNodes = record$7(input.comments)?.nodes;
|
|
4916
|
+
for (const node of Array.isArray(commentNodes) ? commentNodes : []) {
|
|
4917
|
+
if (total >= MAX_COMMENTS) break;
|
|
4918
|
+
const comment = record$7(node);
|
|
4919
|
+
if (comment === void 0 || !Number.isSafeInteger(comment.databaseId)) continue;
|
|
4920
|
+
const body = typeof comment.body === "string" ? comment.body.trim() : "";
|
|
4921
|
+
if (body.length === 0) continue;
|
|
4922
|
+
const author = record$7(comment.author);
|
|
4923
|
+
const avatarUrl = githubAvatarUrl(author?.avatarUrl);
|
|
4924
|
+
const login = typeof author?.login === "string" ? author.login : "unknown";
|
|
4925
|
+
comments.push({
|
|
4926
|
+
id: Number(comment.databaseId),
|
|
4927
|
+
...anchor,
|
|
4928
|
+
author: login,
|
|
4929
|
+
...author?.__typename === "Bot" || login.endsWith("[bot]") ? { bot: true } : {},
|
|
4930
|
+
...avatarUrl === void 0 ? {} : { avatarUrl },
|
|
4931
|
+
body: body.slice(0, MAX_COMMENT_CHARS),
|
|
4932
|
+
url: typeof comment.url === "string" ? comment.url : "",
|
|
4933
|
+
...typeof comment.createdAt === "string" ? { createdAt: comment.createdAt } : {}
|
|
4934
|
+
});
|
|
4935
|
+
total += 1;
|
|
4936
|
+
}
|
|
4937
|
+
if (comments.length === 0) continue;
|
|
4938
|
+
threads.push({
|
|
4939
|
+
id: input.id,
|
|
4940
|
+
...anchor,
|
|
4941
|
+
resolved: input.isResolved === true,
|
|
4942
|
+
outdated: input.isOutdated === true,
|
|
4943
|
+
comments
|
|
4865
4944
|
});
|
|
4866
|
-
if (
|
|
4945
|
+
if (threads.length >= MAX_THREADS || total >= MAX_COMMENTS) break;
|
|
4946
|
+
}
|
|
4947
|
+
return threads;
|
|
4948
|
+
}
|
|
4949
|
+
/** One posted reply, shaped like the thread comments it joins. */
|
|
4950
|
+
function parseReplyComment(value, anchor) {
|
|
4951
|
+
const input = record$7(value);
|
|
4952
|
+
if (input === void 0 || !Number.isSafeInteger(input.id)) return void 0;
|
|
4953
|
+
const body = typeof input.body === "string" ? input.body.trim() : "";
|
|
4954
|
+
if (body.length === 0) return void 0;
|
|
4955
|
+
const user = record$7(input.user);
|
|
4956
|
+
const avatarUrl = githubAvatarUrl(user?.avatar_url);
|
|
4957
|
+
const login = typeof user?.login === "string" ? user.login : "unknown";
|
|
4958
|
+
return {
|
|
4959
|
+
id: Number(input.id),
|
|
4960
|
+
...anchor,
|
|
4961
|
+
author: login,
|
|
4962
|
+
...user?.type === "Bot" || login.endsWith("[bot]") ? { bot: true } : {},
|
|
4963
|
+
...avatarUrl === void 0 ? {} : { avatarUrl },
|
|
4964
|
+
body: body.slice(0, MAX_COMMENT_CHARS),
|
|
4965
|
+
url: typeof input.html_url === "string" ? input.html_url : "",
|
|
4966
|
+
...typeof input.created_at === "string" ? { createdAt: input.created_at } : {}
|
|
4967
|
+
};
|
|
4968
|
+
}
|
|
4969
|
+
function parseMentionableUsers(value) {
|
|
4970
|
+
const nodes = record$7(record$7(record$7(record$7(value)?.data)?.repository)?.mentionableUsers)?.nodes;
|
|
4971
|
+
if (!Array.isArray(nodes)) return [];
|
|
4972
|
+
const users = [];
|
|
4973
|
+
for (const item of nodes) {
|
|
4974
|
+
const input = record$7(item);
|
|
4975
|
+
if (input === void 0 || typeof input.login !== "string" || input.login.length === 0) continue;
|
|
4976
|
+
const avatarUrl = githubAvatarUrl(input.avatarUrl);
|
|
4977
|
+
users.push({
|
|
4978
|
+
login: input.login,
|
|
4979
|
+
...avatarUrl === void 0 ? {} : { avatarUrl }
|
|
4980
|
+
});
|
|
4981
|
+
if (users.length >= MAX_MENTIONABLE_USERS) break;
|
|
4867
4982
|
}
|
|
4868
|
-
return
|
|
4983
|
+
return users;
|
|
4869
4984
|
}
|
|
4870
4985
|
/** Extract the Actions job id from a check run link, when it is one. */
|
|
4871
4986
|
function actionsJobId(link) {
|
|
@@ -4876,7 +4991,7 @@ function parseFailingChecks(value) {
|
|
|
4876
4991
|
if (!Array.isArray(value)) return [];
|
|
4877
4992
|
const failing = [];
|
|
4878
4993
|
for (const item of value) {
|
|
4879
|
-
const input = record$
|
|
4994
|
+
const input = record$7(item);
|
|
4880
4995
|
if (input === void 0 || input.bucket !== "fail" || typeof input.name !== "string") continue;
|
|
4881
4996
|
failing.push({
|
|
4882
4997
|
name: input.name,
|
|
@@ -4898,22 +5013,103 @@ var PullRequestFeedbackService = class {
|
|
|
4898
5013
|
constructor(runtime) {
|
|
4899
5014
|
this.#runtime = runtime;
|
|
4900
5015
|
}
|
|
4901
|
-
async
|
|
4902
|
-
const
|
|
5016
|
+
async threads(cwd, pullNumber) {
|
|
5017
|
+
const { owner, name } = await this.#repositoryParts(cwd);
|
|
4903
5018
|
const gh = await this.#gh();
|
|
4904
5019
|
const result = await this.#run(gh, [
|
|
4905
5020
|
"api",
|
|
4906
|
-
|
|
4907
|
-
"
|
|
5021
|
+
"graphql",
|
|
5022
|
+
"-f",
|
|
5023
|
+
`owner=${owner}`,
|
|
5024
|
+
"-f",
|
|
5025
|
+
`name=${name}`,
|
|
5026
|
+
"-F",
|
|
5027
|
+
`number=${pullNumber}`,
|
|
5028
|
+
"-f",
|
|
5029
|
+
`query=${REVIEW_THREADS_QUERY}`
|
|
4908
5030
|
], cwd, GH_TIMEOUT_MS);
|
|
4909
5031
|
if (result.exitCode !== 0) throw new PullRequestFeedbackError("comments-unavailable", "Pull request comments could not be loaded.");
|
|
4910
5032
|
try {
|
|
4911
|
-
|
|
4912
|
-
return parseReviewComments(JSON.parse(pages));
|
|
5033
|
+
return parseReviewThreads(JSON.parse(result.stdout));
|
|
4913
5034
|
} catch {
|
|
4914
5035
|
throw new PullRequestFeedbackError("comments-unavailable", "Pull request comments could not be parsed.");
|
|
4915
5036
|
}
|
|
4916
5037
|
}
|
|
5038
|
+
/** Post a reply into the thread the given comment belongs to. GitHub parses
|
|
5039
|
+
* `@login` in the body server-side, so mentions need nothing from us. */
|
|
5040
|
+
async reply(cwd, pullNumber, commentId, body) {
|
|
5041
|
+
const text = body.trim();
|
|
5042
|
+
if (text.length === 0 || text.length > MAX_REPLY_CHARS$1) throw new PullRequestFeedbackError("invalid-request", "The reply body is invalid.");
|
|
5043
|
+
const repository = await this.#repository(cwd);
|
|
5044
|
+
const gh = await this.#gh();
|
|
5045
|
+
const result = await this.#run(gh, [
|
|
5046
|
+
"api",
|
|
5047
|
+
"-X",
|
|
5048
|
+
"POST",
|
|
5049
|
+
`repos/${repository}/pulls/${pullNumber}/comments/${commentId}/replies`,
|
|
5050
|
+
"--input",
|
|
5051
|
+
"-"
|
|
5052
|
+
], cwd, GH_TIMEOUT_MS, JSON.stringify({ body: text }));
|
|
5053
|
+
if (result.exitCode !== 0) throw new PullRequestFeedbackError("reply-failed", "The reply could not be posted.");
|
|
5054
|
+
let posted;
|
|
5055
|
+
try {
|
|
5056
|
+
const parsed = JSON.parse(result.stdout);
|
|
5057
|
+
const path = typeof record$7(parsed)?.path === "string" ? String(record$7(parsed)?.path) : "";
|
|
5058
|
+
const line = Number.isSafeInteger(record$7(parsed)?.line) ? Number(record$7(parsed)?.line) : void 0;
|
|
5059
|
+
posted = parseReplyComment(parsed, {
|
|
5060
|
+
path,
|
|
5061
|
+
...line === void 0 ? {} : { line },
|
|
5062
|
+
side: record$7(parsed)?.side === "LEFT" ? "old" : "new"
|
|
5063
|
+
});
|
|
5064
|
+
} catch {
|
|
5065
|
+
posted = void 0;
|
|
5066
|
+
}
|
|
5067
|
+
if (posted === void 0) throw new PullRequestFeedbackError("reply-failed", "The posted reply could not be read back.");
|
|
5068
|
+
return posted;
|
|
5069
|
+
}
|
|
5070
|
+
/** Resolve or reopen a thread. Returns the state GitHub reports afterwards. */
|
|
5071
|
+
async setResolved(cwd, threadId, resolved) {
|
|
5072
|
+
const gh = await this.#gh();
|
|
5073
|
+
const result = await this.#run(gh, [
|
|
5074
|
+
"api",
|
|
5075
|
+
"graphql",
|
|
5076
|
+
"-f",
|
|
5077
|
+
`threadId=${threadId}`,
|
|
5078
|
+
"-f",
|
|
5079
|
+
`query=${resolved ? RESOLVE_MUTATION : UNRESOLVE_MUTATION}`
|
|
5080
|
+
], cwd, GH_TIMEOUT_MS);
|
|
5081
|
+
if (result.exitCode !== 0) throw new PullRequestFeedbackError("resolve-failed", "The thread could not be updated.");
|
|
5082
|
+
try {
|
|
5083
|
+
const thread = record$7(record$7(record$7(record$7(JSON.parse(result.stdout))?.data)?.[resolved ? "resolveReviewThread" : "unresolveReviewThread"])?.thread);
|
|
5084
|
+
if (typeof thread?.isResolved !== "boolean") throw new Error("missing state");
|
|
5085
|
+
return thread.isResolved;
|
|
5086
|
+
} catch {
|
|
5087
|
+
throw new PullRequestFeedbackError("resolve-failed", "The thread state could not be read back.");
|
|
5088
|
+
}
|
|
5089
|
+
}
|
|
5090
|
+
/** Logins GitHub would notify from this repository, for the reply composer. */
|
|
5091
|
+
async mentionables(cwd, query) {
|
|
5092
|
+
const { owner, name } = await this.#repositoryParts(cwd);
|
|
5093
|
+
const gh = await this.#gh();
|
|
5094
|
+
const result = await this.#run(gh, [
|
|
5095
|
+
"api",
|
|
5096
|
+
"graphql",
|
|
5097
|
+
"-f",
|
|
5098
|
+
`owner=${owner}`,
|
|
5099
|
+
"-f",
|
|
5100
|
+
`name=${name}`,
|
|
5101
|
+
"-f",
|
|
5102
|
+
`q=${query}`,
|
|
5103
|
+
"-f",
|
|
5104
|
+
`query=${MENTIONABLE_USERS_QUERY}`
|
|
5105
|
+
], cwd, GH_TIMEOUT_MS);
|
|
5106
|
+
if (result.exitCode !== 0) return [];
|
|
5107
|
+
try {
|
|
5108
|
+
return parseMentionableUsers(JSON.parse(result.stdout));
|
|
5109
|
+
} catch {
|
|
5110
|
+
return [];
|
|
5111
|
+
}
|
|
5112
|
+
}
|
|
4917
5113
|
async failingChecks(cwd, pullNumber) {
|
|
4918
5114
|
const gh = await this.#gh();
|
|
4919
5115
|
const result = await this.#run(gh, [
|
|
@@ -4961,6 +5157,16 @@ var PullRequestFeedbackService = class {
|
|
|
4961
5157
|
if (repository === void 0) throw new PullRequestFeedbackError("no-github-remote", "The repository has no GitHub origin remote.");
|
|
4962
5158
|
return repository;
|
|
4963
5159
|
}
|
|
5160
|
+
/** GraphQL takes the owner and the name as separate variables, so they never
|
|
5161
|
+
* reach the query text itself. */
|
|
5162
|
+
async #repositoryParts(cwd) {
|
|
5163
|
+
const [owner, name] = (await this.#repository(cwd)).split("/");
|
|
5164
|
+
if (owner === void 0 || name === void 0 || owner.length === 0 || name.length === 0) throw new PullRequestFeedbackError("no-github-remote", "The repository has no GitHub origin remote.");
|
|
5165
|
+
return {
|
|
5166
|
+
owner,
|
|
5167
|
+
name
|
|
5168
|
+
};
|
|
5169
|
+
}
|
|
4964
5170
|
#git() {
|
|
4965
5171
|
this.#gitExecutable ??= this.#runtime.resolveExecutable("git");
|
|
4966
5172
|
return this.#gitExecutable;
|
|
@@ -4971,24 +5177,61 @@ var PullRequestFeedbackService = class {
|
|
|
4971
5177
|
});
|
|
4972
5178
|
return this.#ghExecutable;
|
|
4973
5179
|
}
|
|
4974
|
-
#run(executable, args, cwd, timeoutMs) {
|
|
4975
|
-
|
|
5180
|
+
#run(executable, args, cwd, timeoutMs, input) {
|
|
5181
|
+
const handle = this.#runtime.spawn({
|
|
4976
5182
|
argv: [executable, ...args],
|
|
4977
5183
|
cwd,
|
|
4978
5184
|
stdio: {
|
|
4979
|
-
stdin: "ignore",
|
|
5185
|
+
stdin: input === void 0 ? "ignore" : "pipe",
|
|
4980
5186
|
stdout: { maxBytes: MAX_OUTPUT_BYTES },
|
|
4981
5187
|
stderr: { maxBytes: 65536 }
|
|
4982
5188
|
},
|
|
4983
5189
|
graceMs: 1e3,
|
|
4984
5190
|
signal: AbortSignal.timeout(timeoutMs),
|
|
4985
5191
|
env: {}
|
|
4986
|
-
})
|
|
5192
|
+
});
|
|
5193
|
+
if (input !== void 0) handle.stdin?.end(input);
|
|
5194
|
+
return collect(handle);
|
|
4987
5195
|
}
|
|
4988
5196
|
};
|
|
4989
5197
|
//#endregion
|
|
4990
5198
|
//#region src/pr-feedback-routes.ts
|
|
4991
5199
|
const MAX_SESSION_ID_CHARS$3 = 1024;
|
|
5200
|
+
const MAX_BODY_BYTES$4 = 16384;
|
|
5201
|
+
const MAX_REPLY_CHARS = 2e3;
|
|
5202
|
+
const MAX_THREAD_ID_CHARS = 512;
|
|
5203
|
+
const MAX_MENTION_QUERY_CHARS = 64;
|
|
5204
|
+
function record$6(value) {
|
|
5205
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
5206
|
+
}
|
|
5207
|
+
async function readJson$4(req) {
|
|
5208
|
+
const chunks = [];
|
|
5209
|
+
let size = 0;
|
|
5210
|
+
for await (const chunk of req) {
|
|
5211
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
5212
|
+
size += buffer.length;
|
|
5213
|
+
if (size > MAX_BODY_BYTES$4) throw new PullRequestFeedbackError("body-too-large", "The request body is too large.");
|
|
5214
|
+
chunks.push(buffer);
|
|
5215
|
+
}
|
|
5216
|
+
const value = record$6(JSON.parse(Buffer.concat(chunks).toString("utf8")));
|
|
5217
|
+
if (value === void 0) throw new PullRequestFeedbackError("invalid-request", "The request body is invalid.");
|
|
5218
|
+
return value;
|
|
5219
|
+
}
|
|
5220
|
+
function replyBody(input) {
|
|
5221
|
+
const body = typeof input.body === "string" ? input.body.trim() : "";
|
|
5222
|
+
if (body.length === 0 || body.length > MAX_REPLY_CHARS || body.includes("\0")) throw new PullRequestFeedbackError("invalid-request", "The reply body is invalid.");
|
|
5223
|
+
return body;
|
|
5224
|
+
}
|
|
5225
|
+
function commentId(input) {
|
|
5226
|
+
const value = input.commentId;
|
|
5227
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) throw new PullRequestFeedbackError("invalid-request", "The comment id is invalid.");
|
|
5228
|
+
return value;
|
|
5229
|
+
}
|
|
5230
|
+
function threadId(input) {
|
|
5231
|
+
const value = input.threadId;
|
|
5232
|
+
if (typeof value !== "string" || value.length === 0 || value.length > MAX_THREAD_ID_CHARS) throw new PullRequestFeedbackError("invalid-request", "The thread id is invalid.");
|
|
5233
|
+
return value;
|
|
5234
|
+
}
|
|
4992
5235
|
function sessionId(url) {
|
|
4993
5236
|
const value = url.searchParams.get("sessionId");
|
|
4994
5237
|
if (value === null || value.length === 0 || value.length > MAX_SESSION_ID_CHARS$3) throw new PullRequestFeedbackError("invalid-session", "The session is invalid.");
|
|
@@ -5005,19 +5248,43 @@ function registerPullRequestFeedbackRoute(ctx, service, cwdForSession) {
|
|
|
5005
5248
|
path: CLAUDE_REPOSITORY_FEEDBACK_PATH,
|
|
5006
5249
|
handler: async (req, res) => {
|
|
5007
5250
|
if (!trustedRequest(req)) return json(res, 403, { error: "forbidden" });
|
|
5008
|
-
if (req.method !== "GET") return json(res, 405, { error: "method not allowed" });
|
|
5009
5251
|
const url = new URL(req.url ?? "/", "http://localhost");
|
|
5252
|
+
const reads = req.method === "GET";
|
|
5253
|
+
const writes = req.method === "POST";
|
|
5010
5254
|
try {
|
|
5011
5255
|
const cwd = cwdForSession(sessionId(url));
|
|
5012
5256
|
if (cwd === void 0) throw new PullRequestFeedbackError("session-unavailable", "The Claude session is unavailable.");
|
|
5013
|
-
if (url.pathname === `/plugins/dsh-claude/repository/feedback/comments`)
|
|
5014
|
-
|
|
5257
|
+
if (url.pathname === `/plugins/dsh-claude/repository/feedback/comments`) {
|
|
5258
|
+
if (!reads) return json(res, 405, { error: "method not allowed" });
|
|
5259
|
+
return json(res, 200, { threads: await service.threads(cwd, pullNumber(url)) });
|
|
5260
|
+
}
|
|
5261
|
+
if (url.pathname === `/plugins/dsh-claude/repository/feedback/checks`) {
|
|
5262
|
+
if (!reads) return json(res, 405, { error: "method not allowed" });
|
|
5263
|
+
return json(res, 200, { checks: await service.failingChecks(cwd, pullNumber(url)) });
|
|
5264
|
+
}
|
|
5265
|
+
if (url.pathname === `/plugins/dsh-claude/repository/feedback/mentionables`) {
|
|
5266
|
+
if (!reads) return json(res, 405, { error: "method not allowed" });
|
|
5267
|
+
const query = (url.searchParams.get("q") ?? "").slice(0, MAX_MENTION_QUERY_CHARS);
|
|
5268
|
+
return json(res, 200, { users: await service.mentionables(cwd, query) });
|
|
5269
|
+
}
|
|
5270
|
+
if (url.pathname === `/plugins/dsh-claude/repository/feedback/reply`) {
|
|
5271
|
+
if (!writes) return json(res, 405, { error: "method not allowed" });
|
|
5272
|
+
const input = await readJson$4(req);
|
|
5273
|
+
return json(res, 200, { comment: await service.reply(cwd, pullNumber(url), commentId(input), replyBody(input)) });
|
|
5274
|
+
}
|
|
5275
|
+
if (url.pathname === `/plugins/dsh-claude/repository/feedback/resolve`) {
|
|
5276
|
+
if (!writes) return json(res, 405, { error: "method not allowed" });
|
|
5277
|
+
const input = await readJson$4(req);
|
|
5278
|
+
if (typeof input.resolved !== "boolean") throw new PullRequestFeedbackError("invalid-request", "The resolved field is required.");
|
|
5279
|
+
return json(res, 200, { resolved: await service.setResolved(cwd, threadId(input), input.resolved) });
|
|
5280
|
+
}
|
|
5015
5281
|
return json(res, 404, { error: "not found" });
|
|
5016
5282
|
} catch (error) {
|
|
5017
5283
|
if (error instanceof PullRequestFeedbackError) return json(res, 409, {
|
|
5018
5284
|
error: error.code,
|
|
5019
5285
|
message: error.message
|
|
5020
5286
|
});
|
|
5287
|
+
if (error instanceof SyntaxError) return json(res, 400, { error: "invalid-json" });
|
|
5021
5288
|
return json(res, 500, {
|
|
5022
5289
|
error: "pr-feedback-unavailable",
|
|
5023
5290
|
message: "Pull request feedback is unavailable."
|
|
@@ -5786,6 +6053,52 @@ function registerReviewCommentRoute(ctx, store, ownsSession) {
|
|
|
5786
6053
|
}), "dsh-claude: review comment route");
|
|
5787
6054
|
}
|
|
5788
6055
|
//#endregion
|
|
6056
|
+
//#region src/client-diagnostics-routes.ts
|
|
6057
|
+
/** Enough for a message plus a trimmed stack; the client caps its own volume. */
|
|
6058
|
+
const MAX_DIAGNOSTIC_BYTES = 8192;
|
|
6059
|
+
const MAX_DETAIL_CHARS = 2e3;
|
|
6060
|
+
const MAX_KIND_CHARS = 60;
|
|
6061
|
+
async function readBody(req) {
|
|
6062
|
+
const chunks = [];
|
|
6063
|
+
let size = 0;
|
|
6064
|
+
for await (const chunk of req) {
|
|
6065
|
+
const buffer = chunk;
|
|
6066
|
+
size += buffer.length;
|
|
6067
|
+
if (size > MAX_DIAGNOSTIC_BYTES) throw new Error("diagnostic too large");
|
|
6068
|
+
chunks.push(buffer);
|
|
6069
|
+
}
|
|
6070
|
+
return JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
6071
|
+
}
|
|
6072
|
+
/** `POST <path>` with `{ kind, detail }`: write one renderer finding to the Host log.
|
|
6073
|
+
*
|
|
6074
|
+
* The renderer has no other way to speak. A Slot entry that throws is caught
|
|
6075
|
+
* by the Host's Slot system and dropped, the shipped Desktop opens no
|
|
6076
|
+
* DevTools, and startup still reports `rendererStatus: "healthy"` — so a
|
|
6077
|
+
* plugin whose UI died silently is indistinguishable from a working one.
|
|
6078
|
+
* Every finding here is data written by this package's own client half, but
|
|
6079
|
+
* it is still bounded and redacted like any other untrusted input. */
|
|
6080
|
+
function registerClaudeClientDiagnosticsRoute(ctx) {
|
|
6081
|
+
ctx.effect(() => ctx.webServer.register({
|
|
6082
|
+
kind: "exact",
|
|
6083
|
+
path: CLAUDE_CLIENT_DIAGNOSTICS_PATH,
|
|
6084
|
+
handler: async (req, res) => {
|
|
6085
|
+
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
6086
|
+
if (!trustedRequest(req)) return json(res, 403, { error: "forbidden" });
|
|
6087
|
+
try {
|
|
6088
|
+
const body = await readBody(req);
|
|
6089
|
+
const kind = typeof body?.kind === "string" ? body.kind.slice(0, MAX_KIND_CHARS) : "unknown";
|
|
6090
|
+
const detail = typeof body?.detail === "string" ? body.detail : "";
|
|
6091
|
+
if (detail === "") return json(res, 400, { error: "invalid-request" });
|
|
6092
|
+
ctx.logger.warn(`dsh-claude client [${kind}]: ${redactText(detail, MAX_DETAIL_CHARS)}`);
|
|
6093
|
+
return json(res, 200, { ok: true });
|
|
6094
|
+
} catch (error) {
|
|
6095
|
+
if (error instanceof SyntaxError) return json(res, 400, { error: "invalid-json" });
|
|
6096
|
+
return json(res, 400, { error: "invalid-request" });
|
|
6097
|
+
}
|
|
6098
|
+
}
|
|
6099
|
+
}), "dsh-claude: client diagnostics route");
|
|
6100
|
+
}
|
|
6101
|
+
//#endregion
|
|
5789
6102
|
//#region src/rewind-routes.ts
|
|
5790
6103
|
const MAX_BODY_BYTES = 4096;
|
|
5791
6104
|
const MAX_SESSION_ID_CHARS = 1024;
|
|
@@ -6656,6 +6969,7 @@ async function apply(ctx, config) {
|
|
|
6656
6969
|
ctx.effect(() => () => supervisor.dispose(), "dsh-claude: process supervisor");
|
|
6657
6970
|
ctx.effect(() => () => repositoryStatus.dispose(), "dsh-claude: repository status cache");
|
|
6658
6971
|
ctx.inject(["webServer"], (webCtx) => {
|
|
6972
|
+
registerClaudeClientDiagnosticsRoute(webCtx);
|
|
6659
6973
|
registerClaudeDoctorRoutes(webCtx, webCtx.subprocess, supervisor, supervisorConfig, resolutionError);
|
|
6660
6974
|
const desktopActions = webCtx.get("desktopActions");
|
|
6661
6975
|
registerClaudeUpdateRoutes(webCtx, webCtx.subprocess, { ...typeof desktopActions?.requestRestart === "function" ? { requestRestart: desktopActions.requestRestart.bind(desktopActions) } : {} });
|