@norman-else/dsh-claude 0.1.19 → 0.1.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/INSTALL.md +55 -55
- package/LICENSE +21 -21
- package/README.md +191 -191
- package/cordis.patch.yml +12 -12
- package/legacy-preset/agent.cordis.yml +11 -11
- package/legacy-preset/preset.yml +4 -4
- package/lib/bin.mjs +1 -1
- package/lib/bin.mjs.map +1 -1
- package/lib/client.d.ts +232 -188
- package/lib/client.js +2656 -294
- package/lib/client.js.map +1 -1
- package/lib/command-bridge-C10-lz6A.mjs +71 -0
- package/lib/command-bridge-C10-lz6A.mjs.map +1 -0
- package/lib/{events-DPJaBReT.mjs → events-BdDs9ebF.mjs} +6 -2
- package/lib/events-BdDs9ebF.mjs.map +1 -0
- package/lib/index.d.mts +3 -1
- package/lib/index.mjs +936 -238
- package/lib/index.mjs.map +1 -1
- package/lib/{preset-installer-DnoSEKGm.mjs → preset-installer-DMANIjwu.mjs} +2 -2
- package/lib/preset-installer-DMANIjwu.mjs.map +1 -0
- package/lib/preset-route.mjs +172 -2
- package/lib/preset-route.mjs.map +1 -1
- package/package.json +181 -181
- package/preset/claude/agent.cordis.yml +11 -11
- package/preset/claude/preset.yml +4 -4
- package/lib/events-DPJaBReT.mjs.map +0 -1
- package/lib/presenters-DBRIOgJs.mjs +0 -257
- package/lib/presenters-DBRIOgJs.mjs.map +0 -1
- package/lib/preset-installer-DnoSEKGm.mjs.map +0 -1
package/lib/index.mjs
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { S as
|
|
2
|
-
import {
|
|
3
|
-
import { a as resolveClaudeExecutable, n as ensureManagedPreset, o as runClaudeDoctor, t as ManagedPresetConflictError } from "./preset-installer-
|
|
1
|
+
import { S as CLAUDE_UPDATE_PATH, _ as CLAUDE_PROJECTION_PATH, a as latestClaudeTasks, b as CLAUDE_REVIEW_COMMENT_PATH, c as normalizeTasksEvent, d as CLAUDE_ACTIVITY_EVENT, f as CLAUDE_CODE_PRESET_ID, g as CLAUDE_GLOBAL_SETTINGS_PATH, h as CLAUDE_DOCTOR_PATH, i as latestClaudeSessionBinding, l as redactText, m as CLAUDE_CODE_PROVIDER_IDS, n as currentClaudeActivityCursor, o as normalizeActivity, p as CLAUDE_CODE_PROVIDER, r as latestClaudeContextUsage, s as normalizeContextUsage, t as boundText, u as safeDetail, v as CLAUDE_REPOSITORY_ACTION_PATH, w as TASK_TOOL_NAMES, x as CLAUDE_UPDATE_CHECK_PATH, y as CLAUDE_REPOSITORY_SETUP_PATH } from "./events-BdDs9ebF.mjs";
|
|
2
|
+
import { n as projectClaudeCommands, t as CLAUDE_COMMANDS_SERVICE } from "./command-bridge-C10-lz6A.mjs";
|
|
3
|
+
import { a as resolveClaudeExecutable, n as ensureManagedPreset, o as runClaudeDoctor, t as ManagedPresetConflictError } from "./preset-installer-DMANIjwu.mjs";
|
|
4
4
|
import z from "@deepseek-ai/schemastery";
|
|
5
|
-
import { randomUUID } from "node:crypto";
|
|
5
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
6
6
|
import { chmod, mkdir, opendir, readFile, realpath, rename, rm, writeFile } from "node:fs/promises";
|
|
7
7
|
import { basename, dirname, extname, isAbsolute, join, resolve } from "node:path";
|
|
8
8
|
import { dshHomePath, resolveDshHome } from "@deepseek-ai/dsh-home-paths";
|
|
9
9
|
import { homedir } from "node:os";
|
|
10
10
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
11
|
-
import { CallId, LlmAdapter, ReasoningEffortId, createToolResultMessage } from "@deepseek-ai/dsh-llm";
|
|
12
11
|
import { EventEmitter } from "node:events";
|
|
13
12
|
import { DSH_ENV_PREFIX, SENSITIVE_ENV_PATTERN } from "@deepseek-ai/dsh-subprocess";
|
|
13
|
+
import { LlmAdapter, ReasoningEffortId } from "@deepseek-ai/dsh-llm";
|
|
14
14
|
import { fileURLToPath } from "node:url";
|
|
15
15
|
//#region src/sidecar.ts
|
|
16
16
|
const SIDECAR_SCHEMA_VERSION = 1;
|
|
@@ -22,18 +22,18 @@ function emptyProjection() {
|
|
|
22
22
|
activities: []
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
|
-
function record$
|
|
25
|
+
function record$5(value) {
|
|
26
26
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
27
27
|
}
|
|
28
28
|
function finiteInteger(value) {
|
|
29
29
|
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
30
30
|
}
|
|
31
|
-
function string$
|
|
31
|
+
function string$3(value, max) {
|
|
32
32
|
return typeof value === "string" && value.length > 0 && value.length <= max;
|
|
33
33
|
}
|
|
34
34
|
function binding(value) {
|
|
35
|
-
const input = record$
|
|
36
|
-
if (input === void 0 || !string$
|
|
35
|
+
const input = record$5(value);
|
|
36
|
+
if (input === void 0 || !string$3(input.claudeSessionId, 512) || !string$3(input.sdkVersion, 128) || !string$3(input.cwd, 4096) || input.cliVersion !== void 0 && !string$3(input.cliVersion, 128)) return void 0;
|
|
37
37
|
return {
|
|
38
38
|
claudeSessionId: input.claudeSessionId,
|
|
39
39
|
sdkVersion: input.sdkVersion,
|
|
@@ -42,6 +42,7 @@ function binding(value) {
|
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
44
|
const ACTIVITY_KINDS = /* @__PURE__ */ new Set([
|
|
45
|
+
"text",
|
|
45
46
|
"status",
|
|
46
47
|
"thinking",
|
|
47
48
|
"tool-call",
|
|
@@ -61,22 +62,22 @@ const ACTIVITY_PHASES = /* @__PURE__ */ new Set([
|
|
|
61
62
|
"failed"
|
|
62
63
|
]);
|
|
63
64
|
function activity(value) {
|
|
64
|
-
const input = record$
|
|
65
|
+
const input = record$5(value);
|
|
65
66
|
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;
|
|
66
67
|
return normalizeActivity(input);
|
|
67
68
|
}
|
|
68
69
|
function contextUsage(value) {
|
|
69
|
-
const input = record$
|
|
70
|
+
const input = record$5(value);
|
|
70
71
|
if (input === void 0 || !Array.isArray(input.categories)) return void 0;
|
|
71
72
|
return normalizeContextUsage(input);
|
|
72
73
|
}
|
|
73
74
|
function tasks(value) {
|
|
74
|
-
const input = record$
|
|
75
|
+
const input = record$5(value);
|
|
75
76
|
if (input === void 0 || !Array.isArray(input.tasks)) return void 0;
|
|
76
77
|
return normalizeTasksEvent(input.tasks);
|
|
77
78
|
}
|
|
78
79
|
function parseClaudeSidecar(value) {
|
|
79
|
-
const input = record$
|
|
80
|
+
const input = record$5(value);
|
|
80
81
|
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");
|
|
81
82
|
const activities = input.activities.map(activity);
|
|
82
83
|
if (activities.some((item) => item === void 0)) throw new Error("dsh-claude: invalid sidecar activity");
|
|
@@ -525,10 +526,10 @@ function createUserQuestionBridge(userQuestions, activeContext) {
|
|
|
525
526
|
}
|
|
526
527
|
//#endregion
|
|
527
528
|
//#region src/sdk-messages.ts
|
|
528
|
-
function record$
|
|
529
|
+
function record$4(value) {
|
|
529
530
|
return value !== null && typeof value === "object" ? value : void 0;
|
|
530
531
|
}
|
|
531
|
-
function string$
|
|
532
|
+
function string$2(value) {
|
|
532
533
|
return typeof value === "string" ? value : void 0;
|
|
533
534
|
}
|
|
534
535
|
function taskUsageOf(usage) {
|
|
@@ -540,7 +541,7 @@ function taskUsageOf(usage) {
|
|
|
540
541
|
return Object.keys(normalized).length === 0 ? void 0 : normalized;
|
|
541
542
|
}
|
|
542
543
|
function resultUsage(message) {
|
|
543
|
-
const usage = record$
|
|
544
|
+
const usage = record$4(message.usage);
|
|
544
545
|
const normalized = {};
|
|
545
546
|
if (usage !== void 0) {
|
|
546
547
|
if (typeof usage.input_tokens === "number") normalized.inputTokens = usage.input_tokens;
|
|
@@ -552,26 +553,26 @@ function resultUsage(message) {
|
|
|
552
553
|
return normalized;
|
|
553
554
|
}
|
|
554
555
|
function normalizeAssistant(message) {
|
|
555
|
-
const content = record$
|
|
556
|
+
const content = record$4(message.message)?.content;
|
|
556
557
|
if (!Array.isArray(content)) return [{
|
|
557
558
|
kind: "protocol-error",
|
|
558
559
|
title: "Malformed Claude assistant message",
|
|
559
560
|
detail: message
|
|
560
561
|
}];
|
|
561
|
-
const parentToolUseId = string$
|
|
562
|
+
const parentToolUseId = string$2(message.parent_tool_use_id);
|
|
562
563
|
const normalized = [];
|
|
563
564
|
for (const item of content) {
|
|
564
|
-
const block = record$
|
|
565
|
+
const block = record$4(item);
|
|
565
566
|
if (block === void 0) continue;
|
|
566
567
|
if (block.type === "text") {
|
|
567
|
-
const text = string$
|
|
568
|
+
const text = string$2(block.text);
|
|
568
569
|
if (text !== void 0 && text.length > 0) normalized.push({
|
|
569
570
|
kind: "assistant-text",
|
|
570
571
|
text,
|
|
571
572
|
...parentToolUseId === void 0 ? {} : { parentToolUseId }
|
|
572
573
|
});
|
|
573
574
|
} else if (block.type === "thinking") {
|
|
574
|
-
const text = string$
|
|
575
|
+
const text = string$2(block.thinking);
|
|
575
576
|
if (text !== void 0 && text.length > 0) normalized.push({
|
|
576
577
|
kind: "thinking",
|
|
577
578
|
text,
|
|
@@ -579,8 +580,8 @@ function normalizeAssistant(message) {
|
|
|
579
580
|
...parentToolUseId === void 0 ? {} : { parentToolUseId }
|
|
580
581
|
});
|
|
581
582
|
} else if (block.type === "tool_use") {
|
|
582
|
-
const toolUseId = string$
|
|
583
|
-
const toolName = string$
|
|
583
|
+
const toolUseId = string$2(block.id);
|
|
584
|
+
const toolName = string$2(block.name);
|
|
584
585
|
if (toolUseId !== void 0 && toolName !== void 0) normalized.push({
|
|
585
586
|
kind: "tool-call",
|
|
586
587
|
toolUseId,
|
|
@@ -593,18 +594,18 @@ function normalizeAssistant(message) {
|
|
|
593
594
|
return normalized;
|
|
594
595
|
}
|
|
595
596
|
function normalizeUser(message) {
|
|
596
|
-
const content = record$
|
|
597
|
+
const content = record$4(message.message)?.content;
|
|
597
598
|
if (!Array.isArray(content)) return [{
|
|
598
599
|
kind: "protocol-error",
|
|
599
600
|
title: "Malformed Claude user message",
|
|
600
601
|
detail: message
|
|
601
602
|
}];
|
|
602
|
-
const parentToolUseId = string$
|
|
603
|
+
const parentToolUseId = string$2(message.parent_tool_use_id);
|
|
603
604
|
const normalized = [];
|
|
604
605
|
for (const item of content) {
|
|
605
|
-
const block = record$
|
|
606
|
+
const block = record$4(item);
|
|
606
607
|
if (block?.type !== "tool_result") continue;
|
|
607
|
-
const toolUseId = string$
|
|
608
|
+
const toolUseId = string$2(block.tool_use_id);
|
|
608
609
|
if (toolUseId === void 0) continue;
|
|
609
610
|
normalized.push({
|
|
610
611
|
kind: "tool-result",
|
|
@@ -617,11 +618,11 @@ function normalizeUser(message) {
|
|
|
617
618
|
return normalized;
|
|
618
619
|
}
|
|
619
620
|
function normalizeSystem(message) {
|
|
620
|
-
const subtype = string$
|
|
621
|
+
const subtype = string$2(message.subtype);
|
|
621
622
|
if (subtype === "init") {
|
|
622
|
-
const sessionId = string$
|
|
623
|
-
const cliVersion = string$
|
|
624
|
-
const cwd = string$
|
|
623
|
+
const sessionId = string$2(message.session_id);
|
|
624
|
+
const cliVersion = string$2(message.claude_code_version);
|
|
625
|
+
const cwd = string$2(message.cwd);
|
|
625
626
|
return sessionId !== void 0 && cliVersion !== void 0 && cwd !== void 0 ? [{
|
|
626
627
|
kind: "init",
|
|
627
628
|
sessionId,
|
|
@@ -650,20 +651,20 @@ function normalizeSystem(message) {
|
|
|
650
651
|
title: `Claude session ${String(message.state)}`
|
|
651
652
|
}];
|
|
652
653
|
if (subtype === "permission_denied") {
|
|
653
|
-
const toolUseId = string$
|
|
654
|
-
const toolName = string$
|
|
654
|
+
const toolUseId = string$2(message.tool_use_id);
|
|
655
|
+
const toolName = string$2(message.tool_name);
|
|
655
656
|
if (toolUseId !== void 0 && toolName !== void 0) return [{
|
|
656
657
|
kind: "permission-denied",
|
|
657
658
|
toolUseId,
|
|
658
659
|
toolName,
|
|
659
|
-
summary: string$
|
|
660
|
+
summary: string$2(message.message) ?? "Claude Code denied the action"
|
|
660
661
|
}];
|
|
661
662
|
}
|
|
662
663
|
if (subtype === "task_started") {
|
|
663
|
-
const taskId = string$
|
|
664
|
-
const description = string$
|
|
665
|
-
const subagentType = string$
|
|
666
|
-
const taskType = string$
|
|
664
|
+
const taskId = string$2(message.task_id);
|
|
665
|
+
const description = string$2(message.description);
|
|
666
|
+
const subagentType = string$2(message.subagent_type);
|
|
667
|
+
const taskType = string$2(message.task_type);
|
|
667
668
|
return [{
|
|
668
669
|
kind: "subagent",
|
|
669
670
|
title: description ?? taskId ?? "Claude subagent started",
|
|
@@ -678,12 +679,12 @@ function normalizeSystem(message) {
|
|
|
678
679
|
}];
|
|
679
680
|
}
|
|
680
681
|
if (subtype === "task_progress") {
|
|
681
|
-
const taskId = string$
|
|
682
|
-
const description = string$
|
|
683
|
-
const summary = string$
|
|
684
|
-
const subagentType = string$
|
|
685
|
-
const lastToolName = string$
|
|
686
|
-
const usage = taskUsageOf(record$
|
|
682
|
+
const taskId = string$2(message.task_id);
|
|
683
|
+
const description = string$2(message.description);
|
|
684
|
+
const summary = string$2(message.summary);
|
|
685
|
+
const subagentType = string$2(message.subagent_type);
|
|
686
|
+
const lastToolName = string$2(message.last_tool_name);
|
|
687
|
+
const usage = taskUsageOf(record$4(message.usage));
|
|
687
688
|
return [{
|
|
688
689
|
kind: "subagent",
|
|
689
690
|
title: summary ?? description ?? "Claude subagent update",
|
|
@@ -699,11 +700,11 @@ function normalizeSystem(message) {
|
|
|
699
700
|
}];
|
|
700
701
|
}
|
|
701
702
|
if (subtype === "task_updated") {
|
|
702
|
-
const patch = record$
|
|
703
|
-
const status = string$
|
|
704
|
-
const taskId = string$
|
|
705
|
-
const description = string$
|
|
706
|
-
const error = string$
|
|
703
|
+
const patch = record$4(message.patch);
|
|
704
|
+
const status = string$2(patch?.status);
|
|
705
|
+
const taskId = string$2(message.task_id);
|
|
706
|
+
const description = string$2(patch?.description);
|
|
707
|
+
const error = string$2(patch?.error);
|
|
707
708
|
const taskStatus = status === void 0 ? void 0 : status === "killed" ? "killed" : status === "completed" ? "completed" : status === "failed" ? "failed" : "running";
|
|
708
709
|
return [{
|
|
709
710
|
kind: "subagent",
|
|
@@ -719,10 +720,10 @@ function normalizeSystem(message) {
|
|
|
719
720
|
if (subtype === "task_notification") {
|
|
720
721
|
const failed = message.status === "failed";
|
|
721
722
|
const stopped = message.status === "stopped" || message.status === "cancelled";
|
|
722
|
-
const taskId = string$
|
|
723
|
-
const summary = string$
|
|
723
|
+
const taskId = string$2(message.task_id);
|
|
724
|
+
const summary = string$2(message.summary);
|
|
724
725
|
const taskStatus = failed ? "failed" : stopped ? "stopped" : "completed";
|
|
725
|
-
const usage = taskUsageOf(record$
|
|
726
|
+
const usage = taskUsageOf(record$4(message.usage));
|
|
726
727
|
return [{
|
|
727
728
|
kind: "subagent",
|
|
728
729
|
title: summary ?? taskId ?? "Claude subagent finished",
|
|
@@ -737,10 +738,10 @@ function normalizeSystem(message) {
|
|
|
737
738
|
if (subtype === "background_tasks_changed") return [{
|
|
738
739
|
kind: "background-tasks",
|
|
739
740
|
tasks: (Array.isArray(message.tasks) ? message.tasks : []).flatMap((item) => {
|
|
740
|
-
const entry = record$
|
|
741
|
-
const taskId = string$
|
|
742
|
-
const description = string$
|
|
743
|
-
const taskType = string$
|
|
741
|
+
const entry = record$4(item);
|
|
742
|
+
const taskId = string$2(entry?.task_id);
|
|
743
|
+
const description = string$2(entry?.description);
|
|
744
|
+
const taskType = string$2(entry?.task_type);
|
|
744
745
|
if (taskId === void 0 || description === void 0) return [];
|
|
745
746
|
return [{
|
|
746
747
|
taskId,
|
|
@@ -756,7 +757,7 @@ function normalizeSystem(message) {
|
|
|
756
757
|
}];
|
|
757
758
|
if (subtype === "informational" || subtype === "notification" || subtype === "local_command_output") return [{
|
|
758
759
|
kind: message.level === "warning" ? "warning" : "status",
|
|
759
|
-
title: string$
|
|
760
|
+
title: string$2(message.content) ?? string$2(message.text) ?? "Claude Code notice",
|
|
760
761
|
detail: message
|
|
761
762
|
}];
|
|
762
763
|
if (subtype?.startsWith("hook_") === true || subtype === "plugin_install") return [{
|
|
@@ -780,12 +781,12 @@ const RESULT_ERROR_SUBTYPES = /* @__PURE__ */ new Set([
|
|
|
780
781
|
function normalizeSdkMessage(message) {
|
|
781
782
|
const value = message;
|
|
782
783
|
if (value.type === "stream_event") {
|
|
783
|
-
const event = record$
|
|
784
|
-
const parentToolUseId = string$
|
|
784
|
+
const event = record$4(value.event);
|
|
785
|
+
const parentToolUseId = string$2(value.parent_tool_use_id);
|
|
785
786
|
if (event?.type === "content_block_delta") {
|
|
786
|
-
const delta = record$
|
|
787
|
+
const delta = record$4(event.delta);
|
|
787
788
|
if (delta?.type === "text_delta") {
|
|
788
|
-
const text = string$
|
|
789
|
+
const text = string$2(delta.text);
|
|
789
790
|
return text === void 0 ? [] : [{
|
|
790
791
|
kind: "text-delta",
|
|
791
792
|
text,
|
|
@@ -793,7 +794,7 @@ function normalizeSdkMessage(message) {
|
|
|
793
794
|
}];
|
|
794
795
|
}
|
|
795
796
|
if (delta?.type === "thinking_delta") {
|
|
796
|
-
const text = string$
|
|
797
|
+
const text = string$2(delta.thinking);
|
|
797
798
|
return text === void 0 ? [] : [{
|
|
798
799
|
kind: "thinking",
|
|
799
800
|
text,
|
|
@@ -808,7 +809,7 @@ function normalizeSdkMessage(message) {
|
|
|
808
809
|
if (value.type === "user") return normalizeUser(value);
|
|
809
810
|
if (value.type === "system") return normalizeSystem(value);
|
|
810
811
|
if (value.type === "result") {
|
|
811
|
-
const sessionId = string$
|
|
812
|
+
const sessionId = string$2(value.session_id);
|
|
812
813
|
if (sessionId === void 0 || value.subtype !== "success" && !RESULT_ERROR_SUBTYPES.has(String(value.subtype))) return [{
|
|
813
814
|
kind: "protocol-error",
|
|
814
815
|
title: "Malformed Claude result message",
|
|
@@ -816,11 +817,11 @@ function normalizeSdkMessage(message) {
|
|
|
816
817
|
}];
|
|
817
818
|
const success = value.subtype === "success" && value.is_error !== true;
|
|
818
819
|
const errors = Array.isArray(value.errors) ? value.errors.filter((item) => typeof item === "string") : void 0;
|
|
819
|
-
const terminalReason = string$
|
|
820
|
-
const userMessageUuid = string$
|
|
821
|
-
const permissionDenials = Array.isArray(value.permission_denials) ? value.permission_denials.map((item) => record$
|
|
822
|
-
const toolName = string$
|
|
823
|
-
const toolUseId = string$
|
|
820
|
+
const terminalReason = string$2(value.terminal_reason);
|
|
821
|
+
const userMessageUuid = string$2(value.user_message_uuid);
|
|
822
|
+
const permissionDenials = Array.isArray(value.permission_denials) ? value.permission_denials.map((item) => record$4(item)).filter((item) => item !== void 0).map((item) => {
|
|
823
|
+
const toolName = string$2(item.tool_name);
|
|
824
|
+
const toolUseId = string$2(item.tool_use_id);
|
|
824
825
|
return toolName === void 0 || toolUseId === void 0 ? void 0 : {
|
|
825
826
|
toolName,
|
|
826
827
|
toolUseId
|
|
@@ -844,11 +845,10 @@ function normalizeSdkMessage(message) {
|
|
|
844
845
|
detail: value.error ?? value.output
|
|
845
846
|
}];
|
|
846
847
|
if (value.type === "rate_limit_event") {
|
|
847
|
-
const status = string$
|
|
848
|
-
const blocking = status !== void 0 && status !== "allowed";
|
|
848
|
+
const status = string$2(record$4(value.rate_limit_info)?.status);
|
|
849
849
|
return [{
|
|
850
|
-
kind:
|
|
851
|
-
title:
|
|
850
|
+
kind: "status",
|
|
851
|
+
title: status !== void 0 && status !== "allowed" ? "Claude rate limit is blocking requests" : "Claude rate limit status changed",
|
|
852
852
|
detail: value.rate_limit_info
|
|
853
853
|
}];
|
|
854
854
|
}
|
|
@@ -1034,6 +1034,7 @@ function rootCallSummary(toolName, input) {
|
|
|
1034
1034
|
}
|
|
1035
1035
|
var ClaudeSupervisor = class {
|
|
1036
1036
|
#entries = /* @__PURE__ */ new Map();
|
|
1037
|
+
#interruptions = /* @__PURE__ */ new Map();
|
|
1037
1038
|
#runtime;
|
|
1038
1039
|
#approval;
|
|
1039
1040
|
#userQuestions;
|
|
@@ -1041,7 +1042,6 @@ var ClaudeSupervisor = class {
|
|
|
1041
1042
|
#queryFactory;
|
|
1042
1043
|
#runDetached;
|
|
1043
1044
|
#sidecar;
|
|
1044
|
-
#dynamicPresenterNames = /* @__PURE__ */ new WeakMap();
|
|
1045
1045
|
#contextWindows = /* @__PURE__ */ new Map();
|
|
1046
1046
|
#disposed = false;
|
|
1047
1047
|
#admissionGate = Promise.resolve();
|
|
@@ -1082,6 +1082,8 @@ var ClaudeSupervisor = class {
|
|
|
1082
1082
|
return this.#contextWindows.get(model);
|
|
1083
1083
|
}
|
|
1084
1084
|
runTurn(request) {
|
|
1085
|
+
const interruption = this.#interruptions.get(request.agent.id);
|
|
1086
|
+
if (interruption !== void 0) return interruption.then(() => this.runTurn(request));
|
|
1085
1087
|
const operation = this.#admissionGate.then(() => this.#runTurnAdmitted(request));
|
|
1086
1088
|
this.#admissionGate = operation.then(() => void 0, () => void 0);
|
|
1087
1089
|
return operation;
|
|
@@ -1134,6 +1136,8 @@ var ClaudeSupervisor = class {
|
|
|
1134
1136
|
sawActivity: false,
|
|
1135
1137
|
sawTextDelta: false,
|
|
1136
1138
|
text: "",
|
|
1139
|
+
transcriptText: "",
|
|
1140
|
+
transcriptTextOrdinal: void 0,
|
|
1137
1141
|
thinking: "",
|
|
1138
1142
|
aborted: false,
|
|
1139
1143
|
deniedToolUseIds: /* @__PURE__ */ new Set(),
|
|
@@ -1172,7 +1176,7 @@ var ClaudeSupervisor = class {
|
|
|
1172
1176
|
}
|
|
1173
1177
|
if (request.signal !== void 0) {
|
|
1174
1178
|
const abortListener = () => {
|
|
1175
|
-
this.#
|
|
1179
|
+
this.#startInterrupt(entry);
|
|
1176
1180
|
};
|
|
1177
1181
|
active.abortListener = abortListener;
|
|
1178
1182
|
request.signal.addEventListener("abort", abortListener, { once: true });
|
|
@@ -1387,7 +1391,14 @@ var ClaudeSupervisor = class {
|
|
|
1387
1391
|
if (message.userMessageUuid !== void 0 && message.userMessageUuid === entry.handshakeUuid) return;
|
|
1388
1392
|
if (entry.claudeSessionId === void 0) throw new ClaudeProtocolError("Claude Code sent a result before initialization");
|
|
1389
1393
|
if (message.sessionId !== entry.claudeSessionId) throw new ClaudeProtocolError(`Claude Code result session ${message.sessionId} does not match ${entry.claudeSessionId}`);
|
|
1390
|
-
if (
|
|
1394
|
+
if (active.phase === "waiting-tasks") {
|
|
1395
|
+
await this.#completeProgressSegment(active, message);
|
|
1396
|
+
return;
|
|
1397
|
+
}
|
|
1398
|
+
if (message.userMessageUuid !== void 0 && message.userMessageUuid !== active.promptUuid) {
|
|
1399
|
+
if (active.phase === "follow-up") return;
|
|
1400
|
+
throw new ClaudeProtocolError(`Claude Code result for user message ${message.userMessageUuid} does not match active request ${active.promptUuid}`);
|
|
1401
|
+
}
|
|
1391
1402
|
await this.#completeTurn(entry, active, message);
|
|
1392
1403
|
return;
|
|
1393
1404
|
}
|
|
@@ -1398,6 +1409,8 @@ var ClaudeSupervisor = class {
|
|
|
1398
1409
|
if (message.parentToolUseId !== void 0) return;
|
|
1399
1410
|
active.sawTextDelta = true;
|
|
1400
1411
|
active.text += message.text;
|
|
1412
|
+
active.transcriptText += message.text;
|
|
1413
|
+
await this.#upsertTranscriptText(active);
|
|
1401
1414
|
active.output.push({
|
|
1402
1415
|
type: "text-delta",
|
|
1403
1416
|
text: message.text
|
|
@@ -1408,6 +1421,8 @@ var ClaudeSupervisor = class {
|
|
|
1408
1421
|
if (!active.sawTextDelta) {
|
|
1409
1422
|
active.sawTextDelta = true;
|
|
1410
1423
|
active.text += message.text;
|
|
1424
|
+
active.transcriptText += message.text;
|
|
1425
|
+
await this.#upsertTranscriptText(active);
|
|
1411
1426
|
active.output.push({
|
|
1412
1427
|
type: "text-delta",
|
|
1413
1428
|
text: message.text
|
|
@@ -1429,6 +1444,7 @@ var ClaudeSupervisor = class {
|
|
|
1429
1444
|
});
|
|
1430
1445
|
return;
|
|
1431
1446
|
case "tool-call":
|
|
1447
|
+
if (message.parentToolUseId === void 0) this.#closeTranscriptTextSegment(active);
|
|
1432
1448
|
await this.#appendActivity(active, {
|
|
1433
1449
|
kind: message.parentToolUseId === void 0 ? "tool-call" : "subagent",
|
|
1434
1450
|
phase: "started",
|
|
@@ -1439,11 +1455,7 @@ var ClaudeSupervisor = class {
|
|
|
1439
1455
|
summary: message.parentToolUseId === void 0 ? rootCallSummary(message.toolName, message.input) : `Subagent called ${message.toolName}`,
|
|
1440
1456
|
detail: message.input
|
|
1441
1457
|
});
|
|
1442
|
-
if (message.parentToolUseId === void 0)
|
|
1443
|
-
active.callNames.set(message.toolUseId, message.toolName);
|
|
1444
|
-
this.#ensureDynamicPresenter(active.agent, message.toolName);
|
|
1445
|
-
if (!TASK_TOOL_NAMES.has(message.toolName)) await this.#appendNativeToolCall(active, message);
|
|
1446
|
-
}
|
|
1458
|
+
if (message.parentToolUseId === void 0) active.callNames.set(message.toolUseId, message.toolName);
|
|
1447
1459
|
return;
|
|
1448
1460
|
case "tool-result":
|
|
1449
1461
|
await this.#appendActivity(active, {
|
|
@@ -1455,7 +1467,6 @@ var ClaudeSupervisor = class {
|
|
|
1455
1467
|
detail: message.output,
|
|
1456
1468
|
isError: message.isError
|
|
1457
1469
|
});
|
|
1458
|
-
if (message.parentToolUseId === void 0 && !TASK_TOOL_NAMES.has(active.callNames.get(message.toolUseId) ?? "")) await this.#appendNativeToolResult(active, message);
|
|
1459
1470
|
return;
|
|
1460
1471
|
case "subagent":
|
|
1461
1472
|
await this.#appendActivity(active, {
|
|
@@ -1488,12 +1499,6 @@ var ClaudeSupervisor = class {
|
|
|
1488
1499
|
title: message.toolName,
|
|
1489
1500
|
summary: message.summary
|
|
1490
1501
|
});
|
|
1491
|
-
if (!TASK_TOOL_NAMES.has(active.callNames.get(message.toolUseId) ?? message.toolName)) await this.#appendNativeToolResult(active, {
|
|
1492
|
-
kind: "tool-result",
|
|
1493
|
-
toolUseId: message.toolUseId,
|
|
1494
|
-
output: message.summary,
|
|
1495
|
-
isError: true
|
|
1496
|
-
});
|
|
1497
1502
|
return;
|
|
1498
1503
|
}
|
|
1499
1504
|
}
|
|
@@ -1571,6 +1576,7 @@ var ClaudeSupervisor = class {
|
|
|
1571
1576
|
active.promptUuid = randomUUID();
|
|
1572
1577
|
active.sawTextDelta = false;
|
|
1573
1578
|
active.text = "";
|
|
1579
|
+
this.#closeTranscriptTextSegment(active);
|
|
1574
1580
|
active.thinking = "";
|
|
1575
1581
|
await this.#appendSafely(active, {
|
|
1576
1582
|
kind: "status",
|
|
@@ -1604,9 +1610,43 @@ var ClaudeSupervisor = class {
|
|
|
1604
1610
|
entry.taskSnapshotAt = Date.now();
|
|
1605
1611
|
await this.#sidecar.writeTasks(entry.sessionId, [...entry.tasks.values()]).catch(() => void 0);
|
|
1606
1612
|
}
|
|
1613
|
+
async #completeProgressSegment(active, result, recordUsage = true) {
|
|
1614
|
+
if (recordUsage && (result.usage.inputTokens !== void 0 || result.usage.outputTokens !== void 0 || result.usage.cumulativeCostUsd !== void 0)) {
|
|
1615
|
+
await this.#appendSafely(active, {
|
|
1616
|
+
kind: "usage",
|
|
1617
|
+
phase: "completed",
|
|
1618
|
+
title: "Claude usage",
|
|
1619
|
+
summary: usageSummary(result.usage),
|
|
1620
|
+
usage: result.usage
|
|
1621
|
+
});
|
|
1622
|
+
active.output.push({
|
|
1623
|
+
type: "usage",
|
|
1624
|
+
usage: result.usage
|
|
1625
|
+
});
|
|
1626
|
+
}
|
|
1627
|
+
if (!active.sawTextDelta && active.text.length === 0 && result.text !== void 0) {
|
|
1628
|
+
active.text = result.text;
|
|
1629
|
+
active.transcriptText = result.text;
|
|
1630
|
+
await this.#upsertTranscriptText(active);
|
|
1631
|
+
active.output.push({
|
|
1632
|
+
type: "text-delta",
|
|
1633
|
+
text: result.text
|
|
1634
|
+
});
|
|
1635
|
+
}
|
|
1636
|
+
await this.#upsertTranscriptText(active);
|
|
1637
|
+
active.output.push({
|
|
1638
|
+
type: "segment-complete",
|
|
1639
|
+
text: active.text
|
|
1640
|
+
});
|
|
1641
|
+
active.sawTextDelta = false;
|
|
1642
|
+
active.text = "";
|
|
1643
|
+
this.#closeTranscriptTextSegment(active);
|
|
1644
|
+
active.thinking = "";
|
|
1645
|
+
}
|
|
1607
1646
|
async #completeTurn(entry, active, result) {
|
|
1608
1647
|
if (entry.active !== active) return;
|
|
1609
1648
|
if (active.aborted) {
|
|
1649
|
+
await this.#upsertTranscriptText(active);
|
|
1610
1650
|
await this.#appendSafely(active, {
|
|
1611
1651
|
kind: "status",
|
|
1612
1652
|
phase: "failed",
|
|
@@ -1639,6 +1679,16 @@ var ClaudeSupervisor = class {
|
|
|
1639
1679
|
summary: unmatchedDenials.map((denial) => denial.toolName).join(", ")
|
|
1640
1680
|
});
|
|
1641
1681
|
if (!result.success) {
|
|
1682
|
+
if (!active.sawTextDelta && active.text.length === 0 && result.text !== void 0) {
|
|
1683
|
+
active.text = result.text;
|
|
1684
|
+
active.transcriptText = result.text;
|
|
1685
|
+
await this.#upsertTranscriptText(active);
|
|
1686
|
+
active.output.push({
|
|
1687
|
+
type: "text-delta",
|
|
1688
|
+
text: result.text
|
|
1689
|
+
});
|
|
1690
|
+
}
|
|
1691
|
+
await this.#upsertTranscriptText(active);
|
|
1642
1692
|
const message = result.errors?.join("\n") ?? (result.terminalReason !== void 0 ? `Claude Code failed the turn (${result.terminalReason})` : "Claude Code failed the turn");
|
|
1643
1693
|
await this.#appendSafely(active, {
|
|
1644
1694
|
kind: "error",
|
|
@@ -1651,11 +1701,14 @@ var ClaudeSupervisor = class {
|
|
|
1651
1701
|
} else {
|
|
1652
1702
|
if (!active.sawTextDelta && active.text.length === 0 && result.text !== void 0) {
|
|
1653
1703
|
active.text = result.text;
|
|
1704
|
+
active.transcriptText = result.text;
|
|
1705
|
+
await this.#upsertTranscriptText(active);
|
|
1654
1706
|
active.output.push({
|
|
1655
1707
|
type: "text-delta",
|
|
1656
1708
|
text: result.text
|
|
1657
1709
|
});
|
|
1658
1710
|
}
|
|
1711
|
+
await this.#upsertTranscriptText(active);
|
|
1659
1712
|
if (active.phase === "primary" && this.#hasRunningTasks(entry, active)) {
|
|
1660
1713
|
active.phase = "waiting-tasks";
|
|
1661
1714
|
await this.#appendSafely(active, {
|
|
@@ -1663,10 +1716,7 @@ var ClaudeSupervisor = class {
|
|
|
1663
1716
|
phase: "updated",
|
|
1664
1717
|
title: "Claude Code is waiting for background tasks"
|
|
1665
1718
|
});
|
|
1666
|
-
active
|
|
1667
|
-
type: "segment-complete",
|
|
1668
|
-
text: active.text
|
|
1669
|
-
});
|
|
1719
|
+
await this.#completeProgressSegment(active, result, false);
|
|
1670
1720
|
return;
|
|
1671
1721
|
}
|
|
1672
1722
|
await this.#appendSafely(active, {
|
|
@@ -1686,6 +1736,25 @@ var ClaudeSupervisor = class {
|
|
|
1686
1736
|
entry.lastUsedAt = Date.now();
|
|
1687
1737
|
this.#armIdleTimer(entry);
|
|
1688
1738
|
}
|
|
1739
|
+
async #upsertTranscriptText(active) {
|
|
1740
|
+
if (active.transcriptText.length === 0) return;
|
|
1741
|
+
const ordinal = active.transcriptTextOrdinal ?? active.cursor.nextOrdinal++;
|
|
1742
|
+
active.transcriptTextOrdinal = ordinal;
|
|
1743
|
+
try {
|
|
1744
|
+
await this.#sidecar.appendActivity(active.agent.id, {
|
|
1745
|
+
kind: "text",
|
|
1746
|
+
phase: "updated",
|
|
1747
|
+
text: active.transcriptText,
|
|
1748
|
+
turn: active.cursor.turn,
|
|
1749
|
+
step: active.cursor.step,
|
|
1750
|
+
ordinal
|
|
1751
|
+
});
|
|
1752
|
+
} catch {}
|
|
1753
|
+
}
|
|
1754
|
+
#closeTranscriptTextSegment(active) {
|
|
1755
|
+
active.transcriptText = "";
|
|
1756
|
+
active.transcriptTextOrdinal = void 0;
|
|
1757
|
+
}
|
|
1689
1758
|
async #appendActivity(active, activity) {
|
|
1690
1759
|
const ordinal = active.cursor.nextOrdinal++;
|
|
1691
1760
|
await this.#sidecar.appendActivity(active.agent.id, {
|
|
@@ -1700,53 +1769,14 @@ var ClaudeSupervisor = class {
|
|
|
1700
1769
|
async #appendSafely(active, activity) {
|
|
1701
1770
|
await this.#appendActivity(active, activity).catch(() => void 0);
|
|
1702
1771
|
}
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
known = /* @__PURE__ */ new Set();
|
|
1712
|
-
this.#dynamicPresenterNames.set(agent, known);
|
|
1713
|
-
}
|
|
1714
|
-
if (known.has(name)) return;
|
|
1715
|
-
try {
|
|
1716
|
-
agent.ctx.tools.register(dynamicPresenterDefinition(name));
|
|
1717
|
-
known.add(name);
|
|
1718
|
-
} catch {}
|
|
1719
|
-
}
|
|
1720
|
-
/** Mirror one root Claude tool call into the durable native tool channel so
|
|
1721
|
-
* the host's tool presentation renders it exactly like a DSH-executed call.
|
|
1722
|
-
* Presentation duplication is best-effort and never unsettles the turn. */
|
|
1723
|
-
async #appendNativeToolCall(active, message) {
|
|
1724
|
-
try {
|
|
1725
|
-
await active.agent.session.append("tool/call", {
|
|
1726
|
-
turn: active.cursor.turn,
|
|
1727
|
-
step: active.cursor.step,
|
|
1728
|
-
callId: CallId(message.toolUseId),
|
|
1729
|
-
name: message.toolName,
|
|
1730
|
-
arguments: safeDetail(message.input) ?? "{}"
|
|
1731
|
-
});
|
|
1732
|
-
} catch {}
|
|
1733
|
-
}
|
|
1734
|
-
async #appendNativeToolResult(active, message) {
|
|
1735
|
-
const text = typeof message.output === "string" ? redactText(message.output) : safeDetail(message.output) ?? "";
|
|
1736
|
-
try {
|
|
1737
|
-
await active.agent.session.append("tool/result", {
|
|
1738
|
-
turn: active.cursor.turn,
|
|
1739
|
-
step: active.cursor.step,
|
|
1740
|
-
message: createToolResultMessage({
|
|
1741
|
-
callId: CallId(message.toolUseId),
|
|
1742
|
-
content: [{
|
|
1743
|
-
type: "text",
|
|
1744
|
-
text
|
|
1745
|
-
}],
|
|
1746
|
-
isError: message.isError
|
|
1747
|
-
})
|
|
1748
|
-
}, { surfaceOp: "append" });
|
|
1749
|
-
} catch {}
|
|
1772
|
+
#startInterrupt(entry) {
|
|
1773
|
+
const existing = this.#interruptions.get(entry.sessionId);
|
|
1774
|
+
if (existing !== void 0) return existing;
|
|
1775
|
+
const interruption = this.#interrupt(entry).finally(() => {
|
|
1776
|
+
this.#interruptions.delete(entry.sessionId);
|
|
1777
|
+
});
|
|
1778
|
+
this.#interruptions.set(entry.sessionId, interruption);
|
|
1779
|
+
return interruption;
|
|
1750
1780
|
}
|
|
1751
1781
|
async #interrupt(entry) {
|
|
1752
1782
|
const active = entry.active;
|
|
@@ -1754,6 +1784,7 @@ var ClaudeSupervisor = class {
|
|
|
1754
1784
|
entry.state = "interrupting";
|
|
1755
1785
|
active.aborted = true;
|
|
1756
1786
|
active.output.fail(abortFailure());
|
|
1787
|
+
await this.#upsertTranscriptText(active);
|
|
1757
1788
|
let interruptError;
|
|
1758
1789
|
try {
|
|
1759
1790
|
if (((await withTimeout(entry.query.interrupt(), 5e3, "Claude Code interrupt"))?.still_queued ?? []).includes(active.promptUuid)) throw new Error(`Claude Code interrupt left submitted prompt ${active.promptUuid} queued`);
|
|
@@ -1775,6 +1806,7 @@ var ClaudeSupervisor = class {
|
|
|
1775
1806
|
const active = entry.active;
|
|
1776
1807
|
const stderr = entry.process?.stderrTail();
|
|
1777
1808
|
if (active !== void 0) {
|
|
1809
|
+
await this.#upsertTranscriptText(active);
|
|
1778
1810
|
if (active.signal !== void 0 && active.abortListener !== void 0) active.signal.removeEventListener("abort", active.abortListener);
|
|
1779
1811
|
const unknown = active.sawActivity;
|
|
1780
1812
|
entry.state = unknown ? "outcome-unknown" : "disconnected";
|
|
@@ -1830,6 +1862,79 @@ var ClaudeSupervisor = class {
|
|
|
1830
1862
|
}
|
|
1831
1863
|
};
|
|
1832
1864
|
//#endregion
|
|
1865
|
+
//#region src/review-comments.ts
|
|
1866
|
+
const MAX_COMMENTS_PER_SESSION = 50;
|
|
1867
|
+
const MAX_TEXT_CHARS$1 = 2e3;
|
|
1868
|
+
const MAX_PATH_CHARS$1 = 1024;
|
|
1869
|
+
const MAX_LINE = 1e7;
|
|
1870
|
+
var ReviewCommentError = class extends Error {
|
|
1871
|
+
code;
|
|
1872
|
+
constructor(code, message) {
|
|
1873
|
+
super(message);
|
|
1874
|
+
this.name = "ReviewCommentError";
|
|
1875
|
+
this.code = code;
|
|
1876
|
+
}
|
|
1877
|
+
};
|
|
1878
|
+
function validSide(value) {
|
|
1879
|
+
return value === "old" || value === "new";
|
|
1880
|
+
}
|
|
1881
|
+
/** Pending line comments queued per session until the next user turn drains them. */
|
|
1882
|
+
var ReviewCommentStore = class {
|
|
1883
|
+
#comments = /* @__PURE__ */ new Map();
|
|
1884
|
+
add(sessionId, input) {
|
|
1885
|
+
const path = typeof input.path === "string" ? input.path.trim() : "";
|
|
1886
|
+
const text = typeof input.text === "string" ? input.text.trim() : "";
|
|
1887
|
+
if (path.length === 0 || path.length > MAX_PATH_CHARS$1 || /[\0\r\n]/u.test(path)) throw new ReviewCommentError("invalid-request", "The comment path is invalid.");
|
|
1888
|
+
if (text.length === 0 || text.length > MAX_TEXT_CHARS$1 || text.includes("\0")) throw new ReviewCommentError("invalid-request", "The comment text is invalid.");
|
|
1889
|
+
if (typeof input.line !== "number" || !Number.isSafeInteger(input.line) || input.line < 1 || input.line > MAX_LINE) throw new ReviewCommentError("invalid-request", "The comment line is invalid.");
|
|
1890
|
+
if (!validSide(input.side)) throw new ReviewCommentError("invalid-request", "The comment side is invalid.");
|
|
1891
|
+
const existing = this.#comments.get(sessionId) ?? [];
|
|
1892
|
+
if (existing.length >= MAX_COMMENTS_PER_SESSION) throw new ReviewCommentError("too-many-comments", "Too many pending review comments. Remove one before adding another.");
|
|
1893
|
+
const comment = {
|
|
1894
|
+
id: randomUUID(),
|
|
1895
|
+
path,
|
|
1896
|
+
line: input.line,
|
|
1897
|
+
side: input.side,
|
|
1898
|
+
text
|
|
1899
|
+
};
|
|
1900
|
+
this.#comments.set(sessionId, [...existing, comment]);
|
|
1901
|
+
return comment;
|
|
1902
|
+
}
|
|
1903
|
+
remove(sessionId, id) {
|
|
1904
|
+
const existing = this.#comments.get(sessionId);
|
|
1905
|
+
if (existing === void 0) return false;
|
|
1906
|
+
const next = existing.filter((comment) => comment.id !== id);
|
|
1907
|
+
if (next.length === existing.length) return false;
|
|
1908
|
+
if (next.length === 0) this.#comments.delete(sessionId);
|
|
1909
|
+
else this.#comments.set(sessionId, next);
|
|
1910
|
+
return true;
|
|
1911
|
+
}
|
|
1912
|
+
list(sessionId) {
|
|
1913
|
+
return this.#comments.get(sessionId) ?? [];
|
|
1914
|
+
}
|
|
1915
|
+
/** Remove and return every pending comment; called when a user turn consumes them. */
|
|
1916
|
+
drain(sessionId) {
|
|
1917
|
+
const existing = this.#comments.get(sessionId) ?? [];
|
|
1918
|
+
this.#comments.delete(sessionId);
|
|
1919
|
+
return existing;
|
|
1920
|
+
}
|
|
1921
|
+
disposeSession(sessionId) {
|
|
1922
|
+
this.#comments.delete(sessionId);
|
|
1923
|
+
}
|
|
1924
|
+
dispose() {
|
|
1925
|
+
this.#comments.clear();
|
|
1926
|
+
}
|
|
1927
|
+
};
|
|
1928
|
+
/** Render drained comments as the prompt block preceding the user's message text. */
|
|
1929
|
+
function formatReviewComments(comments) {
|
|
1930
|
+
return [
|
|
1931
|
+
"<user-review-comments>",
|
|
1932
|
+
"The user attached these code review comments to this message. Each references a file and line from the current working tree diff:",
|
|
1933
|
+
...comments.map((comment, index) => `${index + 1}. ${comment.path}:${comment.line}${comment.side === "old" ? " (old side)" : ""} — ${comment.text}`),
|
|
1934
|
+
"</user-review-comments>"
|
|
1935
|
+
].join("\n");
|
|
1936
|
+
}
|
|
1937
|
+
//#endregion
|
|
1833
1938
|
//#region src/adapter.ts
|
|
1834
1939
|
const MODELS = [
|
|
1835
1940
|
{
|
|
@@ -1926,6 +2031,22 @@ function validateImageRef(ref, attachments, imageIndex) {
|
|
|
1926
2031
|
const maxDimension = "maxImageDimension" in limits && finiteNonNegative(limits.maxImageDimension) ? limits.maxImageDimension : void 0;
|
|
1927
2032
|
if (!finiteNonNegative(ref.width) || !finiteNonNegative(ref.height) || ref.width * ref.height > limits.maxImagePixels || maxDimension !== void 0 && (ref.width > maxDimension || ref.height > maxDimension)) throw new Error(`dsh-claude: image ${imageIndex} exceeds the configured dimension limit`);
|
|
1928
2033
|
}
|
|
2034
|
+
/**
|
|
2035
|
+
* Prepend the session's pending diff-review comments to the outgoing user
|
|
2036
|
+
* prompt. The comments are drained once per turn: they are formatted into one
|
|
2037
|
+
* `<user-review-comments>` block placed ahead of the user's own text (or as a
|
|
2038
|
+
* leading text block for multi-part prompts) so Claude reads them in the same
|
|
2039
|
+
* turn that consumed them.
|
|
2040
|
+
*/
|
|
2041
|
+
function injectReviewComments(prompt, comments) {
|
|
2042
|
+
if (comments.length === 0) return prompt;
|
|
2043
|
+
const block = formatReviewComments(comments);
|
|
2044
|
+
if (typeof prompt === "string") return `${block}\n\n${prompt}`;
|
|
2045
|
+
return [{
|
|
2046
|
+
type: "text",
|
|
2047
|
+
text: block
|
|
2048
|
+
}, ...prompt];
|
|
2049
|
+
}
|
|
1929
2050
|
function imageBlock(data, mediaType) {
|
|
1930
2051
|
return {
|
|
1931
2052
|
type: "image",
|
|
@@ -2008,12 +2129,14 @@ var ClaudeCodeAdapter = class extends LlmAdapter {
|
|
|
2008
2129
|
#agents;
|
|
2009
2130
|
#attachments;
|
|
2010
2131
|
#presetIdFor;
|
|
2011
|
-
|
|
2132
|
+
#drainReviewComments;
|
|
2133
|
+
constructor(supervisor, agents, attachments, presetIdFor, drainReviewComments = () => []) {
|
|
2012
2134
|
super();
|
|
2013
2135
|
this.#supervisor = supervisor;
|
|
2014
2136
|
this.#agents = agents;
|
|
2015
2137
|
this.#attachments = attachments;
|
|
2016
2138
|
this.#presetIdFor = presetIdFor;
|
|
2139
|
+
this.#drainReviewComments = drainReviewComments;
|
|
2017
2140
|
}
|
|
2018
2141
|
providerInfo(provider) {
|
|
2019
2142
|
return {
|
|
@@ -2080,42 +2203,20 @@ var ClaudeCodeAdapter = class extends LlmAdapter {
|
|
|
2080
2203
|
}
|
|
2081
2204
|
const events = await this.#supervisor.runTurn({
|
|
2082
2205
|
agent,
|
|
2083
|
-
prompt,
|
|
2206
|
+
prompt: injectReviewComments(prompt, this.#drainReviewComments(agent.id)),
|
|
2084
2207
|
model: options.model,
|
|
2085
2208
|
...thinkingMode === void 0 ? {} : { thinkingMode },
|
|
2086
2209
|
...options.signal === void 0 ? {} : { signal: options.signal }
|
|
2087
2210
|
});
|
|
2088
|
-
let text = "";
|
|
2089
2211
|
let pendingUsage;
|
|
2090
2212
|
let completed = false;
|
|
2091
|
-
let blockIndex = 0;
|
|
2092
2213
|
try {
|
|
2093
|
-
for await (const event of events)
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
type: "block-start",
|
|
2099
|
-
index: blockIndex,
|
|
2100
|
-
blockType: "text"
|
|
2101
|
-
};
|
|
2102
|
-
yield {
|
|
2103
|
-
type: "text-delta",
|
|
2104
|
-
index: blockIndex,
|
|
2105
|
-
text
|
|
2106
|
-
};
|
|
2107
|
-
yield {
|
|
2108
|
-
type: "block-end",
|
|
2109
|
-
index: blockIndex,
|
|
2110
|
-
block: {
|
|
2111
|
-
type: "text",
|
|
2112
|
-
text
|
|
2113
|
-
}
|
|
2114
|
-
};
|
|
2115
|
-
blockIndex += 1;
|
|
2214
|
+
for await (const event of events) {
|
|
2215
|
+
if (event.type === "text-delta" || event.type === "segment-complete") continue;
|
|
2216
|
+
if (event.type === "usage") {
|
|
2217
|
+
pendingUsage = tokenUsage(event.usage);
|
|
2218
|
+
continue;
|
|
2116
2219
|
}
|
|
2117
|
-
text = "";
|
|
2118
|
-
if (event.type === "segment-complete") continue;
|
|
2119
2220
|
completed = true;
|
|
2120
2221
|
if (pendingUsage !== void 0) yield {
|
|
2121
2222
|
type: "usage",
|
|
@@ -2129,26 +2230,6 @@ var ClaudeCodeAdapter = class extends LlmAdapter {
|
|
|
2129
2230
|
} catch (error) {
|
|
2130
2231
|
if (error.name === "AbortError") {
|
|
2131
2232
|
completed = true;
|
|
2132
|
-
if (text.length > 0) {
|
|
2133
|
-
yield {
|
|
2134
|
-
type: "block-start",
|
|
2135
|
-
index: blockIndex,
|
|
2136
|
-
blockType: "text"
|
|
2137
|
-
};
|
|
2138
|
-
yield {
|
|
2139
|
-
type: "text-delta",
|
|
2140
|
-
index: blockIndex,
|
|
2141
|
-
text
|
|
2142
|
-
};
|
|
2143
|
-
yield {
|
|
2144
|
-
type: "block-end",
|
|
2145
|
-
index: blockIndex,
|
|
2146
|
-
block: {
|
|
2147
|
-
type: "text",
|
|
2148
|
-
text
|
|
2149
|
-
}
|
|
2150
|
-
};
|
|
2151
|
-
}
|
|
2152
2233
|
yield {
|
|
2153
2234
|
type: "finish",
|
|
2154
2235
|
reason: {
|
|
@@ -2166,8 +2247,8 @@ var ClaudeCodeAdapter = class extends LlmAdapter {
|
|
|
2166
2247
|
if (!completed) throw new Error("dsh-claude: Claude turn stream ended without a result");
|
|
2167
2248
|
}
|
|
2168
2249
|
};
|
|
2169
|
-
function createClaudeCodeAdapter(supervisor, agents, attachments, presetIdFor) {
|
|
2170
|
-
return new ClaudeCodeAdapter(supervisor, agents, attachments, presetIdFor);
|
|
2250
|
+
function createClaudeCodeAdapter(supervisor, agents, attachments, presetIdFor, drainReviewComments = () => []) {
|
|
2251
|
+
return new ClaudeCodeAdapter(supervisor, agents, attachments, presetIdFor, drainReviewComments);
|
|
2171
2252
|
}
|
|
2172
2253
|
//#endregion
|
|
2173
2254
|
//#region src/http.ts
|
|
@@ -2294,8 +2375,8 @@ function registerClaudeDoctorRoutes(ctx, runtime, supervisor, config, resolution
|
|
|
2294
2375
|
}
|
|
2295
2376
|
//#endregion
|
|
2296
2377
|
//#region src/projection-routes.ts
|
|
2297
|
-
const MAX_SESSION_ID_CHARS = 1024;
|
|
2298
|
-
function sessionIdFromUrl(rawUrl) {
|
|
2378
|
+
const MAX_SESSION_ID_CHARS$2 = 1024;
|
|
2379
|
+
function sessionIdFromUrl$1(rawUrl) {
|
|
2299
2380
|
try {
|
|
2300
2381
|
const pathname = new URL(rawUrl ?? "/", "http://localhost").pathname;
|
|
2301
2382
|
const prefix = `${CLAUDE_PROJECTION_PATH}/`;
|
|
@@ -2303,21 +2384,21 @@ function sessionIdFromUrl(rawUrl) {
|
|
|
2303
2384
|
const encoded = pathname.slice(prefix.length);
|
|
2304
2385
|
if (encoded.length === 0 || encoded.includes("/")) return void 0;
|
|
2305
2386
|
const sessionId = decodeURIComponent(encoded);
|
|
2306
|
-
if (sessionId.length === 0 || sessionId.length > MAX_SESSION_ID_CHARS) return void 0;
|
|
2387
|
+
if (sessionId.length === 0 || sessionId.length > MAX_SESSION_ID_CHARS$2) return void 0;
|
|
2307
2388
|
return sessionId;
|
|
2308
2389
|
} catch {
|
|
2309
2390
|
return;
|
|
2310
2391
|
}
|
|
2311
2392
|
}
|
|
2312
2393
|
/** Register the browser-readable, credential-free sidecar projection endpoint. */
|
|
2313
|
-
function registerClaudeProjectionRoute(ctx, sidecar, ownsSession, commandsForSession = () => [], repositoryForSession = async () => void 0) {
|
|
2394
|
+
function registerClaudeProjectionRoute(ctx, sidecar, ownsSession, commandsForSession = () => [], repositoryForSession = async () => void 0, reviewCommentsForSession = () => []) {
|
|
2314
2395
|
ctx.effect(() => ctx.webServer.register({
|
|
2315
2396
|
kind: "prefix",
|
|
2316
2397
|
path: CLAUDE_PROJECTION_PATH,
|
|
2317
2398
|
handler: async (req, res) => {
|
|
2318
2399
|
if (req.method !== "GET") return json(res, 405, { error: "method not allowed" });
|
|
2319
2400
|
if (!trustedRequest(req)) return json(res, 403, { error: "forbidden" });
|
|
2320
|
-
const sessionId = sessionIdFromUrl(req.url);
|
|
2401
|
+
const sessionId = sessionIdFromUrl$1(req.url);
|
|
2321
2402
|
if (sessionId === void 0) return json(res, 400, { error: "invalid session id" });
|
|
2322
2403
|
try {
|
|
2323
2404
|
const projection = await sidecar.read(sessionId);
|
|
@@ -2331,7 +2412,8 @@ function registerClaudeProjectionRoute(ctx, sidecar, ownsSession, commandsForSes
|
|
|
2331
2412
|
activities: projection.activities,
|
|
2332
2413
|
...projection.contextUsage === void 0 ? {} : { contextUsage: projection.contextUsage },
|
|
2333
2414
|
...projection.tasks === void 0 ? {} : { tasks: projection.tasks },
|
|
2334
|
-
...repository === void 0 ? {} : { repository }
|
|
2415
|
+
...repository === void 0 ? {} : { repository },
|
|
2416
|
+
reviewComments: owned ? reviewCommentsForSession(sessionId) : []
|
|
2335
2417
|
});
|
|
2336
2418
|
} catch {
|
|
2337
2419
|
return json(res, 500, { error: "projection unavailable" });
|
|
@@ -2341,16 +2423,17 @@ function registerClaudeProjectionRoute(ctx, sidecar, ownsSession, commandsForSes
|
|
|
2341
2423
|
}
|
|
2342
2424
|
//#endregion
|
|
2343
2425
|
//#region src/repository-status.ts
|
|
2344
|
-
const MAX_OUTPUT_BYTES$
|
|
2426
|
+
const MAX_OUTPUT_BYTES$2 = 65536;
|
|
2345
2427
|
const MAX_DIFF_BYTES = 262144;
|
|
2346
|
-
const
|
|
2428
|
+
const MAX_UNTRACKED_DIFFS = 50;
|
|
2429
|
+
const GIT_TIMEOUT_MS$2 = 5e3;
|
|
2347
2430
|
const GH_TIMEOUT_MS = 8e3;
|
|
2348
2431
|
const CACHE_TTL_MS = 5e3;
|
|
2349
2432
|
const MAX_TEXT_CHARS = 1024;
|
|
2350
2433
|
function bounded(value) {
|
|
2351
2434
|
return value.trim().slice(0, MAX_TEXT_CHARS);
|
|
2352
2435
|
}
|
|
2353
|
-
async function collect$
|
|
2436
|
+
async function collect$2(handle) {
|
|
2354
2437
|
const outcome = await handle.done;
|
|
2355
2438
|
const stdout = handle.collected.stdout?.readFrom(0);
|
|
2356
2439
|
return {
|
|
@@ -2359,15 +2442,15 @@ async function collect$1(handle) {
|
|
|
2359
2442
|
lossy: stdout?.lossy === true
|
|
2360
2443
|
};
|
|
2361
2444
|
}
|
|
2362
|
-
async function run(runtime, executable, args, cwd, timeoutMs, maxBytes = MAX_OUTPUT_BYTES$
|
|
2445
|
+
async function run(runtime, executable, args, cwd, timeoutMs, maxBytes = MAX_OUTPUT_BYTES$2) {
|
|
2363
2446
|
const signal = AbortSignal.timeout(timeoutMs);
|
|
2364
|
-
return collect$
|
|
2447
|
+
return collect$2(runtime.spawn({
|
|
2365
2448
|
argv: [executable, ...args],
|
|
2366
2449
|
cwd,
|
|
2367
2450
|
stdio: {
|
|
2368
2451
|
stdin: "ignore",
|
|
2369
2452
|
stdout: { maxBytes },
|
|
2370
|
-
stderr: { maxBytes: MAX_OUTPUT_BYTES$
|
|
2453
|
+
stderr: { maxBytes: MAX_OUTPUT_BYTES$2 }
|
|
2371
2454
|
},
|
|
2372
2455
|
graceMs: 1e3,
|
|
2373
2456
|
signal,
|
|
@@ -2381,6 +2464,9 @@ function parseGitStatus(output) {
|
|
|
2381
2464
|
let branch;
|
|
2382
2465
|
let detached = false;
|
|
2383
2466
|
let dirty = false;
|
|
2467
|
+
let upstream = false;
|
|
2468
|
+
let ahead;
|
|
2469
|
+
let behind;
|
|
2384
2470
|
for (const line of output.split(/\r?\n/u)) {
|
|
2385
2471
|
if (line.startsWith("# branch.head ")) {
|
|
2386
2472
|
const head = bounded(line.slice(14));
|
|
@@ -2388,12 +2474,27 @@ function parseGitStatus(output) {
|
|
|
2388
2474
|
else if (head.length > 0 && head !== "(unknown)") branch = head;
|
|
2389
2475
|
continue;
|
|
2390
2476
|
}
|
|
2477
|
+
if (line.startsWith("# branch.upstream ")) {
|
|
2478
|
+
upstream = true;
|
|
2479
|
+
continue;
|
|
2480
|
+
}
|
|
2481
|
+
if (line.startsWith("# branch.ab ")) {
|
|
2482
|
+
const counts = /^# branch\.ab \+(\d+) -(\d+)$/u.exec(line);
|
|
2483
|
+
if (counts !== null) {
|
|
2484
|
+
ahead = Number(counts[1]);
|
|
2485
|
+
behind = Number(counts[2]);
|
|
2486
|
+
}
|
|
2487
|
+
continue;
|
|
2488
|
+
}
|
|
2391
2489
|
if (line.length > 0 && !line.startsWith("# ")) dirty = true;
|
|
2392
2490
|
}
|
|
2393
2491
|
return {
|
|
2394
2492
|
...branch === void 0 ? {} : { branch },
|
|
2395
2493
|
detached,
|
|
2396
|
-
dirty
|
|
2494
|
+
dirty,
|
|
2495
|
+
upstream,
|
|
2496
|
+
...ahead === void 0 ? {} : { ahead },
|
|
2497
|
+
...behind === void 0 ? {} : { behind }
|
|
2397
2498
|
};
|
|
2398
2499
|
}
|
|
2399
2500
|
function parseDiffNumstat(value) {
|
|
@@ -2419,14 +2520,14 @@ function parseGitHubRemote(value) {
|
|
|
2419
2520
|
if (match?.[1] === void 0 || match[2] === void 0) return void 0;
|
|
2420
2521
|
return `${match[1]}/${match[2]}`;
|
|
2421
2522
|
}
|
|
2422
|
-
function record$
|
|
2523
|
+
function record$3(value) {
|
|
2423
2524
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
2424
2525
|
}
|
|
2425
2526
|
function aggregateChecks(value) {
|
|
2426
2527
|
if (!Array.isArray(value) || value.length === 0) return "none";
|
|
2427
2528
|
let pending = false;
|
|
2428
2529
|
for (const item of value) {
|
|
2429
|
-
const check = record$
|
|
2530
|
+
const check = record$3(item);
|
|
2430
2531
|
if (check === void 0) continue;
|
|
2431
2532
|
const conclusion = typeof check.conclusion === "string" ? check.conclusion.toUpperCase() : void 0;
|
|
2432
2533
|
const status = typeof check.status === "string" ? check.status.toUpperCase() : void 0;
|
|
@@ -2450,7 +2551,7 @@ function reviewState(value) {
|
|
|
2450
2551
|
return "none";
|
|
2451
2552
|
}
|
|
2452
2553
|
function parsePullRequest(value) {
|
|
2453
|
-
const input = record$
|
|
2554
|
+
const input = record$3(value);
|
|
2454
2555
|
if (input === void 0 || !Number.isSafeInteger(input.number) || Number(input.number) <= 0 || typeof input.title !== "string" || typeof input.url !== "string") return void 0;
|
|
2455
2556
|
let url;
|
|
2456
2557
|
try {
|
|
@@ -2471,8 +2572,9 @@ function parsePullRequest(value) {
|
|
|
2471
2572
|
review: reviewState(input.reviewDecision),
|
|
2472
2573
|
checks: aggregateChecks(input.statusCheckRollup),
|
|
2473
2574
|
...typeof input.mergeStateStatus === "string" ? { mergeState: bounded(input.mergeStateStatus) } : {},
|
|
2474
|
-
...typeof record$
|
|
2575
|
+
...typeof record$3(input.author)?.login === "string" ? { author: bounded(String(record$3(input.author)?.login)) } : {},
|
|
2475
2576
|
...typeof input.createdAt === "string" && Number.isFinite(Date.parse(input.createdAt)) ? { createdAt: new Date(input.createdAt).toISOString() } : {},
|
|
2577
|
+
...typeof input.mergedAt === "string" && Number.isFinite(Date.parse(input.mergedAt)) ? { mergedAt: new Date(input.mergedAt).toISOString() } : {},
|
|
2476
2578
|
...typeof input.baseRefName === "string" && bounded(input.baseRefName).length > 0 ? { baseBranch: bounded(input.baseRefName) } : {}
|
|
2477
2579
|
};
|
|
2478
2580
|
}
|
|
@@ -2498,6 +2600,10 @@ var RepositoryStatusService = class {
|
|
|
2498
2600
|
value.catch(() => this.#cache.delete(cwd));
|
|
2499
2601
|
return value;
|
|
2500
2602
|
}
|
|
2603
|
+
invalidate(cwd) {
|
|
2604
|
+
this.#cache.delete(cwd);
|
|
2605
|
+
this.#lastReady.delete(cwd);
|
|
2606
|
+
}
|
|
2501
2607
|
dispose() {
|
|
2502
2608
|
this.#cache.clear();
|
|
2503
2609
|
this.#lastReady.clear();
|
|
@@ -2539,7 +2645,7 @@ var RepositoryStatusService = class {
|
|
|
2539
2645
|
"--show-toplevel",
|
|
2540
2646
|
"--absolute-git-dir",
|
|
2541
2647
|
"--git-common-dir"
|
|
2542
|
-
], cwd, GIT_TIMEOUT_MS$
|
|
2648
|
+
], cwd, GIT_TIMEOUT_MS$2);
|
|
2543
2649
|
if (paths.exitCode !== 0) return {
|
|
2544
2650
|
status: "not-repository",
|
|
2545
2651
|
cwd: safeCwd
|
|
@@ -2556,8 +2662,8 @@ var RepositoryStatusService = class {
|
|
|
2556
2662
|
"status",
|
|
2557
2663
|
"--porcelain=v2",
|
|
2558
2664
|
"--branch",
|
|
2559
|
-
"--untracked-files=
|
|
2560
|
-
], cwd, GIT_TIMEOUT_MS$
|
|
2665
|
+
"--untracked-files=normal"
|
|
2666
|
+
], cwd, GIT_TIMEOUT_MS$2);
|
|
2561
2667
|
if (statusResult.exitCode !== 0) return {
|
|
2562
2668
|
status: "unavailable",
|
|
2563
2669
|
cwd: safeCwd
|
|
@@ -2567,7 +2673,7 @@ var RepositoryStatusService = class {
|
|
|
2567
2673
|
"remote",
|
|
2568
2674
|
"get-url",
|
|
2569
2675
|
"origin"
|
|
2570
|
-
], cwd, GIT_TIMEOUT_MS$
|
|
2676
|
+
], cwd, GIT_TIMEOUT_MS$2);
|
|
2571
2677
|
const remote = remoteResult.exitCode === 0 ? parseGitHubRemote(remoteResult.stdout) : void 0;
|
|
2572
2678
|
const pullRequest = status.branch === void 0 || remote === void 0 ? void 0 : await this.#pullRequest(cwd, remote, status.branch);
|
|
2573
2679
|
const diffBase = pullRequest?.baseBranch === void 0 ? "HEAD" : await this.#mergeBase(cwd, git, pullRequest.baseBranch) ?? "HEAD";
|
|
@@ -2600,7 +2706,7 @@ var RepositoryStatusService = class {
|
|
|
2600
2706
|
"merge-base",
|
|
2601
2707
|
"HEAD",
|
|
2602
2708
|
`refs/remotes/origin/${baseBranch}`
|
|
2603
|
-
], cwd, GIT_TIMEOUT_MS$
|
|
2709
|
+
], cwd, GIT_TIMEOUT_MS$2);
|
|
2604
2710
|
if (result.exitCode !== 0 || result.lossy) return void 0;
|
|
2605
2711
|
const oid = bounded(result.stdout);
|
|
2606
2712
|
return /^[0-9a-f]{40}$/iu.test(oid) ? oid : void 0;
|
|
@@ -2616,7 +2722,7 @@ var RepositoryStatusService = class {
|
|
|
2616
2722
|
"--numstat",
|
|
2617
2723
|
base,
|
|
2618
2724
|
"--"
|
|
2619
|
-
], cwd, GIT_TIMEOUT_MS$
|
|
2725
|
+
], cwd, GIT_TIMEOUT_MS$2);
|
|
2620
2726
|
if (numstat.exitCode !== 0 || numstat.lossy) return void 0;
|
|
2621
2727
|
const summary = parseDiffNumstat(numstat.stdout);
|
|
2622
2728
|
const patch = await run(this.#runtime, git, [
|
|
@@ -2626,20 +2732,70 @@ var RepositoryStatusService = class {
|
|
|
2626
2732
|
"--unified=3",
|
|
2627
2733
|
base,
|
|
2628
2734
|
"--"
|
|
2629
|
-
], cwd, GIT_TIMEOUT_MS$
|
|
2735
|
+
], cwd, GIT_TIMEOUT_MS$2, MAX_DIFF_BYTES);
|
|
2630
2736
|
if (patch.exitCode !== 0) return {
|
|
2631
2737
|
...summary,
|
|
2632
2738
|
truncated: true
|
|
2633
2739
|
};
|
|
2740
|
+
const untracked = await this.#untrackedDiff(cwd, git);
|
|
2741
|
+
const combinedPatch = `${patch.stdout}${untracked.patch}`;
|
|
2634
2742
|
return {
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2743
|
+
additions: summary.additions + untracked.additions,
|
|
2744
|
+
deletions: summary.deletions,
|
|
2745
|
+
files: summary.files + untracked.files,
|
|
2746
|
+
...patch.lossy ? {} : { patch: combinedPatch.slice(0, MAX_DIFF_BYTES) },
|
|
2747
|
+
truncated: patch.lossy || untracked.truncated || combinedPatch.length > MAX_DIFF_BYTES
|
|
2638
2748
|
};
|
|
2639
2749
|
} catch {
|
|
2640
2750
|
return;
|
|
2641
2751
|
}
|
|
2642
2752
|
}
|
|
2753
|
+
async #untrackedDiff(cwd, git) {
|
|
2754
|
+
const listed = await run(this.#runtime, git, [
|
|
2755
|
+
"ls-files",
|
|
2756
|
+
"--others",
|
|
2757
|
+
"--exclude-standard",
|
|
2758
|
+
"-z"
|
|
2759
|
+
], cwd, GIT_TIMEOUT_MS$2);
|
|
2760
|
+
if (listed.exitCode !== 0 || listed.lossy) return {
|
|
2761
|
+
additions: 0,
|
|
2762
|
+
files: 0,
|
|
2763
|
+
patch: "",
|
|
2764
|
+
truncated: listed.lossy
|
|
2765
|
+
};
|
|
2766
|
+
const paths = listed.stdout.split("\0").filter((path) => path.length > 0);
|
|
2767
|
+
let additions = 0;
|
|
2768
|
+
let patch = "";
|
|
2769
|
+
let truncated = paths.length > MAX_UNTRACKED_DIFFS;
|
|
2770
|
+
for (const path of paths.slice(0, MAX_UNTRACKED_DIFFS)) {
|
|
2771
|
+
const result = await run(this.#runtime, git, [
|
|
2772
|
+
"diff",
|
|
2773
|
+
"--no-ext-diff",
|
|
2774
|
+
"--no-color",
|
|
2775
|
+
"--unified=3",
|
|
2776
|
+
"--numstat",
|
|
2777
|
+
"--patch",
|
|
2778
|
+
"--no-index",
|
|
2779
|
+
"--",
|
|
2780
|
+
"/dev/null",
|
|
2781
|
+
path
|
|
2782
|
+
], cwd, GIT_TIMEOUT_MS$2, MAX_DIFF_BYTES);
|
|
2783
|
+
if (result.exitCode !== 0 && result.exitCode !== 1) {
|
|
2784
|
+
truncated = true;
|
|
2785
|
+
continue;
|
|
2786
|
+
}
|
|
2787
|
+
const start = result.stdout.indexOf("diff --git ");
|
|
2788
|
+
additions += parseDiffNumstat(start >= 0 ? result.stdout.slice(0, start) : result.stdout).additions;
|
|
2789
|
+
if (result.lossy) truncated = true;
|
|
2790
|
+
else if (start >= 0) patch += result.stdout.slice(start);
|
|
2791
|
+
}
|
|
2792
|
+
return {
|
|
2793
|
+
additions,
|
|
2794
|
+
files: paths.length,
|
|
2795
|
+
patch,
|
|
2796
|
+
truncated
|
|
2797
|
+
};
|
|
2798
|
+
}
|
|
2643
2799
|
async #pullRequest(cwd, repository, branch) {
|
|
2644
2800
|
const gh = await this.#gh();
|
|
2645
2801
|
if (gh === void 0) return void 0;
|
|
@@ -2662,8 +2818,8 @@ var RepositoryStatusService = class {
|
|
|
2662
2818
|
};
|
|
2663
2819
|
//#endregion
|
|
2664
2820
|
//#region src/repository-setup.ts
|
|
2665
|
-
const MAX_OUTPUT_BYTES = 131072;
|
|
2666
|
-
const GIT_TIMEOUT_MS = 1e4;
|
|
2821
|
+
const MAX_OUTPUT_BYTES$1 = 131072;
|
|
2822
|
+
const GIT_TIMEOUT_MS$1 = 1e4;
|
|
2667
2823
|
const GIT_FETCH_TIMEOUT_MS = 6e4;
|
|
2668
2824
|
const MAX_PATH_CHARS = 4096;
|
|
2669
2825
|
const MAX_BRANCH_CHARS = 512;
|
|
@@ -2676,7 +2832,7 @@ var RepositorySetupError = class extends Error {
|
|
|
2676
2832
|
this.code = code;
|
|
2677
2833
|
}
|
|
2678
2834
|
};
|
|
2679
|
-
async function collect(handle) {
|
|
2835
|
+
async function collect$1(handle) {
|
|
2680
2836
|
const outcome = await handle.done;
|
|
2681
2837
|
const stdout = handle.collected.stdout?.readFrom(0);
|
|
2682
2838
|
const stderr = handle.collected.stderr?.readFrom(0);
|
|
@@ -2969,14 +3125,14 @@ var RepositorySetupService = class {
|
|
|
2969
3125
|
this.#gitPath ??= this.#runtime.resolveExecutable("git");
|
|
2970
3126
|
return this.#gitPath;
|
|
2971
3127
|
}
|
|
2972
|
-
async #run(executable, args, cwd, timeoutMs = GIT_TIMEOUT_MS) {
|
|
2973
|
-
return collect(this.#runtime.spawn({
|
|
3128
|
+
async #run(executable, args, cwd, timeoutMs = GIT_TIMEOUT_MS$1) {
|
|
3129
|
+
return collect$1(this.#runtime.spawn({
|
|
2974
3130
|
argv: [executable, ...args],
|
|
2975
3131
|
cwd,
|
|
2976
3132
|
stdio: {
|
|
2977
3133
|
stdin: "ignore",
|
|
2978
|
-
stdout: { maxBytes: MAX_OUTPUT_BYTES },
|
|
2979
|
-
stderr: { maxBytes: MAX_OUTPUT_BYTES }
|
|
3134
|
+
stdout: { maxBytes: MAX_OUTPUT_BYTES$1 },
|
|
3135
|
+
stderr: { maxBytes: MAX_OUTPUT_BYTES$1 }
|
|
2980
3136
|
},
|
|
2981
3137
|
graceMs: 1e3,
|
|
2982
3138
|
signal: AbortSignal.timeout(timeoutMs),
|
|
@@ -3016,30 +3172,400 @@ var RepositorySetupService = class {
|
|
|
3016
3172
|
}
|
|
3017
3173
|
};
|
|
3018
3174
|
//#endregion
|
|
3175
|
+
//#region src/repository-actions.ts
|
|
3176
|
+
const MAX_OUTPUT_BYTES = 262144;
|
|
3177
|
+
const MAX_PATCH_CHARS = 65536;
|
|
3178
|
+
const MAX_MESSAGE_CHARS = 512;
|
|
3179
|
+
const MAX_PR_TEXT_CHARS = 8192;
|
|
3180
|
+
const MAX_UNPUSHED_COMMITS = 20;
|
|
3181
|
+
const GIT_TIMEOUT_MS = 15e3;
|
|
3182
|
+
const REMOTE_TIMEOUT_MS = 6e4;
|
|
3183
|
+
const GENERATE_TIMEOUT_MS = 6e4;
|
|
3184
|
+
var RepositoryActionError = class extends Error {
|
|
3185
|
+
code;
|
|
3186
|
+
commit;
|
|
3187
|
+
constructor(code, message, commit) {
|
|
3188
|
+
super(message);
|
|
3189
|
+
this.name = "RepositoryActionError";
|
|
3190
|
+
this.code = code;
|
|
3191
|
+
if (commit !== void 0) this.commit = commit;
|
|
3192
|
+
}
|
|
3193
|
+
};
|
|
3194
|
+
async function collect(handle) {
|
|
3195
|
+
const outcome = await handle.done;
|
|
3196
|
+
const stdout = handle.collected.stdout?.readFrom(0);
|
|
3197
|
+
const stderr = handle.collected.stderr?.readFrom(0);
|
|
3198
|
+
return {
|
|
3199
|
+
exitCode: outcome.exitCode,
|
|
3200
|
+
stdout: stdout?.text ?? "",
|
|
3201
|
+
stderr: stderr?.text ?? "",
|
|
3202
|
+
lossy: stdout?.lossy === true || stderr?.lossy === true
|
|
3203
|
+
};
|
|
3204
|
+
}
|
|
3205
|
+
function safeText(value, maximum, label) {
|
|
3206
|
+
const text = value.trim();
|
|
3207
|
+
if (text.length === 0 || text.length > maximum || /[\0\r]/u.test(text)) throw new RepositoryActionError("invalid-request", `${label} is invalid.`);
|
|
3208
|
+
return text;
|
|
3209
|
+
}
|
|
3210
|
+
function isProtectedWarpPath(path) {
|
|
3211
|
+
return basename(path.replaceAll("\\", "/")).toLocaleLowerCase("en-US") === "warp.md";
|
|
3212
|
+
}
|
|
3213
|
+
function parseRepositoryActionStatus(output) {
|
|
3214
|
+
const files = /* @__PURE__ */ new Map();
|
|
3215
|
+
const records = output.includes("\0") ? output.split("\0") : output.split(/\r?\n/u);
|
|
3216
|
+
for (let position = 0; position < records.length; position += 1) {
|
|
3217
|
+
const line = records[position] ?? "";
|
|
3218
|
+
if (line.length < 4) continue;
|
|
3219
|
+
const index = line[0] ?? " ";
|
|
3220
|
+
const worktree = line[1] ?? " ";
|
|
3221
|
+
let path = line.slice(3);
|
|
3222
|
+
const rename = path.lastIndexOf(" -> ");
|
|
3223
|
+
if (rename >= 0) path = path.slice(rename + 4);
|
|
3224
|
+
if (output.includes("\0") && (index === "R" || index === "C" || worktree === "R" || worktree === "C")) position += 1;
|
|
3225
|
+
if (path.length === 0 || path.includes("\0") || isProtectedWarpPath(path)) continue;
|
|
3226
|
+
files.set(path, {
|
|
3227
|
+
path,
|
|
3228
|
+
staged: index !== " " && index !== "?",
|
|
3229
|
+
unstaged: worktree !== " " && worktree !== "?",
|
|
3230
|
+
untracked: index === "?" && worktree === "?"
|
|
3231
|
+
});
|
|
3232
|
+
}
|
|
3233
|
+
return [...files.values()].sort((left, right) => left.path.localeCompare(right.path));
|
|
3234
|
+
}
|
|
3235
|
+
function fallbackCommitMessage(files) {
|
|
3236
|
+
if (files.length === 1) return `Update ${files[0]?.path ?? "repository files"}`;
|
|
3237
|
+
return `Update ${files.length} repository files`;
|
|
3238
|
+
}
|
|
3239
|
+
function normalizedGeneratedMessage(value, fallback) {
|
|
3240
|
+
const first = value.split(/\r?\n/u).map((line) => line.trim()).find(Boolean);
|
|
3241
|
+
if (first === void 0) return fallback;
|
|
3242
|
+
const message = first.replace(/^['"`]+|['"`]+$/gu, "").replace(/[\0\r\n]/gu, " ").trim();
|
|
3243
|
+
return message.length === 0 || message.length > 72 ? fallback : message;
|
|
3244
|
+
}
|
|
3245
|
+
function validPrUrl(value) {
|
|
3246
|
+
try {
|
|
3247
|
+
const url = new URL(value.trim());
|
|
3248
|
+
return url.protocol === "https:" && url.hostname === "github.com" ? url.href : void 0;
|
|
3249
|
+
} catch {
|
|
3250
|
+
return;
|
|
3251
|
+
}
|
|
3252
|
+
}
|
|
3253
|
+
function validPullRequestBody(value) {
|
|
3254
|
+
const body = value.trim();
|
|
3255
|
+
const match = /^Summary:\s+([^\r\n]+)\r?\n\r?\nChanges:\s*\r?\n([\s\S]+)$/u.exec(body);
|
|
3256
|
+
const summary = match?.[1]?.trim();
|
|
3257
|
+
const changes = match?.[2]?.trim();
|
|
3258
|
+
if (summary === void 0 || summary.length === 0 || changes === void 0 || changes.length === 0) return false;
|
|
3259
|
+
return !/^#{1,6}\s|^[A-Za-z][A-Za-z ]+:\s*$/mu.test(changes);
|
|
3260
|
+
}
|
|
3261
|
+
var RepositoryActionService = class {
|
|
3262
|
+
#runtime;
|
|
3263
|
+
#claudeExecutable;
|
|
3264
|
+
#invalidate;
|
|
3265
|
+
#gitExecutable;
|
|
3266
|
+
#ghExecutable;
|
|
3267
|
+
#pending = Promise.resolve();
|
|
3268
|
+
constructor(runtime, claudeExecutable, invalidate = () => {}) {
|
|
3269
|
+
this.#runtime = runtime;
|
|
3270
|
+
this.#claudeExecutable = claudeExecutable;
|
|
3271
|
+
this.#invalidate = invalidate;
|
|
3272
|
+
}
|
|
3273
|
+
preview(cwd) {
|
|
3274
|
+
return this.#preview(cwd);
|
|
3275
|
+
}
|
|
3276
|
+
async generateMessage(cwd, fingerprint) {
|
|
3277
|
+
const preview = await this.#preview(cwd);
|
|
3278
|
+
if (preview.fingerprint !== fingerprint) throw new RepositoryActionError("repository-changed", "Repository changes have changed. Refresh the commit panel.");
|
|
3279
|
+
const fallback = fallbackCommitMessage(preview.files);
|
|
3280
|
+
const prompt = [
|
|
3281
|
+
"Write one concise English git commit subject (imperative mood, maximum 72 characters).",
|
|
3282
|
+
"Return only the subject without quotes, markdown, body, or explanation.",
|
|
3283
|
+
`Files: ${preview.files.map((file) => file.path).join(", ")}`,
|
|
3284
|
+
`Diff:\n${preview.patch.slice(0, 24576)}`
|
|
3285
|
+
].join("\n");
|
|
3286
|
+
try {
|
|
3287
|
+
const result = await this.#run(this.#claudeExecutable, [
|
|
3288
|
+
"-p",
|
|
3289
|
+
"--tools",
|
|
3290
|
+
"",
|
|
3291
|
+
"--output-format",
|
|
3292
|
+
"text",
|
|
3293
|
+
prompt
|
|
3294
|
+
], preview.root, GENERATE_TIMEOUT_MS);
|
|
3295
|
+
return result.exitCode === 0 && !result.lossy ? normalizedGeneratedMessage(result.stdout, fallback) : fallback;
|
|
3296
|
+
} catch {
|
|
3297
|
+
return fallback;
|
|
3298
|
+
}
|
|
3299
|
+
}
|
|
3300
|
+
execute(cwd, request) {
|
|
3301
|
+
const operation = this.#pending.then(() => this.#execute(cwd, request));
|
|
3302
|
+
this.#pending = operation.then(() => void 0, () => void 0);
|
|
3303
|
+
return operation;
|
|
3304
|
+
}
|
|
3305
|
+
async #execute(cwd, request) {
|
|
3306
|
+
const before = await this.#preview(cwd);
|
|
3307
|
+
if (before.fingerprint !== request.fingerprint) throw new RepositoryActionError("repository-changed", "Repository changes have changed. Refresh the commit panel.");
|
|
3308
|
+
if (request.action === "push") {
|
|
3309
|
+
const git = await this.#git();
|
|
3310
|
+
try {
|
|
3311
|
+
await this.#push(git, before.root, before.branch);
|
|
3312
|
+
} catch (error) {
|
|
3313
|
+
throw new RepositoryActionError("push-failed", error instanceof Error ? error.message : "Git push failed.");
|
|
3314
|
+
}
|
|
3315
|
+
this.#invalidate(before.root);
|
|
3316
|
+
return {
|
|
3317
|
+
commit: before.head,
|
|
3318
|
+
pushed: true
|
|
3319
|
+
};
|
|
3320
|
+
}
|
|
3321
|
+
const message = safeText(request.message, MAX_MESSAGE_CHARS, "Commit message");
|
|
3322
|
+
if (before.files.length === 0 && request.action !== "create-pr") throw new RepositoryActionError("nothing-to-commit", "There are no changes to commit.");
|
|
3323
|
+
const git = await this.#git();
|
|
3324
|
+
let oid = before.head;
|
|
3325
|
+
if (before.files.length > 0) {
|
|
3326
|
+
await this.#rejectStagedWarp(git, before.root);
|
|
3327
|
+
if (request.includeUnstaged) {
|
|
3328
|
+
const paths = before.files.filter((file) => file.unstaged || file.untracked).map((file) => file.path);
|
|
3329
|
+
if (paths.length > 0) await this.#mustRun(git, [
|
|
3330
|
+
"add",
|
|
3331
|
+
"--",
|
|
3332
|
+
...paths
|
|
3333
|
+
], before.root, GIT_TIMEOUT_MS, "stage-failed", "Changes could not be staged.");
|
|
3334
|
+
}
|
|
3335
|
+
await this.#rejectStagedWarp(git, before.root);
|
|
3336
|
+
const staged = await this.#run(git, [
|
|
3337
|
+
"diff",
|
|
3338
|
+
"--cached",
|
|
3339
|
+
"--quiet",
|
|
3340
|
+
"--exit-code",
|
|
3341
|
+
"--"
|
|
3342
|
+
], before.root, GIT_TIMEOUT_MS);
|
|
3343
|
+
if (staged.exitCode === 0) throw new RepositoryActionError("nothing-to-commit", "There are no staged changes to commit.");
|
|
3344
|
+
if (staged.exitCode !== 1) throw new RepositoryActionError("repository-unavailable", "The staged changes could not be verified.");
|
|
3345
|
+
await this.#mustRun(git, [
|
|
3346
|
+
"commit",
|
|
3347
|
+
"-m",
|
|
3348
|
+
message,
|
|
3349
|
+
"--"
|
|
3350
|
+
], before.root, GIT_TIMEOUT_MS, "commit-failed", "Git commit failed.");
|
|
3351
|
+
oid = (await this.#mustRun(git, ["rev-parse", "HEAD"], before.root, GIT_TIMEOUT_MS, "commit-failed", "The new commit could not be verified.")).stdout.trim();
|
|
3352
|
+
this.#invalidate(before.root);
|
|
3353
|
+
if (request.action === "commit") return {
|
|
3354
|
+
commit: oid,
|
|
3355
|
+
pushed: false
|
|
3356
|
+
};
|
|
3357
|
+
}
|
|
3358
|
+
try {
|
|
3359
|
+
await this.#push(git, before.root, before.branch);
|
|
3360
|
+
} catch (error) {
|
|
3361
|
+
throw new RepositoryActionError("push-failed", error instanceof Error ? error.message : "Git push failed.", oid);
|
|
3362
|
+
}
|
|
3363
|
+
this.#invalidate(before.root);
|
|
3364
|
+
if (request.action === "commit-push") return {
|
|
3365
|
+
commit: oid,
|
|
3366
|
+
pushed: true
|
|
3367
|
+
};
|
|
3368
|
+
const title = safeText(request.prTitle ?? message, 256, "Pull request title");
|
|
3369
|
+
const body = safeText(request.prBody ?? "", MAX_PR_TEXT_CHARS, "Pull request description");
|
|
3370
|
+
if (!validPullRequestBody(body)) throw new RepositoryActionError("invalid-pr-description", "Pull request description must contain only Summary and Changes sections.", oid);
|
|
3371
|
+
let gh;
|
|
3372
|
+
try {
|
|
3373
|
+
gh = await this.#gh();
|
|
3374
|
+
} catch (error) {
|
|
3375
|
+
throw new RepositoryActionError("gh-unavailable", error instanceof Error ? error.message : "GitHub CLI is unavailable.", oid);
|
|
3376
|
+
}
|
|
3377
|
+
const args = [
|
|
3378
|
+
"pr",
|
|
3379
|
+
"create",
|
|
3380
|
+
"--title",
|
|
3381
|
+
title,
|
|
3382
|
+
"--body",
|
|
3383
|
+
body
|
|
3384
|
+
];
|
|
3385
|
+
if (request.draft !== false) args.push("--draft");
|
|
3386
|
+
if (request.baseBranch !== void 0) args.push("--base", safeText(request.baseBranch, 512, "Base branch"));
|
|
3387
|
+
const created = await this.#run(gh, args, before.root, REMOTE_TIMEOUT_MS);
|
|
3388
|
+
const url = created.exitCode === 0 && !created.lossy ? validPrUrl(created.stdout) : void 0;
|
|
3389
|
+
if (url === void 0) throw new RepositoryActionError("pr-failed", "The pull request could not be created.", oid);
|
|
3390
|
+
return {
|
|
3391
|
+
commit: oid,
|
|
3392
|
+
pushed: true,
|
|
3393
|
+
pullRequestUrl: url
|
|
3394
|
+
};
|
|
3395
|
+
}
|
|
3396
|
+
async #preview(cwd) {
|
|
3397
|
+
const git = await this.#git();
|
|
3398
|
+
const root = (await this.#mustRun(git, [
|
|
3399
|
+
"rev-parse",
|
|
3400
|
+
"--path-format=absolute",
|
|
3401
|
+
"--show-toplevel"
|
|
3402
|
+
], cwd, GIT_TIMEOUT_MS, "not-repository", "The session directory is not a Git repository.")).stdout.trim();
|
|
3403
|
+
const [branchResult, headResult, statusResult, stagedPatch, unstagedPatch] = await Promise.all([
|
|
3404
|
+
this.#run(git, [
|
|
3405
|
+
"symbolic-ref",
|
|
3406
|
+
"--quiet",
|
|
3407
|
+
"--short",
|
|
3408
|
+
"HEAD"
|
|
3409
|
+
], root, GIT_TIMEOUT_MS),
|
|
3410
|
+
this.#run(git, ["rev-parse", "HEAD"], root, GIT_TIMEOUT_MS),
|
|
3411
|
+
this.#run(git, [
|
|
3412
|
+
"status",
|
|
3413
|
+
"--porcelain=v1",
|
|
3414
|
+
"-z",
|
|
3415
|
+
"--untracked-files=all"
|
|
3416
|
+
], root, GIT_TIMEOUT_MS),
|
|
3417
|
+
this.#run(git, [
|
|
3418
|
+
"diff",
|
|
3419
|
+
"--cached",
|
|
3420
|
+
"--no-ext-diff",
|
|
3421
|
+
"--no-color",
|
|
3422
|
+
"--unified=3",
|
|
3423
|
+
"--",
|
|
3424
|
+
":(exclude)WARP.md",
|
|
3425
|
+
":(exclude)**/WARP.md"
|
|
3426
|
+
], root, GIT_TIMEOUT_MS, MAX_OUTPUT_BYTES),
|
|
3427
|
+
this.#run(git, [
|
|
3428
|
+
"diff",
|
|
3429
|
+
"--no-ext-diff",
|
|
3430
|
+
"--no-color",
|
|
3431
|
+
"--unified=3",
|
|
3432
|
+
"--",
|
|
3433
|
+
":(exclude)WARP.md",
|
|
3434
|
+
":(exclude)**/WARP.md"
|
|
3435
|
+
], root, GIT_TIMEOUT_MS, MAX_OUTPUT_BYTES)
|
|
3436
|
+
]);
|
|
3437
|
+
if (branchResult.exitCode !== 0) throw new RepositoryActionError("detached-head", "A detached HEAD cannot be committed from this panel.");
|
|
3438
|
+
if (headResult.exitCode !== 0 || statusResult.exitCode !== 0 || statusResult.lossy) throw new RepositoryActionError("repository-unavailable", "Repository state is unavailable.");
|
|
3439
|
+
const files = parseRepositoryActionStatus(statusResult.stdout);
|
|
3440
|
+
const patch = `${stagedPatch.stdout}${stagedPatch.stdout.length > 0 && unstagedPatch.stdout.length > 0 ? "\n" : ""}${unstagedPatch.stdout}`.slice(0, MAX_PATCH_CHARS);
|
|
3441
|
+
const branch = branchResult.stdout.trim();
|
|
3442
|
+
const head = headResult.stdout.trim();
|
|
3443
|
+
const fingerprint = createHash("sha256").update([
|
|
3444
|
+
head,
|
|
3445
|
+
branch,
|
|
3446
|
+
statusResult.stdout,
|
|
3447
|
+
stagedPatch.stdout,
|
|
3448
|
+
unstagedPatch.stdout
|
|
3449
|
+
].join("\0")).digest("hex");
|
|
3450
|
+
const upstreamResult = await this.#run(git, [
|
|
3451
|
+
"rev-parse",
|
|
3452
|
+
"--abbrev-ref",
|
|
3453
|
+
"--symbolic-full-name",
|
|
3454
|
+
"@{upstream}"
|
|
3455
|
+
], root, GIT_TIMEOUT_MS);
|
|
3456
|
+
const upstream = upstreamResult.exitCode === 0 && !upstreamResult.lossy ? upstreamResult.stdout.trim() : void 0;
|
|
3457
|
+
const logResult = await this.#run(git, [
|
|
3458
|
+
"log",
|
|
3459
|
+
"--format=%H%x09%s",
|
|
3460
|
+
`-n`,
|
|
3461
|
+
String(21),
|
|
3462
|
+
upstream === void 0 ? "HEAD" : "@{upstream}..HEAD",
|
|
3463
|
+
"--"
|
|
3464
|
+
], root, GIT_TIMEOUT_MS);
|
|
3465
|
+
const commitLines = logResult.exitCode === 0 && !logResult.lossy ? logResult.stdout.split(/\r?\n/u).filter((line) => line.includes(" ")) : [];
|
|
3466
|
+
const unpushedCommits = commitLines.slice(0, MAX_UNPUSHED_COMMITS).flatMap((line) => {
|
|
3467
|
+
const tab = line.indexOf(" ");
|
|
3468
|
+
const hash = line.slice(0, tab);
|
|
3469
|
+
return /^[0-9a-f]{40}$/iu.test(hash) ? [{
|
|
3470
|
+
hash,
|
|
3471
|
+
subject: line.slice(tab + 1).slice(0, 140)
|
|
3472
|
+
}] : [];
|
|
3473
|
+
});
|
|
3474
|
+
return {
|
|
3475
|
+
root,
|
|
3476
|
+
branch,
|
|
3477
|
+
head,
|
|
3478
|
+
fingerprint,
|
|
3479
|
+
files,
|
|
3480
|
+
patch,
|
|
3481
|
+
truncated: stagedPatch.lossy || unstagedPatch.lossy || stagedPatch.stdout.length + unstagedPatch.stdout.length > MAX_PATCH_CHARS,
|
|
3482
|
+
hasStaged: files.some((file) => file.staged),
|
|
3483
|
+
hasUnstaged: files.some((file) => file.unstaged),
|
|
3484
|
+
hasUntracked: files.some((file) => file.untracked),
|
|
3485
|
+
...upstream === void 0 ? {} : { upstream },
|
|
3486
|
+
unpushedCommits,
|
|
3487
|
+
unpushedTruncated: commitLines.length > MAX_UNPUSHED_COMMITS
|
|
3488
|
+
};
|
|
3489
|
+
}
|
|
3490
|
+
async #rejectStagedWarp(git, cwd) {
|
|
3491
|
+
const staged = await this.#run(git, [
|
|
3492
|
+
"diff",
|
|
3493
|
+
"--cached",
|
|
3494
|
+
"--name-only",
|
|
3495
|
+
"--"
|
|
3496
|
+
], cwd, GIT_TIMEOUT_MS);
|
|
3497
|
+
if (staged.exitCode !== 0 || staged.lossy) throw new RepositoryActionError("repository-unavailable", "Staged files could not be verified.");
|
|
3498
|
+
if (staged.stdout.split(/\r?\n/u).some((path) => path.length > 0 && isProtectedWarpPath(path))) throw new RepositoryActionError("protected-warp-file", "WARP.md files cannot be committed. Unstage them before continuing.");
|
|
3499
|
+
}
|
|
3500
|
+
async #push(git, cwd, branch) {
|
|
3501
|
+
const args = (await this.#run(git, [
|
|
3502
|
+
"rev-parse",
|
|
3503
|
+
"--abbrev-ref",
|
|
3504
|
+
"--symbolic-full-name",
|
|
3505
|
+
"@{upstream}"
|
|
3506
|
+
], cwd, GIT_TIMEOUT_MS)).exitCode === 0 ? ["push"] : [
|
|
3507
|
+
"push",
|
|
3508
|
+
"--set-upstream",
|
|
3509
|
+
"origin",
|
|
3510
|
+
branch
|
|
3511
|
+
];
|
|
3512
|
+
await this.#mustRun(git, args, cwd, REMOTE_TIMEOUT_MS, "push-failed", "Git push failed.");
|
|
3513
|
+
}
|
|
3514
|
+
#git() {
|
|
3515
|
+
this.#gitExecutable ??= this.#runtime.resolveExecutable("git");
|
|
3516
|
+
return this.#gitExecutable;
|
|
3517
|
+
}
|
|
3518
|
+
#gh() {
|
|
3519
|
+
this.#ghExecutable ??= this.#runtime.resolveExecutable("gh").catch(() => {
|
|
3520
|
+
throw new RepositoryActionError("gh-unavailable", "GitHub CLI is unavailable.");
|
|
3521
|
+
});
|
|
3522
|
+
return this.#ghExecutable;
|
|
3523
|
+
}
|
|
3524
|
+
async #mustRun(executable, args, cwd, timeoutMs, code, message) {
|
|
3525
|
+
const result = await this.#run(executable, args, cwd, timeoutMs);
|
|
3526
|
+
if (result.exitCode !== 0 || result.lossy) throw new RepositoryActionError(code, message);
|
|
3527
|
+
return result;
|
|
3528
|
+
}
|
|
3529
|
+
#run(executable, args, cwd, timeoutMs, maxBytes = MAX_OUTPUT_BYTES) {
|
|
3530
|
+
return collect(this.#runtime.spawn({
|
|
3531
|
+
argv: [executable, ...args],
|
|
3532
|
+
cwd,
|
|
3533
|
+
stdio: {
|
|
3534
|
+
stdin: "ignore",
|
|
3535
|
+
stdout: { maxBytes },
|
|
3536
|
+
stderr: { maxBytes: MAX_OUTPUT_BYTES }
|
|
3537
|
+
},
|
|
3538
|
+
graceMs: 1e3,
|
|
3539
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
3540
|
+
env: {}
|
|
3541
|
+
}));
|
|
3542
|
+
}
|
|
3543
|
+
};
|
|
3544
|
+
//#endregion
|
|
3019
3545
|
//#region src/repository-setup-routes.ts
|
|
3020
|
-
const MAX_BODY_BYTES = 16384;
|
|
3021
|
-
function record(value) {
|
|
3546
|
+
const MAX_BODY_BYTES$2 = 16384;
|
|
3547
|
+
function record$2(value) {
|
|
3022
3548
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
3023
3549
|
}
|
|
3024
|
-
async function readJson(req) {
|
|
3550
|
+
async function readJson$2(req) {
|
|
3025
3551
|
const chunks = [];
|
|
3026
3552
|
let size = 0;
|
|
3027
3553
|
for await (const chunk of req) {
|
|
3028
3554
|
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
3029
3555
|
size += buffer.length;
|
|
3030
|
-
if (size > MAX_BODY_BYTES) throw new RepositorySetupError("body-too-large", "The request body is too large.");
|
|
3556
|
+
if (size > MAX_BODY_BYTES$2) throw new RepositorySetupError("body-too-large", "The request body is too large.");
|
|
3031
3557
|
chunks.push(buffer);
|
|
3032
3558
|
}
|
|
3033
|
-
const value = record(JSON.parse(Buffer.concat(chunks).toString("utf8")));
|
|
3559
|
+
const value = record$2(JSON.parse(Buffer.concat(chunks).toString("utf8")));
|
|
3034
3560
|
if (value === void 0) throw new RepositorySetupError("invalid-request", "The request body is invalid.");
|
|
3035
3561
|
return value;
|
|
3036
3562
|
}
|
|
3037
|
-
function string(input, key) {
|
|
3563
|
+
function string$1(input, key) {
|
|
3038
3564
|
const value = input[key];
|
|
3039
3565
|
if (typeof value !== "string") throw new RepositorySetupError("invalid-request", `The ${key} field is required.`);
|
|
3040
3566
|
return value;
|
|
3041
3567
|
}
|
|
3042
|
-
function optionalString(input, key) {
|
|
3568
|
+
function optionalString$1(input, key) {
|
|
3043
3569
|
const value = input[key];
|
|
3044
3570
|
if (value === void 0) return void 0;
|
|
3045
3571
|
if (typeof value !== "string") throw new RepositorySetupError("invalid-request", `The ${key} field must be a string.`);
|
|
@@ -3062,7 +3588,7 @@ async function streamSetup(res, service, input) {
|
|
|
3062
3588
|
try {
|
|
3063
3589
|
ndjson(res, {
|
|
3064
3590
|
type: "complete",
|
|
3065
|
-
result: await service.setup(string(input, "cwd"), string(input, "branch"), input.worktree, optionalString(input, "branchName"), (stage) => {
|
|
3591
|
+
result: await service.setup(string$1(input, "cwd"), string$1(input, "branch"), input.worktree, optionalString$1(input, "branchName"), (stage) => {
|
|
3066
3592
|
if (!ended) ndjson(res, {
|
|
3067
3593
|
type: "progress",
|
|
3068
3594
|
stage
|
|
@@ -3098,15 +3624,15 @@ function registerRepositorySetupRoute(ctx, service) {
|
|
|
3098
3624
|
}
|
|
3099
3625
|
if (pathname === "/plugins/dsh-claude/repository/setup") {
|
|
3100
3626
|
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
3101
|
-
const input = await readJson(req);
|
|
3627
|
+
const input = await readJson$2(req);
|
|
3102
3628
|
if (typeof input.worktree !== "boolean") throw new RepositorySetupError("invalid-request", "The worktree field is required.");
|
|
3103
3629
|
await streamSetup(res, service, input);
|
|
3104
3630
|
return;
|
|
3105
3631
|
}
|
|
3106
3632
|
if (pathname === `/plugins/dsh-claude/repository/setup/bind`) {
|
|
3107
3633
|
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
3108
|
-
const input = await readJson(req);
|
|
3109
|
-
await service.bindLease(string(input, "leaseId"), string(input, "sessionId"));
|
|
3634
|
+
const input = await readJson$2(req);
|
|
3635
|
+
await service.bindLease(string$1(input, "leaseId"), string$1(input, "sessionId"));
|
|
3110
3636
|
return json(res, 200, { ok: true });
|
|
3111
3637
|
}
|
|
3112
3638
|
return json(res, 404, { error: "not found" });
|
|
@@ -3122,6 +3648,168 @@ function registerRepositorySetupRoute(ctx, service) {
|
|
|
3122
3648
|
}), "dsh-claude: repository setup route");
|
|
3123
3649
|
}
|
|
3124
3650
|
//#endregion
|
|
3651
|
+
//#region src/repository-action-routes.ts
|
|
3652
|
+
const MAX_BODY_BYTES$1 = 16384;
|
|
3653
|
+
const MAX_SESSION_ID_CHARS$1 = 1024;
|
|
3654
|
+
const ACTIONS = /* @__PURE__ */ new Set([
|
|
3655
|
+
"commit",
|
|
3656
|
+
"commit-push",
|
|
3657
|
+
"push",
|
|
3658
|
+
"create-pr"
|
|
3659
|
+
]);
|
|
3660
|
+
function record$1(value) {
|
|
3661
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
3662
|
+
}
|
|
3663
|
+
async function readJson$1(req) {
|
|
3664
|
+
const chunks = [];
|
|
3665
|
+
let size = 0;
|
|
3666
|
+
for await (const chunk of req) {
|
|
3667
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
3668
|
+
size += buffer.length;
|
|
3669
|
+
if (size > MAX_BODY_BYTES$1) throw new RepositoryActionError("body-too-large", "The request body is too large.");
|
|
3670
|
+
chunks.push(buffer);
|
|
3671
|
+
}
|
|
3672
|
+
const value = record$1(JSON.parse(Buffer.concat(chunks).toString("utf8")));
|
|
3673
|
+
if (value === void 0) throw new RepositoryActionError("invalid-request", "The request body is invalid.");
|
|
3674
|
+
return value;
|
|
3675
|
+
}
|
|
3676
|
+
function sessionId(url) {
|
|
3677
|
+
const value = url.searchParams.get("sessionId");
|
|
3678
|
+
if (value === null || value.length === 0 || value.length > MAX_SESSION_ID_CHARS$1) throw new RepositoryActionError("invalid-session", "The session is invalid.");
|
|
3679
|
+
return value;
|
|
3680
|
+
}
|
|
3681
|
+
function string(input, key) {
|
|
3682
|
+
const value = input[key];
|
|
3683
|
+
if (typeof value !== "string") throw new RepositoryActionError("invalid-request", `The ${key} field is required.`);
|
|
3684
|
+
return value;
|
|
3685
|
+
}
|
|
3686
|
+
function optionalString(input, key) {
|
|
3687
|
+
const value = input[key];
|
|
3688
|
+
if (value === void 0) return void 0;
|
|
3689
|
+
if (typeof value !== "string") throw new RepositoryActionError("invalid-request", `The ${key} field must be a string.`);
|
|
3690
|
+
return value;
|
|
3691
|
+
}
|
|
3692
|
+
function actionRequest(input) {
|
|
3693
|
+
const action = input.action;
|
|
3694
|
+
if (typeof action !== "string" || !ACTIONS.has(action) || typeof input.includeUnstaged !== "boolean") throw new RepositoryActionError("invalid-request", "The repository action is invalid.");
|
|
3695
|
+
return {
|
|
3696
|
+
action,
|
|
3697
|
+
fingerprint: string(input, "fingerprint"),
|
|
3698
|
+
message: action === "push" ? optionalString(input, "message") ?? "" : string(input, "message"),
|
|
3699
|
+
includeUnstaged: input.includeUnstaged,
|
|
3700
|
+
...optionalString(input, "prTitle") === void 0 ? {} : { prTitle: optionalString(input, "prTitle") },
|
|
3701
|
+
...optionalString(input, "prBody") === void 0 ? {} : { prBody: optionalString(input, "prBody") },
|
|
3702
|
+
...optionalString(input, "baseBranch") === void 0 ? {} : { baseBranch: optionalString(input, "baseBranch") },
|
|
3703
|
+
...input.draft === void 0 ? {} : typeof input.draft === "boolean" ? { draft: input.draft } : (() => {
|
|
3704
|
+
throw new RepositoryActionError("invalid-request", "The draft field must be a boolean.");
|
|
3705
|
+
})()
|
|
3706
|
+
};
|
|
3707
|
+
}
|
|
3708
|
+
function registerRepositoryActionRoute(ctx, service, cwdForSession) {
|
|
3709
|
+
ctx.effect(() => ctx.webServer.register({
|
|
3710
|
+
kind: "prefix",
|
|
3711
|
+
path: CLAUDE_REPOSITORY_ACTION_PATH,
|
|
3712
|
+
handler: async (req, res) => {
|
|
3713
|
+
if (!trustedRequest(req)) return json(res, 403, { error: "forbidden" });
|
|
3714
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
3715
|
+
try {
|
|
3716
|
+
const cwd = cwdForSession(sessionId(url));
|
|
3717
|
+
if (cwd === void 0) throw new RepositoryActionError("session-unavailable", "The Claude session is unavailable.");
|
|
3718
|
+
if (url.pathname === `/plugins/dsh-claude/repository/action/preview`) {
|
|
3719
|
+
if (req.method !== "GET") return json(res, 405, { error: "method not allowed" });
|
|
3720
|
+
return json(res, 200, await service.preview(cwd));
|
|
3721
|
+
}
|
|
3722
|
+
if (url.pathname === `/plugins/dsh-claude/repository/action/message`) {
|
|
3723
|
+
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
3724
|
+
const input = await readJson$1(req);
|
|
3725
|
+
return json(res, 200, { message: await service.generateMessage(cwd, string(input, "fingerprint")) });
|
|
3726
|
+
}
|
|
3727
|
+
if (url.pathname === "/plugins/dsh-claude/repository/action") {
|
|
3728
|
+
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
3729
|
+
return json(res, 200, await service.execute(cwd, actionRequest(await readJson$1(req))));
|
|
3730
|
+
}
|
|
3731
|
+
return json(res, 404, { error: "not found" });
|
|
3732
|
+
} catch (error) {
|
|
3733
|
+
if (error instanceof RepositoryActionError) return json(res, 409, {
|
|
3734
|
+
error: error.code,
|
|
3735
|
+
message: error.message,
|
|
3736
|
+
...error.commit === void 0 ? {} : { commit: error.commit }
|
|
3737
|
+
});
|
|
3738
|
+
if (error instanceof SyntaxError) return json(res, 400, { error: "invalid-json" });
|
|
3739
|
+
return json(res, 500, {
|
|
3740
|
+
error: "repository-action-unavailable",
|
|
3741
|
+
message: "Repository action is unavailable."
|
|
3742
|
+
});
|
|
3743
|
+
}
|
|
3744
|
+
}
|
|
3745
|
+
}), "dsh-claude: repository action route");
|
|
3746
|
+
}
|
|
3747
|
+
//#endregion
|
|
3748
|
+
//#region src/review-comment-routes.ts
|
|
3749
|
+
const MAX_BODY_BYTES = 16384;
|
|
3750
|
+
const MAX_SESSION_ID_CHARS = 1024;
|
|
3751
|
+
function record(value) {
|
|
3752
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
3753
|
+
}
|
|
3754
|
+
async function readJson(req) {
|
|
3755
|
+
const chunks = [];
|
|
3756
|
+
let size = 0;
|
|
3757
|
+
for await (const chunk of req) {
|
|
3758
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
3759
|
+
size += buffer.length;
|
|
3760
|
+
if (size > MAX_BODY_BYTES) throw new ReviewCommentError("body-too-large", "The request body is too large.");
|
|
3761
|
+
chunks.push(buffer);
|
|
3762
|
+
}
|
|
3763
|
+
const value = record(JSON.parse(Buffer.concat(chunks).toString("utf8")));
|
|
3764
|
+
if (value === void 0) throw new ReviewCommentError("invalid-request", "The request body is invalid.");
|
|
3765
|
+
return value;
|
|
3766
|
+
}
|
|
3767
|
+
function sessionIdFromUrl(url) {
|
|
3768
|
+
const value = url.searchParams.get("sessionId");
|
|
3769
|
+
if (value === null || value.length === 0 || value.length > MAX_SESSION_ID_CHARS) throw new ReviewCommentError("invalid-session", "The session is invalid.");
|
|
3770
|
+
return value;
|
|
3771
|
+
}
|
|
3772
|
+
function registerReviewCommentRoute(ctx, store, ownsSession) {
|
|
3773
|
+
ctx.effect(() => ctx.webServer.register({
|
|
3774
|
+
kind: "prefix",
|
|
3775
|
+
path: CLAUDE_REVIEW_COMMENT_PATH,
|
|
3776
|
+
handler: async (req, res) => {
|
|
3777
|
+
if (!trustedRequest(req)) return json(res, 403, { error: "forbidden" });
|
|
3778
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
3779
|
+
try {
|
|
3780
|
+
const sessionId = sessionIdFromUrl(url);
|
|
3781
|
+
if (!ownsSession(sessionId)) throw new ReviewCommentError("session-unavailable", "The Claude session is unavailable.");
|
|
3782
|
+
if (url.pathname === "/plugins/dsh-claude/review-comments" && req.method === "POST") {
|
|
3783
|
+
const input = await readJson(req);
|
|
3784
|
+
return json(res, 200, { comment: store.add(sessionId, {
|
|
3785
|
+
path: input.path,
|
|
3786
|
+
line: input.line,
|
|
3787
|
+
side: input.side,
|
|
3788
|
+
text: input.text
|
|
3789
|
+
}) });
|
|
3790
|
+
}
|
|
3791
|
+
if (url.pathname === `/plugins/dsh-claude/review-comments/clear` && req.method === "POST") return json(res, 200, { removed: store.drain(sessionId).length });
|
|
3792
|
+
if (url.pathname === `/plugins/dsh-claude/review-comments/remove` && req.method === "POST") {
|
|
3793
|
+
const input = await readJson(req);
|
|
3794
|
+
if (typeof input.id !== "string" || input.id.length === 0 || input.id.length > 128) throw new ReviewCommentError("invalid-request", "The comment id is invalid.");
|
|
3795
|
+
return json(res, 200, { removed: store.remove(sessionId, input.id) });
|
|
3796
|
+
}
|
|
3797
|
+
return json(res, 404, { error: "not found" });
|
|
3798
|
+
} catch (error) {
|
|
3799
|
+
if (error instanceof ReviewCommentError) return json(res, 409, {
|
|
3800
|
+
error: error.code,
|
|
3801
|
+
message: error.message
|
|
3802
|
+
});
|
|
3803
|
+
if (error instanceof SyntaxError) return json(res, 400, { error: "invalid-json" });
|
|
3804
|
+
return json(res, 500, {
|
|
3805
|
+
error: "review-comment-unavailable",
|
|
3806
|
+
message: "Review comments are unavailable."
|
|
3807
|
+
});
|
|
3808
|
+
}
|
|
3809
|
+
}
|
|
3810
|
+
}), "dsh-claude: review comment route");
|
|
3811
|
+
}
|
|
3812
|
+
//#endregion
|
|
3125
3813
|
//#region src/update-routes.ts
|
|
3126
3814
|
const PLUGIN_PACKAGE_NAME = "@norman-else/dsh-claude";
|
|
3127
3815
|
const UPDATE_TIMEOUT_MS = 3e4;
|
|
@@ -3752,6 +4440,7 @@ async function apply(ctx, config) {
|
|
|
3752
4440
|
const sidecar = new ClaudeSidecarRepository();
|
|
3753
4441
|
const repositoryStatus = new RepositoryStatusService(ctx.subprocess);
|
|
3754
4442
|
const repositorySetup = new RepositorySetupService(ctx.subprocess, { branchPrefix: () => readWorktreeBranchPrefix() });
|
|
4443
|
+
const reviewComments = new ReviewCommentStore();
|
|
3755
4444
|
const commandCatalogs = /* @__PURE__ */ new Map();
|
|
3756
4445
|
const supervisor = new ClaudeSupervisor({
|
|
3757
4446
|
runtime: ctx.subprocess,
|
|
@@ -3764,7 +4453,7 @@ async function apply(ctx, config) {
|
|
|
3764
4453
|
let resolutionError;
|
|
3765
4454
|
try {
|
|
3766
4455
|
supervisorConfig.executablePath = (await resolveClaudeExecutable(ctx.subprocess, config.executablePath === void 0 || config.executablePath.length === 0 ? void 0 : config.executablePath)).path;
|
|
3767
|
-
ctx.llm.registerAdapter([...CLAUDE_CODE_PROVIDER_IDS], createClaudeCodeAdapter(supervisor, ctx.agents, ctx.attachments, (agent) => ctx.agentPresets.composedPreset(agent.ctx)));
|
|
4456
|
+
ctx.llm.registerAdapter([...CLAUDE_CODE_PROVIDER_IDS], createClaudeCodeAdapter(supervisor, ctx.agents, ctx.attachments, (agent) => ctx.agentPresets.composedPreset(agent.ctx), (sessionId) => reviewComments.drain(sessionId)));
|
|
3768
4457
|
ctx.effect(() => {
|
|
3769
4458
|
const mounted = /* @__PURE__ */ new Map();
|
|
3770
4459
|
const pending = /* @__PURE__ */ new Set();
|
|
@@ -3825,9 +4514,11 @@ async function apply(ctx, config) {
|
|
|
3825
4514
|
resolutionError = error;
|
|
3826
4515
|
}
|
|
3827
4516
|
ctx.on("agent/disposed", async ({ agent }) => {
|
|
4517
|
+
reviewComments.disposeSession(agent.id);
|
|
3828
4518
|
await supervisor.disposeSession(agent.id);
|
|
3829
4519
|
await repositorySetup.cleanupSession(agent.id);
|
|
3830
4520
|
});
|
|
4521
|
+
ctx.effect(() => () => reviewComments.dispose(), "dsh-claude: review comments store");
|
|
3831
4522
|
ctx.effect(() => () => supervisor.dispose(), "dsh-claude: process supervisor");
|
|
3832
4523
|
ctx.effect(() => () => repositoryStatus.dispose(), "dsh-claude: repository status cache");
|
|
3833
4524
|
ctx.inject(["webServer"], (webCtx) => {
|
|
@@ -3836,15 +4527,22 @@ async function apply(ctx, config) {
|
|
|
3836
4527
|
registerClaudeUpdateRoutes(webCtx, webCtx.subprocess, { ...typeof desktopActions?.requestRestart === "function" ? { requestRestart: desktopActions.requestRestart.bind(desktopActions) } : {} });
|
|
3837
4528
|
registerClaudeGlobalSettingsRoute(webCtx);
|
|
3838
4529
|
registerRepositorySetupRoute(webCtx, repositorySetup);
|
|
3839
|
-
|
|
4530
|
+
registerRepositoryActionRoute(webCtx, new RepositoryActionService(webCtx.subprocess, supervisorConfig.executablePath, (cwd) => repositoryStatus.invalidate(cwd)), (sessionId) => {
|
|
4531
|
+
const agent = webCtx.agents.get(sessionId);
|
|
4532
|
+
if (agent === void 0 || webCtx.agentPresets.composedPreset(agent.ctx) !== "claude") return void 0;
|
|
4533
|
+
return agent.session.header.cwd;
|
|
4534
|
+
});
|
|
4535
|
+
const ownsClaudeSession = (sessionId) => {
|
|
3840
4536
|
const agent = webCtx.agents.get(sessionId);
|
|
3841
4537
|
return agent !== void 0 && webCtx.agentPresets.composedPreset(agent.ctx) === "claude";
|
|
3842
|
-
}
|
|
4538
|
+
};
|
|
4539
|
+
registerReviewCommentRoute(webCtx, reviewComments, ownsClaudeSession);
|
|
4540
|
+
registerClaudeProjectionRoute(webCtx, sidecar, ownsClaudeSession, (sessionId) => commandCatalogs.get(sessionId) ?? [], async (sessionId) => {
|
|
3843
4541
|
const agent = webCtx.agents.get(sessionId);
|
|
3844
4542
|
if (agent === void 0 || webCtx.agentPresets.composedPreset(agent.ctx) !== "claude") return void 0;
|
|
3845
4543
|
const cwd = agent.session.header.cwd;
|
|
3846
4544
|
return cwd === void 0 ? void 0 : repositoryStatus.inspect(cwd);
|
|
3847
|
-
});
|
|
4545
|
+
}, (sessionId) => reviewComments.list(sessionId));
|
|
3848
4546
|
});
|
|
3849
4547
|
}
|
|
3850
4548
|
//#endregion
|