@norman-else/dsh-claude 0.1.28 → 0.1.32
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 +55 -55
- package/LICENSE +21 -21
- package/README.md +82 -82
- package/cordis.patch.yml +12 -12
- package/legacy-preset/agent.cordis.yml +11 -11
- package/legacy-preset/preset.yml +4 -4
- package/lib/bin.mjs +1 -1
- package/lib/bin.mjs.map +1 -1
- package/lib/client.d.ts +22 -1
- package/lib/client.js +5882 -342
- package/lib/client.js.map +1 -1
- package/lib/{command-bridge-BYj0VF4J.mjs → command-bridge-tqsu15hR.mjs} +2 -2
- package/lib/command-bridge-tqsu15hR.mjs.map +1 -0
- package/lib/{events-lDt9nTUw.mjs → events-CSDcXsWE.mjs} +4 -2
- package/lib/events-CSDcXsWE.mjs.map +1 -0
- package/lib/index.d.mts +38 -1
- package/lib/index.mjs +716 -150
- package/lib/index.mjs.map +1 -1
- package/lib/{preset-installer-CPOH9lAr.mjs → preset-installer-CVdbU87E.mjs} +2 -2
- package/lib/preset-installer-CVdbU87E.mjs.map +1 -0
- package/lib/preset-route.mjs +2 -2
- package/lib/preset-route.mjs.map +1 -1
- package/package.json +184 -181
- package/preset/claude/agent.cordis.yml +11 -11
- package/preset/claude/preset.yml +4 -4
- package/lib/command-bridge-BYj0VF4J.mjs.map +0 -1
- package/lib/events-lDt9nTUw.mjs.map +0 -1
- package/lib/preset-installer-CPOH9lAr.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_USAGE_PATH, C as CLAUDE_REPOSITORY_FILE_PATH, D as CLAUDE_REWIND_PATH, E as CLAUDE_REVIEW_COMMENT_PATH, M as TASK_TOOL_NAMES, O as CLAUDE_UPDATE_CHECK_PATH, S as CLAUDE_REPOSITORY_FEEDBACK_PATH, T as CLAUDE_REPOSITORY_STATUS_PATH, _ as CLAUDE_EDITOR_OPEN_PATH, a as latestClaudeTasks, b as CLAUDE_PROJECTION_PATH, c as normalizeTasksEvent, d as CLAUDE_ACTIVITY_EVENT, f as CLAUDE_ASK_PATH, g as CLAUDE_DOCTOR_PATH, h as CLAUDE_CODE_PROVIDER_IDS, i as latestClaudeSessionBinding, k as CLAUDE_UPDATE_PATH, l as redactText, m as CLAUDE_CODE_PROVIDER, n as currentClaudeActivityCursor, o as normalizeActivity, p as CLAUDE_CODE_PRESET_ID, r as latestClaudeContextUsage, s as normalizeContextUsage, t as boundText, u as safeDetail, v as CLAUDE_GLOBAL_SETTINGS_PATH, w as CLAUDE_REPOSITORY_SETUP_PATH, x as CLAUDE_REPOSITORY_ACTION_PATH, y as CLAUDE_JIRA_PATH } from "./events-CSDcXsWE.mjs";
|
|
2
|
+
import { n as projectClaudeCommands, t as CLAUDE_COMMANDS_SERVICE } from "./command-bridge-tqsu15hR.mjs";
|
|
3
|
+
import { a as resolveClaudeExecutable, n as ensureManagedPreset, o as runClaudeDoctor, t as ManagedPresetConflictError } from "./preset-installer-CVdbU87E.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";
|
|
@@ -13,6 +13,67 @@ import { DSH_ENV_PREFIX, SENSITIVE_ENV_PATTERN } from "@deepseek-ai/dsh-subproce
|
|
|
13
13
|
import { LlmAdapter, ReasoningEffortId } from "@deepseek-ai/dsh-llm";
|
|
14
14
|
import { fileURLToPath } from "node:url";
|
|
15
15
|
import { StringDecoder } from "node:string_decoder";
|
|
16
|
+
//#region src/rewind.ts
|
|
17
|
+
const EMPTY_REWIND_STATE = {
|
|
18
|
+
ranges: [],
|
|
19
|
+
anchors: []
|
|
20
|
+
};
|
|
21
|
+
/** Absorb one span into an ascending, non-overlapping range list. Adjacent
|
|
22
|
+
* spans merge so a rewind of a rewind reads as one hidden block. */
|
|
23
|
+
function mergeRewindRanges(ranges, addition) {
|
|
24
|
+
const merged = [];
|
|
25
|
+
let { start, end } = addition;
|
|
26
|
+
for (const range of [...ranges].sort((left, right) => left.start - right.start)) if (range.end + 1 < start) merged.push(range);
|
|
27
|
+
else if (range.start > end + 1) {
|
|
28
|
+
merged.push({
|
|
29
|
+
start,
|
|
30
|
+
end
|
|
31
|
+
});
|
|
32
|
+
start = range.start;
|
|
33
|
+
end = range.end;
|
|
34
|
+
} else {
|
|
35
|
+
start = Math.min(start, range.start);
|
|
36
|
+
end = Math.max(end, range.end);
|
|
37
|
+
}
|
|
38
|
+
merged.push({
|
|
39
|
+
start,
|
|
40
|
+
end
|
|
41
|
+
});
|
|
42
|
+
return merged.slice(-200);
|
|
43
|
+
}
|
|
44
|
+
/** Record one completed turn's last chain entry, replacing a re-run turn. */
|
|
45
|
+
function recordRewindAnchor(state, anchor) {
|
|
46
|
+
const anchors = [...state.anchors.filter((item) => item.turn !== anchor.turn), anchor].sort((left, right) => left.turn - right.turn).slice(-2e3);
|
|
47
|
+
return {
|
|
48
|
+
...state,
|
|
49
|
+
anchors
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/** The turn a surface seq belongs to: the first turn opened at or after it.
|
|
53
|
+
* A message accepted but never run belongs to no logged turn, so nothing
|
|
54
|
+
* Claude holds is discarded and every anchor stays valid. */
|
|
55
|
+
function turnAtOrAfter(events, seq) {
|
|
56
|
+
for (const event of events) if (event.seq >= seq && event.type === "turn/start") return event.data.turn;
|
|
57
|
+
}
|
|
58
|
+
/** Plan one rewind at `seq`, or undefined when the seq is not in the log.
|
|
59
|
+
* Anchors of the discarded turns go with them: after this rewind Claude no
|
|
60
|
+
* longer holds those entries, so a later rewind must never fork at one. */
|
|
61
|
+
function planRewind(state, events, seq) {
|
|
62
|
+
const last = events.at(-1)?.seq;
|
|
63
|
+
if (last === void 0 || seq > last) return void 0;
|
|
64
|
+
const turn = turnAtOrAfter(events, seq) ?? Number.MAX_SAFE_INTEGER;
|
|
65
|
+
const anchors = state.anchors.filter((anchor) => anchor.turn < turn);
|
|
66
|
+
const kept = anchors.at(-1);
|
|
67
|
+
return {
|
|
68
|
+
ranges: mergeRewindRanges(state.ranges, {
|
|
69
|
+
start: seq,
|
|
70
|
+
end: last
|
|
71
|
+
}),
|
|
72
|
+
anchors,
|
|
73
|
+
pending: kept === void 0 ? { fresh: true } : { resumeAt: kept.uuid }
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
//#endregion
|
|
16
77
|
//#region src/sidecar.ts
|
|
17
78
|
const SIDECAR_SCHEMA_VERSION = 1;
|
|
18
79
|
const MAX_ACTIVITIES = 1e4;
|
|
@@ -26,7 +87,7 @@ function emptyProjection() {
|
|
|
26
87
|
activities: []
|
|
27
88
|
};
|
|
28
89
|
}
|
|
29
|
-
function record$
|
|
90
|
+
function record$12(value) {
|
|
30
91
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
31
92
|
}
|
|
32
93
|
function finiteInteger(value) {
|
|
@@ -36,7 +97,7 @@ function string$4(value, max) {
|
|
|
36
97
|
return typeof value === "string" && value.length > 0 && value.length <= max;
|
|
37
98
|
}
|
|
38
99
|
function binding(value) {
|
|
39
|
-
const input = record$
|
|
100
|
+
const input = record$12(value);
|
|
40
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;
|
|
41
102
|
return {
|
|
42
103
|
claudeSessionId: input.claudeSessionId,
|
|
@@ -48,6 +109,7 @@ function binding(value) {
|
|
|
48
109
|
const ACTIVITY_KINDS = /* @__PURE__ */ new Set([
|
|
49
110
|
"text",
|
|
50
111
|
"status",
|
|
112
|
+
"compaction",
|
|
51
113
|
"thinking",
|
|
52
114
|
"tool-call",
|
|
53
115
|
"tool-result",
|
|
@@ -66,36 +128,77 @@ const ACTIVITY_PHASES = /* @__PURE__ */ new Set([
|
|
|
66
128
|
"failed"
|
|
67
129
|
]);
|
|
68
130
|
function activity(value) {
|
|
69
|
-
const input = record$
|
|
131
|
+
const input = record$12(value);
|
|
70
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;
|
|
71
133
|
return normalizeActivity(input);
|
|
72
134
|
}
|
|
73
135
|
function contextUsage(value) {
|
|
74
|
-
const input = record$
|
|
136
|
+
const input = record$12(value);
|
|
75
137
|
if (input === void 0 || !Array.isArray(input.categories)) return void 0;
|
|
76
138
|
return normalizeContextUsage(input);
|
|
77
139
|
}
|
|
140
|
+
function rewind(value) {
|
|
141
|
+
const input = record$12(value);
|
|
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
|
+
const ranges = [];
|
|
144
|
+
for (const item of input.ranges) {
|
|
145
|
+
const range = record$12(item);
|
|
146
|
+
if (range === void 0 || !finiteInteger(range.start) || !finiteInteger(range.end) || range.end < range.start) return void 0;
|
|
147
|
+
ranges.push({
|
|
148
|
+
start: range.start,
|
|
149
|
+
end: range.end
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
const anchors = [];
|
|
153
|
+
for (const item of input.anchors) {
|
|
154
|
+
const anchor = record$12(item);
|
|
155
|
+
if (anchor === void 0 || !finiteInteger(anchor.turn) || !string$4(anchor.uuid, 128)) return void 0;
|
|
156
|
+
anchors.push({
|
|
157
|
+
turn: anchor.turn,
|
|
158
|
+
uuid: anchor.uuid
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
const pending = record$12(input.pending);
|
|
162
|
+
if (input.pending !== void 0 && pending === void 0) return void 0;
|
|
163
|
+
if (pending === void 0) return {
|
|
164
|
+
ranges,
|
|
165
|
+
anchors
|
|
166
|
+
};
|
|
167
|
+
if (pending.fresh === true) return {
|
|
168
|
+
ranges,
|
|
169
|
+
anchors,
|
|
170
|
+
pending: { fresh: true }
|
|
171
|
+
};
|
|
172
|
+
if (!string$4(pending.resumeAt, 128)) return void 0;
|
|
173
|
+
return {
|
|
174
|
+
ranges,
|
|
175
|
+
anchors,
|
|
176
|
+
pending: { resumeAt: pending.resumeAt }
|
|
177
|
+
};
|
|
178
|
+
}
|
|
78
179
|
function tasks(value) {
|
|
79
|
-
const input = record$
|
|
180
|
+
const input = record$12(value);
|
|
80
181
|
if (input === void 0 || !Array.isArray(input.tasks)) return void 0;
|
|
81
182
|
return normalizeTasksEvent(input.tasks);
|
|
82
183
|
}
|
|
83
184
|
function parseClaudeSidecar(value) {
|
|
84
|
-
const input = record$
|
|
185
|
+
const input = record$12(value);
|
|
85
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");
|
|
86
187
|
const activities = input.activities.map(activity);
|
|
87
188
|
if (activities.some((item) => item === void 0)) throw new Error("dsh-claude: invalid sidecar activity");
|
|
88
189
|
const parsedBinding = input.binding === void 0 ? void 0 : binding(input.binding);
|
|
89
190
|
const parsedUsage = input.contextUsage === void 0 ? void 0 : contextUsage(input.contextUsage);
|
|
90
191
|
const parsedTasks = input.tasks === void 0 ? void 0 : tasks(input.tasks);
|
|
91
|
-
|
|
192
|
+
const parsedRewind = input.rewind === void 0 ? void 0 : rewind(input.rewind);
|
|
193
|
+
if (input.binding !== void 0 && parsedBinding === void 0 || input.contextUsage !== void 0 && parsedUsage === void 0 || input.tasks !== void 0 && parsedTasks === void 0 || input.rewind !== void 0 && parsedRewind === void 0) throw new Error("dsh-claude: invalid sidecar projection");
|
|
92
194
|
return {
|
|
93
195
|
schemaVersion: SIDECAR_SCHEMA_VERSION,
|
|
94
196
|
revision: input.revision,
|
|
95
197
|
activities,
|
|
96
198
|
...parsedBinding === void 0 ? {} : { binding: parsedBinding },
|
|
97
199
|
...parsedUsage === void 0 ? {} : { contextUsage: parsedUsage },
|
|
98
|
-
...parsedTasks === void 0 ? {} : { tasks: parsedTasks }
|
|
200
|
+
...parsedTasks === void 0 ? {} : { tasks: parsedTasks },
|
|
201
|
+
...parsedRewind === void 0 ? {} : { rewind: parsedRewind }
|
|
99
202
|
};
|
|
100
203
|
}
|
|
101
204
|
function compareActivity(left, right) {
|
|
@@ -279,6 +382,35 @@ var ClaudeSidecarRepository = class {
|
|
|
279
382
|
value: normalized
|
|
280
383
|
});
|
|
281
384
|
}
|
|
385
|
+
/** Land one planned rewind: hidden ranges, surviving anchors, and the fork
|
|
386
|
+
* target the next Claude spawn consumes. */
|
|
387
|
+
writeRewind(sessionId, value) {
|
|
388
|
+
return this.#update(sessionId, (current) => ({
|
|
389
|
+
...current,
|
|
390
|
+
rewind: value
|
|
391
|
+
}), false, { kind: "sync" });
|
|
392
|
+
}
|
|
393
|
+
/** Remember where Claude's chain ended for one completed DSH turn. */
|
|
394
|
+
recordRewindAnchor(sessionId, turn, uuid) {
|
|
395
|
+
return this.#update(sessionId, (current) => ({
|
|
396
|
+
...current,
|
|
397
|
+
rewind: recordRewindAnchor(current.rewind ?? EMPTY_REWIND_STATE, {
|
|
398
|
+
turn,
|
|
399
|
+
uuid
|
|
400
|
+
})
|
|
401
|
+
}));
|
|
402
|
+
}
|
|
403
|
+
/** Disarm the fork target once a Claude process has resumed at it, so a
|
|
404
|
+
* later respawn continues the rewound session instead of re-truncating it. */
|
|
405
|
+
clearRewindPending(sessionId) {
|
|
406
|
+
return this.#update(sessionId, (current) => current.rewind?.pending === void 0 ? current : {
|
|
407
|
+
...current,
|
|
408
|
+
rewind: {
|
|
409
|
+
ranges: current.rewind.ranges,
|
|
410
|
+
anchors: current.rewind.anchors
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
}
|
|
282
414
|
importLegacy(sessionId, events) {
|
|
283
415
|
const importedActivities = events.filter((event) => event.type === CLAUDE_ACTIVITY_EVENT).map((event) => activity(event.data)).filter((item) => item !== void 0);
|
|
284
416
|
const importedBinding = latestClaudeSessionBinding(events);
|
|
@@ -654,12 +786,15 @@ function createUserQuestionBridge(userQuestions, activeContext) {
|
|
|
654
786
|
}
|
|
655
787
|
//#endregion
|
|
656
788
|
//#region src/sdk-messages.ts
|
|
657
|
-
function record$
|
|
789
|
+
function record$11(value) {
|
|
658
790
|
return value !== null && typeof value === "object" ? value : void 0;
|
|
659
791
|
}
|
|
660
792
|
function string$3(value) {
|
|
661
793
|
return typeof value === "string" ? value : void 0;
|
|
662
794
|
}
|
|
795
|
+
function finiteNumber(value) {
|
|
796
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
797
|
+
}
|
|
663
798
|
function taskUsageOf(usage) {
|
|
664
799
|
if (usage === void 0) return void 0;
|
|
665
800
|
const normalized = {};
|
|
@@ -668,20 +803,28 @@ function taskUsageOf(usage) {
|
|
|
668
803
|
if (typeof usage.duration_ms === "number") normalized.durationMs = usage.duration_ms;
|
|
669
804
|
return Object.keys(normalized).length === 0 ? void 0 : normalized;
|
|
670
805
|
}
|
|
671
|
-
|
|
672
|
-
|
|
806
|
+
/** Read one Anthropic `usage` envelope. Shared by the per-call sample on an
|
|
807
|
+
* assistant message and the turn total on a result message. */
|
|
808
|
+
function usageOf(usage) {
|
|
673
809
|
const normalized = {};
|
|
674
|
-
if (usage
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
810
|
+
if (usage === void 0) return normalized;
|
|
811
|
+
if (typeof usage.input_tokens === "number") normalized.inputTokens = usage.input_tokens;
|
|
812
|
+
if (typeof usage.output_tokens === "number") normalized.outputTokens = usage.output_tokens;
|
|
813
|
+
if (typeof usage.cache_read_input_tokens === "number") normalized.cacheReadTokens = usage.cache_read_input_tokens;
|
|
814
|
+
if (typeof usage.cache_creation_input_tokens === "number") normalized.cacheCreationTokens = usage.cache_creation_input_tokens;
|
|
815
|
+
return normalized;
|
|
816
|
+
}
|
|
817
|
+
function hasUsageCounts(usage) {
|
|
818
|
+
return usage.inputTokens !== void 0 || usage.outputTokens !== void 0 || usage.cacheReadTokens !== void 0 || usage.cacheCreationTokens !== void 0;
|
|
819
|
+
}
|
|
820
|
+
function resultUsage(message) {
|
|
821
|
+
const normalized = usageOf(record$11(message.usage));
|
|
680
822
|
if (typeof message.total_cost_usd === "number") normalized.cumulativeCostUsd = message.total_cost_usd;
|
|
681
823
|
return normalized;
|
|
682
824
|
}
|
|
683
825
|
function normalizeAssistant(message) {
|
|
684
|
-
const
|
|
826
|
+
const envelope = record$11(message.message);
|
|
827
|
+
const content = envelope?.content;
|
|
685
828
|
if (!Array.isArray(content)) return [{
|
|
686
829
|
kind: "protocol-error",
|
|
687
830
|
title: "Malformed Claude assistant message",
|
|
@@ -690,7 +833,7 @@ function normalizeAssistant(message) {
|
|
|
690
833
|
const parentToolUseId = string$3(message.parent_tool_use_id);
|
|
691
834
|
const normalized = [];
|
|
692
835
|
for (const item of content) {
|
|
693
|
-
const block = record$
|
|
836
|
+
const block = record$11(item);
|
|
694
837
|
if (block === void 0) continue;
|
|
695
838
|
if (block.type === "text") {
|
|
696
839
|
const text = string$3(block.text);
|
|
@@ -719,11 +862,17 @@ function normalizeAssistant(message) {
|
|
|
719
862
|
});
|
|
720
863
|
}
|
|
721
864
|
}
|
|
865
|
+
const usage = usageOf(record$11(envelope?.usage));
|
|
866
|
+
if (hasUsageCounts(usage)) normalized.push({
|
|
867
|
+
kind: "request-usage",
|
|
868
|
+
usage,
|
|
869
|
+
...parentToolUseId === void 0 ? {} : { parentToolUseId }
|
|
870
|
+
});
|
|
722
871
|
return normalized;
|
|
723
872
|
}
|
|
724
873
|
function normalizeUser(message) {
|
|
725
874
|
if (message.isReplay === true) return [];
|
|
726
|
-
const content = record$
|
|
875
|
+
const content = record$11(message.message)?.content;
|
|
727
876
|
if (typeof content === "string") return [];
|
|
728
877
|
if (!Array.isArray(content)) return [{
|
|
729
878
|
kind: "protocol-error",
|
|
@@ -733,7 +882,7 @@ function normalizeUser(message) {
|
|
|
733
882
|
const parentToolUseId = string$3(message.parent_tool_use_id);
|
|
734
883
|
const normalized = [];
|
|
735
884
|
for (const item of content) {
|
|
736
|
-
const block = record$
|
|
885
|
+
const block = record$11(item);
|
|
737
886
|
if (block?.type !== "tool_result") continue;
|
|
738
887
|
const toolUseId = string$3(block.tool_use_id);
|
|
739
888
|
if (toolUseId === void 0) continue;
|
|
@@ -814,7 +963,7 @@ function normalizeSystem(message) {
|
|
|
814
963
|
const summary = string$3(message.summary);
|
|
815
964
|
const subagentType = string$3(message.subagent_type);
|
|
816
965
|
const lastToolName = string$3(message.last_tool_name);
|
|
817
|
-
const usage = taskUsageOf(record$
|
|
966
|
+
const usage = taskUsageOf(record$11(message.usage));
|
|
818
967
|
return [{
|
|
819
968
|
kind: "subagent",
|
|
820
969
|
title: summary ?? description ?? "Claude subagent update",
|
|
@@ -830,7 +979,7 @@ function normalizeSystem(message) {
|
|
|
830
979
|
}];
|
|
831
980
|
}
|
|
832
981
|
if (subtype === "task_updated") {
|
|
833
|
-
const patch = record$
|
|
982
|
+
const patch = record$11(message.patch);
|
|
834
983
|
const status = string$3(patch?.status);
|
|
835
984
|
const taskId = string$3(message.task_id);
|
|
836
985
|
const description = string$3(patch?.description);
|
|
@@ -853,7 +1002,7 @@ function normalizeSystem(message) {
|
|
|
853
1002
|
const taskId = string$3(message.task_id);
|
|
854
1003
|
const summary = string$3(message.summary);
|
|
855
1004
|
const taskStatus = failed ? "failed" : stopped ? "stopped" : "completed";
|
|
856
|
-
const usage = taskUsageOf(record$
|
|
1005
|
+
const usage = taskUsageOf(record$11(message.usage));
|
|
857
1006
|
return [{
|
|
858
1007
|
kind: "subagent",
|
|
859
1008
|
title: summary ?? taskId ?? "Claude subagent finished",
|
|
@@ -868,7 +1017,7 @@ function normalizeSystem(message) {
|
|
|
868
1017
|
if (subtype === "background_tasks_changed") return [{
|
|
869
1018
|
kind: "background-tasks",
|
|
870
1019
|
tasks: (Array.isArray(message.tasks) ? message.tasks : []).flatMap((item) => {
|
|
871
|
-
const entry = record$
|
|
1020
|
+
const entry = record$11(item);
|
|
872
1021
|
const taskId = string$3(entry?.task_id);
|
|
873
1022
|
const description = string$3(entry?.description);
|
|
874
1023
|
const taskType = string$3(entry?.task_type);
|
|
@@ -885,6 +1034,20 @@ function normalizeSystem(message) {
|
|
|
885
1034
|
title: "Claude API retry",
|
|
886
1035
|
detail: message
|
|
887
1036
|
}];
|
|
1037
|
+
if (subtype === "compact_boundary") {
|
|
1038
|
+
const metadata = record$11(message.compact_metadata);
|
|
1039
|
+
const trigger = metadata?.trigger === "auto" || metadata?.trigger === "manual" ? metadata.trigger : void 0;
|
|
1040
|
+
const preTokens = finiteNumber(metadata?.pre_tokens);
|
|
1041
|
+
const postTokens = finiteNumber(metadata?.post_tokens);
|
|
1042
|
+
const durationMs = finiteNumber(metadata?.duration_ms);
|
|
1043
|
+
return [{
|
|
1044
|
+
kind: "compaction",
|
|
1045
|
+
...trigger === void 0 ? {} : { trigger },
|
|
1046
|
+
...preTokens === void 0 ? {} : { preTokens },
|
|
1047
|
+
...postTokens === void 0 ? {} : { postTokens },
|
|
1048
|
+
...durationMs === void 0 ? {} : { durationMs }
|
|
1049
|
+
}];
|
|
1050
|
+
}
|
|
888
1051
|
if (subtype === "informational" || subtype === "notification" || subtype === "local_command_output") return [{
|
|
889
1052
|
kind: message.level === "warning" ? "warning" : "status",
|
|
890
1053
|
title: string$3(message.content) ?? string$3(message.text) ?? "Claude Code notice",
|
|
@@ -911,10 +1074,10 @@ const RESULT_ERROR_SUBTYPES = /* @__PURE__ */ new Set([
|
|
|
911
1074
|
function normalizeSdkMessage(message) {
|
|
912
1075
|
const value = message;
|
|
913
1076
|
if (value.type === "stream_event") {
|
|
914
|
-
const event = record$
|
|
1077
|
+
const event = record$11(value.event);
|
|
915
1078
|
const parentToolUseId = string$3(value.parent_tool_use_id);
|
|
916
1079
|
if (event?.type === "content_block_delta") {
|
|
917
|
-
const delta = record$
|
|
1080
|
+
const delta = record$11(event.delta);
|
|
918
1081
|
if (delta?.type === "text_delta") {
|
|
919
1082
|
const text = string$3(delta.text);
|
|
920
1083
|
return text === void 0 ? [] : [{
|
|
@@ -949,7 +1112,7 @@ function normalizeSdkMessage(message) {
|
|
|
949
1112
|
const errors = Array.isArray(value.errors) ? value.errors.filter((item) => typeof item === "string") : void 0;
|
|
950
1113
|
const terminalReason = string$3(value.terminal_reason);
|
|
951
1114
|
const userMessageUuid = string$3(value.user_message_uuid);
|
|
952
|
-
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$11(item)).filter((item) => item !== void 0).map((item) => {
|
|
953
1116
|
const toolName = string$3(item.tool_name);
|
|
954
1117
|
const toolUseId = string$3(item.tool_use_id);
|
|
955
1118
|
return toolName === void 0 || toolUseId === void 0 ? void 0 : {
|
|
@@ -975,7 +1138,7 @@ function normalizeSdkMessage(message) {
|
|
|
975
1138
|
detail: value.error ?? value.output
|
|
976
1139
|
}];
|
|
977
1140
|
if (value.type === "rate_limit_event") {
|
|
978
|
-
const status = string$3(record$
|
|
1141
|
+
const status = string$3(record$11(value.rate_limit_info)?.status);
|
|
979
1142
|
return [{
|
|
980
1143
|
kind: "status",
|
|
981
1144
|
title: status !== void 0 && status !== "allowed" ? "Claude rate limit is blocking requests" : "Claude rate limit status changed",
|
|
@@ -1014,7 +1177,7 @@ const FIXED_WINDOWS = [
|
|
|
1014
1177
|
"seven_day_opus",
|
|
1015
1178
|
"seven_day_sonnet"
|
|
1016
1179
|
];
|
|
1017
|
-
function record$
|
|
1180
|
+
function record$10(value) {
|
|
1018
1181
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
1019
1182
|
}
|
|
1020
1183
|
/** Utilization is documented as 0-100; clamp so a server glitch cannot render
|
|
@@ -1026,7 +1189,7 @@ function resetsAt(value) {
|
|
|
1026
1189
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
1027
1190
|
}
|
|
1028
1191
|
function window(id, source, label) {
|
|
1029
|
-
const entry = record$
|
|
1192
|
+
const entry = record$10(source);
|
|
1030
1193
|
if (entry === void 0) return void 0;
|
|
1031
1194
|
const used = utilization(entry.utilization);
|
|
1032
1195
|
const reset = resetsAt(entry.resets_at);
|
|
@@ -1040,9 +1203,9 @@ function window(id, source, label) {
|
|
|
1040
1203
|
}
|
|
1041
1204
|
/** Project the SDK's `/usage` response onto the windows the settings card shows. */
|
|
1042
1205
|
function normalizePlanUsage(value, fetchedAt) {
|
|
1043
|
-
const response = record$
|
|
1206
|
+
const response = record$10(value);
|
|
1044
1207
|
const subscription = typeof response?.subscription_type === "string" ? response.subscription_type : void 0;
|
|
1045
|
-
const limits = record$
|
|
1208
|
+
const limits = record$10(response?.rate_limits);
|
|
1046
1209
|
if (response?.rate_limits_available !== true || limits === void 0) return {
|
|
1047
1210
|
available: false,
|
|
1048
1211
|
...subscription === void 0 ? {} : { subscription },
|
|
@@ -1050,7 +1213,7 @@ function normalizePlanUsage(value, fetchedAt) {
|
|
|
1050
1213
|
fetchedAt
|
|
1051
1214
|
};
|
|
1052
1215
|
const windows = [...FIXED_WINDOWS.map((id) => window(id, limits[id])), ...(Array.isArray(limits.model_scoped) ? limits.model_scoped : []).map((entry, index) => {
|
|
1053
|
-
const name = record$
|
|
1216
|
+
const name = record$10(entry)?.display_name;
|
|
1054
1217
|
return window(`model:${typeof name === "string" ? name : index}`, entry, typeof name === "string" ? name : void 0);
|
|
1055
1218
|
})].filter((entry) => entry !== void 0);
|
|
1056
1219
|
return {
|
|
@@ -1086,7 +1249,9 @@ async function probePlanUsage(executablePath, fetchedAt, factory = query) {
|
|
|
1086
1249
|
(async () => {
|
|
1087
1250
|
for await (const _ of query$1);
|
|
1088
1251
|
})().catch(() => void 0);
|
|
1089
|
-
return normalizePlanUsage(await readPlanUsageFrom(query$1),
|
|
1252
|
+
return normalizePlanUsage(await Promise.race([readPlanUsageFrom(query$1), new Promise((_resolve, reject) => {
|
|
1253
|
+
setTimeout(() => reject(/* @__PURE__ */ new Error("dsh-claude: the plan usage request did not answer in time")), PLAN_USAGE_TIMEOUT_MS).unref?.();
|
|
1254
|
+
})]), fetchedAt);
|
|
1090
1255
|
} finally {
|
|
1091
1256
|
clearTimeout(timer);
|
|
1092
1257
|
lifetime.abort();
|
|
@@ -1237,6 +1402,14 @@ async function withTimeout(operation, timeoutMs, label) {
|
|
|
1237
1402
|
if (timer !== void 0) clearTimeout(timer);
|
|
1238
1403
|
}
|
|
1239
1404
|
}
|
|
1405
|
+
/** The uuid of one main-chain transcript entry, or undefined for anything a
|
|
1406
|
+
* rewind must not fork at: stream partials, results, and sidechain traffic. */
|
|
1407
|
+
function chainEntryUuid(message) {
|
|
1408
|
+
if (message.type !== "assistant" && message.type !== "user") return void 0;
|
|
1409
|
+
const envelope = message;
|
|
1410
|
+
if (typeof envelope.parent_tool_use_id === "string") return void 0;
|
|
1411
|
+
return typeof envelope.uuid === "string" && envelope.uuid.length > 0 ? envelope.uuid : void 0;
|
|
1412
|
+
}
|
|
1240
1413
|
function sdkUserMessage(prompt, uuid) {
|
|
1241
1414
|
return {
|
|
1242
1415
|
type: "user",
|
|
@@ -1307,13 +1480,39 @@ var ClaudeSupervisor = class {
|
|
|
1307
1480
|
}
|
|
1308
1481
|
async contextUsage(agent, model = this.#config.defaultModel) {
|
|
1309
1482
|
const usage = await this.#runMetadata(agent, model, (query) => query.getContextUsage());
|
|
1310
|
-
|
|
1311
|
-
if (contextWindow > 0) {
|
|
1312
|
-
this.#contextWindows.set(model, contextWindow);
|
|
1313
|
-
this.#contextWindows.set(usage.model, contextWindow);
|
|
1314
|
-
}
|
|
1483
|
+
this.#recordContextWindow(model, usage);
|
|
1315
1484
|
return usage;
|
|
1316
1485
|
}
|
|
1486
|
+
/** Cache a window under both the selector id the caller asked for and the
|
|
1487
|
+
* concrete model the CLI reports, so either name resolves it later. */
|
|
1488
|
+
#recordContextWindow(model, usage) {
|
|
1489
|
+
const contextWindow = usage.rawMaxTokens > 0 ? usage.rawMaxTokens : usage.maxTokens;
|
|
1490
|
+
if (contextWindow <= 0) return;
|
|
1491
|
+
this.#contextWindows.set(model, contextWindow);
|
|
1492
|
+
this.#contextWindows.set(usage.model, contextWindow);
|
|
1493
|
+
}
|
|
1494
|
+
/** Learn a model's context window the first time a turn finishes on it.
|
|
1495
|
+
*
|
|
1496
|
+
* DSH hides its context meter entirely unless the route publishes a
|
|
1497
|
+
* capacity, and these numbers move with Claude releases — so none are
|
|
1498
|
+
* hardcoded; the CLI is asked over the session's own live process.
|
|
1499
|
+
*
|
|
1500
|
+
* Turn completion is the earliest honest moment to ask. `entry.model` is
|
|
1501
|
+
* already the model that just ran, so no model switch is provoked — which
|
|
1502
|
+
* rules out asking from `resolveModel`, since DSH resolves every model in
|
|
1503
|
+
* the catalog to build its picker and `#metadataEntry` would switch the live
|
|
1504
|
+
* session once per entry. And nothing is lost by waiting: the meter needs a
|
|
1505
|
+
* usage sample too, and no turn has reported one before the first turn ends.
|
|
1506
|
+
*
|
|
1507
|
+
* Best-effort — a failure leaves the window unknown (the meter stays hidden,
|
|
1508
|
+
* exactly as before) and the next completed turn tries again. */
|
|
1509
|
+
async #learnContextWindow(entry) {
|
|
1510
|
+
if (this.#contextWindows.has(entry.model)) return;
|
|
1511
|
+
try {
|
|
1512
|
+
const usage = await withTimeout(entry.query.getContextUsage(), CLAUDE_METADATA_TIMEOUT_MS, "Claude context window probe");
|
|
1513
|
+
this.#recordContextWindow(entry.model, usage);
|
|
1514
|
+
} catch {}
|
|
1515
|
+
}
|
|
1317
1516
|
contextWindow(model) {
|
|
1318
1517
|
return this.#contextWindows.get(model);
|
|
1319
1518
|
}
|
|
@@ -1362,7 +1561,7 @@ var ClaudeSupervisor = class {
|
|
|
1362
1561
|
} else {
|
|
1363
1562
|
await this.#syncPermissionMode(entry);
|
|
1364
1563
|
if (model !== entry.model) {
|
|
1365
|
-
await entry.query.setModel(model);
|
|
1564
|
+
await this.#control(entry, entry.query.setModel(model), "Claude Code model switch");
|
|
1366
1565
|
entry.model = model;
|
|
1367
1566
|
}
|
|
1368
1567
|
}
|
|
@@ -1381,6 +1580,7 @@ var ClaudeSupervisor = class {
|
|
|
1381
1580
|
transcriptText: "",
|
|
1382
1581
|
transcriptTextOrdinal: void 0,
|
|
1383
1582
|
thinking: "",
|
|
1583
|
+
requestUsage: void 0,
|
|
1384
1584
|
aborted: false,
|
|
1385
1585
|
deniedToolUseIds: /* @__PURE__ */ new Set(),
|
|
1386
1586
|
callNames: /* @__PURE__ */ new Map(),
|
|
@@ -1432,7 +1632,7 @@ var ClaudeSupervisor = class {
|
|
|
1432
1632
|
const entry = await this.#metadataEntry(agent, model);
|
|
1433
1633
|
try {
|
|
1434
1634
|
await entry.sdkInitialization;
|
|
1435
|
-
return await
|
|
1635
|
+
return await this.#control(entry, operation(entry.query, entry), "Claude metadata request");
|
|
1436
1636
|
} finally {
|
|
1437
1637
|
entry.lastUsedAt = Date.now();
|
|
1438
1638
|
if (entry.active === void 0 && entry.state === "idle") this.#armIdleTimer(entry);
|
|
@@ -1462,15 +1662,34 @@ var ClaudeSupervisor = class {
|
|
|
1462
1662
|
}
|
|
1463
1663
|
await this.#syncPermissionMode(entry);
|
|
1464
1664
|
if (model !== entry.model) {
|
|
1465
|
-
await entry.query.setModel(model);
|
|
1665
|
+
await this.#control(entry, entry.query.setModel(model), "Claude Code model switch");
|
|
1466
1666
|
entry.model = model;
|
|
1467
1667
|
}
|
|
1468
1668
|
return entry;
|
|
1469
1669
|
}
|
|
1670
|
+
/** Run one SDK control request against a live entry, and discard the entry if
|
|
1671
|
+
* it does not answer.
|
|
1672
|
+
*
|
|
1673
|
+
* Turn admission and every metadata read share one process-wide gate, so an
|
|
1674
|
+
* unbounded control request stalls every session until the Host restarts.
|
|
1675
|
+
* Bounding it is only half the cure: a timeout also proves this query has
|
|
1676
|
+
* stopped answering, and keeping the entry means the next caller reuses the
|
|
1677
|
+
* same dead process — timing out again, forever. Discarding it lets the next
|
|
1678
|
+
* attempt spawn a fresh one. Every control request goes through here so a
|
|
1679
|
+
* new call site cannot quietly reintroduce either half. */
|
|
1680
|
+
async #control(entry, operation, label, timeoutMs = CLAUDE_METADATA_TIMEOUT_MS) {
|
|
1681
|
+
try {
|
|
1682
|
+
return await withTimeout(operation, timeoutMs, label);
|
|
1683
|
+
} catch (error) {
|
|
1684
|
+
if (this.#entries.get(entry.sessionId) === entry) this.#entries.delete(entry.sessionId);
|
|
1685
|
+
await this.#disposeEntry(entry);
|
|
1686
|
+
throw error;
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1470
1689
|
async #syncPermissionMode(entry) {
|
|
1471
1690
|
const mode = claudePermissionMode(entry.ownerAgent.session.events);
|
|
1472
1691
|
if (mode === entry.permissionMode) return;
|
|
1473
|
-
await entry.query.setPermissionMode(mode);
|
|
1692
|
+
await this.#control(entry, entry.query.setPermissionMode(mode), "Claude Code permission mode switch");
|
|
1474
1693
|
entry.permissionMode = mode;
|
|
1475
1694
|
}
|
|
1476
1695
|
async disposeSession(sessionId) {
|
|
@@ -1498,7 +1717,11 @@ var ClaudeSupervisor = class {
|
|
|
1498
1717
|
const cwd = agent.session.header.cwd ?? process.cwd();
|
|
1499
1718
|
const input = new AsyncQueue();
|
|
1500
1719
|
const lifetime = new AbortController();
|
|
1501
|
-
const
|
|
1720
|
+
const projection = await this.#sidecar.importLegacy(sessionId, agent.session.events);
|
|
1721
|
+
const binding = projection.binding;
|
|
1722
|
+
const pendingRewind = projection.rewind?.pending;
|
|
1723
|
+
const forkAt = pendingRewind !== void 0 && "resumeAt" in pendingRewind ? pendingRewind.resumeAt : void 0;
|
|
1724
|
+
const startFresh = pendingRewind !== void 0 && "fresh" in pendingRewind;
|
|
1502
1725
|
const permissionMode = claudePermissionMode(agent.session.events);
|
|
1503
1726
|
const entry = {
|
|
1504
1727
|
sessionId,
|
|
@@ -1511,8 +1734,10 @@ var ClaudeSupervisor = class {
|
|
|
1511
1734
|
lastUsedAt: Date.now(),
|
|
1512
1735
|
input,
|
|
1513
1736
|
lifetime,
|
|
1514
|
-
claudeSessionId: binding?.claudeSessionId,
|
|
1515
|
-
expectedResume: binding?.claudeSessionId,
|
|
1737
|
+
claudeSessionId: startFresh ? void 0 : binding?.claudeSessionId,
|
|
1738
|
+
expectedResume: startFresh || forkAt !== void 0 ? void 0 : binding?.claudeSessionId,
|
|
1739
|
+
lastChainUuid: void 0,
|
|
1740
|
+
consumedRewind: pendingRewind !== void 0,
|
|
1516
1741
|
initialized: false,
|
|
1517
1742
|
idleTimer: void 0,
|
|
1518
1743
|
tasks: /* @__PURE__ */ new Map(),
|
|
@@ -1563,7 +1788,10 @@ var ClaudeSupervisor = class {
|
|
|
1563
1788
|
spawnClaudeCodeProcess: createManagedClaudeSpawner(this.#runtime, this.#config.executablePath, (process) => {
|
|
1564
1789
|
entry.process = process;
|
|
1565
1790
|
}),
|
|
1566
|
-
...binding === void 0 ? {} : {
|
|
1791
|
+
...binding === void 0 || startFresh ? {} : {
|
|
1792
|
+
resume: binding.claudeSessionId,
|
|
1793
|
+
...forkAt === void 0 ? {} : { resumeSessionAt: forkAt }
|
|
1794
|
+
},
|
|
1567
1795
|
model,
|
|
1568
1796
|
...thinkingMode === void 0 ? {} : thinkingMode === "off" ? { thinking: { type: "disabled" } } : thinkingMode === "ultracode" ? { settings: { ultracode: true } } : { effort: thinkingMode }
|
|
1569
1797
|
};
|
|
@@ -1580,7 +1808,11 @@ var ClaudeSupervisor = class {
|
|
|
1580
1808
|
}
|
|
1581
1809
|
async #pump(entry) {
|
|
1582
1810
|
try {
|
|
1583
|
-
for await (const sdkMessage of entry.query)
|
|
1811
|
+
for await (const sdkMessage of entry.query) {
|
|
1812
|
+
const chainUuid = chainEntryUuid(sdkMessage);
|
|
1813
|
+
if (chainUuid !== void 0) entry.lastChainUuid = chainUuid;
|
|
1814
|
+
for (const message of normalizeSdkMessage(sdkMessage)) await this.#handleMessage(entry, message);
|
|
1815
|
+
}
|
|
1584
1816
|
if (entry.state !== "disposed") await this.#handleDisconnect(entry, /* @__PURE__ */ new Error("Claude Code stream ended"));
|
|
1585
1817
|
} catch (error) {
|
|
1586
1818
|
if (entry.state !== "disposed") await this.#handleDisconnect(entry, error);
|
|
@@ -1603,6 +1835,10 @@ var ClaudeSupervisor = class {
|
|
|
1603
1835
|
cliVersion: message.cliVersion,
|
|
1604
1836
|
cwd: message.cwd
|
|
1605
1837
|
});
|
|
1838
|
+
if (entry.consumedRewind) {
|
|
1839
|
+
entry.consumedRewind = false;
|
|
1840
|
+
await this.#sidecar.clearRewindPending(entry.sessionId);
|
|
1841
|
+
}
|
|
1606
1842
|
return;
|
|
1607
1843
|
}
|
|
1608
1844
|
const taskId = message.kind === "subagent" ? message.taskId : void 0;
|
|
@@ -1701,6 +1937,23 @@ var ClaudeSupervisor = class {
|
|
|
1701
1937
|
isError: message.phase === "failed"
|
|
1702
1938
|
});
|
|
1703
1939
|
return;
|
|
1940
|
+
case "request-usage":
|
|
1941
|
+
if (message.parentToolUseId === void 0) active.requestUsage = message.usage;
|
|
1942
|
+
return;
|
|
1943
|
+
case "compaction":
|
|
1944
|
+
this.#closeTranscriptTextSegment(active);
|
|
1945
|
+
await this.#appendActivity(active, {
|
|
1946
|
+
kind: "compaction",
|
|
1947
|
+
phase: "completed",
|
|
1948
|
+
title: "Claude compacted the conversation",
|
|
1949
|
+
detail: {
|
|
1950
|
+
...message.trigger === void 0 ? {} : { trigger: message.trigger },
|
|
1951
|
+
...message.preTokens === void 0 ? {} : { preTokens: message.preTokens },
|
|
1952
|
+
...message.postTokens === void 0 ? {} : { postTokens: message.postTokens },
|
|
1953
|
+
...message.durationMs === void 0 ? {} : { durationMs: message.durationMs }
|
|
1954
|
+
}
|
|
1955
|
+
});
|
|
1956
|
+
return;
|
|
1704
1957
|
case "status":
|
|
1705
1958
|
case "warning":
|
|
1706
1959
|
case "unknown":
|
|
@@ -1832,6 +2085,21 @@ var ClaudeSupervisor = class {
|
|
|
1832
2085
|
entry.taskSnapshotAt = Date.now();
|
|
1833
2086
|
await this.#sidecar.writeTasks(entry.sessionId, [...entry.tasks.values()]).catch(() => void 0);
|
|
1834
2087
|
}
|
|
2088
|
+
/** What DSH is told about token usage.
|
|
2089
|
+
*
|
|
2090
|
+
* `TokenUsage` is documented as "token accounting for ONE model call", and
|
|
2091
|
+
* DSH's token meter divides `uncachedInput + cacheRead + cacheWrite` by the
|
|
2092
|
+
* context window to draw context pressure. One Claude turn makes many calls
|
|
2093
|
+
* and the CLI's result usage sums all of them, so reporting that sum pinned
|
|
2094
|
+
* the meter at 100%: a 35-call turn reads the same prompt from cache 35
|
|
2095
|
+
* times, which sums past the window without the conversation ever growing.
|
|
2096
|
+
* The newest single call answers "how big is this conversation now".
|
|
2097
|
+
*
|
|
2098
|
+
* The sidecar activity keeps the turn total instead — that is the audit and
|
|
2099
|
+
* cost record, and nothing divides it by a window. */
|
|
2100
|
+
#reportedUsage(active, result) {
|
|
2101
|
+
return active.requestUsage ?? result.usage;
|
|
2102
|
+
}
|
|
1835
2103
|
async #completeProgressSegment(active, result, recordUsage = true) {
|
|
1836
2104
|
if (recordUsage && (result.usage.inputTokens !== void 0 || result.usage.outputTokens !== void 0 || result.usage.cumulativeCostUsd !== void 0)) {
|
|
1837
2105
|
await this.#appendSafely(active, {
|
|
@@ -1843,7 +2111,7 @@ var ClaudeSupervisor = class {
|
|
|
1843
2111
|
});
|
|
1844
2112
|
active.output.push({
|
|
1845
2113
|
type: "usage",
|
|
1846
|
-
usage: result
|
|
2114
|
+
usage: this.#reportedUsage(active, result)
|
|
1847
2115
|
});
|
|
1848
2116
|
}
|
|
1849
2117
|
if (!active.sawTextDelta && active.text.length === 0 && result.text !== void 0) {
|
|
@@ -1878,6 +2146,7 @@ var ClaudeSupervisor = class {
|
|
|
1878
2146
|
entry.active = void 0;
|
|
1879
2147
|
entry.state = "idle";
|
|
1880
2148
|
entry.lastUsedAt = Date.now();
|
|
2149
|
+
await this.#recordChainAnchor(entry, active);
|
|
1881
2150
|
this.#armIdleTimer(entry);
|
|
1882
2151
|
return;
|
|
1883
2152
|
}
|
|
@@ -1891,7 +2160,7 @@ var ClaudeSupervisor = class {
|
|
|
1891
2160
|
});
|
|
1892
2161
|
active.output.push({
|
|
1893
2162
|
type: "usage",
|
|
1894
|
-
usage: result
|
|
2163
|
+
usage: this.#reportedUsage(active, result)
|
|
1895
2164
|
});
|
|
1896
2165
|
}
|
|
1897
2166
|
const unmatchedDenials = (result.permissionDenials ?? []).filter((denial) => !active.deniedToolUseIds.has(denial.toolUseId));
|
|
@@ -1959,8 +2228,20 @@ var ClaudeSupervisor = class {
|
|
|
1959
2228
|
entry.active = void 0;
|
|
1960
2229
|
entry.state = "idle";
|
|
1961
2230
|
entry.lastUsedAt = Date.now();
|
|
2231
|
+
await this.#recordChainAnchor(entry, active);
|
|
2232
|
+
await this.#learnContextWindow(entry);
|
|
1962
2233
|
this.#armIdleTimer(entry);
|
|
1963
2234
|
}
|
|
2235
|
+
/** Pin where Claude's chain ended for the DSH turn that just settled, so a
|
|
2236
|
+
* later rewind of the following turn can fork exactly here. Best effort:
|
|
2237
|
+
* a missing anchor only makes a rewind fall back to an earlier turn. */
|
|
2238
|
+
async #recordChainAnchor(entry, active) {
|
|
2239
|
+
const uuid = entry.lastChainUuid;
|
|
2240
|
+
if (uuid === void 0) return;
|
|
2241
|
+
try {
|
|
2242
|
+
await this.#sidecar.recordRewindAnchor(entry.sessionId, active.cursor.turn, uuid);
|
|
2243
|
+
} catch {}
|
|
2244
|
+
}
|
|
1964
2245
|
async #upsertTranscriptText(active) {
|
|
1965
2246
|
if (active.transcriptText.length === 0) return;
|
|
1966
2247
|
const ordinal = active.transcriptTextOrdinal ?? active.cursor.nextOrdinal++;
|
|
@@ -2159,28 +2440,28 @@ const MODELS = [
|
|
|
2159
2440
|
{
|
|
2160
2441
|
id: "default",
|
|
2161
2442
|
name: "Default (recommended)",
|
|
2162
|
-
description: "
|
|
2443
|
+
description: ""
|
|
2163
2444
|
},
|
|
2164
2445
|
{
|
|
2165
2446
|
id: "opus[1m]",
|
|
2166
2447
|
name: "Opus (1M context)",
|
|
2167
|
-
description: "
|
|
2448
|
+
description: "",
|
|
2168
2449
|
contextWindow: 1e6
|
|
2169
2450
|
},
|
|
2170
2451
|
{
|
|
2171
2452
|
id: "fable",
|
|
2172
2453
|
name: "Fable",
|
|
2173
|
-
description: "
|
|
2454
|
+
description: ""
|
|
2174
2455
|
},
|
|
2175
2456
|
{
|
|
2176
2457
|
id: "sonnet",
|
|
2177
2458
|
name: "Sonnet",
|
|
2178
|
-
description: "
|
|
2459
|
+
description: ""
|
|
2179
2460
|
},
|
|
2180
2461
|
{
|
|
2181
2462
|
id: "haiku",
|
|
2182
2463
|
name: "Haiku",
|
|
2183
|
-
description: "
|
|
2464
|
+
description: ""
|
|
2184
2465
|
}
|
|
2185
2466
|
];
|
|
2186
2467
|
const THINKING_MODES = [
|
|
@@ -2594,7 +2875,7 @@ function registerClaudeDoctorRoutes(ctx, runtime, supervisor, config, resolution
|
|
|
2594
2875
|
}
|
|
2595
2876
|
//#endregion
|
|
2596
2877
|
//#region src/projection-routes.ts
|
|
2597
|
-
const MAX_SESSION_ID_CHARS$
|
|
2878
|
+
const MAX_SESSION_ID_CHARS$6 = 1024;
|
|
2598
2879
|
/** Slow-moving metadata refresh and stream heartbeat cadence; deliberately off
|
|
2599
2880
|
* the transcript hot path so git/gh latency never delays visible text. */
|
|
2600
2881
|
const META_REFRESH_MS = 5e3;
|
|
@@ -2608,7 +2889,7 @@ function targetFromUrl(rawUrl) {
|
|
|
2608
2889
|
if (stream) encoded = encoded.slice(0, -7);
|
|
2609
2890
|
if (encoded.length === 0 || encoded.includes("/")) return void 0;
|
|
2610
2891
|
const sessionId = decodeURIComponent(encoded);
|
|
2611
|
-
if (sessionId.length === 0 || sessionId.length > MAX_SESSION_ID_CHARS$
|
|
2892
|
+
if (sessionId.length === 0 || sessionId.length > MAX_SESSION_ID_CHARS$6) return void 0;
|
|
2612
2893
|
return {
|
|
2613
2894
|
sessionId,
|
|
2614
2895
|
stream
|
|
@@ -2627,7 +2908,8 @@ function envelope(projection, meta) {
|
|
|
2627
2908
|
...projection.contextUsage === void 0 ? {} : { contextUsage: projection.contextUsage },
|
|
2628
2909
|
...projection.tasks === void 0 ? {} : { tasks: projection.tasks },
|
|
2629
2910
|
...meta.repository === void 0 ? {} : { repository: meta.repository },
|
|
2630
|
-
reviewComments: meta.reviewComments
|
|
2911
|
+
reviewComments: meta.reviewComments,
|
|
2912
|
+
...projection.rewind === void 0 ? {} : { rewind: { ranges: projection.rewind.ranges } }
|
|
2631
2913
|
};
|
|
2632
2914
|
}
|
|
2633
2915
|
/** Register the browser-readable, credential-free sidecar projection endpoint.
|
|
@@ -2768,7 +3050,7 @@ function registerClaudeProjectionRoute(ctx, sidecar, ownsSession, commandsForSes
|
|
|
2768
3050
|
}
|
|
2769
3051
|
//#endregion
|
|
2770
3052
|
//#region src/repository-status.ts
|
|
2771
|
-
const MAX_OUTPUT_BYTES$
|
|
3053
|
+
const MAX_OUTPUT_BYTES$4 = 65536;
|
|
2772
3054
|
const MAX_DIFF_BYTES = 262144;
|
|
2773
3055
|
const MAX_FILE_BYTES = 8388608;
|
|
2774
3056
|
const MAX_UNTRACKED_DIFFS = 50;
|
|
@@ -2788,7 +3070,7 @@ async function collect$3(handle) {
|
|
|
2788
3070
|
lossy: stdout?.lossy === true
|
|
2789
3071
|
};
|
|
2790
3072
|
}
|
|
2791
|
-
async function run(runtime, executable, args, cwd, timeoutMs, maxBytes = MAX_OUTPUT_BYTES$
|
|
3073
|
+
async function run(runtime, executable, args, cwd, timeoutMs, maxBytes = MAX_OUTPUT_BYTES$4) {
|
|
2792
3074
|
const signal = AbortSignal.timeout(timeoutMs);
|
|
2793
3075
|
return collect$3(runtime.spawn({
|
|
2794
3076
|
argv: [executable, ...args],
|
|
@@ -2796,7 +3078,7 @@ async function run(runtime, executable, args, cwd, timeoutMs, maxBytes = MAX_OUT
|
|
|
2796
3078
|
stdio: {
|
|
2797
3079
|
stdin: "ignore",
|
|
2798
3080
|
stdout: { maxBytes },
|
|
2799
|
-
stderr: { maxBytes: MAX_OUTPUT_BYTES$
|
|
3081
|
+
stderr: { maxBytes: MAX_OUTPUT_BYTES$4 }
|
|
2800
3082
|
},
|
|
2801
3083
|
graceMs: 1e3,
|
|
2802
3084
|
signal,
|
|
@@ -2866,14 +3148,14 @@ function parseGitHubRemote(value) {
|
|
|
2866
3148
|
if (match?.[1] === void 0 || match[2] === void 0) return void 0;
|
|
2867
3149
|
return `${match[1]}/${match[2]}`;
|
|
2868
3150
|
}
|
|
2869
|
-
function record$
|
|
3151
|
+
function record$9(value) {
|
|
2870
3152
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
2871
3153
|
}
|
|
2872
3154
|
function aggregateChecks(value) {
|
|
2873
3155
|
if (!Array.isArray(value) || value.length === 0) return "none";
|
|
2874
3156
|
let pending = false;
|
|
2875
3157
|
for (const item of value) {
|
|
2876
|
-
const check = record$
|
|
3158
|
+
const check = record$9(item);
|
|
2877
3159
|
if (check === void 0) continue;
|
|
2878
3160
|
const conclusion = typeof check.conclusion === "string" ? check.conclusion.toUpperCase() : void 0;
|
|
2879
3161
|
const status = typeof check.status === "string" ? check.status.toUpperCase() : void 0;
|
|
@@ -2897,7 +3179,7 @@ function reviewState(value) {
|
|
|
2897
3179
|
return "none";
|
|
2898
3180
|
}
|
|
2899
3181
|
function parsePullRequest(value) {
|
|
2900
|
-
const input = record$
|
|
3182
|
+
const input = record$9(value);
|
|
2901
3183
|
if (input === void 0 || !Number.isSafeInteger(input.number) || Number(input.number) <= 0 || typeof input.title !== "string" || typeof input.url !== "string") return void 0;
|
|
2902
3184
|
let url;
|
|
2903
3185
|
try {
|
|
@@ -2918,7 +3200,7 @@ function parsePullRequest(value) {
|
|
|
2918
3200
|
review: reviewState(input.reviewDecision),
|
|
2919
3201
|
checks: aggregateChecks(input.statusCheckRollup),
|
|
2920
3202
|
...typeof input.mergeStateStatus === "string" ? { mergeState: bounded(input.mergeStateStatus) } : {},
|
|
2921
|
-
...typeof record$
|
|
3203
|
+
...typeof record$9(input.author)?.login === "string" ? { author: bounded(String(record$9(input.author)?.login)) } : {},
|
|
2922
3204
|
...typeof input.createdAt === "string" && Number.isFinite(Date.parse(input.createdAt)) ? { createdAt: new Date(input.createdAt).toISOString() } : {},
|
|
2923
3205
|
...typeof input.mergedAt === "string" && Number.isFinite(Date.parse(input.mergedAt)) ? { mergedAt: new Date(input.mergedAt).toISOString() } : {},
|
|
2924
3206
|
...typeof input.baseRefName === "string" && bounded(input.baseRefName).length > 0 ? { baseBranch: bounded(input.baseRefName) } : {}
|
|
@@ -3212,7 +3494,7 @@ var RepositoryStatusService = class {
|
|
|
3212
3494
|
};
|
|
3213
3495
|
//#endregion
|
|
3214
3496
|
//#region src/repository-setup.ts
|
|
3215
|
-
const MAX_OUTPUT_BYTES$
|
|
3497
|
+
const MAX_OUTPUT_BYTES$3 = 131072;
|
|
3216
3498
|
const GIT_TIMEOUT_MS$2 = 1e4;
|
|
3217
3499
|
const GIT_FETCH_TIMEOUT_MS = 6e4;
|
|
3218
3500
|
const MAX_PATH_CHARS$2 = 4096;
|
|
@@ -3299,8 +3581,18 @@ var RepositorySetupService = class {
|
|
|
3299
3581
|
this.#cleanupGraceMs = options.cleanupGraceMs ?? CLEANUP_GRACE_MS;
|
|
3300
3582
|
}
|
|
3301
3583
|
async listBranches(cwd) {
|
|
3584
|
+
const git = await this.#git();
|
|
3585
|
+
return this.#listBranches(git, await this.#repositoryRoot(git, safePath(cwd)));
|
|
3586
|
+
}
|
|
3587
|
+
/** Refresh remote-tracking refs before listing: a branch pushed after this
|
|
3588
|
+
* checkout last fetched has no local ref, so the picker cannot offer it. */
|
|
3589
|
+
async refreshBranches(cwd) {
|
|
3302
3590
|
const git = await this.#git();
|
|
3303
3591
|
const root = await this.#repositoryRoot(git, safePath(cwd));
|
|
3592
|
+
await this.#fetchRemotes(git, root);
|
|
3593
|
+
return this.#listBranches(git, root);
|
|
3594
|
+
}
|
|
3595
|
+
async #listBranches(git, root) {
|
|
3304
3596
|
const [status, refs, remoteRefs] = await Promise.all([
|
|
3305
3597
|
this.#run(git, [
|
|
3306
3598
|
"status",
|
|
@@ -3514,6 +3806,16 @@ var RepositorySetupService = class {
|
|
|
3514
3806
|
branch: localBranch
|
|
3515
3807
|
};
|
|
3516
3808
|
}
|
|
3809
|
+
async #fetchRemotes(git, root) {
|
|
3810
|
+
const fetched = await this.#run(git, [
|
|
3811
|
+
"-c",
|
|
3812
|
+
"credential.interactive=never",
|
|
3813
|
+
"fetch",
|
|
3814
|
+
"--all",
|
|
3815
|
+
"--prune"
|
|
3816
|
+
], root, GIT_FETCH_TIMEOUT_MS);
|
|
3817
|
+
if (fetched.exitCode !== 0 || fetched.lossy) throw new RepositorySetupError("fetch-failed", "Git could not refresh remote references.");
|
|
3818
|
+
}
|
|
3517
3819
|
async #checkout(info, branch) {
|
|
3518
3820
|
if (info.current !== branch) {
|
|
3519
3821
|
if (info.dirty) throw new RepositorySetupError("dirty-workspace", "Commit or stash workspace changes before switching branches.");
|
|
@@ -3542,14 +3844,7 @@ var RepositorySetupService = class {
|
|
|
3542
3844
|
async #createWorktree(root, baseBranch, baseRef, explicitBranchName, reuseExistingBranch, progress) {
|
|
3543
3845
|
const git = await this.#git();
|
|
3544
3846
|
progress("fetching");
|
|
3545
|
-
|
|
3546
|
-
"-c",
|
|
3547
|
-
"credential.interactive=never",
|
|
3548
|
-
"fetch",
|
|
3549
|
-
"--all",
|
|
3550
|
-
"--prune"
|
|
3551
|
-
], root, GIT_FETCH_TIMEOUT_MS);
|
|
3552
|
-
if (fetched.exitCode !== 0 || fetched.lossy) throw new RepositorySetupError("fetch-failed", "Git could not refresh remote references before creating the worktree.");
|
|
3847
|
+
await this.#fetchRemotes(git, root);
|
|
3553
3848
|
const suffix = randomUUID().slice(0, 8);
|
|
3554
3849
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/gu, "").replace(/\.\d{3}Z$/u, "Z");
|
|
3555
3850
|
const branch = explicitBranchName ?? `${safeBranch(await this.#branchPrefix())}/${slug(baseBranch, "branch")}-${stamp}-${suffix}`;
|
|
@@ -3636,8 +3931,8 @@ var RepositorySetupService = class {
|
|
|
3636
3931
|
cwd,
|
|
3637
3932
|
stdio: {
|
|
3638
3933
|
stdin: "ignore",
|
|
3639
|
-
stdout: { maxBytes: MAX_OUTPUT_BYTES$
|
|
3640
|
-
stderr: { maxBytes: MAX_OUTPUT_BYTES$
|
|
3934
|
+
stdout: { maxBytes: MAX_OUTPUT_BYTES$3 },
|
|
3935
|
+
stderr: { maxBytes: MAX_OUTPUT_BYTES$3 }
|
|
3641
3936
|
},
|
|
3642
3937
|
graceMs: 1e3,
|
|
3643
3938
|
signal: AbortSignal.timeout(timeoutMs),
|
|
@@ -3678,7 +3973,7 @@ var RepositorySetupService = class {
|
|
|
3678
3973
|
};
|
|
3679
3974
|
//#endregion
|
|
3680
3975
|
//#region src/repository-actions.ts
|
|
3681
|
-
const MAX_OUTPUT_BYTES$
|
|
3976
|
+
const MAX_OUTPUT_BYTES$2 = 262144;
|
|
3682
3977
|
const MAX_PATCH_CHARS = 65536;
|
|
3683
3978
|
const MAX_MESSAGE_CHARS = 512;
|
|
3684
3979
|
const MAX_PR_TEXT_CHARS = 8192;
|
|
@@ -4005,7 +4300,7 @@ var RepositoryActionService = class {
|
|
|
4005
4300
|
"--",
|
|
4006
4301
|
":(exclude)WARP.md",
|
|
4007
4302
|
":(exclude)**/WARP.md"
|
|
4008
|
-
], root, GIT_TIMEOUT_MS$1, MAX_OUTPUT_BYTES$
|
|
4303
|
+
], root, GIT_TIMEOUT_MS$1, MAX_OUTPUT_BYTES$2),
|
|
4009
4304
|
this.#run(git, [
|
|
4010
4305
|
"diff",
|
|
4011
4306
|
"--no-ext-diff",
|
|
@@ -4014,7 +4309,7 @@ var RepositoryActionService = class {
|
|
|
4014
4309
|
"--",
|
|
4015
4310
|
":(exclude)WARP.md",
|
|
4016
4311
|
":(exclude)**/WARP.md"
|
|
4017
|
-
], root, GIT_TIMEOUT_MS$1, MAX_OUTPUT_BYTES$
|
|
4312
|
+
], root, GIT_TIMEOUT_MS$1, MAX_OUTPUT_BYTES$2)
|
|
4018
4313
|
]);
|
|
4019
4314
|
if (branchResult.exitCode !== 0) throw new RepositoryActionError("detached-head", "A detached HEAD cannot be committed from this panel.");
|
|
4020
4315
|
if (headResult.exitCode !== 0 || statusResult.exitCode !== 0 || statusResult.lossy) throw new RepositoryActionError("repository-unavailable", "Repository state is unavailable.");
|
|
@@ -4108,14 +4403,14 @@ var RepositoryActionService = class {
|
|
|
4108
4403
|
if (result.exitCode !== 0 || result.lossy) throw new RepositoryActionError(code, message);
|
|
4109
4404
|
return result;
|
|
4110
4405
|
}
|
|
4111
|
-
#run(executable, args, cwd, timeoutMs, maxBytes = MAX_OUTPUT_BYTES$
|
|
4406
|
+
#run(executable, args, cwd, timeoutMs, maxBytes = MAX_OUTPUT_BYTES$2) {
|
|
4112
4407
|
return collect$1(this.#runtime.spawn({
|
|
4113
4408
|
argv: [executable, ...args],
|
|
4114
4409
|
cwd,
|
|
4115
4410
|
stdio: {
|
|
4116
4411
|
stdin: "ignore",
|
|
4117
4412
|
stdout: { maxBytes },
|
|
4118
|
-
stderr: { maxBytes: MAX_OUTPUT_BYTES$
|
|
4413
|
+
stderr: { maxBytes: MAX_OUTPUT_BYTES$2 }
|
|
4119
4414
|
},
|
|
4120
4415
|
graceMs: 1e3,
|
|
4121
4416
|
signal: AbortSignal.timeout(timeoutMs),
|
|
@@ -4125,20 +4420,20 @@ var RepositoryActionService = class {
|
|
|
4125
4420
|
};
|
|
4126
4421
|
//#endregion
|
|
4127
4422
|
//#region src/repository-setup-routes.ts
|
|
4128
|
-
const MAX_BODY_BYTES$
|
|
4129
|
-
function record$
|
|
4423
|
+
const MAX_BODY_BYTES$5 = 16384;
|
|
4424
|
+
function record$8(value) {
|
|
4130
4425
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
4131
4426
|
}
|
|
4132
|
-
async function readJson$
|
|
4427
|
+
async function readJson$5(req) {
|
|
4133
4428
|
const chunks = [];
|
|
4134
4429
|
let size = 0;
|
|
4135
4430
|
for await (const chunk of req) {
|
|
4136
4431
|
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
4137
4432
|
size += buffer.length;
|
|
4138
|
-
if (size > MAX_BODY_BYTES$
|
|
4433
|
+
if (size > MAX_BODY_BYTES$5) throw new RepositorySetupError("body-too-large", "The request body is too large.");
|
|
4139
4434
|
chunks.push(buffer);
|
|
4140
4435
|
}
|
|
4141
|
-
const value = record$
|
|
4436
|
+
const value = record$8(JSON.parse(Buffer.concat(chunks).toString("utf8")));
|
|
4142
4437
|
if (value === void 0) throw new RepositorySetupError("invalid-request", "The request body is invalid.");
|
|
4143
4438
|
return value;
|
|
4144
4439
|
}
|
|
@@ -4198,6 +4493,11 @@ function registerRepositorySetupRoute(ctx, service) {
|
|
|
4198
4493
|
if (!trustedRequest(req)) return json(res, 403, { error: "forbidden" });
|
|
4199
4494
|
const pathname = new URL(req.url ?? "/", "http://localhost").pathname;
|
|
4200
4495
|
try {
|
|
4496
|
+
if (pathname === `/plugins/dsh-claude/repository/setup/branches/refresh`) {
|
|
4497
|
+
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
4498
|
+
const input = await readJson$5(req);
|
|
4499
|
+
return json(res, 200, await service.refreshBranches(string$2(input, "cwd")));
|
|
4500
|
+
}
|
|
4201
4501
|
if (pathname === `/plugins/dsh-claude/repository/setup/branches`) {
|
|
4202
4502
|
if (req.method !== "GET") return json(res, 405, { error: "method not allowed" });
|
|
4203
4503
|
const cwd = new URL(req.url ?? "/", "http://localhost").searchParams.get("cwd");
|
|
@@ -4206,19 +4506,19 @@ function registerRepositorySetupRoute(ctx, service) {
|
|
|
4206
4506
|
}
|
|
4207
4507
|
if (pathname === "/plugins/dsh-claude/repository/setup") {
|
|
4208
4508
|
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
4209
|
-
const input = await readJson$
|
|
4509
|
+
const input = await readJson$5(req);
|
|
4210
4510
|
if (typeof input.worktree !== "boolean") throw new RepositorySetupError("invalid-request", "The worktree field is required.");
|
|
4211
4511
|
await streamSetup(res, service, input);
|
|
4212
4512
|
return;
|
|
4213
4513
|
}
|
|
4214
4514
|
if (pathname === `/plugins/dsh-claude/repository/setup/cleanup`) {
|
|
4215
4515
|
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
4216
|
-
const input = await readJson$
|
|
4516
|
+
const input = await readJson$5(req);
|
|
4217
4517
|
return json(res, 200, await service.cleanupMerged(string$2(input, "path"), string$2(input, "baseBranch")));
|
|
4218
4518
|
}
|
|
4219
4519
|
if (pathname === `/plugins/dsh-claude/repository/setup/bind`) {
|
|
4220
4520
|
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
4221
|
-
const input = await readJson$
|
|
4521
|
+
const input = await readJson$5(req);
|
|
4222
4522
|
await service.bindLease(string$2(input, "leaseId"), string$2(input, "sessionId"));
|
|
4223
4523
|
return json(res, 200, { ok: true });
|
|
4224
4524
|
}
|
|
@@ -4236,8 +4536,8 @@ function registerRepositorySetupRoute(ctx, service) {
|
|
|
4236
4536
|
}
|
|
4237
4537
|
//#endregion
|
|
4238
4538
|
//#region src/repository-action-routes.ts
|
|
4239
|
-
const MAX_BODY_BYTES$
|
|
4240
|
-
const MAX_SESSION_ID_CHARS$
|
|
4539
|
+
const MAX_BODY_BYTES$4 = 16384;
|
|
4540
|
+
const MAX_SESSION_ID_CHARS$5 = 1024;
|
|
4241
4541
|
const ACTIONS = /* @__PURE__ */ new Set([
|
|
4242
4542
|
"commit",
|
|
4243
4543
|
"commit-push",
|
|
@@ -4246,25 +4546,25 @@ const ACTIONS = /* @__PURE__ */ new Set([
|
|
|
4246
4546
|
"merge-pr",
|
|
4247
4547
|
"update-branch"
|
|
4248
4548
|
]);
|
|
4249
|
-
function record$
|
|
4549
|
+
function record$7(value) {
|
|
4250
4550
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
4251
4551
|
}
|
|
4252
|
-
async function readJson$
|
|
4552
|
+
async function readJson$4(req) {
|
|
4253
4553
|
const chunks = [];
|
|
4254
4554
|
let size = 0;
|
|
4255
4555
|
for await (const chunk of req) {
|
|
4256
4556
|
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
4257
4557
|
size += buffer.length;
|
|
4258
|
-
if (size > MAX_BODY_BYTES$
|
|
4558
|
+
if (size > MAX_BODY_BYTES$4) throw new RepositoryActionError("body-too-large", "The request body is too large.");
|
|
4259
4559
|
chunks.push(buffer);
|
|
4260
4560
|
}
|
|
4261
|
-
const value = record$
|
|
4561
|
+
const value = record$7(JSON.parse(Buffer.concat(chunks).toString("utf8")));
|
|
4262
4562
|
if (value === void 0) throw new RepositoryActionError("invalid-request", "The request body is invalid.");
|
|
4263
4563
|
return value;
|
|
4264
4564
|
}
|
|
4265
4565
|
function sessionId$1(url) {
|
|
4266
4566
|
const value = url.searchParams.get("sessionId");
|
|
4267
|
-
if (value === null || value.length === 0 || value.length > MAX_SESSION_ID_CHARS$
|
|
4567
|
+
if (value === null || value.length === 0 || value.length > MAX_SESSION_ID_CHARS$5) throw new RepositoryActionError("invalid-session", "The session is invalid.");
|
|
4268
4568
|
return value;
|
|
4269
4569
|
}
|
|
4270
4570
|
function string$1(input, key) {
|
|
@@ -4313,12 +4613,12 @@ function registerRepositoryActionRoute(ctx, service, cwdForSession) {
|
|
|
4313
4613
|
}
|
|
4314
4614
|
if (url.pathname === `/plugins/dsh-claude/repository/action/message`) {
|
|
4315
4615
|
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
4316
|
-
const input = await readJson$
|
|
4616
|
+
const input = await readJson$4(req);
|
|
4317
4617
|
return json(res, 200, { message: await service.generateMessage(cwd, string$1(input, "fingerprint")) });
|
|
4318
4618
|
}
|
|
4319
4619
|
if (url.pathname === "/plugins/dsh-claude/repository/action") {
|
|
4320
4620
|
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
4321
|
-
return json(res, 200, await service.execute(cwd, actionRequest(await readJson$
|
|
4621
|
+
return json(res, 200, await service.execute(cwd, actionRequest(await readJson$4(req))));
|
|
4322
4622
|
}
|
|
4323
4623
|
return json(res, 404, { error: "not found" });
|
|
4324
4624
|
} catch (error) {
|
|
@@ -4337,6 +4637,182 @@ function registerRepositoryActionRoute(ctx, service, cwdForSession) {
|
|
|
4337
4637
|
}), "dsh-claude: repository action route");
|
|
4338
4638
|
}
|
|
4339
4639
|
//#endregion
|
|
4640
|
+
//#region src/editor-open.ts
|
|
4641
|
+
const MAX_OUTPUT_BYTES$1 = 8192;
|
|
4642
|
+
/** Long enough for a launcher shim to fail loudly, short enough that the
|
|
4643
|
+
* request returns while the IDE is still booting. On Windows and Linux the
|
|
4644
|
+
* IDE binary IS the launched process, so still running past this is success. */
|
|
4645
|
+
const SETTLE_MS = 1500;
|
|
4646
|
+
const EDITOR_IDS = /* @__PURE__ */ new Set(["cursor", "idea"]);
|
|
4647
|
+
/** Launch commands tried in order, first success wins. macOS keeps `open -a`
|
|
4648
|
+
* behind the CLI shim because both shims are opt-in installs there, while
|
|
4649
|
+
* `open -a` finds the bundle wherever Toolbox or the DMG dropped it. */
|
|
4650
|
+
const LAUNCHERS = {
|
|
4651
|
+
cursor: {
|
|
4652
|
+
darwin: [["cursor"], [
|
|
4653
|
+
"open",
|
|
4654
|
+
"-a",
|
|
4655
|
+
"Cursor"
|
|
4656
|
+
]],
|
|
4657
|
+
win32: [["cursor"]],
|
|
4658
|
+
linux: [["cursor"]]
|
|
4659
|
+
},
|
|
4660
|
+
idea: {
|
|
4661
|
+
darwin: [
|
|
4662
|
+
["idea"],
|
|
4663
|
+
[
|
|
4664
|
+
"open",
|
|
4665
|
+
"-a",
|
|
4666
|
+
"IntelliJ IDEA"
|
|
4667
|
+
],
|
|
4668
|
+
[
|
|
4669
|
+
"open",
|
|
4670
|
+
"-a",
|
|
4671
|
+
"IntelliJ IDEA CE"
|
|
4672
|
+
]
|
|
4673
|
+
],
|
|
4674
|
+
win32: [["idea"], ["idea64.exe"]],
|
|
4675
|
+
linux: [["idea"], ["idea.sh"]]
|
|
4676
|
+
}
|
|
4677
|
+
};
|
|
4678
|
+
/** cmd.exe re-interprets these, and a mis-parsed path opens the wrong project
|
|
4679
|
+
* rather than failing. Refuse instead of guessing. */
|
|
4680
|
+
const WINDOWS_UNSAFE = /["&|<>^%]/u;
|
|
4681
|
+
var EditorOpenError = class extends Error {
|
|
4682
|
+
code;
|
|
4683
|
+
constructor(code, message) {
|
|
4684
|
+
super(message);
|
|
4685
|
+
this.name = "EditorOpenError";
|
|
4686
|
+
this.code = code;
|
|
4687
|
+
}
|
|
4688
|
+
};
|
|
4689
|
+
/** Open a session's working directory in a desktop editor. */
|
|
4690
|
+
var EditorOpenService = class {
|
|
4691
|
+
#runtime;
|
|
4692
|
+
#platform;
|
|
4693
|
+
#settleMs;
|
|
4694
|
+
constructor(runtime, platform = process.platform, settleMs = SETTLE_MS) {
|
|
4695
|
+
this.#runtime = runtime;
|
|
4696
|
+
this.#platform = platform;
|
|
4697
|
+
this.#settleMs = settleMs;
|
|
4698
|
+
}
|
|
4699
|
+
async open(cwd, editor) {
|
|
4700
|
+
if (this.#platform === "win32" && WINDOWS_UNSAFE.test(cwd)) throw new EditorOpenError("unsupported-path", "The project path cannot be opened through the Windows shell.");
|
|
4701
|
+
const candidates = LAUNCHERS[editor][this.#platform] ?? LAUNCHERS[editor].linux ?? [];
|
|
4702
|
+
let found = false;
|
|
4703
|
+
for (const candidate of candidates) {
|
|
4704
|
+
const argv = await this.#argv(candidate, cwd);
|
|
4705
|
+
if (argv === void 0) continue;
|
|
4706
|
+
found = true;
|
|
4707
|
+
if (await this.#launch(argv, cwd)) return;
|
|
4708
|
+
}
|
|
4709
|
+
throw found ? new EditorOpenError("launch-failed", "The editor refused to open the project.") : new EditorOpenError("editor-unavailable", "The editor was not found on PATH.");
|
|
4710
|
+
}
|
|
4711
|
+
async #argv(candidate, cwd) {
|
|
4712
|
+
const [program, ...rest] = candidate;
|
|
4713
|
+
if (program === void 0) return void 0;
|
|
4714
|
+
if (this.#platform === "win32") return [
|
|
4715
|
+
"cmd.exe",
|
|
4716
|
+
"/d",
|
|
4717
|
+
"/s",
|
|
4718
|
+
"/c",
|
|
4719
|
+
program,
|
|
4720
|
+
...rest,
|
|
4721
|
+
cwd
|
|
4722
|
+
];
|
|
4723
|
+
try {
|
|
4724
|
+
return [
|
|
4725
|
+
await this.#runtime.resolveExecutable(program),
|
|
4726
|
+
...rest,
|
|
4727
|
+
cwd
|
|
4728
|
+
];
|
|
4729
|
+
} catch {
|
|
4730
|
+
return;
|
|
4731
|
+
}
|
|
4732
|
+
}
|
|
4733
|
+
/** True once the editor is launched: either the shim exited cleanly or the
|
|
4734
|
+
* process is still alive past the settle window. */
|
|
4735
|
+
async #launch(argv, cwd) {
|
|
4736
|
+
let handle;
|
|
4737
|
+
try {
|
|
4738
|
+
handle = this.#runtime.spawn({
|
|
4739
|
+
argv,
|
|
4740
|
+
cwd,
|
|
4741
|
+
stdio: {
|
|
4742
|
+
stdin: "ignore",
|
|
4743
|
+
stdout: { maxBytes: MAX_OUTPUT_BYTES$1 },
|
|
4744
|
+
stderr: { maxBytes: MAX_OUTPUT_BYTES$1 }
|
|
4745
|
+
},
|
|
4746
|
+
graceMs: 1e3,
|
|
4747
|
+
env: {}
|
|
4748
|
+
});
|
|
4749
|
+
} catch {
|
|
4750
|
+
return false;
|
|
4751
|
+
}
|
|
4752
|
+
return await Promise.race([handle.done.then((outcome) => outcome.exitCode === 0), new Promise((resolve) => {
|
|
4753
|
+
setTimeout(() => resolve(true), this.#settleMs).unref?.();
|
|
4754
|
+
})]);
|
|
4755
|
+
}
|
|
4756
|
+
};
|
|
4757
|
+
//#endregion
|
|
4758
|
+
//#region src/editor-open-routes.ts
|
|
4759
|
+
const MAX_SESSION_ID_CHARS$4 = 1024;
|
|
4760
|
+
/** Open the session's working directory in a desktop editor. Query-only: the
|
|
4761
|
+
* request carries two enum-ish values, so there is no body to parse. */
|
|
4762
|
+
function registerEditorOpenRoute(ctx, service, cwdForSession) {
|
|
4763
|
+
ctx.effect(() => ctx.webServer.register({
|
|
4764
|
+
kind: "exact",
|
|
4765
|
+
path: CLAUDE_EDITOR_OPEN_PATH,
|
|
4766
|
+
handler: async (req, res) => {
|
|
4767
|
+
if (!trustedRequest(req)) return json(res, 403, { error: "forbidden" });
|
|
4768
|
+
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
4769
|
+
const params = new URL(req.url ?? "/", "http://localhost").searchParams;
|
|
4770
|
+
const id = params.get("sessionId");
|
|
4771
|
+
const editor = params.get("editor");
|
|
4772
|
+
if (id === null || id.length === 0 || id.length > MAX_SESSION_ID_CHARS$4) return json(res, 400, {
|
|
4773
|
+
error: "invalid-session",
|
|
4774
|
+
message: "The session is invalid."
|
|
4775
|
+
});
|
|
4776
|
+
if (editor === null || !EDITOR_IDS.has(editor)) return json(res, 400, {
|
|
4777
|
+
error: "invalid-editor",
|
|
4778
|
+
message: "The editor is invalid."
|
|
4779
|
+
});
|
|
4780
|
+
const cwd = cwdForSession(id);
|
|
4781
|
+
if (cwd === void 0) return json(res, 409, {
|
|
4782
|
+
error: "session-unavailable",
|
|
4783
|
+
message: "The Claude session is unavailable."
|
|
4784
|
+
});
|
|
4785
|
+
try {
|
|
4786
|
+
await service.open(cwd, editor);
|
|
4787
|
+
return json(res, 200, { opened: true });
|
|
4788
|
+
} catch (error) {
|
|
4789
|
+
if (error instanceof EditorOpenError) return json(res, 409, {
|
|
4790
|
+
error: error.code,
|
|
4791
|
+
message: error.message
|
|
4792
|
+
});
|
|
4793
|
+
return json(res, 500, {
|
|
4794
|
+
error: "editor-open-unavailable",
|
|
4795
|
+
message: "The editor could not be launched."
|
|
4796
|
+
});
|
|
4797
|
+
}
|
|
4798
|
+
}
|
|
4799
|
+
}), "dsh-claude: editor open route");
|
|
4800
|
+
}
|
|
4801
|
+
//#endregion
|
|
4802
|
+
//#region src/github-url.ts
|
|
4803
|
+
/** Only GitHub's own image hosts; the browser loads these directly, so a URL
|
|
4804
|
+
* the API did not vouch for must never become an outbound request. */
|
|
4805
|
+
function githubAvatarUrl(value) {
|
|
4806
|
+
if (typeof value !== "string" || value.length === 0 || value.length > 1024) return void 0;
|
|
4807
|
+
try {
|
|
4808
|
+
const url = new URL(value);
|
|
4809
|
+
const allowed = url.hostname === "github.com" || url.hostname === "githubusercontent.com" || url.hostname.endsWith(".githubusercontent.com");
|
|
4810
|
+
return url.protocol === "https:" && allowed ? url.href : void 0;
|
|
4811
|
+
} catch {
|
|
4812
|
+
return;
|
|
4813
|
+
}
|
|
4814
|
+
}
|
|
4815
|
+
//#endregion
|
|
4340
4816
|
//#region src/pr-feedback.ts
|
|
4341
4817
|
const MAX_OUTPUT_BYTES = 524288;
|
|
4342
4818
|
const GIT_TIMEOUT_MS = 1e4;
|
|
@@ -4362,26 +4838,28 @@ async function collect(handle) {
|
|
|
4362
4838
|
lossy: stdout?.lossy === true
|
|
4363
4839
|
};
|
|
4364
4840
|
}
|
|
4365
|
-
function record$
|
|
4841
|
+
function record$6(value) {
|
|
4366
4842
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
4367
4843
|
}
|
|
4368
4844
|
function parseReviewComments(value) {
|
|
4369
4845
|
if (!Array.isArray(value)) return [];
|
|
4370
4846
|
const comments = [];
|
|
4371
4847
|
for (const item of value) {
|
|
4372
|
-
const input = record$
|
|
4848
|
+
const input = record$6(item);
|
|
4373
4849
|
if (input === void 0 || !Number.isSafeInteger(input.id)) continue;
|
|
4374
4850
|
const body = typeof input.body === "string" ? input.body.trim() : "";
|
|
4375
4851
|
const path = typeof input.path === "string" ? input.path : "";
|
|
4376
4852
|
const url = typeof input.html_url === "string" ? input.html_url : "";
|
|
4377
4853
|
if (body.length === 0 || path.length === 0) continue;
|
|
4854
|
+
const avatarUrl = githubAvatarUrl(record$6(input.user)?.avatar_url);
|
|
4378
4855
|
const line = Number.isSafeInteger(input.line) ? Number(input.line) : Number.isSafeInteger(input.original_line) ? Number(input.original_line) : void 0;
|
|
4379
4856
|
comments.push({
|
|
4380
4857
|
id: Number(input.id),
|
|
4381
4858
|
path,
|
|
4382
4859
|
...line === void 0 ? {} : { line },
|
|
4383
4860
|
side: input.side === "LEFT" ? "old" : "new",
|
|
4384
|
-
author: typeof record$
|
|
4861
|
+
author: typeof record$6(input.user)?.login === "string" ? String(record$6(input.user)?.login) : "unknown",
|
|
4862
|
+
...avatarUrl === void 0 ? {} : { avatarUrl },
|
|
4385
4863
|
body: body.slice(0, MAX_COMMENT_CHARS),
|
|
4386
4864
|
url
|
|
4387
4865
|
});
|
|
@@ -4398,7 +4876,7 @@ function parseFailingChecks(value) {
|
|
|
4398
4876
|
if (!Array.isArray(value)) return [];
|
|
4399
4877
|
const failing = [];
|
|
4400
4878
|
for (const item of value) {
|
|
4401
|
-
const input = record$
|
|
4879
|
+
const input = record$6(item);
|
|
4402
4880
|
if (input === void 0 || input.bucket !== "fail" || typeof input.name !== "string") continue;
|
|
4403
4881
|
failing.push({
|
|
4404
4882
|
name: input.name,
|
|
@@ -4510,10 +4988,10 @@ var PullRequestFeedbackService = class {
|
|
|
4510
4988
|
};
|
|
4511
4989
|
//#endregion
|
|
4512
4990
|
//#region src/pr-feedback-routes.ts
|
|
4513
|
-
const MAX_SESSION_ID_CHARS$
|
|
4991
|
+
const MAX_SESSION_ID_CHARS$3 = 1024;
|
|
4514
4992
|
function sessionId(url) {
|
|
4515
4993
|
const value = url.searchParams.get("sessionId");
|
|
4516
|
-
if (value === null || value.length === 0 || value.length > MAX_SESSION_ID_CHARS$
|
|
4994
|
+
if (value === null || value.length === 0 || value.length > MAX_SESSION_ID_CHARS$3) throw new PullRequestFeedbackError("invalid-session", "The session is invalid.");
|
|
4517
4995
|
return value;
|
|
4518
4996
|
}
|
|
4519
4997
|
function pullNumber(url) {
|
|
@@ -4629,7 +5107,7 @@ var JiraError = class extends Error {
|
|
|
4629
5107
|
this.code = code;
|
|
4630
5108
|
}
|
|
4631
5109
|
};
|
|
4632
|
-
function record$
|
|
5110
|
+
function record$5(value) {
|
|
4633
5111
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
4634
5112
|
}
|
|
4635
5113
|
/** Jira Cloud sites are origins; Data Center may carry a context path. */
|
|
@@ -4647,6 +5125,30 @@ function ticketKeyOf(query) {
|
|
|
4647
5125
|
const match = /^([A-Za-z][A-Za-z0-9_]*)-(\d+)$/u.exec(query.trim());
|
|
4648
5126
|
return match === null ? void 0 : `${match[1].toUpperCase()}-${match[2]}`;
|
|
4649
5127
|
}
|
|
5128
|
+
/** Jira's text index carries summary, description and comments -- never the
|
|
5129
|
+
* issue key -- so a bare ticket number ("5697") can never reach PSOS-5697
|
|
5130
|
+
* through `text ~`. The issue picker is the endpoint behind Jira's own quick
|
|
5131
|
+
* search and does match keys; its hits become the key filter the normal
|
|
5132
|
+
* search then reads the display fields from. */
|
|
5133
|
+
function pickerKeys(value, number) {
|
|
5134
|
+
const sections = record$5(value)?.sections;
|
|
5135
|
+
if (!Array.isArray(sections)) return [];
|
|
5136
|
+
const keys = [];
|
|
5137
|
+
for (const section of sections) {
|
|
5138
|
+
const issues = record$5(section)?.issues;
|
|
5139
|
+
if (!Array.isArray(issues)) continue;
|
|
5140
|
+
for (const issue of issues) {
|
|
5141
|
+
const raw = record$5(issue)?.key;
|
|
5142
|
+
const key = ticketKeyOf(typeof raw === "string" ? raw : "");
|
|
5143
|
+
if (key !== void 0 && key.endsWith(`-${number}`) && !keys.includes(key)) keys.push(key);
|
|
5144
|
+
}
|
|
5145
|
+
}
|
|
5146
|
+
return keys;
|
|
5147
|
+
}
|
|
5148
|
+
/** Keys arrive validated by `ticketKeyOf`, so they cannot break out of the quotes. */
|
|
5149
|
+
function keyJql(keys) {
|
|
5150
|
+
return `key in (${keys.map((key) => `"${key}"`).join(", ")}) ORDER BY updated DESC`;
|
|
5151
|
+
}
|
|
4650
5152
|
function buildJql(query) {
|
|
4651
5153
|
const trimmed = query.trim().slice(0, MAX_QUERY_CHARS);
|
|
4652
5154
|
if (trimmed.length === 0) return "statusCategory != Done ORDER BY updated DESC";
|
|
@@ -4655,15 +5157,15 @@ function buildJql(query) {
|
|
|
4655
5157
|
return `text ~ "${trimmed.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"")}*" ORDER BY updated DESC`;
|
|
4656
5158
|
}
|
|
4657
5159
|
function parseTickets(value, siteUrl) {
|
|
4658
|
-
const issues = record$
|
|
5160
|
+
const issues = record$5(value)?.issues;
|
|
4659
5161
|
if (!Array.isArray(issues)) return [];
|
|
4660
5162
|
const tickets = [];
|
|
4661
5163
|
for (const item of issues) {
|
|
4662
|
-
const issue = record$
|
|
4663
|
-
const fields = record$
|
|
5164
|
+
const issue = record$5(item);
|
|
5165
|
+
const fields = record$5(issue?.fields);
|
|
4664
5166
|
if (issue === void 0 || typeof issue.key !== "string" || fields === void 0) continue;
|
|
4665
|
-
const status = record$
|
|
4666
|
-
const type = record$
|
|
5167
|
+
const status = record$5(fields.status)?.name;
|
|
5168
|
+
const type = record$5(fields.issuetype)?.name;
|
|
4667
5169
|
tickets.push({
|
|
4668
5170
|
key: issue.key,
|
|
4669
5171
|
summary: typeof fields.summary === "string" ? fields.summary.slice(0, 256) : "",
|
|
@@ -4705,7 +5207,7 @@ var JiraService = class {
|
|
|
4705
5207
|
email,
|
|
4706
5208
|
apiToken
|
|
4707
5209
|
};
|
|
4708
|
-
const myself = record$
|
|
5210
|
+
const myself = record$5(await this.#json(connection, "/rest/api/3/myself"));
|
|
4709
5211
|
const displayName = typeof myself?.displayName === "string" ? myself.displayName : void 0;
|
|
4710
5212
|
const accountId = typeof myself?.accountId === "string" ? myself.accountId : void 0;
|
|
4711
5213
|
const store = {
|
|
@@ -4724,7 +5226,7 @@ var JiraService = class {
|
|
|
4724
5226
|
if (ticket === void 0) throw new JiraError("invalid-request", "The ticket key is invalid.");
|
|
4725
5227
|
let accountId = store.accountId;
|
|
4726
5228
|
if (accountId === void 0) {
|
|
4727
|
-
const myself = record$
|
|
5229
|
+
const myself = record$5(await this.#json(store, "/rest/api/3/myself"));
|
|
4728
5230
|
if (typeof myself?.accountId !== "string") throw new JiraError("jira-failed", "The Jira account id is unavailable.");
|
|
4729
5231
|
accountId = myself.accountId;
|
|
4730
5232
|
await this.#write({
|
|
@@ -4744,7 +5246,7 @@ var JiraService = class {
|
|
|
4744
5246
|
const store = await this.#read();
|
|
4745
5247
|
if (store === void 0) throw new JiraError("not-connected", "Connect Jira in Settings first.");
|
|
4746
5248
|
const params = new URLSearchParams({
|
|
4747
|
-
jql:
|
|
5249
|
+
jql: await this.#searchJql(store, query.trim().slice(0, MAX_QUERY_CHARS)),
|
|
4748
5250
|
maxResults: String(MAX_RESULTS),
|
|
4749
5251
|
fields: "summary,status,issuetype"
|
|
4750
5252
|
});
|
|
@@ -4757,6 +5259,14 @@ var JiraService = class {
|
|
|
4757
5259
|
}
|
|
4758
5260
|
return parseTickets(body, store.siteUrl);
|
|
4759
5261
|
}
|
|
5262
|
+
/** A bare ticket number resolves through the picker; everything else is JQL.
|
|
5263
|
+
* A site that cannot serve the picker falls back to the text search rather
|
|
5264
|
+
* than failing the whole lookup. */
|
|
5265
|
+
async #searchJql(store, query) {
|
|
5266
|
+
if (!/^\d+$/u.test(query)) return buildJql(query);
|
|
5267
|
+
const keys = await this.#json(store, `/rest/api/3/issue/picker?query=${encodeURIComponent(query)}`).then((body) => pickerKeys(body, query), () => []);
|
|
5268
|
+
return keys.length === 0 ? buildJql(query) : keyJql(keys);
|
|
5269
|
+
}
|
|
4760
5270
|
async #json(connection, path, init = {}) {
|
|
4761
5271
|
let response;
|
|
4762
5272
|
try {
|
|
@@ -4792,7 +5302,7 @@ var JiraService = class {
|
|
|
4792
5302
|
throw error;
|
|
4793
5303
|
}
|
|
4794
5304
|
if (Buffer.byteLength(text) > MAX_STORE_BYTES) return void 0;
|
|
4795
|
-
const input = record$
|
|
5305
|
+
const input = record$5(JSON.parse(text));
|
|
4796
5306
|
if (input === void 0 || typeof input.siteUrl !== "string" || typeof input.email !== "string" || typeof input.apiToken !== "string") return void 0;
|
|
4797
5307
|
return {
|
|
4798
5308
|
siteUrl: input.siteUrl,
|
|
@@ -4823,20 +5333,20 @@ var JiraService = class {
|
|
|
4823
5333
|
};
|
|
4824
5334
|
//#endregion
|
|
4825
5335
|
//#region src/jira-routes.ts
|
|
4826
|
-
const MAX_BODY_BYTES$
|
|
4827
|
-
function record$
|
|
5336
|
+
const MAX_BODY_BYTES$3 = 8192;
|
|
5337
|
+
function record$4(value) {
|
|
4828
5338
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
4829
5339
|
}
|
|
4830
|
-
async function readJson$
|
|
5340
|
+
async function readJson$3(req) {
|
|
4831
5341
|
const chunks = [];
|
|
4832
5342
|
let size = 0;
|
|
4833
5343
|
for await (const chunk of req) {
|
|
4834
5344
|
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
4835
5345
|
size += buffer.length;
|
|
4836
|
-
if (size > MAX_BODY_BYTES$
|
|
5346
|
+
if (size > MAX_BODY_BYTES$3) throw new JiraError("body-too-large", "The request body is too large.");
|
|
4837
5347
|
chunks.push(buffer);
|
|
4838
5348
|
}
|
|
4839
|
-
const value = record$
|
|
5349
|
+
const value = record$4(JSON.parse(Buffer.concat(chunks).toString("utf8")));
|
|
4840
5350
|
if (value === void 0) throw new JiraError("invalid-request", "The request body is invalid.");
|
|
4841
5351
|
return value;
|
|
4842
5352
|
}
|
|
@@ -4859,7 +5369,7 @@ function registerJiraRoute(ctx, service) {
|
|
|
4859
5369
|
}
|
|
4860
5370
|
if (url.pathname === `/plugins/dsh-claude/jira/connect`) {
|
|
4861
5371
|
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
4862
|
-
const input = await readJson$
|
|
5372
|
+
const input = await readJson$3(req);
|
|
4863
5373
|
return json(res, 200, await service.connect({
|
|
4864
5374
|
siteUrl: string(input, "siteUrl"),
|
|
4865
5375
|
email: string(input, "email"),
|
|
@@ -4873,7 +5383,7 @@ function registerJiraRoute(ctx, service) {
|
|
|
4873
5383
|
}
|
|
4874
5384
|
if (url.pathname === `/plugins/dsh-claude/jira/assign`) {
|
|
4875
5385
|
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
4876
|
-
const input = await readJson$
|
|
5386
|
+
const input = await readJson$3(req);
|
|
4877
5387
|
await service.assignToMe(string(input, "key"));
|
|
4878
5388
|
return json(res, 200, { assigned: true });
|
|
4879
5389
|
}
|
|
@@ -4971,12 +5481,12 @@ function askArguments(preferences) {
|
|
|
4971
5481
|
...READ_ONLY_TOOLS
|
|
4972
5482
|
];
|
|
4973
5483
|
}
|
|
4974
|
-
function record$
|
|
5484
|
+
function record$3(value) {
|
|
4975
5485
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
4976
5486
|
}
|
|
4977
5487
|
/** One-line description of a tool call, mirroring the main window's step titles. */
|
|
4978
5488
|
function toolSummary(input) {
|
|
4979
|
-
const fields = record$
|
|
5489
|
+
const fields = record$3(input);
|
|
4980
5490
|
if (fields === void 0) return void 0;
|
|
4981
5491
|
const candidate = [
|
|
4982
5492
|
fields.command,
|
|
@@ -4990,7 +5500,7 @@ function toolSummary(input) {
|
|
|
4990
5500
|
function eventsOfStreamLine(line) {
|
|
4991
5501
|
let parsed;
|
|
4992
5502
|
try {
|
|
4993
|
-
parsed = record$
|
|
5503
|
+
parsed = record$3(JSON.parse(line));
|
|
4994
5504
|
} catch {
|
|
4995
5505
|
return [];
|
|
4996
5506
|
}
|
|
@@ -5000,9 +5510,9 @@ function eventsOfStreamLine(line) {
|
|
|
5000
5510
|
text: "ready"
|
|
5001
5511
|
}];
|
|
5002
5512
|
if (parsed.type === "stream_event") {
|
|
5003
|
-
const event = record$
|
|
5513
|
+
const event = record$3(parsed.event);
|
|
5004
5514
|
if (event?.type === "content_block_start") {
|
|
5005
|
-
const block = record$
|
|
5515
|
+
const block = record$3(event.content_block);
|
|
5006
5516
|
if (block?.type === "tool_use" && typeof block.id === "string" && typeof block.name === "string") return [{
|
|
5007
5517
|
type: "tool",
|
|
5008
5518
|
id: block.id,
|
|
@@ -5011,7 +5521,7 @@ function eventsOfStreamLine(line) {
|
|
|
5011
5521
|
}];
|
|
5012
5522
|
return [];
|
|
5013
5523
|
}
|
|
5014
|
-
const delta = record$
|
|
5524
|
+
const delta = record$3(event?.delta);
|
|
5015
5525
|
if (event?.type !== "content_block_delta" || delta === void 0) return [];
|
|
5016
5526
|
if (delta.type === "text_delta" && typeof delta.text === "string") return [{
|
|
5017
5527
|
type: "text",
|
|
@@ -5024,11 +5534,11 @@ function eventsOfStreamLine(line) {
|
|
|
5024
5534
|
return [];
|
|
5025
5535
|
}
|
|
5026
5536
|
if (parsed.type === "assistant" || parsed.type === "user") {
|
|
5027
|
-
const content = record$
|
|
5537
|
+
const content = record$3(parsed.message)?.content;
|
|
5028
5538
|
if (!Array.isArray(content)) return [];
|
|
5029
5539
|
const events = [];
|
|
5030
5540
|
for (const item of content) {
|
|
5031
|
-
const block = record$
|
|
5541
|
+
const block = record$3(item);
|
|
5032
5542
|
if (block?.type === "tool_use" && typeof block.id === "string" && typeof block.name === "string") {
|
|
5033
5543
|
const summary = toolSummary(block.input);
|
|
5034
5544
|
events.push({
|
|
@@ -5120,21 +5630,21 @@ var AskService = class {
|
|
|
5120
5630
|
};
|
|
5121
5631
|
//#endregion
|
|
5122
5632
|
//#region src/ask-routes.ts
|
|
5123
|
-
const MAX_BODY_BYTES$
|
|
5124
|
-
const MAX_SESSION_ID_CHARS$
|
|
5125
|
-
function record$
|
|
5633
|
+
const MAX_BODY_BYTES$2 = 131072;
|
|
5634
|
+
const MAX_SESSION_ID_CHARS$2 = 1024;
|
|
5635
|
+
function record$2(value) {
|
|
5126
5636
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
5127
5637
|
}
|
|
5128
|
-
async function readJson$
|
|
5638
|
+
async function readJson$2(req) {
|
|
5129
5639
|
const chunks = [];
|
|
5130
5640
|
let size = 0;
|
|
5131
5641
|
for await (const chunk of req) {
|
|
5132
5642
|
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
5133
5643
|
size += buffer.length;
|
|
5134
|
-
if (size > MAX_BODY_BYTES$
|
|
5644
|
+
if (size > MAX_BODY_BYTES$2) throw new AskError("body-too-large", "The request body is too large.");
|
|
5135
5645
|
chunks.push(buffer);
|
|
5136
5646
|
}
|
|
5137
|
-
const value = record$
|
|
5647
|
+
const value = record$2(JSON.parse(Buffer.concat(chunks).toString("utf8")));
|
|
5138
5648
|
if (value === void 0) throw new AskError("invalid-request", "The request body is invalid.");
|
|
5139
5649
|
return value;
|
|
5140
5650
|
}
|
|
@@ -5162,12 +5672,12 @@ function registerAskRoute(ctx, service, cwdForSession, preferencesFor) {
|
|
|
5162
5672
|
let sessionId;
|
|
5163
5673
|
try {
|
|
5164
5674
|
const value = url.searchParams.get("sessionId");
|
|
5165
|
-
if (value === null || value.length === 0 || value.length > MAX_SESSION_ID_CHARS$
|
|
5675
|
+
if (value === null || value.length === 0 || value.length > MAX_SESSION_ID_CHARS$2) throw new AskError("invalid-session", "The session is invalid.");
|
|
5166
5676
|
sessionId = value;
|
|
5167
5677
|
const resolved = cwdForSession(sessionId);
|
|
5168
5678
|
if (resolved === void 0) throw new AskError("session-unavailable", "The Claude session is unavailable.");
|
|
5169
5679
|
cwd = resolved;
|
|
5170
|
-
request = askRequest(await readJson$
|
|
5680
|
+
request = askRequest(await readJson$2(req));
|
|
5171
5681
|
} catch (error) {
|
|
5172
5682
|
if (error instanceof AskError) return json(res, 409, {
|
|
5173
5683
|
error: error.code,
|
|
@@ -5211,27 +5721,27 @@ function registerAskRoute(ctx, service, cwdForSession, preferencesFor) {
|
|
|
5211
5721
|
}
|
|
5212
5722
|
//#endregion
|
|
5213
5723
|
//#region src/review-comment-routes.ts
|
|
5214
|
-
const MAX_BODY_BYTES = 16384;
|
|
5215
|
-
const MAX_SESSION_ID_CHARS = 1024;
|
|
5216
|
-
function record(value) {
|
|
5724
|
+
const MAX_BODY_BYTES$1 = 16384;
|
|
5725
|
+
const MAX_SESSION_ID_CHARS$1 = 1024;
|
|
5726
|
+
function record$1(value) {
|
|
5217
5727
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
5218
5728
|
}
|
|
5219
|
-
async function readJson(req) {
|
|
5729
|
+
async function readJson$1(req) {
|
|
5220
5730
|
const chunks = [];
|
|
5221
5731
|
let size = 0;
|
|
5222
5732
|
for await (const chunk of req) {
|
|
5223
5733
|
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
5224
5734
|
size += buffer.length;
|
|
5225
|
-
if (size > MAX_BODY_BYTES) throw new ReviewCommentError("body-too-large", "The request body is too large.");
|
|
5735
|
+
if (size > MAX_BODY_BYTES$1) throw new ReviewCommentError("body-too-large", "The request body is too large.");
|
|
5226
5736
|
chunks.push(buffer);
|
|
5227
5737
|
}
|
|
5228
|
-
const value = record(JSON.parse(Buffer.concat(chunks).toString("utf8")));
|
|
5738
|
+
const value = record$1(JSON.parse(Buffer.concat(chunks).toString("utf8")));
|
|
5229
5739
|
if (value === void 0) throw new ReviewCommentError("invalid-request", "The request body is invalid.");
|
|
5230
5740
|
return value;
|
|
5231
5741
|
}
|
|
5232
5742
|
function sessionIdFromUrl(url) {
|
|
5233
5743
|
const value = url.searchParams.get("sessionId");
|
|
5234
|
-
if (value === null || value.length === 0 || value.length > MAX_SESSION_ID_CHARS) throw new ReviewCommentError("invalid-session", "The session is invalid.");
|
|
5744
|
+
if (value === null || value.length === 0 || value.length > MAX_SESSION_ID_CHARS$1) throw new ReviewCommentError("invalid-session", "The session is invalid.");
|
|
5235
5745
|
return value;
|
|
5236
5746
|
}
|
|
5237
5747
|
function registerReviewCommentRoute(ctx, store, ownsSession) {
|
|
@@ -5245,7 +5755,7 @@ function registerReviewCommentRoute(ctx, store, ownsSession) {
|
|
|
5245
5755
|
const sessionId = sessionIdFromUrl(url);
|
|
5246
5756
|
if (!ownsSession(sessionId)) throw new ReviewCommentError("session-unavailable", "The Claude session is unavailable.");
|
|
5247
5757
|
if (url.pathname === "/plugins/dsh-claude/review-comments" && req.method === "POST") {
|
|
5248
|
-
const input = await readJson(req);
|
|
5758
|
+
const input = await readJson$1(req);
|
|
5249
5759
|
return json(res, 200, { comment: store.add(sessionId, {
|
|
5250
5760
|
path: input.path,
|
|
5251
5761
|
line: input.line,
|
|
@@ -5256,7 +5766,7 @@ function registerReviewCommentRoute(ctx, store, ownsSession) {
|
|
|
5256
5766
|
}
|
|
5257
5767
|
if (url.pathname === `/plugins/dsh-claude/review-comments/clear` && req.method === "POST") return json(res, 200, { removed: store.drain(sessionId).length });
|
|
5258
5768
|
if (url.pathname === `/plugins/dsh-claude/review-comments/remove` && req.method === "POST") {
|
|
5259
|
-
const input = await readJson(req);
|
|
5769
|
+
const input = await readJson$1(req);
|
|
5260
5770
|
if (typeof input.id !== "string" || input.id.length === 0 || input.id.length > 128) throw new ReviewCommentError("invalid-request", "The comment id is invalid.");
|
|
5261
5771
|
return json(res, 200, { removed: store.remove(sessionId, input.id) });
|
|
5262
5772
|
}
|
|
@@ -5276,6 +5786,53 @@ function registerReviewCommentRoute(ctx, store, ownsSession) {
|
|
|
5276
5786
|
}), "dsh-claude: review comment route");
|
|
5277
5787
|
}
|
|
5278
5788
|
//#endregion
|
|
5789
|
+
//#region src/rewind-routes.ts
|
|
5790
|
+
const MAX_BODY_BYTES = 4096;
|
|
5791
|
+
const MAX_SESSION_ID_CHARS = 1024;
|
|
5792
|
+
function record(value) {
|
|
5793
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
5794
|
+
}
|
|
5795
|
+
async function readJson(req) {
|
|
5796
|
+
const chunks = [];
|
|
5797
|
+
let size = 0;
|
|
5798
|
+
for await (const chunk of req) {
|
|
5799
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
5800
|
+
size += buffer.length;
|
|
5801
|
+
if (size > MAX_BODY_BYTES) return void 0;
|
|
5802
|
+
chunks.push(buffer);
|
|
5803
|
+
}
|
|
5804
|
+
return record(JSON.parse(Buffer.concat(chunks).toString("utf8")));
|
|
5805
|
+
}
|
|
5806
|
+
/** `POST <path>` with `{ sessionId, seq }`: hide that surface event and every
|
|
5807
|
+
* later one, and arm Claude to resume before the turn it opened. */
|
|
5808
|
+
function registerClaudeRewindRoute(ctx, sidecar, access) {
|
|
5809
|
+
ctx.effect(() => ctx.webServer.register({
|
|
5810
|
+
kind: "exact",
|
|
5811
|
+
path: CLAUDE_REWIND_PATH,
|
|
5812
|
+
handler: async (req, res) => {
|
|
5813
|
+
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
5814
|
+
if (!trustedRequest(req)) return json(res, 403, { error: "forbidden" });
|
|
5815
|
+
try {
|
|
5816
|
+
const input = await readJson(req);
|
|
5817
|
+
const sessionId = input?.sessionId;
|
|
5818
|
+
const seq = input?.seq;
|
|
5819
|
+
if (typeof sessionId !== "string" || sessionId.length === 0 || sessionId.length > MAX_SESSION_ID_CHARS || typeof seq !== "number" || !Number.isSafeInteger(seq) || seq < 0) return json(res, 400, { error: "invalid-request" });
|
|
5820
|
+
const events = access.eventsFor(sessionId);
|
|
5821
|
+
if (events === void 0) return json(res, 409, { error: "session-unavailable" });
|
|
5822
|
+
if (access.busy(sessionId)) return json(res, 409, { error: "session-busy" });
|
|
5823
|
+
const planned = planRewind((await sidecar.read(sessionId)).rewind ?? EMPTY_REWIND_STATE, events, seq);
|
|
5824
|
+
if (planned === void 0) return json(res, 409, { error: "seq-unavailable" });
|
|
5825
|
+
await sidecar.writeRewind(sessionId, planned);
|
|
5826
|
+
await access.reset(sessionId);
|
|
5827
|
+
return json(res, 200, { ranges: planned.ranges });
|
|
5828
|
+
} catch (error) {
|
|
5829
|
+
if (error instanceof SyntaxError) return json(res, 400, { error: "invalid-json" });
|
|
5830
|
+
return json(res, 500, { error: "rewind-unavailable" });
|
|
5831
|
+
}
|
|
5832
|
+
}
|
|
5833
|
+
}), "dsh-claude: rewind route");
|
|
5834
|
+
}
|
|
5835
|
+
//#endregion
|
|
5279
5836
|
//#region src/update-routes.ts
|
|
5280
5837
|
const PLUGIN_PACKAGE_NAME = "@norman-else/dsh-claude";
|
|
5281
5838
|
const UPDATE_TIMEOUT_MS = 3e4;
|
|
@@ -6117,6 +6674,7 @@ async function apply(ctx, config) {
|
|
|
6117
6674
|
return agent.session.header.cwd;
|
|
6118
6675
|
};
|
|
6119
6676
|
registerRepositoryActionRoute(webCtx, repositoryActions, cwdForClaudeSession);
|
|
6677
|
+
registerEditorOpenRoute(webCtx, new EditorOpenService(webCtx.subprocess), cwdForClaudeSession);
|
|
6120
6678
|
registerPullRequestFeedbackRoute(webCtx, new PullRequestFeedbackService(webCtx.subprocess), cwdForClaudeSession);
|
|
6121
6679
|
registerAskRoute(webCtx, new AskService(webCtx.subprocess, supervisorConfig.executablePath), cwdForClaudeSession, (sessionId) => {
|
|
6122
6680
|
const snapshot = supervisor.snapshots().find((item) => item.sessionId === sessionId);
|
|
@@ -6130,6 +6688,14 @@ async function apply(ctx, config) {
|
|
|
6130
6688
|
return agent !== void 0 && webCtx.agentPresets.composedPreset(agent.ctx) === "claude";
|
|
6131
6689
|
};
|
|
6132
6690
|
registerReviewCommentRoute(webCtx, reviewComments, ownsClaudeSession);
|
|
6691
|
+
registerClaudeRewindRoute(webCtx, sidecar, {
|
|
6692
|
+
eventsFor: (sessionId) => {
|
|
6693
|
+
const agent = webCtx.agents.get(sessionId);
|
|
6694
|
+
return agent === void 0 || webCtx.agentPresets.composedPreset(agent.ctx) !== "claude" ? void 0 : agent.session.events;
|
|
6695
|
+
},
|
|
6696
|
+
busy: (sessionId) => supervisor.snapshots().some((item) => item.sessionId === sessionId && (item.state === "running" || item.state === "interrupting")),
|
|
6697
|
+
reset: (sessionId) => supervisor.disposeSession(sessionId)
|
|
6698
|
+
});
|
|
6133
6699
|
registerPlanUsageRoute(webCtx, (fetchedAt) => probePlanUsage(supervisorConfig.executablePath, fetchedAt));
|
|
6134
6700
|
registerClaudeProjectionRoute(webCtx, sidecar, ownsClaudeSession, (sessionId) => commandCatalogs.get(sessionId) ?? [], async (sessionId) => {
|
|
6135
6701
|
const agent = webCtx.agents.get(sessionId);
|