@norman-else/dsh-claude 0.1.40 → 0.1.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/lib/bin.mjs +1 -1
- package/lib/client.d.ts +32 -0
- package/lib/client.js +1653 -634
- package/lib/client.js.map +1 -1
- package/lib/{events-OhBoFNKO.mjs → events-B-FPMzI7.mjs} +7 -2
- package/lib/events-B-FPMzI7.mjs.map +1 -0
- package/lib/index.d.mts +55 -3
- package/lib/index.mjs +769 -165
- package/lib/index.mjs.map +1 -1
- package/lib/{presenters-BBoM1Ju1.mjs → presenters-BV42EKkB.mjs} +21 -2
- package/lib/{presenters-BBoM1Ju1.mjs.map → presenters-BV42EKkB.mjs.map} +1 -1
- package/lib/{preset-installer-loenwnLS.mjs → preset-installer-yRGfkGjd.mjs} +2 -2
- package/lib/{preset-installer-loenwnLS.mjs.map → preset-installer-yRGfkGjd.mjs.map} +1 -1
- package/lib/preset-route.mjs +2 -2
- package/package.json +1 -1
- package/lib/events-OhBoFNKO.mjs.map +0 -1
package/lib/index.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { A as
|
|
2
|
-
import { a as projectClaudeCommands, i as CLAUDE_COMMANDS_SERVICE, r as dynamicPresenterDefinition, t as CLAUDE_PRESENTER_NAMES } from "./presenters-
|
|
3
|
-
import { a as resolveClaudeExecutable, n as ensureManagedPreset, o as runClaudeDoctor, t as ManagedPresetConflictError } from "./preset-installer-
|
|
1
|
+
import { A as CLAUDE_REPOSITORY_STATUS_PATH, B as isClaudeAlertMode, C as CLAUDE_PROJECTION_PATH, D as CLAUDE_REPOSITORY_FEEDBACK_PATH, E as CLAUDE_REPOSITORY_ACTION_PATH, F as CLAUDE_USAGE_PATH, H as isClaudeRenderMode, I as DEFAULT_CLAUDE_PROSE_MODE, L as DEFAULT_CLAUDE_RENDER_MODE, M as CLAUDE_REWIND_PATH, N as CLAUDE_UPDATE_CHECK_PATH, O as CLAUDE_REPOSITORY_FILE_PATH, P as CLAUDE_UPDATE_PATH, S as CLAUDE_PLAN_FEEDBACK_PATH, T as CLAUDE_RENDER_MODES, V as isClaudeProseMode, _ as CLAUDE_CODE_PROVIDER_IDS, a as latestClaudeTasks, b as CLAUDE_GLOBAL_SETTINGS_PATH, c as normalizeTasksEvent, d as CLAUDE_ACTIVITY_EVENT, f as CLAUDE_ALERT_MODES, g as CLAUDE_CODE_PROVIDER, h as CLAUDE_CODE_PRESET_ID, i as latestClaudeSessionBinding, j as CLAUDE_REVIEW_COMMENT_PATH, k as CLAUDE_REPOSITORY_SETUP_PATH, l as redactText, m as CLAUDE_CLIENT_DIAGNOSTICS_PATH, n as currentClaudeActivityCursor, o as normalizeActivity, p as CLAUDE_ASK_PATH, r as latestClaudeContextUsage, s as normalizeContextUsage, t as boundText, u as safeDetail, v as CLAUDE_DOCTOR_PATH, w as CLAUDE_PROSE_MODES, x as CLAUDE_JIRA_PATH, y as CLAUDE_EDITOR_OPEN_PATH, z as TASK_TOOL_NAMES } from "./events-B-FPMzI7.mjs";
|
|
2
|
+
import { a as projectClaudeCommands, i as CLAUDE_COMMANDS_SERVICE, r as dynamicPresenterDefinition, t as CLAUDE_PRESENTER_NAMES } from "./presenters-BV42EKkB.mjs";
|
|
3
|
+
import { a as resolveClaudeExecutable, n as ensureManagedPreset, o as runClaudeDoctor, t as ManagedPresetConflictError } from "./preset-installer-yRGfkGjd.mjs";
|
|
4
4
|
import z from "@deepseek-ai/schemastery";
|
|
5
5
|
import { createHash, randomUUID } from "node:crypto";
|
|
6
6
|
import { chmod, mkdir, opendir, readFile, readdir, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
7
7
|
import { basename, dirname, extname, isAbsolute, join, resolve, sep } from "node:path";
|
|
8
8
|
import { dshHomePath, resolveDshHome } from "@deepseek-ai/dsh-home-paths";
|
|
9
|
-
import { homedir } from "node:os";
|
|
9
|
+
import { homedir, tmpdir } from "node:os";
|
|
10
10
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
11
11
|
import { LlmAdapter, ReasoningEffortId, ToolCallId, createToolResultMessage } from "@deepseek-ai/dsh-llm";
|
|
12
12
|
import { EventEmitter } from "node:events";
|
|
@@ -17,7 +17,8 @@ import { StringDecoder } from "node:string_decoder";
|
|
|
17
17
|
//#region src/rewind.ts
|
|
18
18
|
const EMPTY_REWIND_STATE = {
|
|
19
19
|
ranges: [],
|
|
20
|
-
anchors: []
|
|
20
|
+
anchors: [],
|
|
21
|
+
snapshots: []
|
|
21
22
|
};
|
|
22
23
|
/** Absorb one span into an ascending, non-overlapping range list. Adjacent
|
|
23
24
|
* spans merge so a rewind of a rewind reads as one hidden block. */
|
|
@@ -50,15 +51,32 @@ function recordRewindAnchor(state, anchor) {
|
|
|
50
51
|
anchors
|
|
51
52
|
};
|
|
52
53
|
}
|
|
54
|
+
/** Record the working tree one turn was admitted against, replacing a re-run
|
|
55
|
+
* turn's. */
|
|
56
|
+
function recordRewindSnapshot(state, snapshot) {
|
|
57
|
+
const snapshots = [...state.snapshots.filter((item) => item.turn !== snapshot.turn), snapshot].sort((left, right) => left.turn - right.turn).slice(-100);
|
|
58
|
+
return {
|
|
59
|
+
...state,
|
|
60
|
+
snapshots
|
|
61
|
+
};
|
|
62
|
+
}
|
|
53
63
|
/** The turn a surface seq belongs to: the first turn opened at or after it.
|
|
54
64
|
* A message accepted but never run belongs to no logged turn, so nothing
|
|
55
65
|
* Claude holds is discarded and every anchor stays valid. */
|
|
56
66
|
function turnAtOrAfter(events, seq) {
|
|
57
67
|
for (const event of events) if (event.seq >= seq && event.type === "turn/start") return event.data.turn;
|
|
58
68
|
}
|
|
69
|
+
/** The working tree a rewind at `seq` restores: the snapshot of the first turn
|
|
70
|
+
* it discards. Undefined when nothing is discarded, or when that turn ran
|
|
71
|
+
* before this session captured trees. */
|
|
72
|
+
function rewindRestoreTree(state, events, seq) {
|
|
73
|
+
const turn = turnAtOrAfter(events, seq);
|
|
74
|
+
return turn === void 0 ? void 0 : state.snapshots.find((item) => item.turn === turn)?.tree;
|
|
75
|
+
}
|
|
59
76
|
/** Plan one rewind at `seq`, or undefined when the seq is not in the log.
|
|
60
|
-
* Anchors of the discarded turns go with them: after this
|
|
61
|
-
* longer holds those entries, so a later rewind must never
|
|
77
|
+
* Anchors and snapshots of the discarded turns go with them: after this
|
|
78
|
+
* rewind Claude no longer holds those entries, so a later rewind must never
|
|
79
|
+
* fork at one — nor restore a tree for a turn that no longer exists. */
|
|
62
80
|
function planRewind(state, events, seq) {
|
|
63
81
|
const last = events.at(-1)?.seq;
|
|
64
82
|
if (last === void 0 || seq > last) return void 0;
|
|
@@ -71,6 +89,7 @@ function planRewind(state, events, seq) {
|
|
|
71
89
|
end: last
|
|
72
90
|
}),
|
|
73
91
|
anchors,
|
|
92
|
+
snapshots: state.snapshots.filter((item) => item.turn < turn),
|
|
74
93
|
pending: kept === void 0 ? { fresh: true } : { resumeAt: kept.uuid }
|
|
75
94
|
};
|
|
76
95
|
}
|
|
@@ -88,7 +107,7 @@ function emptyProjection() {
|
|
|
88
107
|
activities: []
|
|
89
108
|
};
|
|
90
109
|
}
|
|
91
|
-
function record$
|
|
110
|
+
function record$14(value) {
|
|
92
111
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
93
112
|
}
|
|
94
113
|
function finiteInteger(value) {
|
|
@@ -98,7 +117,7 @@ function string$4(value, max) {
|
|
|
98
117
|
return typeof value === "string" && value.length > 0 && value.length <= max;
|
|
99
118
|
}
|
|
100
119
|
function binding(value) {
|
|
101
|
-
const input = record$
|
|
120
|
+
const input = record$14(value);
|
|
102
121
|
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;
|
|
103
122
|
return {
|
|
104
123
|
claudeSessionId: input.claudeSessionId,
|
|
@@ -129,21 +148,21 @@ const ACTIVITY_PHASES = /* @__PURE__ */ new Set([
|
|
|
129
148
|
"failed"
|
|
130
149
|
]);
|
|
131
150
|
function activity(value) {
|
|
132
|
-
const input = record$
|
|
151
|
+
const input = record$14(value);
|
|
133
152
|
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;
|
|
134
153
|
return normalizeActivity(input);
|
|
135
154
|
}
|
|
136
155
|
function contextUsage(value) {
|
|
137
|
-
const input = record$
|
|
156
|
+
const input = record$14(value);
|
|
138
157
|
if (input === void 0 || !Array.isArray(input.categories)) return void 0;
|
|
139
158
|
return normalizeContextUsage(input);
|
|
140
159
|
}
|
|
141
160
|
function rewind(value) {
|
|
142
|
-
const input = record$
|
|
161
|
+
const input = record$14(value);
|
|
143
162
|
if (input === void 0 || !Array.isArray(input.ranges) || input.ranges.length > 200 || !Array.isArray(input.anchors) || input.anchors.length > 2e3) return void 0;
|
|
144
163
|
const ranges = [];
|
|
145
164
|
for (const item of input.ranges) {
|
|
146
|
-
const range = record$
|
|
165
|
+
const range = record$14(item);
|
|
147
166
|
if (range === void 0 || !finiteInteger(range.start) || !finiteInteger(range.end) || range.end < range.start) return void 0;
|
|
148
167
|
ranges.push({
|
|
149
168
|
start: range.start,
|
|
@@ -152,38 +171,53 @@ function rewind(value) {
|
|
|
152
171
|
}
|
|
153
172
|
const anchors = [];
|
|
154
173
|
for (const item of input.anchors) {
|
|
155
|
-
const anchor = record$
|
|
174
|
+
const anchor = record$14(item);
|
|
156
175
|
if (anchor === void 0 || !finiteInteger(anchor.turn) || !string$4(anchor.uuid, 128)) return void 0;
|
|
157
176
|
anchors.push({
|
|
158
177
|
turn: anchor.turn,
|
|
159
178
|
uuid: anchor.uuid
|
|
160
179
|
});
|
|
161
180
|
}
|
|
162
|
-
const
|
|
181
|
+
const snapshots = [];
|
|
182
|
+
if (input.snapshots !== void 0) {
|
|
183
|
+
if (!Array.isArray(input.snapshots) || input.snapshots.length > 100) return void 0;
|
|
184
|
+
for (const item of input.snapshots) {
|
|
185
|
+
const snapshot = record$14(item);
|
|
186
|
+
if (snapshot === void 0 || !finiteInteger(snapshot.turn) || !string$4(snapshot.tree, 64)) return void 0;
|
|
187
|
+
snapshots.push({
|
|
188
|
+
turn: snapshot.turn,
|
|
189
|
+
tree: snapshot.tree
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
const pending = record$14(input.pending);
|
|
163
194
|
if (input.pending !== void 0 && pending === void 0) return void 0;
|
|
164
195
|
if (pending === void 0) return {
|
|
165
196
|
ranges,
|
|
166
|
-
anchors
|
|
197
|
+
anchors,
|
|
198
|
+
snapshots
|
|
167
199
|
};
|
|
168
200
|
if (pending.fresh === true) return {
|
|
169
201
|
ranges,
|
|
170
202
|
anchors,
|
|
203
|
+
snapshots,
|
|
171
204
|
pending: { fresh: true }
|
|
172
205
|
};
|
|
173
206
|
if (!string$4(pending.resumeAt, 128)) return void 0;
|
|
174
207
|
return {
|
|
175
208
|
ranges,
|
|
176
209
|
anchors,
|
|
210
|
+
snapshots,
|
|
177
211
|
pending: { resumeAt: pending.resumeAt }
|
|
178
212
|
};
|
|
179
213
|
}
|
|
180
214
|
function tasks(value) {
|
|
181
|
-
const input = record$
|
|
215
|
+
const input = record$14(value);
|
|
182
216
|
if (input === void 0 || !Array.isArray(input.tasks)) return void 0;
|
|
183
217
|
return normalizeTasksEvent(input.tasks);
|
|
184
218
|
}
|
|
185
219
|
function parseClaudeSidecar(value) {
|
|
186
|
-
const input = record$
|
|
220
|
+
const input = record$14(value);
|
|
187
221
|
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");
|
|
188
222
|
const activities = input.activities.map(activity);
|
|
189
223
|
if (activities.some((item) => item === void 0)) throw new Error("dsh-claude: invalid sidecar activity");
|
|
@@ -411,11 +445,19 @@ var ClaudeSidecarRepository = class {
|
|
|
411
445
|
});
|
|
412
446
|
}
|
|
413
447
|
/** Land one planned rewind: hidden ranges, surviving anchors, and the fork
|
|
414
|
-
* target the next Claude spawn consumes.
|
|
415
|
-
|
|
448
|
+
* target the next Claude spawn consumes.
|
|
449
|
+
*
|
|
450
|
+
* `droppedFromTurn` also drops the discarded turns' activity. The hidden
|
|
451
|
+
* ranges are surface seqs and activity records carry none, so a reader that
|
|
452
|
+
* works in turns — the plan panel, the task board — has nothing to filter
|
|
453
|
+
* on and would go on showing a plan the session no longer contains. This
|
|
454
|
+
* projection is rebuildable from the session log, so trimming it is not
|
|
455
|
+
* losing anything the log still holds. */
|
|
456
|
+
writeRewind(sessionId, value, droppedFromTurn) {
|
|
416
457
|
return this.#update(sessionId, (current) => ({
|
|
417
458
|
...current,
|
|
418
|
-
rewind: value
|
|
459
|
+
rewind: value,
|
|
460
|
+
...droppedFromTurn === void 0 ? {} : { activities: current.activities.filter((activity) => activity.turn < droppedFromTurn) }
|
|
419
461
|
}), false, { kind: "sync" });
|
|
420
462
|
}
|
|
421
463
|
/** Remember where Claude's chain ended for one completed DSH turn. */
|
|
@@ -428,6 +470,16 @@ var ClaudeSidecarRepository = class {
|
|
|
428
470
|
})
|
|
429
471
|
}));
|
|
430
472
|
}
|
|
473
|
+
/** Remember the working tree one DSH turn was admitted against. */
|
|
474
|
+
recordRewindSnapshot(sessionId, turn, tree) {
|
|
475
|
+
return this.#update(sessionId, (current) => ({
|
|
476
|
+
...current,
|
|
477
|
+
rewind: recordRewindSnapshot(current.rewind ?? EMPTY_REWIND_STATE, {
|
|
478
|
+
turn,
|
|
479
|
+
tree
|
|
480
|
+
})
|
|
481
|
+
}));
|
|
482
|
+
}
|
|
431
483
|
/** Disarm the fork target once a Claude process has resumed at it, so a
|
|
432
484
|
* later respawn continues the rewound session instead of re-truncating it. */
|
|
433
485
|
clearRewindPending(sessionId) {
|
|
@@ -435,7 +487,8 @@ var ClaudeSidecarRepository = class {
|
|
|
435
487
|
...current,
|
|
436
488
|
rewind: {
|
|
437
489
|
ranges: current.rewind.ranges,
|
|
438
|
-
anchors: current.rewind.anchors
|
|
490
|
+
anchors: current.rewind.anchors,
|
|
491
|
+
snapshots: current.rewind.snapshots
|
|
439
492
|
}
|
|
440
493
|
});
|
|
441
494
|
}
|
|
@@ -600,6 +653,93 @@ var AsyncQueue = class {
|
|
|
600
653
|
}
|
|
601
654
|
};
|
|
602
655
|
//#endregion
|
|
656
|
+
//#region src/plan-feedback.ts
|
|
657
|
+
const MAX_NOTES = 30;
|
|
658
|
+
const MAX_NOTE_CHARS = 2e3;
|
|
659
|
+
const MAX_QUOTE_CHARS = 1e3;
|
|
660
|
+
var PlanFeedbackError = class extends Error {
|
|
661
|
+
code;
|
|
662
|
+
constructor(code, message) {
|
|
663
|
+
super(message);
|
|
664
|
+
this.name = "PlanFeedbackError";
|
|
665
|
+
this.code = code;
|
|
666
|
+
}
|
|
667
|
+
};
|
|
668
|
+
/** Validate and bound one submission's notes. */
|
|
669
|
+
function planNotesOf(value) {
|
|
670
|
+
if (!Array.isArray(value) || value.length === 0 || value.length > MAX_NOTES) throw new PlanFeedbackError("invalid-request", "The review notes are invalid.");
|
|
671
|
+
return value.map((item) => {
|
|
672
|
+
const note = item !== null && typeof item === "object" ? item : void 0;
|
|
673
|
+
const text = typeof note?.text === "string" ? note.text.trim() : "";
|
|
674
|
+
if (text.length === 0 || text.length > MAX_NOTE_CHARS) throw new PlanFeedbackError("invalid-request", "A review note is empty or too long.");
|
|
675
|
+
const quote = typeof note?.quote === "string" ? note.quote.trim().slice(0, MAX_QUOTE_CHARS) : "";
|
|
676
|
+
return quote.length === 0 ? { text } : {
|
|
677
|
+
quote,
|
|
678
|
+
text
|
|
679
|
+
};
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
/** What Claude is told when the reviewer asks for changes.
|
|
683
|
+
*
|
|
684
|
+
* Addressed to Claude rather than logged at it: a rejection it cannot act on
|
|
685
|
+
* is the thing this whole path exists to replace. The quotes are fenced as
|
|
686
|
+
* block quotes so a passage containing its own Markdown cannot be mistaken
|
|
687
|
+
* for the reviewer's instruction. */
|
|
688
|
+
function planFeedbackMessage(notes) {
|
|
689
|
+
return [
|
|
690
|
+
"The user reviewed the plan in DeepSeek Harness and asked for changes rather than approving or rejecting it.",
|
|
691
|
+
"",
|
|
692
|
+
notes.map((note) => note.quote === void 0 ? note.text : `On this part of the plan:\n${note.quote.split("\n").map((line) => `> ${line}`).join("\n")}\n\n${note.text}`).join("\n\n---\n\n"),
|
|
693
|
+
"",
|
|
694
|
+
"Revise the plan accordingly and propose it again."
|
|
695
|
+
].join("\n");
|
|
696
|
+
}
|
|
697
|
+
/** The seam between the panel's "send for changes" and the permission bridge
|
|
698
|
+
* waiting on that plan's approval.
|
|
699
|
+
*
|
|
700
|
+
* The approval promise lives inside the bridge and cannot be resolved from
|
|
701
|
+
* outside, so the bridge races it against this gate instead: whichever
|
|
702
|
+
* answers first decides, and the loser is aborted. One waiter per tool use —
|
|
703
|
+
* a plan is approved once. */
|
|
704
|
+
var PlanFeedbackGate = class {
|
|
705
|
+
#waiting = /* @__PURE__ */ new Map();
|
|
706
|
+
/** Notes for the plan under `toolUseId`, or undefined when the wait is
|
|
707
|
+
* abandoned because the approval surface answered first. */
|
|
708
|
+
wait(toolUseId, signal) {
|
|
709
|
+
return new Promise((resolve) => {
|
|
710
|
+
const settle = (notes) => {
|
|
711
|
+
if (this.#waiting.get(toolUseId) === deliver) this.#waiting.delete(toolUseId);
|
|
712
|
+
signal.removeEventListener("abort", onAbort);
|
|
713
|
+
resolve(notes);
|
|
714
|
+
};
|
|
715
|
+
const deliver = (notes) => {
|
|
716
|
+
settle(notes);
|
|
717
|
+
};
|
|
718
|
+
const onAbort = () => {
|
|
719
|
+
settle(void 0);
|
|
720
|
+
};
|
|
721
|
+
if (signal.aborted) {
|
|
722
|
+
resolve(void 0);
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
726
|
+
this.#waiting.set(toolUseId, deliver);
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
/** Hand notes to a waiting plan approval. False when nothing is waiting —
|
|
730
|
+
* the plan was already decided, or never existed. */
|
|
731
|
+
submit(toolUseId, notes) {
|
|
732
|
+
const deliver = this.#waiting.get(toolUseId);
|
|
733
|
+
if (deliver === void 0) return false;
|
|
734
|
+
deliver(notes);
|
|
735
|
+
return true;
|
|
736
|
+
}
|
|
737
|
+
/** Whether a plan is currently open for review. */
|
|
738
|
+
pending(toolUseId) {
|
|
739
|
+
return this.#waiting.has(toolUseId);
|
|
740
|
+
}
|
|
741
|
+
};
|
|
742
|
+
//#endregion
|
|
603
743
|
//#region src/permission.ts
|
|
604
744
|
function denialMessage(outcome) {
|
|
605
745
|
switch (outcome) {
|
|
@@ -609,10 +749,56 @@ function denialMessage(outcome) {
|
|
|
609
749
|
case "allowed-once": return "";
|
|
610
750
|
}
|
|
611
751
|
}
|
|
752
|
+
/** Claude leaving plan mode is not an ordinary tool call: its argument is the
|
|
753
|
+
* plan, written for the user, and the approval that follows is the user
|
|
754
|
+
* agreeing to it. Everything else reads better as a prompt plus its input. */
|
|
755
|
+
const PLAN_TOOL = "ExitPlanMode";
|
|
756
|
+
const MAX_REASON_CHARS = 1200;
|
|
757
|
+
/** What the approval dialog says instead of the plan.
|
|
758
|
+
*
|
|
759
|
+
* A plan is written to be read at length, and the approval dialog is a
|
|
760
|
+
* cramped modal that renders its reason as plain text: pasting the plan there
|
|
761
|
+
* turned a document into an unreadable wall. The decision stays with the Host
|
|
762
|
+
* — this is still an ordinary tool approval — and the plan itself is drawn by
|
|
763
|
+
* the plugin's own panel, where Markdown and the reader's chosen prose
|
|
764
|
+
* palette both apply. The dialog only has to say what is being decided and
|
|
765
|
+
* where to read it. */
|
|
766
|
+
const PLAN_APPROVAL_PROMPT = "Claude proposed a plan. Read it in the Plan panel, then approve or reject here.";
|
|
767
|
+
/** The plan an `ExitPlanMode` call carries, if it carries one.
|
|
768
|
+
*
|
|
769
|
+
* Travels to the client on the permission activity's `text` field: `summary`
|
|
770
|
+
* is capped at 1k and `detail` at 4k, both of which truncate a real plan,
|
|
771
|
+
* while `text` holds 64k. Only `kind: 'text'` activities are drawn as prose
|
|
772
|
+
* by the transcript, so a plan riding a `kind: 'permission'` record reaches
|
|
773
|
+
* the panel without being painted twice. */
|
|
774
|
+
function planText(toolName, input) {
|
|
775
|
+
if (toolName !== PLAN_TOOL) return void 0;
|
|
776
|
+
return typeof input.plan === "string" && input.plan.length > 0 ? input.plan : void 0;
|
|
777
|
+
}
|
|
778
|
+
/** The session's approval-policy override, or undefined when it never switched.
|
|
779
|
+
*
|
|
780
|
+
* The same fold the approval service does — the last `approval/policy` event
|
|
781
|
+
* wins, because replaying the log IS the state. Read here rather than
|
|
782
|
+
* imported so this package keeps its runtime surface to the Host services it
|
|
783
|
+
* is actually handed. */
|
|
784
|
+
function approvalPolicyOf(events) {
|
|
785
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
786
|
+
const event = events[index];
|
|
787
|
+
if (event?.type !== "approval/policy") continue;
|
|
788
|
+
const policy = event.data.policy;
|
|
789
|
+
return typeof policy === "string" ? policy : void 0;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
/** The policy that answers every request with `rejected` before reaching an
|
|
793
|
+
* answerer, so no approval surface is ever shown. DSH writes it alongside
|
|
794
|
+
* `sandbox/mode: danger-full-access` — Full access means "stop asking". */
|
|
795
|
+
const SILENT_POLICY = "never";
|
|
796
|
+
const ASKING_POLICY = "ask";
|
|
612
797
|
function permissionReason(toolName, input, options) {
|
|
798
|
+
if (planText(toolName, input) !== void 0) return PLAN_APPROVAL_PROMPT;
|
|
613
799
|
const prompt = options.title ?? options.description ?? options.decisionReason ?? `Claude Code wants to use ${toolName}.`;
|
|
614
800
|
const detail = safeDetail(input);
|
|
615
|
-
return boundText(detail === void 0 ? prompt : `${prompt}\nInput: ${detail}`,
|
|
801
|
+
return boundText(detail === void 0 ? prompt : `${prompt}\nInput: ${detail}`, MAX_REASON_CHARS);
|
|
616
802
|
}
|
|
617
803
|
function mapApprovalOutcome(outcome, input, toolUseID) {
|
|
618
804
|
if (outcome === "allowed-once") return {
|
|
@@ -628,7 +814,7 @@ function mapApprovalOutcome(outcome, input, toolUseID) {
|
|
|
628
814
|
decisionClassification: "user_reject"
|
|
629
815
|
};
|
|
630
816
|
}
|
|
631
|
-
function createPermissionBridge(approval, activeContext, userQuestion) {
|
|
817
|
+
function createPermissionBridge(approval, activeContext, userQuestion, planFeedback) {
|
|
632
818
|
return async (toolName, input, options) => {
|
|
633
819
|
if (toolName === "AskUserQuestion") return userQuestion === void 0 ? {
|
|
634
820
|
behavior: "deny",
|
|
@@ -645,6 +831,9 @@ function createPermissionBridge(approval, activeContext, userQuestion) {
|
|
|
645
831
|
};
|
|
646
832
|
active.markActivity?.();
|
|
647
833
|
const reason = permissionReason(toolName, input, options);
|
|
834
|
+
const plan = planText(toolName, input);
|
|
835
|
+
const session = active.agent.session;
|
|
836
|
+
let silenced = false;
|
|
648
837
|
try {
|
|
649
838
|
await active.appendActivity({
|
|
650
839
|
kind: "permission",
|
|
@@ -653,16 +842,50 @@ function createPermissionBridge(approval, activeContext, userQuestion) {
|
|
|
653
842
|
toolName,
|
|
654
843
|
title: options.displayName ?? toolName,
|
|
655
844
|
summary: options.title ?? options.description ?? reason,
|
|
656
|
-
detail: input
|
|
845
|
+
detail: input,
|
|
846
|
+
...plan === void 0 ? {} : { text: plan }
|
|
657
847
|
});
|
|
658
|
-
const
|
|
659
|
-
|
|
848
|
+
const userDecides = plan !== void 0;
|
|
849
|
+
silenced = userDecides && approvalPolicyOf(session.events) === SILENT_POLICY;
|
|
850
|
+
if (silenced) session.append("approval/policy", { policy: ASKING_POLICY });
|
|
851
|
+
const alreadyFullAccess = !userDecides && await active.hasFullAccess?.() === true;
|
|
852
|
+
const revision = new AbortController();
|
|
853
|
+
const notes = plan === void 0 || planFeedback === void 0 ? void 0 : planFeedback.wait(options.toolUseID, AbortSignal.any([options.signal, revision.signal]));
|
|
854
|
+
const decided = new AbortController();
|
|
855
|
+
const asked = alreadyFullAccess ? Promise.resolve("allowed-once") : approval.request({
|
|
660
856
|
agent: active.agent,
|
|
661
857
|
toolName,
|
|
662
858
|
reason,
|
|
663
|
-
signal: options.signal
|
|
859
|
+
signal: notes === void 0 ? options.signal : AbortSignal.any([options.signal, decided.signal])
|
|
664
860
|
});
|
|
665
|
-
const
|
|
861
|
+
const answer = notes === void 0 ? { outcome: await asked } : await Promise.race([asked.then((outcome) => {
|
|
862
|
+
revision.abort();
|
|
863
|
+
return { outcome };
|
|
864
|
+
}), notes.then((value) => value === void 0 ? void 0 : { revisions: value })]).then(async (first) => {
|
|
865
|
+
if (first !== void 0 && "revisions" in first) decided.abort();
|
|
866
|
+
return first ?? { outcome: await asked };
|
|
867
|
+
});
|
|
868
|
+
if ("revisions" in answer) {
|
|
869
|
+
const message = planFeedbackMessage(answer.revisions);
|
|
870
|
+
active.recordDenial?.(options.toolUseID);
|
|
871
|
+
await active.appendActivity({
|
|
872
|
+
kind: "permission",
|
|
873
|
+
phase: "denied",
|
|
874
|
+
toolUseId: options.toolUseID,
|
|
875
|
+
toolName,
|
|
876
|
+
title: options.displayName ?? toolName,
|
|
877
|
+
summary: "Sent back for changes in DeepSeek Harness",
|
|
878
|
+
text: message
|
|
879
|
+
});
|
|
880
|
+
return {
|
|
881
|
+
behavior: "deny",
|
|
882
|
+
message,
|
|
883
|
+
toolUseID: options.toolUseID,
|
|
884
|
+
decisionClassification: "user_reject"
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
const outcome = answer.outcome;
|
|
888
|
+
const fullAccess = alreadyFullAccess || !userDecides && await active.hasFullAccess?.() === true;
|
|
666
889
|
const effectiveOutcome = fullAccess ? "allowed-once" : outcome;
|
|
667
890
|
const result = mapApprovalOutcome(effectiveOutcome, input, options.toolUseID);
|
|
668
891
|
if (result.behavior === "deny") active.recordDenial?.(options.toolUseID);
|
|
@@ -695,6 +918,10 @@ function createPermissionBridge(approval, activeContext, userQuestion) {
|
|
|
695
918
|
toolUseID: options.toolUseID,
|
|
696
919
|
decisionClassification: "user_reject"
|
|
697
920
|
};
|
|
921
|
+
} finally {
|
|
922
|
+
try {
|
|
923
|
+
if (silenced) session.append("approval/policy", { policy: SILENT_POLICY });
|
|
924
|
+
} catch {}
|
|
698
925
|
}
|
|
699
926
|
};
|
|
700
927
|
}
|
|
@@ -814,7 +1041,7 @@ function createUserQuestionBridge(userQuestions, activeContext) {
|
|
|
814
1041
|
}
|
|
815
1042
|
//#endregion
|
|
816
1043
|
//#region src/sdk-messages.ts
|
|
817
|
-
function record$
|
|
1044
|
+
function record$13(value) {
|
|
818
1045
|
return value !== null && typeof value === "object" ? value : void 0;
|
|
819
1046
|
}
|
|
820
1047
|
function string$3(value) {
|
|
@@ -846,12 +1073,12 @@ function hasUsageCounts(usage) {
|
|
|
846
1073
|
return usage.inputTokens !== void 0 || usage.outputTokens !== void 0 || usage.cacheReadTokens !== void 0 || usage.cacheCreationTokens !== void 0;
|
|
847
1074
|
}
|
|
848
1075
|
function resultUsage(message) {
|
|
849
|
-
const normalized = usageOf(record$
|
|
1076
|
+
const normalized = usageOf(record$13(message.usage));
|
|
850
1077
|
if (typeof message.total_cost_usd === "number") normalized.cumulativeCostUsd = message.total_cost_usd;
|
|
851
1078
|
return normalized;
|
|
852
1079
|
}
|
|
853
1080
|
function normalizeAssistant(message) {
|
|
854
|
-
const envelope = record$
|
|
1081
|
+
const envelope = record$13(message.message);
|
|
855
1082
|
const content = envelope?.content;
|
|
856
1083
|
if (!Array.isArray(content)) return [{
|
|
857
1084
|
kind: "protocol-error",
|
|
@@ -861,7 +1088,7 @@ function normalizeAssistant(message) {
|
|
|
861
1088
|
const parentToolUseId = string$3(message.parent_tool_use_id);
|
|
862
1089
|
const normalized = [];
|
|
863
1090
|
for (const item of content) {
|
|
864
|
-
const block = record$
|
|
1091
|
+
const block = record$13(item);
|
|
865
1092
|
if (block === void 0) continue;
|
|
866
1093
|
if (block.type === "text") {
|
|
867
1094
|
const text = string$3(block.text);
|
|
@@ -890,7 +1117,7 @@ function normalizeAssistant(message) {
|
|
|
890
1117
|
});
|
|
891
1118
|
}
|
|
892
1119
|
}
|
|
893
|
-
const usage = usageOf(record$
|
|
1120
|
+
const usage = usageOf(record$13(envelope?.usage));
|
|
894
1121
|
if (hasUsageCounts(usage)) normalized.push({
|
|
895
1122
|
kind: "request-usage",
|
|
896
1123
|
usage,
|
|
@@ -900,7 +1127,7 @@ function normalizeAssistant(message) {
|
|
|
900
1127
|
}
|
|
901
1128
|
function normalizeUser(message) {
|
|
902
1129
|
if (message.isReplay === true) return [];
|
|
903
|
-
const content = record$
|
|
1130
|
+
const content = record$13(message.message)?.content;
|
|
904
1131
|
if (typeof content === "string") return [];
|
|
905
1132
|
if (!Array.isArray(content)) return [{
|
|
906
1133
|
kind: "protocol-error",
|
|
@@ -910,7 +1137,7 @@ function normalizeUser(message) {
|
|
|
910
1137
|
const parentToolUseId = string$3(message.parent_tool_use_id);
|
|
911
1138
|
const normalized = [];
|
|
912
1139
|
for (const item of content) {
|
|
913
|
-
const block = record$
|
|
1140
|
+
const block = record$13(item);
|
|
914
1141
|
if (block?.type !== "tool_result") continue;
|
|
915
1142
|
const toolUseId = string$3(block.tool_use_id);
|
|
916
1143
|
if (toolUseId === void 0) continue;
|
|
@@ -991,7 +1218,7 @@ function normalizeSystem(message) {
|
|
|
991
1218
|
const summary = string$3(message.summary);
|
|
992
1219
|
const subagentType = string$3(message.subagent_type);
|
|
993
1220
|
const lastToolName = string$3(message.last_tool_name);
|
|
994
|
-
const usage = taskUsageOf(record$
|
|
1221
|
+
const usage = taskUsageOf(record$13(message.usage));
|
|
995
1222
|
return [{
|
|
996
1223
|
kind: "subagent",
|
|
997
1224
|
title: summary ?? description ?? "Claude subagent update",
|
|
@@ -1007,7 +1234,7 @@ function normalizeSystem(message) {
|
|
|
1007
1234
|
}];
|
|
1008
1235
|
}
|
|
1009
1236
|
if (subtype === "task_updated") {
|
|
1010
|
-
const patch = record$
|
|
1237
|
+
const patch = record$13(message.patch);
|
|
1011
1238
|
const status = string$3(patch?.status);
|
|
1012
1239
|
const taskId = string$3(message.task_id);
|
|
1013
1240
|
const description = string$3(patch?.description);
|
|
@@ -1030,7 +1257,7 @@ function normalizeSystem(message) {
|
|
|
1030
1257
|
const taskId = string$3(message.task_id);
|
|
1031
1258
|
const summary = string$3(message.summary);
|
|
1032
1259
|
const taskStatus = failed ? "failed" : stopped ? "stopped" : "completed";
|
|
1033
|
-
const usage = taskUsageOf(record$
|
|
1260
|
+
const usage = taskUsageOf(record$13(message.usage));
|
|
1034
1261
|
return [{
|
|
1035
1262
|
kind: "subagent",
|
|
1036
1263
|
title: summary ?? taskId ?? "Claude subagent finished",
|
|
@@ -1045,7 +1272,7 @@ function normalizeSystem(message) {
|
|
|
1045
1272
|
if (subtype === "background_tasks_changed") return [{
|
|
1046
1273
|
kind: "background-tasks",
|
|
1047
1274
|
tasks: (Array.isArray(message.tasks) ? message.tasks : []).flatMap((item) => {
|
|
1048
|
-
const entry = record$
|
|
1275
|
+
const entry = record$13(item);
|
|
1049
1276
|
const taskId = string$3(entry?.task_id);
|
|
1050
1277
|
const description = string$3(entry?.description);
|
|
1051
1278
|
const taskType = string$3(entry?.task_type);
|
|
@@ -1063,7 +1290,7 @@ function normalizeSystem(message) {
|
|
|
1063
1290
|
detail: message
|
|
1064
1291
|
}];
|
|
1065
1292
|
if (subtype === "compact_boundary") {
|
|
1066
|
-
const metadata = record$
|
|
1293
|
+
const metadata = record$13(message.compact_metadata);
|
|
1067
1294
|
const trigger = metadata?.trigger === "auto" || metadata?.trigger === "manual" ? metadata.trigger : void 0;
|
|
1068
1295
|
const preTokens = finiteNumber(metadata?.pre_tokens);
|
|
1069
1296
|
const postTokens = finiteNumber(metadata?.post_tokens);
|
|
@@ -1102,10 +1329,10 @@ const RESULT_ERROR_SUBTYPES = /* @__PURE__ */ new Set([
|
|
|
1102
1329
|
function normalizeSdkMessage(message) {
|
|
1103
1330
|
const value = message;
|
|
1104
1331
|
if (value.type === "stream_event") {
|
|
1105
|
-
const event = record$
|
|
1332
|
+
const event = record$13(value.event);
|
|
1106
1333
|
const parentToolUseId = string$3(value.parent_tool_use_id);
|
|
1107
1334
|
if (event?.type === "content_block_delta") {
|
|
1108
|
-
const delta = record$
|
|
1335
|
+
const delta = record$13(event.delta);
|
|
1109
1336
|
if (delta?.type === "text_delta") {
|
|
1110
1337
|
const text = string$3(delta.text);
|
|
1111
1338
|
return text === void 0 ? [] : [{
|
|
@@ -1140,7 +1367,7 @@ function normalizeSdkMessage(message) {
|
|
|
1140
1367
|
const errors = Array.isArray(value.errors) ? value.errors.filter((item) => typeof item === "string") : void 0;
|
|
1141
1368
|
const terminalReason = string$3(value.terminal_reason);
|
|
1142
1369
|
const userMessageUuid = string$3(value.user_message_uuid);
|
|
1143
|
-
const permissionDenials = Array.isArray(value.permission_denials) ? value.permission_denials.map((item) => record$
|
|
1370
|
+
const permissionDenials = Array.isArray(value.permission_denials) ? value.permission_denials.map((item) => record$13(item)).filter((item) => item !== void 0).map((item) => {
|
|
1144
1371
|
const toolName = string$3(item.tool_name);
|
|
1145
1372
|
const toolUseId = string$3(item.tool_use_id);
|
|
1146
1373
|
return toolName === void 0 || toolUseId === void 0 ? void 0 : {
|
|
@@ -1166,7 +1393,7 @@ function normalizeSdkMessage(message) {
|
|
|
1166
1393
|
detail: value.error ?? value.output
|
|
1167
1394
|
}];
|
|
1168
1395
|
if (value.type === "rate_limit_event") {
|
|
1169
|
-
const status = string$3(record$
|
|
1396
|
+
const status = string$3(record$13(value.rate_limit_info)?.status);
|
|
1170
1397
|
return [{
|
|
1171
1398
|
kind: "status",
|
|
1172
1399
|
title: status !== void 0 && status !== "allowed" ? "Claude rate limit is blocking requests" : "Claude rate limit status changed",
|
|
@@ -1279,7 +1506,7 @@ const FIXED_WINDOWS = [
|
|
|
1279
1506
|
"seven_day_opus",
|
|
1280
1507
|
"seven_day_sonnet"
|
|
1281
1508
|
];
|
|
1282
|
-
function record$
|
|
1509
|
+
function record$12(value) {
|
|
1283
1510
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
1284
1511
|
}
|
|
1285
1512
|
/** Utilization is documented as 0-100; clamp so a server glitch cannot render
|
|
@@ -1291,7 +1518,7 @@ function resetsAt(value) {
|
|
|
1291
1518
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
1292
1519
|
}
|
|
1293
1520
|
function window(id, source, label) {
|
|
1294
|
-
const entry = record$
|
|
1521
|
+
const entry = record$12(source);
|
|
1295
1522
|
if (entry === void 0) return void 0;
|
|
1296
1523
|
const used = utilization(entry.utilization);
|
|
1297
1524
|
const reset = resetsAt(entry.resets_at);
|
|
@@ -1305,9 +1532,9 @@ function window(id, source, label) {
|
|
|
1305
1532
|
}
|
|
1306
1533
|
/** Project the SDK's `/usage` response onto the windows the settings card shows. */
|
|
1307
1534
|
function normalizePlanUsage(value, fetchedAt) {
|
|
1308
|
-
const response = record$
|
|
1535
|
+
const response = record$12(value);
|
|
1309
1536
|
const subscription = typeof response?.subscription_type === "string" ? response.subscription_type : void 0;
|
|
1310
|
-
const limits = record$
|
|
1537
|
+
const limits = record$12(response?.rate_limits);
|
|
1311
1538
|
if (response?.rate_limits_available !== true || limits === void 0) return {
|
|
1312
1539
|
available: false,
|
|
1313
1540
|
...subscription === void 0 ? {} : { subscription },
|
|
@@ -1315,7 +1542,7 @@ function normalizePlanUsage(value, fetchedAt) {
|
|
|
1315
1542
|
fetchedAt
|
|
1316
1543
|
};
|
|
1317
1544
|
const windows = [...FIXED_WINDOWS.map((id) => window(id, limits[id])), ...(Array.isArray(limits.model_scoped) ? limits.model_scoped : []).map((entry, index) => {
|
|
1318
|
-
const name = record$
|
|
1545
|
+
const name = record$12(entry)?.display_name;
|
|
1319
1546
|
return window(`model:${typeof name === "string" ? name : index}`, entry, typeof name === "string" ? name : void 0);
|
|
1320
1547
|
})].filter((entry) => entry !== void 0);
|
|
1321
1548
|
return {
|
|
@@ -1442,6 +1669,115 @@ function createManagedClaudeSpawner(runtime, executablePath, observe) {
|
|
|
1442
1669
|
};
|
|
1443
1670
|
}
|
|
1444
1671
|
//#endregion
|
|
1672
|
+
//#region src/worktree-snapshot.ts
|
|
1673
|
+
/** Working-tree snapshots taken and restored with git's own plumbing.
|
|
1674
|
+
*
|
|
1675
|
+
* A rewind that only truncates Claude's transcript leaves the files Claude
|
|
1676
|
+
* wrote on disk, so the next turn resumes against a checkout the model no
|
|
1677
|
+
* longer remembers writing. Git already stores trees: a throwaway index turns
|
|
1678
|
+
* the working tree into one tree object without touching HEAD, the real
|
|
1679
|
+
* index, or the checkout, and a restore reads that tree back.
|
|
1680
|
+
*
|
|
1681
|
+
* Ignored files stay outside the snapshot in both directions. `git add -A`
|
|
1682
|
+
* honours `.gitignore`, so build output and `node_modules` are neither
|
|
1683
|
+
* captured nor removed.
|
|
1684
|
+
*/
|
|
1685
|
+
const MAX_OUTPUT_BYTES$5 = 65536;
|
|
1686
|
+
/** `add -A` walks the whole checkout; a large repository needs more than the
|
|
1687
|
+
* status probes' five seconds, and this sits in front of every turn. */
|
|
1688
|
+
const GIT_TIMEOUT_MS$4 = 3e4;
|
|
1689
|
+
const OBJECT_NAME = /^[0-9a-f]{40}$|^[0-9a-f]{64}$/u;
|
|
1690
|
+
async function collect$4(handle) {
|
|
1691
|
+
return {
|
|
1692
|
+
exitCode: (await handle.done).exitCode,
|
|
1693
|
+
stdout: handle.collected.stdout?.readFrom(0).text ?? ""
|
|
1694
|
+
};
|
|
1695
|
+
}
|
|
1696
|
+
async function run$1(runtime, git, args, cwd, env = {}) {
|
|
1697
|
+
return collect$4(runtime.spawn({
|
|
1698
|
+
argv: [git, ...args],
|
|
1699
|
+
cwd,
|
|
1700
|
+
stdio: {
|
|
1701
|
+
stdin: "ignore",
|
|
1702
|
+
stdout: { maxBytes: MAX_OUTPUT_BYTES$5 },
|
|
1703
|
+
stderr: { maxBytes: MAX_OUTPUT_BYTES$5 }
|
|
1704
|
+
},
|
|
1705
|
+
graceMs: 1e3,
|
|
1706
|
+
signal: AbortSignal.timeout(GIT_TIMEOUT_MS$4),
|
|
1707
|
+
env
|
|
1708
|
+
}));
|
|
1709
|
+
}
|
|
1710
|
+
/** Capture the working tree as a git tree object.
|
|
1711
|
+
*
|
|
1712
|
+
* Undefined whenever git cannot answer -- no git, not a repository, a locked
|
|
1713
|
+
* or broken checkout. Snapshots are advisory: a turn without one simply
|
|
1714
|
+
* cannot offer a file rewind, and must never fail for it.
|
|
1715
|
+
*
|
|
1716
|
+
* ponytail: the tree is unreachable from any ref, so `git gc --prune` will
|
|
1717
|
+
* eventually collect it. Two weeks is git's default grace period and a rewind
|
|
1718
|
+
* happens minutes after the turn it undoes; anchor it to a real ref only if
|
|
1719
|
+
* snapshots ever need to outlive a gc.
|
|
1720
|
+
*/
|
|
1721
|
+
async function captureWorktreeTree(runtime, cwd) {
|
|
1722
|
+
let git;
|
|
1723
|
+
try {
|
|
1724
|
+
git = await runtime.resolveExecutable("git");
|
|
1725
|
+
} catch {
|
|
1726
|
+
return;
|
|
1727
|
+
}
|
|
1728
|
+
const indexFile = join(tmpdir(), `dsh-claude-index-${process.pid}-${randomUUID()}`);
|
|
1729
|
+
const env = { GIT_INDEX_FILE: indexFile };
|
|
1730
|
+
try {
|
|
1731
|
+
await run$1(runtime, git, ["read-tree", "HEAD"], cwd, env);
|
|
1732
|
+
if ((await run$1(runtime, git, ["add", "-A"], cwd, env)).exitCode !== 0) return void 0;
|
|
1733
|
+
const written = await run$1(runtime, git, ["write-tree"], cwd, env);
|
|
1734
|
+
const tree = written.stdout.trim();
|
|
1735
|
+
return written.exitCode === 0 && OBJECT_NAME.test(tree) ? tree : void 0;
|
|
1736
|
+
} catch {
|
|
1737
|
+
return;
|
|
1738
|
+
} finally {
|
|
1739
|
+
await rm(indexFile, { force: true }).catch(() => void 0);
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
/** Put a captured tree back over the working tree, reporting whether it landed.
|
|
1743
|
+
*
|
|
1744
|
+
* Files the tree holds are rewritten, and files created after the snapshot are
|
|
1745
|
+
* removed. Nothing outside the snapshot's own scope is touched: ignored files
|
|
1746
|
+
* survive, because `clean` runs without `-x`.
|
|
1747
|
+
*/
|
|
1748
|
+
async function restoreWorktreeTree(runtime, cwd, tree) {
|
|
1749
|
+
if (!OBJECT_NAME.test(tree)) return false;
|
|
1750
|
+
let git;
|
|
1751
|
+
try {
|
|
1752
|
+
git = await runtime.resolveExecutable("git");
|
|
1753
|
+
} catch {
|
|
1754
|
+
return false;
|
|
1755
|
+
}
|
|
1756
|
+
try {
|
|
1757
|
+
const kind = await run$1(runtime, git, [
|
|
1758
|
+
"cat-file",
|
|
1759
|
+
"-t",
|
|
1760
|
+
tree
|
|
1761
|
+
], cwd);
|
|
1762
|
+
if (kind.exitCode !== 0 || kind.stdout.trim() !== "tree") return false;
|
|
1763
|
+
if ((await run$1(runtime, git, [
|
|
1764
|
+
"read-tree",
|
|
1765
|
+
"--reset",
|
|
1766
|
+
"-u",
|
|
1767
|
+
tree
|
|
1768
|
+
], cwd)).exitCode !== 0) return false;
|
|
1769
|
+
await run$1(runtime, git, ["clean", "-fd"], cwd);
|
|
1770
|
+
await run$1(runtime, git, [
|
|
1771
|
+
"reset",
|
|
1772
|
+
"--mixed",
|
|
1773
|
+
"HEAD"
|
|
1774
|
+
], cwd);
|
|
1775
|
+
return true;
|
|
1776
|
+
} catch {
|
|
1777
|
+
return false;
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
//#endregion
|
|
1445
1781
|
//#region src/supervisor.ts
|
|
1446
1782
|
const CLAUDE_INITIALIZATION_TIMEOUT_MS = 3e4;
|
|
1447
1783
|
/** Control requests must settle; a wedged one must not clog the metadata chain. */
|
|
@@ -1549,6 +1885,8 @@ var ClaudeSupervisor = class {
|
|
|
1549
1885
|
#runtime;
|
|
1550
1886
|
#approval;
|
|
1551
1887
|
#userQuestions;
|
|
1888
|
+
/** Lets the plan panel answer a plan's approval with revisions. */
|
|
1889
|
+
planFeedback = new PlanFeedbackGate();
|
|
1552
1890
|
#config;
|
|
1553
1891
|
#queryFactory;
|
|
1554
1892
|
#runDetached;
|
|
@@ -1670,6 +2008,7 @@ var ClaudeSupervisor = class {
|
|
|
1670
2008
|
const active = {
|
|
1671
2009
|
agent: request.agent,
|
|
1672
2010
|
cursor,
|
|
2011
|
+
native: (request.renderMode ?? this.#config.renderMode ?? "plugin") === "native",
|
|
1673
2012
|
output: new AsyncQueue(),
|
|
1674
2013
|
promptUuid,
|
|
1675
2014
|
phase: "primary",
|
|
@@ -1690,6 +2029,7 @@ var ClaudeSupervisor = class {
|
|
|
1690
2029
|
entry.active = active;
|
|
1691
2030
|
entry.state = "running";
|
|
1692
2031
|
entry.lastUsedAt = Date.now();
|
|
2032
|
+
await this.#captureWorktree(entry, cursor.turn);
|
|
1693
2033
|
try {
|
|
1694
2034
|
await this.#appendActivity(active, {
|
|
1695
2035
|
kind: "status",
|
|
@@ -1860,7 +2200,7 @@ var ClaudeSupervisor = class {
|
|
|
1860
2200
|
};
|
|
1861
2201
|
};
|
|
1862
2202
|
const userQuestion = createUserQuestionBridge(this.#userQuestions, activeInteraction);
|
|
1863
|
-
const canUseTool = createPermissionBridge(this.#approval, activeInteraction, userQuestion);
|
|
2203
|
+
const canUseTool = createPermissionBridge(this.#approval, activeInteraction, userQuestion, this.planFeedback);
|
|
1864
2204
|
const options = {
|
|
1865
2205
|
pathToClaudeCodeExecutable: this.#config.executablePath,
|
|
1866
2206
|
cwd,
|
|
@@ -2000,7 +2340,7 @@ var ClaudeSupervisor = class {
|
|
|
2000
2340
|
title: "Claude thinking",
|
|
2001
2341
|
summary: message.text
|
|
2002
2342
|
});
|
|
2003
|
-
if (
|
|
2343
|
+
if (active.native) active.output.push({
|
|
2004
2344
|
type: "thinking",
|
|
2005
2345
|
text: message.text
|
|
2006
2346
|
});
|
|
@@ -2019,7 +2359,7 @@ var ClaudeSupervisor = class {
|
|
|
2019
2359
|
});
|
|
2020
2360
|
if (message.parentToolUseId === void 0) {
|
|
2021
2361
|
active.openCalls.set(message.toolUseId, message.toolName);
|
|
2022
|
-
if (
|
|
2362
|
+
if (active.native) {
|
|
2023
2363
|
this.#ensureDynamicPresenter(active.agent, message.toolName);
|
|
2024
2364
|
await this.#appendNativeToolCall(active, message);
|
|
2025
2365
|
}
|
|
@@ -2036,7 +2376,7 @@ var ClaudeSupervisor = class {
|
|
|
2036
2376
|
detail: message.output,
|
|
2037
2377
|
isError: message.isError
|
|
2038
2378
|
});
|
|
2039
|
-
if (message.parentToolUseId === void 0 &&
|
|
2379
|
+
if (message.parentToolUseId === void 0 && active.native) await this.#appendNativeToolResult(active, message);
|
|
2040
2380
|
return;
|
|
2041
2381
|
case "subagent":
|
|
2042
2382
|
await this.#appendActivity(active, {
|
|
@@ -2086,7 +2426,7 @@ var ClaudeSupervisor = class {
|
|
|
2086
2426
|
title: message.toolName,
|
|
2087
2427
|
summary: message.summary
|
|
2088
2428
|
});
|
|
2089
|
-
if (
|
|
2429
|
+
if (active.native) await this.#appendNativeToolResult(active, {
|
|
2090
2430
|
kind: "tool-result",
|
|
2091
2431
|
toolUseId: message.toolUseId,
|
|
2092
2432
|
output: message.summary,
|
|
@@ -2106,9 +2446,6 @@ var ClaudeSupervisor = class {
|
|
|
2106
2446
|
...active.firstOutputAt === void 0 ? {} : { ttftMs: Math.max(0, active.firstOutputAt - active.startedAt) }
|
|
2107
2447
|
};
|
|
2108
2448
|
}
|
|
2109
|
-
#nativeRendering() {
|
|
2110
|
-
return (this.#config.renderMode ?? "plugin") === "native";
|
|
2111
|
-
}
|
|
2112
2449
|
/** Register one presenter-only mirror for a tool name the static preset
|
|
2113
2450
|
* registry does not cover (MCP tools, newly added built-ins). Runs in the
|
|
2114
2451
|
* agent scope so the mirror is visible only to this preset's sessions and
|
|
@@ -2436,6 +2773,20 @@ var ClaudeSupervisor = class {
|
|
|
2436
2773
|
this.#sidecar.checkpoint(entry.sessionId);
|
|
2437
2774
|
} catch {}
|
|
2438
2775
|
}
|
|
2776
|
+
/** Pin the working tree this turn is about to change, so a rewind of it can
|
|
2777
|
+
* put the checkout back where the turn found it.
|
|
2778
|
+
*
|
|
2779
|
+
* Awaited, and deliberately: a snapshot taken after Claude's first edit
|
|
2780
|
+
* would restore to a state that never existed. It costs one `git add -A`
|
|
2781
|
+
* against a throwaway index per turn, and best effort throughout -- a
|
|
2782
|
+
* session with no repository simply never offers a file rewind. */
|
|
2783
|
+
async #captureWorktree(entry, turn) {
|
|
2784
|
+
try {
|
|
2785
|
+
const tree = await captureWorktreeTree(this.#runtime, entry.cwd);
|
|
2786
|
+
if (tree === void 0) return;
|
|
2787
|
+
await this.#sidecar.recordRewindSnapshot(entry.sessionId, turn, tree);
|
|
2788
|
+
} catch {}
|
|
2789
|
+
}
|
|
2439
2790
|
/** Pin where Claude's chain ended for the DSH turn that just settled, so a
|
|
2440
2791
|
* later rewind of the following turn can fork exactly here. Best effort:
|
|
2441
2792
|
* a missing anchor only makes a rewind fall back to an earlier turn. */
|
|
@@ -2453,7 +2804,7 @@ var ClaudeSupervisor = class {
|
|
|
2453
2804
|
try {
|
|
2454
2805
|
this.#sidecar.appendTranscriptText(active.agent.id, {
|
|
2455
2806
|
text: active.transcriptText,
|
|
2456
|
-
...
|
|
2807
|
+
...active.native ? { renderer: "native" } : {},
|
|
2457
2808
|
turn: active.cursor.turn,
|
|
2458
2809
|
step: active.cursor.step,
|
|
2459
2810
|
ordinal
|
|
@@ -2472,7 +2823,7 @@ var ClaudeSupervisor = class {
|
|
|
2472
2823
|
const ordinal = active.cursor.nextOrdinal++;
|
|
2473
2824
|
await this.#sidecar.appendActivity(active.agent.id, {
|
|
2474
2825
|
...activity,
|
|
2475
|
-
...
|
|
2826
|
+
...active.native ? { renderer: "native" } : {},
|
|
2476
2827
|
turn: active.cursor.turn,
|
|
2477
2828
|
step: active.cursor.step,
|
|
2478
2829
|
ordinal
|
|
@@ -2511,7 +2862,7 @@ var ClaudeSupervisor = class {
|
|
|
2511
2862
|
summary,
|
|
2512
2863
|
isError: true
|
|
2513
2864
|
});
|
|
2514
|
-
if (
|
|
2865
|
+
if (active.native) await this.#appendNativeToolResult(active, {
|
|
2515
2866
|
kind: "tool-result",
|
|
2516
2867
|
toolUseId,
|
|
2517
2868
|
output: summary,
|
|
@@ -2670,6 +3021,85 @@ function formatReviewComments(comments) {
|
|
|
2670
3021
|
].join("\n");
|
|
2671
3022
|
}
|
|
2672
3023
|
//#endregion
|
|
3024
|
+
//#region src/session-title.ts
|
|
3025
|
+
/** Answer DSH's auxiliary session-title request with a throwaway Haiku turn.
|
|
3026
|
+
*
|
|
3027
|
+
* DSH titles a session by asking the model behind the session's own route for
|
|
3028
|
+
* a summary of the first human message. That route is this plugin, and the
|
|
3029
|
+
* session's Claude process must not answer it: the title call carries a
|
|
3030
|
+
* plugin-authored system prompt and would land in the user's transcript. A
|
|
3031
|
+
* separate one-shot turn keeps it out, for the same reasons as the branch-name
|
|
3032
|
+
* summary and the plan-usage probe. Deployments that never installed a second
|
|
3033
|
+
* model provider still get a readable title, since the only model this plugin
|
|
3034
|
+
* needs is the one it already runs. */
|
|
3035
|
+
/** Cheapest model that can summarize a sentence in the language it was written in. */
|
|
3036
|
+
const SESSION_TITLE_MODEL = "haiku";
|
|
3037
|
+
/** Backstop only. The title service wraps its own deadline (60s by default)
|
|
3038
|
+
* around the call and passes it as `signal`, so a shorter budget here just
|
|
3039
|
+
* kills a turn the caller was still happy to wait for — a cold CLI start plus
|
|
3040
|
+
* one Haiku reply routinely passes ten seconds. */
|
|
3041
|
+
const SESSION_TITLE_TIMEOUT_MS = 6e4;
|
|
3042
|
+
/** DSH frames the messages as JSON under its own byte cap; this only bounds a
|
|
3043
|
+
* caller that does not. */
|
|
3044
|
+
const MAX_INPUT_CHARS = 8e3;
|
|
3045
|
+
/** Longer than any title DSH accepts (80 bytes), short enough to bound prose. */
|
|
3046
|
+
const MAX_TITLE_CHARS = 200;
|
|
3047
|
+
/** Carry DSH's instruction as the prompt's own preamble: a Claude Code turn has
|
|
3048
|
+
* no separate system slot this plugin can borrow without replacing the CLI's. */
|
|
3049
|
+
function sessionTitlePrompt(request) {
|
|
3050
|
+
const input = request.input.trim().slice(0, MAX_INPUT_CHARS);
|
|
3051
|
+
return request.system === void 0 || request.system.length === 0 ? input : `${request.system}\n\n${input}`;
|
|
3052
|
+
}
|
|
3053
|
+
/** The one line of the reply that is the title. DSH strips control characters
|
|
3054
|
+
* and truncates to its own byte cap, so nothing else is cleaned here. */
|
|
3055
|
+
function sessionTitleLine(reply) {
|
|
3056
|
+
const line = reply.split("\n").map((candidate) => candidate.trim()).find((candidate) => candidate.length > 0);
|
|
3057
|
+
return line === void 0 ? "" : line.slice(0, MAX_TITLE_CHARS);
|
|
3058
|
+
}
|
|
3059
|
+
/**
|
|
3060
|
+
* Summarize the framed messages into one title line.
|
|
3061
|
+
*
|
|
3062
|
+
* Rejects rather than returning a placeholder: the title service logs the
|
|
3063
|
+
* failure and keeps the deterministic first-words fallback, which is a better
|
|
3064
|
+
* label than anything this function could invent.
|
|
3065
|
+
*/
|
|
3066
|
+
async function summarizeSessionTitle(executablePath, request, factory = query) {
|
|
3067
|
+
const prompt = sessionTitlePrompt(request);
|
|
3068
|
+
if (prompt.length === 0) throw new Error("dsh-claude: the session-title request carried no text");
|
|
3069
|
+
const lifetime = new AbortController();
|
|
3070
|
+
const abort = () => {
|
|
3071
|
+
lifetime.abort();
|
|
3072
|
+
};
|
|
3073
|
+
request.signal?.addEventListener("abort", abort, { once: true });
|
|
3074
|
+
const timer = setTimeout(abort, SESSION_TITLE_TIMEOUT_MS);
|
|
3075
|
+
timer.unref?.();
|
|
3076
|
+
try {
|
|
3077
|
+
const query = factory({
|
|
3078
|
+
prompt,
|
|
3079
|
+
options: {
|
|
3080
|
+
cwd: process.cwd(),
|
|
3081
|
+
abortController: lifetime,
|
|
3082
|
+
model: SESSION_TITLE_MODEL,
|
|
3083
|
+
allowedTools: [],
|
|
3084
|
+
settingSources: [],
|
|
3085
|
+
maxTurns: 1,
|
|
3086
|
+
...executablePath.length === 0 ? {} : { pathToClaudeCodeExecutable: executablePath }
|
|
3087
|
+
}
|
|
3088
|
+
});
|
|
3089
|
+
for await (const message of query) {
|
|
3090
|
+
if (message.type !== "result" || message.subtype !== "success") continue;
|
|
3091
|
+
const title = sessionTitleLine(message.result);
|
|
3092
|
+
if (title.length > 0) return title;
|
|
3093
|
+
break;
|
|
3094
|
+
}
|
|
3095
|
+
throw new Error("dsh-claude: the session-title turn produced no title");
|
|
3096
|
+
} finally {
|
|
3097
|
+
clearTimeout(timer);
|
|
3098
|
+
request.signal?.removeEventListener("abort", abort);
|
|
3099
|
+
lifetime.abort();
|
|
3100
|
+
}
|
|
3101
|
+
}
|
|
3102
|
+
//#endregion
|
|
2673
3103
|
//#region src/adapter.ts
|
|
2674
3104
|
const THINKING_MODES = [
|
|
2675
3105
|
{
|
|
@@ -2822,6 +3252,12 @@ function tokenUsage(usage) {
|
|
|
2822
3252
|
if (usage.cacheCreationTokens !== void 0) normalized.cacheWriteTokens = usage.cacheCreationTokens;
|
|
2823
3253
|
return normalized;
|
|
2824
3254
|
}
|
|
3255
|
+
/** Flatten a hand-built auxiliary request to text. Unlike a conversation turn
|
|
3256
|
+
* it has no images and no human-sourced message to single out: every message
|
|
3257
|
+
* in it was assembled by the plugin that asked the question. */
|
|
3258
|
+
function auxiliaryText(messages) {
|
|
3259
|
+
return messages.flatMap((message) => message.content.filter((block) => block.type === "text").map((block) => block.text)).join("\n");
|
|
3260
|
+
}
|
|
2825
3261
|
function resolveAgent(agents, options) {
|
|
2826
3262
|
const initiator = agents.currentInitiator();
|
|
2827
3263
|
if (initiator !== void 0) return initiator;
|
|
@@ -2837,8 +3273,12 @@ var ClaudeCodeAdapter = class extends LlmAdapter {
|
|
|
2837
3273
|
#attachments;
|
|
2838
3274
|
#presetIdFor;
|
|
2839
3275
|
#drainReviewComments;
|
|
3276
|
+
/** The renderer setting, read from its file at the start of each turn. A
|
|
3277
|
+
* cached copy would go stale whenever the file is edited outside the
|
|
3278
|
+
* Settings dialog, and the read is dwarfed by the process the turn spawns. */
|
|
2840
3279
|
#renderMode;
|
|
2841
|
-
|
|
3280
|
+
#summarizeTitle;
|
|
3281
|
+
constructor(supervisor, agents, attachments, presetIdFor, drainReviewComments = () => [], renderMode = async () => DEFAULT_CLAUDE_RENDER_MODE, summarizeTitle = (request) => summarizeSessionTitle("", request)) {
|
|
2842
3282
|
super();
|
|
2843
3283
|
this.#supervisor = supervisor;
|
|
2844
3284
|
this.#agents = agents;
|
|
@@ -2846,6 +3286,7 @@ var ClaudeCodeAdapter = class extends LlmAdapter {
|
|
|
2846
3286
|
this.#presetIdFor = presetIdFor;
|
|
2847
3287
|
this.#drainReviewComments = drainReviewComments;
|
|
2848
3288
|
this.#renderMode = renderMode;
|
|
3289
|
+
this.#summarizeTitle = summarizeTitle;
|
|
2849
3290
|
}
|
|
2850
3291
|
providerInfo(provider) {
|
|
2851
3292
|
return {
|
|
@@ -2888,7 +3329,47 @@ var ClaudeCodeAdapter = class extends LlmAdapter {
|
|
|
2888
3329
|
stream: (options) => this.stream(options)
|
|
2889
3330
|
};
|
|
2890
3331
|
}
|
|
3332
|
+
/** Title the session from its own provider without touching its process.
|
|
3333
|
+
*
|
|
3334
|
+
* DSH routes the title request at the session's model, which is this
|
|
3335
|
+
* adapter; a deployment with no second provider configured would otherwise
|
|
3336
|
+
* never get a title at all and keep the first five words of the first
|
|
3337
|
+
* message. A throwaway Haiku turn answers it, so the session's transcript,
|
|
3338
|
+
* context, and permission bridge stay out of it. */
|
|
3339
|
+
async *#titleStream(options) {
|
|
3340
|
+
const title = await this.#summarizeTitle({
|
|
3341
|
+
...options.system === void 0 ? {} : { system: options.system },
|
|
3342
|
+
input: auxiliaryText(options.messages),
|
|
3343
|
+
...options.signal === void 0 ? {} : { signal: options.signal }
|
|
3344
|
+
});
|
|
3345
|
+
yield {
|
|
3346
|
+
type: "block-start",
|
|
3347
|
+
index: 0,
|
|
3348
|
+
blockType: "text"
|
|
3349
|
+
};
|
|
3350
|
+
yield {
|
|
3351
|
+
type: "text-delta",
|
|
3352
|
+
index: 0,
|
|
3353
|
+
text: title
|
|
3354
|
+
};
|
|
3355
|
+
yield {
|
|
3356
|
+
type: "block-end",
|
|
3357
|
+
index: 0,
|
|
3358
|
+
block: {
|
|
3359
|
+
type: "text",
|
|
3360
|
+
text: title
|
|
3361
|
+
}
|
|
3362
|
+
};
|
|
3363
|
+
yield {
|
|
3364
|
+
type: "finish",
|
|
3365
|
+
reason: { kind: "stop" }
|
|
3366
|
+
};
|
|
3367
|
+
}
|
|
2891
3368
|
async *stream(options) {
|
|
3369
|
+
if (options.purpose === "session-title") {
|
|
3370
|
+
yield* this.#titleStream(options);
|
|
3371
|
+
return;
|
|
3372
|
+
}
|
|
2892
3373
|
if (options.purpose !== void 0) throw new Error(`dsh-claude: auxiliary ${options.purpose} calls are not routed into the Claude session`);
|
|
2893
3374
|
const agent = resolveAgent(this.#agents, options);
|
|
2894
3375
|
if (this.#presetIdFor(agent) !== "claude") throw new Error(`dsh-claude: provider ${CLAUDE_CODE_PROVIDER} is available only to the ${CLAUDE_CODE_PRESET_ID} preset`);
|
|
@@ -2910,14 +3391,16 @@ var ClaudeCodeAdapter = class extends LlmAdapter {
|
|
|
2910
3391
|
};
|
|
2911
3392
|
return;
|
|
2912
3393
|
}
|
|
3394
|
+
const renderMode = await this.#renderMode();
|
|
3395
|
+
const native = renderMode === "native";
|
|
2913
3396
|
const events = await this.#supervisor.runTurn({
|
|
2914
3397
|
agent,
|
|
2915
3398
|
prompt: injectReviewComments(prompt, this.#drainReviewComments(agent.id)),
|
|
2916
3399
|
model: options.model,
|
|
3400
|
+
renderMode,
|
|
2917
3401
|
...thinkingMode === void 0 ? {} : { thinkingMode },
|
|
2918
3402
|
...options.signal === void 0 ? {} : { signal: options.signal }
|
|
2919
3403
|
});
|
|
2920
|
-
const native = this.#renderMode() === "native";
|
|
2921
3404
|
let pendingUsage;
|
|
2922
3405
|
let completed = false;
|
|
2923
3406
|
let blockIndex = 0;
|
|
@@ -3016,8 +3499,8 @@ var ClaudeCodeAdapter = class extends LlmAdapter {
|
|
|
3016
3499
|
if (!completed) throw new Error("dsh-claude: Claude turn stream ended without a result");
|
|
3017
3500
|
}
|
|
3018
3501
|
};
|
|
3019
|
-
function createClaudeCodeAdapter(supervisor, agents, attachments, presetIdFor, drainReviewComments = () => [], renderMode = () => DEFAULT_CLAUDE_RENDER_MODE) {
|
|
3020
|
-
return new ClaudeCodeAdapter(supervisor, agents, attachments, presetIdFor, drainReviewComments, renderMode);
|
|
3502
|
+
function createClaudeCodeAdapter(supervisor, agents, attachments, presetIdFor, drainReviewComments = () => [], renderMode = async () => DEFAULT_CLAUDE_RENDER_MODE, summarizeTitle = (request) => summarizeSessionTitle("", request)) {
|
|
3503
|
+
return new ClaudeCodeAdapter(supervisor, agents, attachments, presetIdFor, drainReviewComments, renderMode, summarizeTitle);
|
|
3021
3504
|
}
|
|
3022
3505
|
//#endregion
|
|
3023
3506
|
//#region src/plugin-budget.ts
|
|
@@ -3326,7 +3809,7 @@ function registerClaudeDoctorRoutes(ctx, runtime, supervisor, config, resolution
|
|
|
3326
3809
|
}
|
|
3327
3810
|
//#endregion
|
|
3328
3811
|
//#region src/projection-routes.ts
|
|
3329
|
-
const MAX_SESSION_ID_CHARS$
|
|
3812
|
+
const MAX_SESSION_ID_CHARS$7 = 1024;
|
|
3330
3813
|
/** Slow-moving metadata refresh and stream heartbeat cadence; deliberately off
|
|
3331
3814
|
* the transcript hot path so git/gh latency never delays visible text. */
|
|
3332
3815
|
const META_REFRESH_MS = 5e3;
|
|
@@ -3334,7 +3817,7 @@ const META_REFRESH_MS = 5e3;
|
|
|
3334
3817
|
* so this cannot collide with one. */
|
|
3335
3818
|
const MULTI_SEGMENT = "multi";
|
|
3336
3819
|
function validSessionId(value) {
|
|
3337
|
-
return value.length > 0 && value.length <= MAX_SESSION_ID_CHARS$
|
|
3820
|
+
return value.length > 0 && value.length <= MAX_SESSION_ID_CHARS$7;
|
|
3338
3821
|
}
|
|
3339
3822
|
function targetFromUrl(url) {
|
|
3340
3823
|
const prefix = `${CLAUDE_PROJECTION_PATH}/`;
|
|
@@ -3676,14 +4159,14 @@ function parseGitHubRemote(value) {
|
|
|
3676
4159
|
if (match?.[1] === void 0 || match[2] === void 0) return void 0;
|
|
3677
4160
|
return `${match[1]}/${match[2]}`;
|
|
3678
4161
|
}
|
|
3679
|
-
function record$
|
|
4162
|
+
function record$11(value) {
|
|
3680
4163
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
3681
4164
|
}
|
|
3682
4165
|
function aggregateChecks(value) {
|
|
3683
4166
|
if (!Array.isArray(value) || value.length === 0) return "none";
|
|
3684
4167
|
let pending = false;
|
|
3685
4168
|
for (const item of value) {
|
|
3686
|
-
const check = record$
|
|
4169
|
+
const check = record$11(item);
|
|
3687
4170
|
if (check === void 0) continue;
|
|
3688
4171
|
const conclusion = typeof check.conclusion === "string" ? check.conclusion.toUpperCase() : void 0;
|
|
3689
4172
|
const status = typeof check.status === "string" ? check.status.toUpperCase() : void 0;
|
|
@@ -3707,7 +4190,7 @@ function reviewState(value) {
|
|
|
3707
4190
|
return "none";
|
|
3708
4191
|
}
|
|
3709
4192
|
function parsePullRequest(value) {
|
|
3710
|
-
const input = record$
|
|
4193
|
+
const input = record$11(value);
|
|
3711
4194
|
if (input === void 0 || !Number.isSafeInteger(input.number) || Number(input.number) <= 0 || typeof input.title !== "string" || typeof input.url !== "string") return void 0;
|
|
3712
4195
|
let url;
|
|
3713
4196
|
try {
|
|
@@ -3728,7 +4211,7 @@ function parsePullRequest(value) {
|
|
|
3728
4211
|
review: reviewState(input.reviewDecision),
|
|
3729
4212
|
checks: aggregateChecks(input.statusCheckRollup),
|
|
3730
4213
|
...typeof input.mergeStateStatus === "string" ? { mergeState: bounded(input.mergeStateStatus) } : {},
|
|
3731
|
-
...typeof record$
|
|
4214
|
+
...typeof record$11(input.author)?.login === "string" ? { author: bounded(String(record$11(input.author)?.login)) } : {},
|
|
3732
4215
|
...typeof input.createdAt === "string" && Number.isFinite(Date.parse(input.createdAt)) ? { createdAt: new Date(input.createdAt).toISOString() } : {},
|
|
3733
4216
|
...typeof input.mergedAt === "string" && Number.isFinite(Date.parse(input.mergedAt)) ? { mergedAt: new Date(input.mergedAt).toISOString() } : {},
|
|
3734
4217
|
...typeof input.baseRefName === "string" && bounded(input.baseRefName).length > 0 ? { baseBranch: bounded(input.baseRefName) } : {}
|
|
@@ -5135,19 +5618,19 @@ var RepositoryActionService = class {
|
|
|
5135
5618
|
};
|
|
5136
5619
|
//#endregion
|
|
5137
5620
|
//#region src/repository-setup-routes.ts
|
|
5138
|
-
const MAX_BODY_BYTES$
|
|
5139
|
-
function record$
|
|
5621
|
+
const MAX_BODY_BYTES$7 = 16384;
|
|
5622
|
+
function record$10(value) {
|
|
5140
5623
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
5141
5624
|
}
|
|
5142
|
-
async function readJson$
|
|
5625
|
+
async function readJson$6(io) {
|
|
5143
5626
|
let parsed;
|
|
5144
5627
|
try {
|
|
5145
|
-
parsed = await io.body(MAX_BODY_BYTES$
|
|
5628
|
+
parsed = await io.body(MAX_BODY_BYTES$7);
|
|
5146
5629
|
} catch (error) {
|
|
5147
5630
|
if (error instanceof SyntaxError) throw error;
|
|
5148
5631
|
throw new RepositorySetupError("body-too-large", "The request body is too large.");
|
|
5149
5632
|
}
|
|
5150
|
-
const value = record$
|
|
5633
|
+
const value = record$10(parsed);
|
|
5151
5634
|
if (value === void 0) throw new RepositorySetupError("invalid-request", "The request body is invalid.");
|
|
5152
5635
|
return value;
|
|
5153
5636
|
}
|
|
@@ -5216,7 +5699,7 @@ function registerRepositorySetupRoute(ctx, service, sweep) {
|
|
|
5216
5699
|
try {
|
|
5217
5700
|
if (pathname === `/plugins/dsh-claude/repository/setup/branches/refresh`) {
|
|
5218
5701
|
if (io.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
5219
|
-
const input = await readJson$
|
|
5702
|
+
const input = await readJson$6(io);
|
|
5220
5703
|
return json(res, 200, await service.refreshBranches(string$2(input, "cwd")));
|
|
5221
5704
|
}
|
|
5222
5705
|
if (pathname === `/plugins/dsh-claude/repository/setup/branches`) {
|
|
@@ -5227,14 +5710,14 @@ function registerRepositorySetupRoute(ctx, service, sweep) {
|
|
|
5227
5710
|
}
|
|
5228
5711
|
if (pathname === "/plugins/dsh-claude/repository/setup") {
|
|
5229
5712
|
if (io.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
5230
|
-
const input = await readJson$
|
|
5713
|
+
const input = await readJson$6(io);
|
|
5231
5714
|
if (typeof input.worktree !== "boolean") throw new RepositorySetupError("invalid-request", "The worktree field is required.");
|
|
5232
5715
|
await streamSetup(res, service, input);
|
|
5233
5716
|
return;
|
|
5234
5717
|
}
|
|
5235
5718
|
if (pathname === `/plugins/dsh-claude/repository/setup/cleanup`) {
|
|
5236
5719
|
if (io.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
5237
|
-
const input = await readJson$
|
|
5720
|
+
const input = await readJson$6(io);
|
|
5238
5721
|
return json(res, 200, await service.cleanupMerged(string$2(input, "path"), string$2(input, "baseBranch")));
|
|
5239
5722
|
}
|
|
5240
5723
|
if (pathname === `/plugins/dsh-claude/repository/setup/sweep`) {
|
|
@@ -5244,7 +5727,7 @@ function registerRepositorySetupRoute(ctx, service, sweep) {
|
|
|
5244
5727
|
}
|
|
5245
5728
|
if (pathname === `/plugins/dsh-claude/repository/setup/bind`) {
|
|
5246
5729
|
if (io.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
5247
|
-
const input = await readJson$
|
|
5730
|
+
const input = await readJson$6(io);
|
|
5248
5731
|
await service.bindLease(string$2(input, "leaseId"), string$2(input, "sessionId"));
|
|
5249
5732
|
return json(res, 200, { ok: true });
|
|
5250
5733
|
}
|
|
@@ -5262,8 +5745,8 @@ function registerRepositorySetupRoute(ctx, service, sweep) {
|
|
|
5262
5745
|
}
|
|
5263
5746
|
//#endregion
|
|
5264
5747
|
//#region src/repository-action-routes.ts
|
|
5265
|
-
const MAX_BODY_BYTES$
|
|
5266
|
-
const MAX_SESSION_ID_CHARS$
|
|
5748
|
+
const MAX_BODY_BYTES$6 = 16384;
|
|
5749
|
+
const MAX_SESSION_ID_CHARS$6 = 1024;
|
|
5267
5750
|
const ACTIONS = /* @__PURE__ */ new Set([
|
|
5268
5751
|
"commit",
|
|
5269
5752
|
"commit-push",
|
|
@@ -5272,24 +5755,24 @@ const ACTIONS = /* @__PURE__ */ new Set([
|
|
|
5272
5755
|
"merge-pr",
|
|
5273
5756
|
"update-branch"
|
|
5274
5757
|
]);
|
|
5275
|
-
function record$
|
|
5758
|
+
function record$9(value) {
|
|
5276
5759
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
5277
5760
|
}
|
|
5278
|
-
async function readJson$
|
|
5761
|
+
async function readJson$5(io) {
|
|
5279
5762
|
let parsed;
|
|
5280
5763
|
try {
|
|
5281
|
-
parsed = await io.body(MAX_BODY_BYTES$
|
|
5764
|
+
parsed = await io.body(MAX_BODY_BYTES$6);
|
|
5282
5765
|
} catch (error) {
|
|
5283
5766
|
if (error instanceof SyntaxError) throw error;
|
|
5284
5767
|
throw new RepositoryActionError("body-too-large", "The request body is too large.");
|
|
5285
5768
|
}
|
|
5286
|
-
const value = record$
|
|
5769
|
+
const value = record$9(parsed);
|
|
5287
5770
|
if (value === void 0) throw new RepositoryActionError("invalid-request", "The request body is invalid.");
|
|
5288
5771
|
return value;
|
|
5289
5772
|
}
|
|
5290
5773
|
function sessionId$1(url) {
|
|
5291
5774
|
const value = url.searchParams.get("sessionId");
|
|
5292
|
-
if (value === null || value.length === 0 || value.length > MAX_SESSION_ID_CHARS$
|
|
5775
|
+
if (value === null || value.length === 0 || value.length > MAX_SESSION_ID_CHARS$6) throw new RepositoryActionError("invalid-session", "The session is invalid.");
|
|
5293
5776
|
return value;
|
|
5294
5777
|
}
|
|
5295
5778
|
function string$1(input, key) {
|
|
@@ -5353,7 +5836,7 @@ function registerRepositoryActionRoute(ctx, service, cwdForSession) {
|
|
|
5353
5836
|
status: 405,
|
|
5354
5837
|
value: { error: "method not allowed" }
|
|
5355
5838
|
};
|
|
5356
|
-
const input = await readJson$
|
|
5839
|
+
const input = await readJson$5(io);
|
|
5357
5840
|
return {
|
|
5358
5841
|
status: 200,
|
|
5359
5842
|
value: { message: await service.generateMessage(cwd, string$1(input, "fingerprint")) }
|
|
@@ -5366,7 +5849,7 @@ function registerRepositoryActionRoute(ctx, service, cwdForSession) {
|
|
|
5366
5849
|
};
|
|
5367
5850
|
return {
|
|
5368
5851
|
status: 200,
|
|
5369
|
-
value: await service.execute(cwd, actionRequest(await readJson$
|
|
5852
|
+
value: await service.execute(cwd, actionRequest(await readJson$5(io)))
|
|
5370
5853
|
};
|
|
5371
5854
|
}
|
|
5372
5855
|
return {
|
|
@@ -5517,7 +6000,7 @@ var EditorOpenService = class {
|
|
|
5517
6000
|
};
|
|
5518
6001
|
//#endregion
|
|
5519
6002
|
//#region src/editor-open-routes.ts
|
|
5520
|
-
const MAX_SESSION_ID_CHARS$
|
|
6003
|
+
const MAX_SESSION_ID_CHARS$5 = 1024;
|
|
5521
6004
|
/** Open the session's working directory in a desktop editor. Query-only: the
|
|
5522
6005
|
* request carries two enum-ish values, so there is no body to parse. */
|
|
5523
6006
|
function registerEditorOpenRoute(ctx, service, cwdForSession) {
|
|
@@ -5531,7 +6014,7 @@ function registerEditorOpenRoute(ctx, service, cwdForSession) {
|
|
|
5531
6014
|
const params = io.url.searchParams;
|
|
5532
6015
|
const id = params.get("sessionId");
|
|
5533
6016
|
const editor = params.get("editor");
|
|
5534
|
-
if (id === null || id.length === 0 || id.length > MAX_SESSION_ID_CHARS$
|
|
6017
|
+
if (id === null || id.length === 0 || id.length > MAX_SESSION_ID_CHARS$5) return {
|
|
5535
6018
|
status: 400,
|
|
5536
6019
|
value: {
|
|
5537
6020
|
error: "invalid-session",
|
|
@@ -5646,7 +6129,7 @@ async function collect(handle) {
|
|
|
5646
6129
|
lossy: stdout?.lossy === true
|
|
5647
6130
|
};
|
|
5648
6131
|
}
|
|
5649
|
-
function record$
|
|
6132
|
+
function record$8(value) {
|
|
5650
6133
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
5651
6134
|
}
|
|
5652
6135
|
/** Read `repository.pullRequest.reviewThreads.nodes` out of a GraphQL response.
|
|
@@ -5654,12 +6137,12 @@ function record$7(value) {
|
|
|
5654
6137
|
* throwing: the caller distinguishes "no threads" from "call failed" by the
|
|
5655
6138
|
* process exit code. */
|
|
5656
6139
|
function parseReviewThreads(value) {
|
|
5657
|
-
const nodes = record$
|
|
6140
|
+
const nodes = record$8(record$8(record$8(record$8(record$8(value)?.data)?.repository)?.pullRequest)?.reviewThreads)?.nodes;
|
|
5658
6141
|
if (!Array.isArray(nodes)) return [];
|
|
5659
6142
|
const threads = [];
|
|
5660
6143
|
let total = 0;
|
|
5661
6144
|
for (const item of nodes) {
|
|
5662
|
-
const input = record$
|
|
6145
|
+
const input = record$8(item);
|
|
5663
6146
|
if (input === void 0 || typeof input.id !== "string" || input.id.length === 0) continue;
|
|
5664
6147
|
const path = typeof input.path === "string" ? input.path : "";
|
|
5665
6148
|
if (path.length === 0) continue;
|
|
@@ -5671,14 +6154,14 @@ function parseReviewThreads(value) {
|
|
|
5671
6154
|
side
|
|
5672
6155
|
};
|
|
5673
6156
|
const comments = [];
|
|
5674
|
-
const commentNodes = record$
|
|
6157
|
+
const commentNodes = record$8(input.comments)?.nodes;
|
|
5675
6158
|
for (const node of Array.isArray(commentNodes) ? commentNodes : []) {
|
|
5676
6159
|
if (total >= MAX_COMMENTS) break;
|
|
5677
|
-
const comment = record$
|
|
6160
|
+
const comment = record$8(node);
|
|
5678
6161
|
if (comment === void 0 || !Number.isSafeInteger(comment.databaseId)) continue;
|
|
5679
6162
|
const body = typeof comment.body === "string" ? comment.body.trim() : "";
|
|
5680
6163
|
if (body.length === 0) continue;
|
|
5681
|
-
const author = record$
|
|
6164
|
+
const author = record$8(comment.author);
|
|
5682
6165
|
const avatarUrl = githubAvatarUrl(author?.avatarUrl);
|
|
5683
6166
|
const login = typeof author?.login === "string" ? author.login : "unknown";
|
|
5684
6167
|
comments.push({
|
|
@@ -5707,11 +6190,11 @@ function parseReviewThreads(value) {
|
|
|
5707
6190
|
}
|
|
5708
6191
|
/** One posted reply, shaped like the thread comments it joins. */
|
|
5709
6192
|
function parseReplyComment(value, anchor) {
|
|
5710
|
-
const input = record$
|
|
6193
|
+
const input = record$8(value);
|
|
5711
6194
|
if (input === void 0 || !Number.isSafeInteger(input.id)) return void 0;
|
|
5712
6195
|
const body = typeof input.body === "string" ? input.body.trim() : "";
|
|
5713
6196
|
if (body.length === 0) return void 0;
|
|
5714
|
-
const user = record$
|
|
6197
|
+
const user = record$8(input.user);
|
|
5715
6198
|
const avatarUrl = githubAvatarUrl(user?.avatar_url);
|
|
5716
6199
|
const login = typeof user?.login === "string" ? user.login : "unknown";
|
|
5717
6200
|
return {
|
|
@@ -5726,11 +6209,11 @@ function parseReplyComment(value, anchor) {
|
|
|
5726
6209
|
};
|
|
5727
6210
|
}
|
|
5728
6211
|
function parseMentionableUsers(value) {
|
|
5729
|
-
const nodes = record$
|
|
6212
|
+
const nodes = record$8(record$8(record$8(record$8(value)?.data)?.repository)?.mentionableUsers)?.nodes;
|
|
5730
6213
|
if (!Array.isArray(nodes)) return [];
|
|
5731
6214
|
const users = [];
|
|
5732
6215
|
for (const item of nodes) {
|
|
5733
|
-
const input = record$
|
|
6216
|
+
const input = record$8(item);
|
|
5734
6217
|
if (input === void 0 || typeof input.login !== "string" || input.login.length === 0) continue;
|
|
5735
6218
|
const avatarUrl = githubAvatarUrl(input.avatarUrl);
|
|
5736
6219
|
users.push({
|
|
@@ -5750,7 +6233,7 @@ function parseFailingChecks(value) {
|
|
|
5750
6233
|
if (!Array.isArray(value)) return [];
|
|
5751
6234
|
const failing = [];
|
|
5752
6235
|
for (const item of value) {
|
|
5753
|
-
const input = record$
|
|
6236
|
+
const input = record$8(item);
|
|
5754
6237
|
if (input === void 0 || input.bucket !== "fail" || typeof input.name !== "string") continue;
|
|
5755
6238
|
failing.push({
|
|
5756
6239
|
name: input.name,
|
|
@@ -5813,12 +6296,12 @@ var PullRequestFeedbackService = class {
|
|
|
5813
6296
|
let posted;
|
|
5814
6297
|
try {
|
|
5815
6298
|
const parsed = JSON.parse(result.stdout);
|
|
5816
|
-
const path = typeof record$
|
|
5817
|
-
const line = Number.isSafeInteger(record$
|
|
6299
|
+
const path = typeof record$8(parsed)?.path === "string" ? String(record$8(parsed)?.path) : "";
|
|
6300
|
+
const line = Number.isSafeInteger(record$8(parsed)?.line) ? Number(record$8(parsed)?.line) : void 0;
|
|
5818
6301
|
posted = parseReplyComment(parsed, {
|
|
5819
6302
|
path,
|
|
5820
6303
|
...line === void 0 ? {} : { line },
|
|
5821
|
-
side: record$
|
|
6304
|
+
side: record$8(parsed)?.side === "LEFT" ? "old" : "new"
|
|
5822
6305
|
});
|
|
5823
6306
|
} catch {
|
|
5824
6307
|
posted = void 0;
|
|
@@ -5839,7 +6322,7 @@ var PullRequestFeedbackService = class {
|
|
|
5839
6322
|
], cwd, GH_TIMEOUT_MS);
|
|
5840
6323
|
if (result.exitCode !== 0) throw new PullRequestFeedbackError("resolve-failed", "The thread could not be updated.");
|
|
5841
6324
|
try {
|
|
5842
|
-
const thread = record$
|
|
6325
|
+
const thread = record$8(record$8(record$8(record$8(JSON.parse(result.stdout))?.data)?.[resolved ? "resolveReviewThread" : "unresolveReviewThread"])?.thread);
|
|
5843
6326
|
if (typeof thread?.isResolved !== "boolean") throw new Error("missing state");
|
|
5844
6327
|
return thread.isResolved;
|
|
5845
6328
|
} catch {
|
|
@@ -5962,23 +6445,23 @@ var PullRequestFeedbackService = class {
|
|
|
5962
6445
|
};
|
|
5963
6446
|
//#endregion
|
|
5964
6447
|
//#region src/pr-feedback-routes.ts
|
|
5965
|
-
const MAX_SESSION_ID_CHARS$
|
|
5966
|
-
const MAX_BODY_BYTES$
|
|
6448
|
+
const MAX_SESSION_ID_CHARS$4 = 1024;
|
|
6449
|
+
const MAX_BODY_BYTES$5 = 16384;
|
|
5967
6450
|
const MAX_REPLY_CHARS = 2e3;
|
|
5968
6451
|
const MAX_THREAD_ID_CHARS = 512;
|
|
5969
6452
|
const MAX_MENTION_QUERY_CHARS = 64;
|
|
5970
|
-
function record$
|
|
6453
|
+
function record$7(value) {
|
|
5971
6454
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
5972
6455
|
}
|
|
5973
|
-
async function readJson$
|
|
6456
|
+
async function readJson$4(io) {
|
|
5974
6457
|
let parsed;
|
|
5975
6458
|
try {
|
|
5976
|
-
parsed = await io.body(MAX_BODY_BYTES$
|
|
6459
|
+
parsed = await io.body(MAX_BODY_BYTES$5);
|
|
5977
6460
|
} catch (error) {
|
|
5978
6461
|
if (error instanceof SyntaxError) throw error;
|
|
5979
6462
|
throw new PullRequestFeedbackError("body-too-large", "The request body is too large.");
|
|
5980
6463
|
}
|
|
5981
|
-
const value = record$
|
|
6464
|
+
const value = record$7(parsed);
|
|
5982
6465
|
if (value === void 0) throw new PullRequestFeedbackError("invalid-request", "The request body is invalid.");
|
|
5983
6466
|
return value;
|
|
5984
6467
|
}
|
|
@@ -5999,7 +6482,7 @@ function threadId(input) {
|
|
|
5999
6482
|
}
|
|
6000
6483
|
function sessionId(url) {
|
|
6001
6484
|
const value = url.searchParams.get("sessionId");
|
|
6002
|
-
if (value === null || value.length === 0 || value.length > MAX_SESSION_ID_CHARS$
|
|
6485
|
+
if (value === null || value.length === 0 || value.length > MAX_SESSION_ID_CHARS$4) throw new PullRequestFeedbackError("invalid-session", "The session is invalid.");
|
|
6003
6486
|
return value;
|
|
6004
6487
|
}
|
|
6005
6488
|
function pullNumber(url) {
|
|
@@ -6058,7 +6541,7 @@ function registerPullRequestFeedbackRoute(ctx, service, cwdForSession) {
|
|
|
6058
6541
|
status: 405,
|
|
6059
6542
|
value: { error: "method not allowed" }
|
|
6060
6543
|
};
|
|
6061
|
-
const input = await readJson$
|
|
6544
|
+
const input = await readJson$4(io);
|
|
6062
6545
|
return {
|
|
6063
6546
|
status: 200,
|
|
6064
6547
|
value: { comment: await service.reply(cwd, pullNumber(url), commentId(input), replyBody(input)) }
|
|
@@ -6069,7 +6552,7 @@ function registerPullRequestFeedbackRoute(ctx, service, cwdForSession) {
|
|
|
6069
6552
|
status: 405,
|
|
6070
6553
|
value: { error: "method not allowed" }
|
|
6071
6554
|
};
|
|
6072
|
-
const input = await readJson$
|
|
6555
|
+
const input = await readJson$4(io);
|
|
6073
6556
|
if (typeof input.resolved !== "boolean") throw new PullRequestFeedbackError("invalid-request", "The resolved field is required.");
|
|
6074
6557
|
return {
|
|
6075
6558
|
status: 200,
|
|
@@ -6215,7 +6698,7 @@ var JiraError = class extends Error {
|
|
|
6215
6698
|
this.code = code;
|
|
6216
6699
|
}
|
|
6217
6700
|
};
|
|
6218
|
-
function record$
|
|
6701
|
+
function record$6(value) {
|
|
6219
6702
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
6220
6703
|
}
|
|
6221
6704
|
/** Jira Cloud sites are origins; Data Center may carry a context path. */
|
|
@@ -6239,14 +6722,14 @@ function ticketKeyOf(query) {
|
|
|
6239
6722
|
* search and does match keys; its hits become the key filter the normal
|
|
6240
6723
|
* search then reads the display fields from. */
|
|
6241
6724
|
function pickerKeys(value, number) {
|
|
6242
|
-
const sections = record$
|
|
6725
|
+
const sections = record$6(value)?.sections;
|
|
6243
6726
|
if (!Array.isArray(sections)) return [];
|
|
6244
6727
|
const keys = [];
|
|
6245
6728
|
for (const section of sections) {
|
|
6246
|
-
const issues = record$
|
|
6729
|
+
const issues = record$6(section)?.issues;
|
|
6247
6730
|
if (!Array.isArray(issues)) continue;
|
|
6248
6731
|
for (const issue of issues) {
|
|
6249
|
-
const raw = record$
|
|
6732
|
+
const raw = record$6(issue)?.key;
|
|
6250
6733
|
const key = ticketKeyOf(typeof raw === "string" ? raw : "");
|
|
6251
6734
|
if (key !== void 0 && key.endsWith(`-${number}`) && !keys.includes(key)) keys.push(key);
|
|
6252
6735
|
}
|
|
@@ -6265,15 +6748,15 @@ function buildJql(query) {
|
|
|
6265
6748
|
return `text ~ "${trimmed.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"")}*" ORDER BY updated DESC`;
|
|
6266
6749
|
}
|
|
6267
6750
|
function parseTickets(value, siteUrl) {
|
|
6268
|
-
const issues = record$
|
|
6751
|
+
const issues = record$6(value)?.issues;
|
|
6269
6752
|
if (!Array.isArray(issues)) return [];
|
|
6270
6753
|
const tickets = [];
|
|
6271
6754
|
for (const item of issues) {
|
|
6272
|
-
const issue = record$
|
|
6273
|
-
const fields = record$
|
|
6755
|
+
const issue = record$6(item);
|
|
6756
|
+
const fields = record$6(issue?.fields);
|
|
6274
6757
|
if (issue === void 0 || typeof issue.key !== "string" || fields === void 0) continue;
|
|
6275
|
-
const status = record$
|
|
6276
|
-
const type = record$
|
|
6758
|
+
const status = record$6(fields.status)?.name;
|
|
6759
|
+
const type = record$6(fields.issuetype)?.name;
|
|
6277
6760
|
tickets.push({
|
|
6278
6761
|
key: issue.key,
|
|
6279
6762
|
summary: typeof fields.summary === "string" ? fields.summary.slice(0, 256) : "",
|
|
@@ -6315,7 +6798,7 @@ var JiraService = class {
|
|
|
6315
6798
|
email,
|
|
6316
6799
|
apiToken
|
|
6317
6800
|
};
|
|
6318
|
-
const myself = record$
|
|
6801
|
+
const myself = record$6(await this.#json(connection, "/rest/api/3/myself"));
|
|
6319
6802
|
const displayName = typeof myself?.displayName === "string" ? myself.displayName : void 0;
|
|
6320
6803
|
const accountId = typeof myself?.accountId === "string" ? myself.accountId : void 0;
|
|
6321
6804
|
const store = {
|
|
@@ -6334,7 +6817,7 @@ var JiraService = class {
|
|
|
6334
6817
|
if (ticket === void 0) throw new JiraError("invalid-request", "The ticket key is invalid.");
|
|
6335
6818
|
let accountId = store.accountId;
|
|
6336
6819
|
if (accountId === void 0) {
|
|
6337
|
-
const myself = record$
|
|
6820
|
+
const myself = record$6(await this.#json(store, "/rest/api/3/myself"));
|
|
6338
6821
|
if (typeof myself?.accountId !== "string") throw new JiraError("jira-failed", "The Jira account id is unavailable.");
|
|
6339
6822
|
accountId = myself.accountId;
|
|
6340
6823
|
await this.#write({
|
|
@@ -6410,7 +6893,7 @@ var JiraService = class {
|
|
|
6410
6893
|
throw error;
|
|
6411
6894
|
}
|
|
6412
6895
|
if (Buffer.byteLength(text) > MAX_STORE_BYTES) return void 0;
|
|
6413
|
-
const input = record$
|
|
6896
|
+
const input = record$6(JSON.parse(text));
|
|
6414
6897
|
if (input === void 0 || typeof input.siteUrl !== "string" || typeof input.email !== "string" || typeof input.apiToken !== "string") return void 0;
|
|
6415
6898
|
return {
|
|
6416
6899
|
siteUrl: input.siteUrl,
|
|
@@ -6441,21 +6924,21 @@ var JiraService = class {
|
|
|
6441
6924
|
};
|
|
6442
6925
|
//#endregion
|
|
6443
6926
|
//#region src/jira-routes.ts
|
|
6444
|
-
const MAX_BODY_BYTES$
|
|
6445
|
-
function record$
|
|
6927
|
+
const MAX_BODY_BYTES$4 = 8192;
|
|
6928
|
+
function record$5(value) {
|
|
6446
6929
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
6447
6930
|
}
|
|
6448
6931
|
/** The wrapper enforces the byte cap; its plain rejection is translated back
|
|
6449
6932
|
* into the JiraError shape the panel already knows how to render. */
|
|
6450
|
-
async function readJson$
|
|
6933
|
+
async function readJson$3(io) {
|
|
6451
6934
|
let body;
|
|
6452
6935
|
try {
|
|
6453
|
-
body = await io.body(MAX_BODY_BYTES$
|
|
6936
|
+
body = await io.body(MAX_BODY_BYTES$4);
|
|
6454
6937
|
} catch (error) {
|
|
6455
6938
|
if (error instanceof SyntaxError) throw error;
|
|
6456
6939
|
throw new JiraError("body-too-large", "The request body is too large.");
|
|
6457
6940
|
}
|
|
6458
|
-
const value = record$
|
|
6941
|
+
const value = record$5(body);
|
|
6459
6942
|
if (value === void 0) throw new JiraError("invalid-request", "The request body is invalid.");
|
|
6460
6943
|
return value;
|
|
6461
6944
|
}
|
|
@@ -6489,7 +6972,7 @@ function registerJiraRoute(ctx, service) {
|
|
|
6489
6972
|
status: 405,
|
|
6490
6973
|
value: { error: "method not allowed" }
|
|
6491
6974
|
};
|
|
6492
|
-
const input = await readJson$
|
|
6975
|
+
const input = await readJson$3(io);
|
|
6493
6976
|
return {
|
|
6494
6977
|
status: 200,
|
|
6495
6978
|
value: await service.connect({
|
|
@@ -6515,7 +6998,7 @@ function registerJiraRoute(ctx, service) {
|
|
|
6515
6998
|
status: 405,
|
|
6516
6999
|
value: { error: "method not allowed" }
|
|
6517
7000
|
};
|
|
6518
|
-
const input = await readJson$
|
|
7001
|
+
const input = await readJson$3(io);
|
|
6519
7002
|
await service.assignToMe(string(input, "key"));
|
|
6520
7003
|
return {
|
|
6521
7004
|
status: 200,
|
|
@@ -6634,12 +7117,12 @@ function askArguments(preferences) {
|
|
|
6634
7117
|
...READ_ONLY_TOOLS
|
|
6635
7118
|
];
|
|
6636
7119
|
}
|
|
6637
|
-
function record$
|
|
7120
|
+
function record$4(value) {
|
|
6638
7121
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
6639
7122
|
}
|
|
6640
7123
|
/** One-line description of a tool call, mirroring the main window's step titles. */
|
|
6641
7124
|
function toolSummary(input) {
|
|
6642
|
-
const fields = record$
|
|
7125
|
+
const fields = record$4(input);
|
|
6643
7126
|
if (fields === void 0) return void 0;
|
|
6644
7127
|
const candidate = [
|
|
6645
7128
|
fields.command,
|
|
@@ -6653,7 +7136,7 @@ function toolSummary(input) {
|
|
|
6653
7136
|
function eventsOfStreamLine(line) {
|
|
6654
7137
|
let parsed;
|
|
6655
7138
|
try {
|
|
6656
|
-
parsed = record$
|
|
7139
|
+
parsed = record$4(JSON.parse(line));
|
|
6657
7140
|
} catch {
|
|
6658
7141
|
return [];
|
|
6659
7142
|
}
|
|
@@ -6663,9 +7146,9 @@ function eventsOfStreamLine(line) {
|
|
|
6663
7146
|
text: "ready"
|
|
6664
7147
|
}];
|
|
6665
7148
|
if (parsed.type === "stream_event") {
|
|
6666
|
-
const event = record$
|
|
7149
|
+
const event = record$4(parsed.event);
|
|
6667
7150
|
if (event?.type === "content_block_start") {
|
|
6668
|
-
const block = record$
|
|
7151
|
+
const block = record$4(event.content_block);
|
|
6669
7152
|
if (block?.type === "tool_use" && typeof block.id === "string" && typeof block.name === "string") return [{
|
|
6670
7153
|
type: "tool",
|
|
6671
7154
|
id: block.id,
|
|
@@ -6674,7 +7157,7 @@ function eventsOfStreamLine(line) {
|
|
|
6674
7157
|
}];
|
|
6675
7158
|
return [];
|
|
6676
7159
|
}
|
|
6677
|
-
const delta = record$
|
|
7160
|
+
const delta = record$4(event?.delta);
|
|
6678
7161
|
if (event?.type !== "content_block_delta" || delta === void 0) return [];
|
|
6679
7162
|
if (delta.type === "text_delta" && typeof delta.text === "string") return [{
|
|
6680
7163
|
type: "text",
|
|
@@ -6687,11 +7170,11 @@ function eventsOfStreamLine(line) {
|
|
|
6687
7170
|
return [];
|
|
6688
7171
|
}
|
|
6689
7172
|
if (parsed.type === "assistant" || parsed.type === "user") {
|
|
6690
|
-
const content = record$
|
|
7173
|
+
const content = record$4(parsed.message)?.content;
|
|
6691
7174
|
if (!Array.isArray(content)) return [];
|
|
6692
7175
|
const events = [];
|
|
6693
7176
|
for (const item of content) {
|
|
6694
|
-
const block = record$
|
|
7177
|
+
const block = record$4(item);
|
|
6695
7178
|
if (block?.type === "tool_use" && typeof block.id === "string" && typeof block.name === "string") {
|
|
6696
7179
|
const summary = toolSummary(block.input);
|
|
6697
7180
|
events.push({
|
|
@@ -6783,11 +7266,11 @@ var AskService = class {
|
|
|
6783
7266
|
};
|
|
6784
7267
|
//#endregion
|
|
6785
7268
|
//#region src/ask-routes.ts
|
|
6786
|
-
const MAX_BODY_BYTES$
|
|
6787
|
-
const MAX_SESSION_ID_CHARS$
|
|
7269
|
+
const MAX_BODY_BYTES$3 = 131072;
|
|
7270
|
+
const MAX_SESSION_ID_CHARS$3 = 1024;
|
|
6788
7271
|
/** Two sessions may await an answer at once; a third evicts the oldest. */
|
|
6789
7272
|
const MAX_CONCURRENT_ASKS = 2;
|
|
6790
|
-
function record$
|
|
7273
|
+
function record$3(value) {
|
|
6791
7274
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
6792
7275
|
}
|
|
6793
7276
|
function askRequest(input) {
|
|
@@ -6819,12 +7302,12 @@ function registerAskRoute(ctx, service, cwdForSession, preferencesFor) {
|
|
|
6819
7302
|
let sessionId;
|
|
6820
7303
|
try {
|
|
6821
7304
|
const value = io.url.searchParams.get("sessionId");
|
|
6822
|
-
if (value === null || value.length === 0 || value.length > MAX_SESSION_ID_CHARS$
|
|
7305
|
+
if (value === null || value.length === 0 || value.length > MAX_SESSION_ID_CHARS$3) throw new AskError("invalid-session", "The session is invalid.");
|
|
6823
7306
|
sessionId = value;
|
|
6824
7307
|
const resolved = cwdForSession(sessionId);
|
|
6825
7308
|
if (resolved === void 0) throw new AskError("session-unavailable", "The Claude session is unavailable.");
|
|
6826
7309
|
cwd = resolved;
|
|
6827
|
-
const body = record$
|
|
7310
|
+
const body = record$3(await io.body(MAX_BODY_BYTES$3));
|
|
6828
7311
|
if (body === void 0) throw new AskError("invalid-request", "The request body is invalid.");
|
|
6829
7312
|
request = askRequest(body);
|
|
6830
7313
|
} catch (error) {
|
|
@@ -6864,26 +7347,26 @@ function registerAskRoute(ctx, service, cwdForSession, preferencesFor) {
|
|
|
6864
7347
|
}
|
|
6865
7348
|
//#endregion
|
|
6866
7349
|
//#region src/review-comment-routes.ts
|
|
6867
|
-
const MAX_BODY_BYTES$
|
|
6868
|
-
const MAX_SESSION_ID_CHARS$
|
|
6869
|
-
function record$
|
|
7350
|
+
const MAX_BODY_BYTES$2 = 16384;
|
|
7351
|
+
const MAX_SESSION_ID_CHARS$2 = 1024;
|
|
7352
|
+
function record$2(value) {
|
|
6870
7353
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
6871
7354
|
}
|
|
6872
|
-
async function readJson$
|
|
7355
|
+
async function readJson$2(io) {
|
|
6873
7356
|
let parsed;
|
|
6874
7357
|
try {
|
|
6875
|
-
parsed = await io.body(MAX_BODY_BYTES$
|
|
7358
|
+
parsed = await io.body(MAX_BODY_BYTES$2);
|
|
6876
7359
|
} catch (error) {
|
|
6877
7360
|
if (error instanceof SyntaxError) throw error;
|
|
6878
7361
|
throw new ReviewCommentError("body-too-large", "The request body is too large.");
|
|
6879
7362
|
}
|
|
6880
|
-
const value = record$
|
|
7363
|
+
const value = record$2(parsed);
|
|
6881
7364
|
if (value === void 0) throw new ReviewCommentError("invalid-request", "The request body is invalid.");
|
|
6882
7365
|
return value;
|
|
6883
7366
|
}
|
|
6884
7367
|
function sessionIdFromUrl(url) {
|
|
6885
7368
|
const value = url.searchParams.get("sessionId");
|
|
6886
|
-
if (value === null || value.length === 0 || value.length > MAX_SESSION_ID_CHARS$
|
|
7369
|
+
if (value === null || value.length === 0 || value.length > MAX_SESSION_ID_CHARS$2) throw new ReviewCommentError("invalid-session", "The session is invalid.");
|
|
6887
7370
|
return value;
|
|
6888
7371
|
}
|
|
6889
7372
|
function registerReviewCommentRoute(ctx, store, ownsSession) {
|
|
@@ -6899,7 +7382,7 @@ function registerReviewCommentRoute(ctx, store, ownsSession) {
|
|
|
6899
7382
|
if (!ownsSession(sessionId)) throw new ReviewCommentError("session-unavailable", "The Claude session is unavailable.");
|
|
6900
7383
|
const pathname = io.url.pathname;
|
|
6901
7384
|
if (pathname === "/plugins/dsh-claude/review-comments") {
|
|
6902
|
-
const input = await readJson$
|
|
7385
|
+
const input = await readJson$2(io);
|
|
6903
7386
|
return {
|
|
6904
7387
|
status: 200,
|
|
6905
7388
|
value: { comment: store.add(sessionId, {
|
|
@@ -6916,7 +7399,7 @@ function registerReviewCommentRoute(ctx, store, ownsSession) {
|
|
|
6916
7399
|
value: { removed: store.drain(sessionId).length }
|
|
6917
7400
|
};
|
|
6918
7401
|
if (pathname === `/plugins/dsh-claude/review-comments/remove`) {
|
|
6919
|
-
const input = await readJson$
|
|
7402
|
+
const input = await readJson$2(io);
|
|
6920
7403
|
if (typeof input.id !== "string" || input.id.length === 0 || input.id.length > 128) throw new ReviewCommentError("invalid-request", "The comment id is invalid.");
|
|
6921
7404
|
return {
|
|
6922
7405
|
status: 200,
|
|
@@ -6951,6 +7434,85 @@ function registerReviewCommentRoute(ctx, store, ownsSession) {
|
|
|
6951
7434
|
});
|
|
6952
7435
|
}
|
|
6953
7436
|
//#endregion
|
|
7437
|
+
//#region src/plan-feedback-routes.ts
|
|
7438
|
+
const MAX_BODY_BYTES$1 = 65536;
|
|
7439
|
+
const MAX_SESSION_ID_CHARS$1 = 1024;
|
|
7440
|
+
const MAX_TOOL_USE_ID_CHARS = 256;
|
|
7441
|
+
function record$1(value) {
|
|
7442
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
7443
|
+
}
|
|
7444
|
+
async function readJson$1(io) {
|
|
7445
|
+
let parsed;
|
|
7446
|
+
try {
|
|
7447
|
+
parsed = await io.body(MAX_BODY_BYTES$1);
|
|
7448
|
+
} catch (error) {
|
|
7449
|
+
if (error instanceof SyntaxError) throw error;
|
|
7450
|
+
throw new PlanFeedbackError("body-too-large", "The request body is too large.");
|
|
7451
|
+
}
|
|
7452
|
+
const value = record$1(parsed);
|
|
7453
|
+
if (value === void 0) throw new PlanFeedbackError("invalid-request", "The request body is invalid.");
|
|
7454
|
+
return value;
|
|
7455
|
+
}
|
|
7456
|
+
/** Send one plan back for changes.
|
|
7457
|
+
*
|
|
7458
|
+
* Unary rather than a stream: the panel hands over what the reviewer wrote
|
|
7459
|
+
* and the turn carries on in the transcript it was already watching. Answers
|
|
7460
|
+
* 409 when nothing is waiting, which is what the panel shows if the approval
|
|
7461
|
+
* dialog was answered while the reviewer was still typing. */
|
|
7462
|
+
function registerPlanFeedbackRoute(ctx, gate, ownsSession) {
|
|
7463
|
+
registerPluginRoute(ctx, {
|
|
7464
|
+
mode: "unary",
|
|
7465
|
+
budget: "fast",
|
|
7466
|
+
kind: "exact",
|
|
7467
|
+
path: CLAUDE_PLAN_FEEDBACK_PATH,
|
|
7468
|
+
methods: ["POST"],
|
|
7469
|
+
handler: async (io) => {
|
|
7470
|
+
try {
|
|
7471
|
+
const sessionId = io.url.searchParams.get("sessionId");
|
|
7472
|
+
if (sessionId === null || sessionId.length === 0 || sessionId.length > MAX_SESSION_ID_CHARS$1) return {
|
|
7473
|
+
status: 400,
|
|
7474
|
+
value: { error: "invalid-session" }
|
|
7475
|
+
};
|
|
7476
|
+
if (!ownsSession(sessionId)) return {
|
|
7477
|
+
status: 409,
|
|
7478
|
+
value: { error: "session-unavailable" }
|
|
7479
|
+
};
|
|
7480
|
+
const body = await readJson$1(io);
|
|
7481
|
+
const toolUseId = body.toolUseId;
|
|
7482
|
+
if (typeof toolUseId !== "string" || toolUseId.length === 0 || toolUseId.length > MAX_TOOL_USE_ID_CHARS) return {
|
|
7483
|
+
status: 400,
|
|
7484
|
+
value: { error: "invalid-request" }
|
|
7485
|
+
};
|
|
7486
|
+
const notes = planNotesOf(body.notes);
|
|
7487
|
+
if (!gate.submit(toolUseId, notes)) return {
|
|
7488
|
+
status: 409,
|
|
7489
|
+
value: { error: "plan-settled" }
|
|
7490
|
+
};
|
|
7491
|
+
return {
|
|
7492
|
+
status: 200,
|
|
7493
|
+
value: { ok: true }
|
|
7494
|
+
};
|
|
7495
|
+
} catch (error) {
|
|
7496
|
+
if (error instanceof PlanFeedbackError) return {
|
|
7497
|
+
status: 400,
|
|
7498
|
+
value: {
|
|
7499
|
+
error: error.code,
|
|
7500
|
+
message: error.message
|
|
7501
|
+
}
|
|
7502
|
+
};
|
|
7503
|
+
if (error instanceof SyntaxError) return {
|
|
7504
|
+
status: 400,
|
|
7505
|
+
value: { error: "invalid-json" }
|
|
7506
|
+
};
|
|
7507
|
+
return {
|
|
7508
|
+
status: 500,
|
|
7509
|
+
value: { error: "plan-feedback-unavailable" }
|
|
7510
|
+
};
|
|
7511
|
+
}
|
|
7512
|
+
}
|
|
7513
|
+
});
|
|
7514
|
+
}
|
|
7515
|
+
//#endregion
|
|
6954
7516
|
//#region src/client-diagnostics-routes.ts
|
|
6955
7517
|
/** Enough for a message plus a trimmed stack; the client caps its own volume. */
|
|
6956
7518
|
const MAX_DIAGNOSTIC_BYTES = 8192;
|
|
@@ -7015,8 +7577,15 @@ async function readJson(io) {
|
|
|
7015
7577
|
return;
|
|
7016
7578
|
}
|
|
7017
7579
|
}
|
|
7018
|
-
/** `POST <path>` with `{ sessionId, seq }`: hide that surface
|
|
7019
|
-
* later one, and arm Claude to resume before the turn it
|
|
7580
|
+
/** `POST <path>` with `{ sessionId, seq, restoreFiles? }`: hide that surface
|
|
7581
|
+
* event and every later one, and arm Claude to resume before the turn it
|
|
7582
|
+
* opened. With `restoreFiles`, the checkout is also put back to the tree that
|
|
7583
|
+
* turn was admitted against.
|
|
7584
|
+
*
|
|
7585
|
+
* The conversation rewind is what the user confirmed, so it lands first and
|
|
7586
|
+
* stands on its own; a checkout that cannot be restored — no snapshot, a
|
|
7587
|
+
* collected tree, no git — reports `filesRestored: false` rather than
|
|
7588
|
+
* failing the rewind. */
|
|
7020
7589
|
function registerClaudeRewindRoute(ctx, sidecar, access) {
|
|
7021
7590
|
registerPluginRoute(ctx, {
|
|
7022
7591
|
mode: "unary",
|
|
@@ -7042,16 +7611,22 @@ function registerClaudeRewindRoute(ctx, sidecar, access) {
|
|
|
7042
7611
|
status: 409,
|
|
7043
7612
|
value: { error: "session-busy" }
|
|
7044
7613
|
};
|
|
7045
|
-
const
|
|
7614
|
+
const current = (await sidecar.read(sessionId)).rewind ?? EMPTY_REWIND_STATE;
|
|
7615
|
+
const planned = planRewind(current, events, seq);
|
|
7046
7616
|
if (planned === void 0) return {
|
|
7047
7617
|
status: 409,
|
|
7048
7618
|
value: { error: "seq-unavailable" }
|
|
7049
7619
|
};
|
|
7050
|
-
|
|
7620
|
+
const tree = input?.restoreFiles === true ? rewindRestoreTree(current, events, seq) : void 0;
|
|
7621
|
+
await sidecar.writeRewind(sessionId, planned, turnAtOrAfter(events, seq));
|
|
7051
7622
|
await access.reset(sessionId);
|
|
7623
|
+
const filesRestored = tree === void 0 || access.restoreFiles === void 0 ? false : await access.restoreFiles(sessionId, tree).catch(() => false);
|
|
7052
7624
|
return {
|
|
7053
7625
|
status: 200,
|
|
7054
|
-
value: {
|
|
7626
|
+
value: {
|
|
7627
|
+
ranges: planned.ranges,
|
|
7628
|
+
filesRestored
|
|
7629
|
+
}
|
|
7055
7630
|
};
|
|
7056
7631
|
} catch (error) {
|
|
7057
7632
|
if (error instanceof SyntaxError) return {
|
|
@@ -7555,6 +8130,31 @@ const PROSE = {
|
|
|
7555
8130
|
else document.prose = value;
|
|
7556
8131
|
}
|
|
7557
8132
|
};
|
|
8133
|
+
/** Whether a session that needs the user interrupts them. Presentation only,
|
|
8134
|
+
* and read by the Client at delivery time, so like {@link PROSE} the switch
|
|
8135
|
+
* lands the moment it is saved. */
|
|
8136
|
+
const ALERTS = {
|
|
8137
|
+
key: "alerts",
|
|
8138
|
+
kind: "select",
|
|
8139
|
+
document: "plugin",
|
|
8140
|
+
effect: "immediate",
|
|
8141
|
+
async options() {
|
|
8142
|
+
return CLAUDE_ALERT_MODES.map((value) => ({
|
|
8143
|
+
value,
|
|
8144
|
+
label: value,
|
|
8145
|
+
source: "built-in"
|
|
8146
|
+
}));
|
|
8147
|
+
},
|
|
8148
|
+
read(document) {
|
|
8149
|
+
const value = document.alerts;
|
|
8150
|
+
return isClaudeAlertMode(value) ? value : "on";
|
|
8151
|
+
},
|
|
8152
|
+
apply(document, value) {
|
|
8153
|
+
if (!isClaudeAlertMode(value)) throw new Error("Invalid value for global setting alerts");
|
|
8154
|
+
if (value === "on") delete document.alerts;
|
|
8155
|
+
else document.alerts = value;
|
|
8156
|
+
}
|
|
8157
|
+
};
|
|
7558
8158
|
function isBoundedInteger(value, min, max) {
|
|
7559
8159
|
return typeof value === "number" && Number.isInteger(value) && value >= min && value <= max;
|
|
7560
8160
|
}
|
|
@@ -7580,6 +8180,7 @@ const DESCRIPTORS = [
|
|
|
7580
8180
|
OUTPUT_STYLE,
|
|
7581
8181
|
RENDERER,
|
|
7582
8182
|
PROSE,
|
|
8183
|
+
ALERTS,
|
|
7583
8184
|
WORKTREE_BRANCH_PREFIX,
|
|
7584
8185
|
integerSetting("maxProcesses", 1, MAX_PROCESSES_LIMIT, (limits) => limits.maxProcesses),
|
|
7585
8186
|
integerSetting("idleTimeoutMinutes", 1, MAX_IDLE_TIMEOUT_MINUTES, (limits) => Math.max(1, Math.round(limits.idleTimeoutMs / 6e4)))
|
|
@@ -7863,14 +8464,12 @@ async function apply(ctx, config) {
|
|
|
7863
8464
|
const supervisorConfig = {
|
|
7864
8465
|
executablePath: "",
|
|
7865
8466
|
defaultModel: config.model ?? "default",
|
|
7866
|
-
renderMode: DEFAULT_CLAUDE_RENDER_MODE,
|
|
7867
8467
|
...defaultLimits
|
|
7868
8468
|
};
|
|
7869
8469
|
const applySettingsOverrides = async () => {
|
|
7870
8470
|
const overrides = await readSupervisorLimitOverrides();
|
|
7871
8471
|
supervisorConfig.idleTimeoutMs = overrides.idleTimeoutMs ?? defaultLimits.idleTimeoutMs;
|
|
7872
8472
|
supervisorConfig.maxProcesses = overrides.maxProcesses ?? defaultLimits.maxProcesses;
|
|
7873
|
-
supervisorConfig.renderMode = await readRenderMode();
|
|
7874
8473
|
};
|
|
7875
8474
|
await applySettingsOverrides();
|
|
7876
8475
|
const sidecar = new ClaudeSidecarRepository();
|
|
@@ -7892,7 +8491,7 @@ async function apply(ctx, config) {
|
|
|
7892
8491
|
let resolutionError;
|
|
7893
8492
|
try {
|
|
7894
8493
|
supervisorConfig.executablePath = (await resolveClaudeExecutable(ctx.subprocess, config.executablePath === void 0 || config.executablePath.length === 0 ? void 0 : config.executablePath)).path;
|
|
7895
|
-
ctx.llm.registerAdapter([...CLAUDE_CODE_PROVIDER_IDS], createClaudeCodeAdapter(supervisor, ctx.agents, ctx.attachments, (agent) => ctx.agentPresets.composedPreset(agent.ctx), (sessionId) => reviewComments.drain(sessionId), () => supervisorConfig.
|
|
8494
|
+
ctx.llm.registerAdapter([...CLAUDE_CODE_PROVIDER_IDS], createClaudeCodeAdapter(supervisor, ctx.agents, ctx.attachments, (agent) => ctx.agentPresets.composedPreset(agent.ctx), (sessionId) => reviewComments.drain(sessionId), () => readRenderMode(), (request) => summarizeSessionTitle(supervisorConfig.executablePath, request)));
|
|
7896
8495
|
ctx.effect(() => {
|
|
7897
8496
|
const mounted = /* @__PURE__ */ new Map();
|
|
7898
8497
|
const pending = /* @__PURE__ */ new Set();
|
|
@@ -8023,13 +8622,18 @@ async function apply(ctx, config) {
|
|
|
8023
8622
|
return agent !== void 0 && webCtx.agentPresets.composedPreset(agent.ctx) === "claude";
|
|
8024
8623
|
};
|
|
8025
8624
|
registerReviewCommentRoute(webCtx, reviewComments, ownsClaudeSession);
|
|
8625
|
+
registerPlanFeedbackRoute(webCtx, supervisor.planFeedback, ownsClaudeSession);
|
|
8026
8626
|
registerClaudeRewindRoute(webCtx, sidecar, {
|
|
8027
8627
|
eventsFor: (sessionId) => {
|
|
8028
8628
|
const agent = webCtx.agents.get(sessionId);
|
|
8029
8629
|
return agent === void 0 || webCtx.agentPresets.composedPreset(agent.ctx) !== "claude" ? void 0 : agent.session.events;
|
|
8030
8630
|
},
|
|
8031
8631
|
busy: (sessionId) => supervisor.snapshots().some((item) => item.sessionId === sessionId && (item.state === "running" || item.state === "interrupting")),
|
|
8032
|
-
reset: (sessionId) => supervisor.disposeSession(sessionId)
|
|
8632
|
+
reset: (sessionId) => supervisor.disposeSession(sessionId),
|
|
8633
|
+
restoreFiles: async (sessionId, tree) => {
|
|
8634
|
+
const cwd = webCtx.agents.get(sessionId)?.session.header.cwd;
|
|
8635
|
+
return cwd === void 0 ? false : restoreWorktreeTree(ctx.subprocess, cwd, tree);
|
|
8636
|
+
}
|
|
8033
8637
|
});
|
|
8034
8638
|
registerPlanUsageRoute(webCtx, (fetchedAt) => probePlanUsage(supervisorConfig.executablePath, fetchedAt));
|
|
8035
8639
|
registerClaudeProjectionRoute(webCtx, sidecar, ownsClaudeSession, (sessionId) => commandCatalogs.get(sessionId) ?? [], async (sessionId) => {
|