@norman-else/dsh-claude 0.1.34 → 0.1.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +196 -191
- package/lib/bin.mjs +1 -1
- package/lib/client.d.ts +13 -0
- package/lib/client.js +1991 -1174
- package/lib/client.js.map +1 -1
- package/lib/{events-DeSV0S1-.mjs → events-OhBoFNKO.mjs} +13 -2
- package/lib/events-OhBoFNKO.mjs.map +1 -0
- package/lib/index.d.mts +59 -2
- package/lib/index.mjs +1261 -483
- package/lib/index.mjs.map +1 -1
- package/lib/presenters-BBoM1Ju1.mjs +300 -0
- package/lib/presenters-BBoM1Ju1.mjs.map +1 -0
- package/lib/{preset-installer-BY4846KK.mjs → preset-installer-loenwnLS.mjs} +2 -2
- package/lib/{preset-installer-BY4846KK.mjs.map → preset-installer-loenwnLS.mjs.map} +1 -1
- package/lib/preset-route.mjs +2 -172
- package/lib/preset-route.mjs.map +1 -1
- package/package.json +189 -188
- package/lib/command-bridge-DXI6nWhB.mjs +0 -71
- package/lib/command-bridge-DXI6nWhB.mjs.map +0 -1
- package/lib/events-DeSV0S1-.mjs.map +0 -1
package/lib/client.js
CHANGED
|
@@ -27,6 +27,13 @@ window.__ModuleLoader__.load({
|
|
|
27
27
|
const CLAUDE_ASK_PATH = "/plugins/dsh-claude/ask";
|
|
28
28
|
const CLAUDE_EDITOR_OPEN_PATH = "/plugins/dsh-claude/editor/open";
|
|
29
29
|
const CLAUDE_REWIND_PATH = "/plugins/dsh-claude/rewind";
|
|
30
|
+
function isClaudeRenderMode(value) {
|
|
31
|
+
return value === "plugin" || value === "native";
|
|
32
|
+
}
|
|
33
|
+
const DEFAULT_CLAUDE_PROSE_MODE = "plain";
|
|
34
|
+
function isClaudeProseMode(value) {
|
|
35
|
+
return value === "plain" || value === "enhanced";
|
|
36
|
+
}
|
|
30
37
|
//#endregion
|
|
31
38
|
//#region src/client/task-projection.ts
|
|
32
39
|
/** Tasks UI is reserved for detached work and genuine Claude subagents. */
|
|
@@ -239,7 +246,7 @@ window.__ModuleLoader__.load({
|
|
|
239
246
|
case "WebFetch": {
|
|
240
247
|
const url = inputString(input, "url") ?? "a web page";
|
|
241
248
|
completed = `Fetched ${url}`;
|
|
242
|
-
failedAction = `
|
|
249
|
+
failedAction = `load ${url}`;
|
|
243
250
|
break;
|
|
244
251
|
}
|
|
245
252
|
case "WebSearch":
|
|
@@ -267,8 +274,76 @@ window.__ModuleLoader__.load({
|
|
|
267
274
|
}
|
|
268
275
|
return failed ? `Failed to ${failedAction}` : completed;
|
|
269
276
|
}
|
|
277
|
+
/** The call id a task's own lifecycle pings were dispatched from. */
|
|
278
|
+
function taskParentToolUseId(activities, taskId) {
|
|
279
|
+
for (const activity of activities) {
|
|
280
|
+
if (activity.taskId !== taskId) continue;
|
|
281
|
+
const id = inputRecord(activity.detail)?.tool_use_id;
|
|
282
|
+
if (typeof id === "string" && id.length > 0) return id;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* The tools one task ran, in the shape the transcript's tool cards take.
|
|
287
|
+
*
|
|
288
|
+
* The task's own activities are lifecycle pings whose detail is the raw
|
|
289
|
+
* protocol message -- useful to nobody reading a panel. The work is in the
|
|
290
|
+
* activities addressed to the call that dispatched the task: a subagent's
|
|
291
|
+
* nested calls carry it as `parentToolUseId`, and a backgrounded command IS
|
|
292
|
+
* that call. Both are folded here the way {@link transcriptItemsForStep} folds
|
|
293
|
+
* a step, so one card renderer serves the transcript and the task panel.
|
|
294
|
+
*/
|
|
295
|
+
function taskTools(activities, taskId) {
|
|
296
|
+
const parent = taskParentToolUseId(activities, taskId);
|
|
297
|
+
if (parent === void 0) return [];
|
|
298
|
+
const tools = /* @__PURE__ */ new Map();
|
|
299
|
+
const ordered = [...activities].sort((left, right) => left.ordinal - right.ordinal);
|
|
300
|
+
for (const activity of ordered) {
|
|
301
|
+
const own = activity.parentToolUseId === parent;
|
|
302
|
+
const isRoot = activity.parentToolUseId === void 0 && activity.toolUseId === parent;
|
|
303
|
+
if (!own && !isRoot) continue;
|
|
304
|
+
const toolUseId = activity.toolUseId;
|
|
305
|
+
if (toolUseId === void 0) continue;
|
|
306
|
+
const previous = tools.get(toolUseId);
|
|
307
|
+
if (previous === void 0) {
|
|
308
|
+
if (activity.toolName === void 0) continue;
|
|
309
|
+
const input = inputRecord(activity.detail);
|
|
310
|
+
tools.set(toolUseId, {
|
|
311
|
+
toolUseId,
|
|
312
|
+
toolName: activity.toolName,
|
|
313
|
+
description: toolDescription(activity.toolName, input),
|
|
314
|
+
...activity.summary === void 0 ? {} : { summary: activity.summary },
|
|
315
|
+
...activity.detail === void 0 ? {} : { input: activity.detail },
|
|
316
|
+
...activity.phase === void 0 ? {} : { phase: activity.phase },
|
|
317
|
+
...activity.isError === void 0 ? {} : { isError: activity.isError },
|
|
318
|
+
subcalls: []
|
|
319
|
+
});
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
const failed = activity.isError === true || activity.phase === "failed";
|
|
323
|
+
tools.set(toolUseId, {
|
|
324
|
+
...previous,
|
|
325
|
+
description: toolDescription(previous.toolName, inputRecord(previous.input), failed),
|
|
326
|
+
...activity.detail === void 0 ? {} : { output: activity.detail },
|
|
327
|
+
...activity.phase === void 0 ? {} : { phase: activity.phase },
|
|
328
|
+
...activity.isError === void 0 ? {} : { isError: activity.isError }
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
return [...tools.values()];
|
|
332
|
+
}
|
|
333
|
+
/** Whether the Host drew this step with DSH's own renderer.
|
|
334
|
+
*
|
|
335
|
+
* The stamp rides on the records themselves rather than on a Client-side copy
|
|
336
|
+
* of the setting, because the two can disagree: the Host switches on the next
|
|
337
|
+
* turn while a running Client keeps whatever it decided at boot, and the
|
|
338
|
+
* failure mode of disagreeing is drawing every step twice. Reading it back per
|
|
339
|
+
* step also keeps history honest in both directions -- a turn recorded under
|
|
340
|
+
* one renderer keeps it after the setting changes. */
|
|
341
|
+
function nativelyRenderedStep(activities, turn, step) {
|
|
342
|
+
return activities.some((activity) => activity.turn === turn && activity.step === step && activity.renderer === "native");
|
|
343
|
+
}
|
|
270
344
|
/** Fold one step's shared ordinal stream into Claude Code-style prose and tool groups. */
|
|
271
345
|
function transcriptItemsForStep(activities, turn, step, tasks = []) {
|
|
346
|
+
if (nativelyRenderedStep(activities, turn, step)) return [];
|
|
272
347
|
const ordered = activities.filter((activity) => activity.turn === turn && activity.step === step && isProjectedTaskActivity(activity, tasks)).slice().sort((left, right) => left.ordinal - right.ordinal);
|
|
273
348
|
const rows = foldedRows(ordered, tasks);
|
|
274
349
|
const placed = /* @__PURE__ */ new Set();
|
|
@@ -327,6 +402,15 @@ window.__ModuleLoader__.load({
|
|
|
327
402
|
});
|
|
328
403
|
continue;
|
|
329
404
|
}
|
|
405
|
+
if (activity.kind === "usage" && activity.usage !== void 0) {
|
|
406
|
+
flushGroup();
|
|
407
|
+
items.push({
|
|
408
|
+
kind: "usage",
|
|
409
|
+
ordinal: activity.ordinal,
|
|
410
|
+
usage: activity.usage
|
|
411
|
+
});
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
330
414
|
if (activity.kind === "tool-call" && activity.toolUseId !== void 0 && activity.toolName !== void 0) {
|
|
331
415
|
group ??= {
|
|
332
416
|
ordinal: activity.ordinal,
|
|
@@ -674,30 +758,65 @@ window.__ModuleLoader__.load({
|
|
|
674
758
|
fontSize: 14,
|
|
675
759
|
lineHeight: "22px"
|
|
676
760
|
};
|
|
677
|
-
const
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
761
|
+
const settingSelectTriggerClass = "dshClaudeSettingSelectTrigger";
|
|
762
|
+
const settingSelectChevronClass = "dshClaudeSettingSelectChevron";
|
|
763
|
+
/**
|
|
764
|
+
* The listbox trigger and its chevron, in a stylesheet rather than inline
|
|
765
|
+
* because the states it has to draw cannot be expressed inline.
|
|
766
|
+
*
|
|
767
|
+
* Focus is the reason. An inline style carries no pseudo-class, so the blue
|
|
768
|
+
* ring used to ride on the open state alone: choosing an option closed the
|
|
769
|
+
* menu and took the ring with it while the button kept DOM focus, leaving the
|
|
770
|
+
* UA's own focus ring -- white on a dark theme, drawn outside the radius --
|
|
771
|
+
* as the only indicator. Focus and open are separate states; both draw the
|
|
772
|
+
* ring here, and `outline: none` retires the UA's.
|
|
773
|
+
*
|
|
774
|
+
* The open state reads `aria-expanded` instead of a second class because the
|
|
775
|
+
* attribute is already on the element and already correct. Its value is left
|
|
776
|
+
* unquoted: a valid identifier needs no quotes, and the sheet then survives
|
|
777
|
+
* React's server escaping, which would otherwise write `"` into markup a
|
|
778
|
+
* browser reads as raw text.
|
|
779
|
+
*/
|
|
780
|
+
const settingSelectCss = `
|
|
781
|
+
.${settingSelectTriggerClass} {
|
|
782
|
+
width: 100%;
|
|
783
|
+
min-height: 38px;
|
|
784
|
+
display: flex;
|
|
785
|
+
align-items: center;
|
|
786
|
+
justify-content: space-between;
|
|
787
|
+
gap: 12px;
|
|
788
|
+
padding: 7px 11px 7px 13px;
|
|
789
|
+
border: 1px solid var(--dsw-alias-border-l2, color-mix(in srgb, currentColor 16%, transparent));
|
|
790
|
+
border-radius: 10px;
|
|
791
|
+
background: var(--dsw-alias-bg-layer-2);
|
|
792
|
+
color: var(--dsw-alias-label-primary);
|
|
793
|
+
box-shadow: 0 1px 2px color-mix(in srgb, var(--dsw-alias-label-primary) 5%, transparent);
|
|
794
|
+
font: inherit;
|
|
795
|
+
font-size: 14px;
|
|
796
|
+
line-height: 22px;
|
|
797
|
+
text-align: left;
|
|
798
|
+
cursor: pointer;
|
|
799
|
+
transition: border-color 120ms ease, box-shadow 120ms ease, background 120ms ease;
|
|
800
|
+
}
|
|
801
|
+
.${settingSelectTriggerClass}:focus-visible,
|
|
802
|
+
.${settingSelectTriggerClass}[aria-expanded=true] {
|
|
803
|
+
outline: none;
|
|
804
|
+
border-color: var(--dsw-static-blue-450);
|
|
805
|
+
box-shadow: 0 0 0 3px color-mix(in srgb, var(--dsw-static-blue-450) 15%, transparent);
|
|
806
|
+
}
|
|
807
|
+
.${settingSelectChevronClass} {
|
|
808
|
+
flex: none;
|
|
809
|
+
display: grid;
|
|
810
|
+
place-items: center;
|
|
811
|
+
width: 16px;
|
|
812
|
+
height: 16px;
|
|
813
|
+
color: var(--dsw-alias-label-tertiary);
|
|
814
|
+
transition: transform 120ms ease;
|
|
815
|
+
}
|
|
816
|
+
.${settingSelectTriggerClass}[aria-expanded=true] .${settingSelectChevronClass} {
|
|
817
|
+
transform: rotate(180deg);
|
|
818
|
+
}
|
|
819
|
+
`;
|
|
701
820
|
const settingSelectValue = {
|
|
702
821
|
minWidth: 0,
|
|
703
822
|
flex: 1,
|
|
@@ -705,15 +824,6 @@ window.__ModuleLoader__.load({
|
|
|
705
824
|
textOverflow: "ellipsis",
|
|
706
825
|
whiteSpace: "nowrap"
|
|
707
826
|
};
|
|
708
|
-
const settingSelectChevron = {
|
|
709
|
-
flex: "none",
|
|
710
|
-
color: "var(--dsw-alias-label-tertiary)",
|
|
711
|
-
fontSize: 16,
|
|
712
|
-
lineHeight: 1,
|
|
713
|
-
transform: "translateY(-1px)",
|
|
714
|
-
transition: "transform 120ms ease"
|
|
715
|
-
};
|
|
716
|
-
const settingSelectChevronOpen = { transform: "translateY(1px) rotate(180deg)" };
|
|
717
827
|
const settingSelectMenu = {
|
|
718
828
|
position: "absolute",
|
|
719
829
|
zIndex: 20,
|
|
@@ -1065,6 +1175,14 @@ window.__ModuleLoader__.load({
|
|
|
1065
1175
|
cursor: "pointer"
|
|
1066
1176
|
};
|
|
1067
1177
|
const taskActivitySection = { margin: "7px 0 0 27px" };
|
|
1178
|
+
/** The task's tool cards. No inset panel of their own: each card already
|
|
1179
|
+
* carries its own surface, and nesting two would read as a box in a box. */
|
|
1180
|
+
const taskToolList = {
|
|
1181
|
+
display: "flex",
|
|
1182
|
+
flexDirection: "column",
|
|
1183
|
+
gap: 2,
|
|
1184
|
+
marginTop: 7
|
|
1185
|
+
};
|
|
1068
1186
|
const taskActivityList = {
|
|
1069
1187
|
display: "flex",
|
|
1070
1188
|
flexDirection: "column",
|
|
@@ -3311,460 +3429,576 @@ window.__ModuleLoader__.load({
|
|
|
3311
3429
|
lineHeight: "20px"
|
|
3312
3430
|
};
|
|
3313
3431
|
//#endregion
|
|
3314
|
-
//#region src/client/
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3432
|
+
//#region src/client/markdown-theme.ts
|
|
3433
|
+
/** Claude Code's code presentation over the Host's Markdown renderer.
|
|
3434
|
+
*
|
|
3435
|
+
* This package renders no Markdown of its own — prose, fenced blocks and the
|
|
3436
|
+
* copy affordance all come from the Host's `MarkdownText` primitive. What it
|
|
3437
|
+
* can own is what that primitive reads: the palette (custom properties) and
|
|
3438
|
+
* the block's chrome (CSS over the primitive's own markup).
|
|
3439
|
+
*
|
|
3440
|
+
* PARITY IS PARTIAL BY CONSTRUCTION. Both renderers highlight with shiki, but
|
|
3441
|
+
* Claude Code's desktop build loads a full TextMate theme (Pierre Dark /
|
|
3442
|
+
* Pierre Light Soft: 248 tokenColor rules over 424 scopes) and bakes the
|
|
3443
|
+
* resolved colour into every span, while the Host loads shiki's legacy
|
|
3444
|
+
* `css-variables` theme, which collapses every scope into the eleven buckets
|
|
3445
|
+
* below. Those eleven carry the colours that dominate a code block —
|
|
3446
|
+
* keywords, strings, comments, functions, numbers — and nothing here can
|
|
3447
|
+
* recover the rest: `constant.numeric` and `constant` are two different
|
|
3448
|
+
* colours in Pierre and one bucket here, and Pierre's string-coloured string
|
|
3449
|
+
* delimiters share this sheet's single punctuation bucket. Matching the rest
|
|
3450
|
+
* means this package running its own shiki, which is a different decision
|
|
3451
|
+
* with a different cost.
|
|
3452
|
+
*
|
|
3453
|
+
* Every rule fails open the way `host-chrome` does: the palette is
|
|
3454
|
+
* declarations on this package's own wrapper, and the chrome rules match the
|
|
3455
|
+
* primitive's CSS Module local names, so a Host that renames either simply
|
|
3456
|
+
* stops matching and its stock presentation comes back.
|
|
3457
|
+
*/
|
|
3458
|
+
/** Wrapper class carrying the palette and scoping the chrome rules.
|
|
3459
|
+
* `display:contents` so the extra element generates no box — custom
|
|
3460
|
+
* properties inherit through it regardless. */
|
|
3461
|
+
const CLAUDE_MARKDOWN_SCOPE = "dsh-claude-markdown";
|
|
3462
|
+
/** Pierre mapped onto the Host's eleven buckets. Each entry names the theme
|
|
3463
|
+
* scope the value was taken from, because the mapping — not the colour — is
|
|
3464
|
+
* the part a reader has to check. */
|
|
3465
|
+
const PIERRE_DARK = [
|
|
3466
|
+
[
|
|
3467
|
+
"--shiki-background",
|
|
3468
|
+
"#1a1a19",
|
|
3469
|
+
"UI surface (--cds-surface-2)"
|
|
3470
|
+
],
|
|
3471
|
+
[
|
|
3472
|
+
"--shiki-foreground",
|
|
3473
|
+
"#fafafa",
|
|
3474
|
+
"editor.foreground"
|
|
3475
|
+
],
|
|
3476
|
+
[
|
|
3477
|
+
"--shiki-token-comment",
|
|
3478
|
+
"#737373",
|
|
3479
|
+
"comment"
|
|
3480
|
+
],
|
|
3481
|
+
[
|
|
3482
|
+
"--shiki-token-keyword",
|
|
3483
|
+
"#ff678d",
|
|
3484
|
+
"keyword, storage.type"
|
|
3485
|
+
],
|
|
3486
|
+
[
|
|
3487
|
+
"--shiki-token-string",
|
|
3488
|
+
"#5ecc71",
|
|
3489
|
+
"string"
|
|
3490
|
+
],
|
|
3491
|
+
[
|
|
3492
|
+
"--shiki-token-string-expression",
|
|
3493
|
+
"#ffa359",
|
|
3494
|
+
"punctuation.section.embedded"
|
|
3495
|
+
],
|
|
3496
|
+
[
|
|
3497
|
+
"--shiki-token-function",
|
|
3498
|
+
"#9d6afb",
|
|
3499
|
+
"entity.name.function"
|
|
3500
|
+
],
|
|
3501
|
+
[
|
|
3502
|
+
"--shiki-token-constant",
|
|
3503
|
+
"#68cdf2",
|
|
3504
|
+
"constant.numeric, constant.language"
|
|
3505
|
+
],
|
|
3506
|
+
[
|
|
3507
|
+
"--shiki-token-parameter",
|
|
3508
|
+
"#a3a3a3",
|
|
3509
|
+
"variable.parameter"
|
|
3510
|
+
],
|
|
3511
|
+
[
|
|
3512
|
+
"--shiki-token-punctuation",
|
|
3513
|
+
"#636363",
|
|
3514
|
+
"punctuation"
|
|
3515
|
+
],
|
|
3516
|
+
[
|
|
3517
|
+
"--shiki-token-link",
|
|
3518
|
+
"#ff678d",
|
|
3519
|
+
"markup.underline.link.markdown"
|
|
3520
|
+
]
|
|
3521
|
+
];
|
|
3522
|
+
const PIERRE_LIGHT = [
|
|
3523
|
+
[
|
|
3524
|
+
"--shiki-background",
|
|
3525
|
+
"#ffffff",
|
|
3526
|
+
"UI surface (--cds-surface-2)"
|
|
3527
|
+
],
|
|
3528
|
+
[
|
|
3529
|
+
"--shiki-foreground",
|
|
3530
|
+
"#525252",
|
|
3531
|
+
"editor.foreground"
|
|
3532
|
+
],
|
|
3533
|
+
[
|
|
3534
|
+
"--shiki-token-comment",
|
|
3535
|
+
"#8a8a8a",
|
|
3536
|
+
"comment"
|
|
3537
|
+
],
|
|
3538
|
+
[
|
|
3539
|
+
"--shiki-token-keyword",
|
|
3540
|
+
"#ff678d",
|
|
3541
|
+
"keyword, storage.type"
|
|
3542
|
+
],
|
|
3543
|
+
[
|
|
3544
|
+
"--shiki-token-string",
|
|
3545
|
+
"#0dbe4e",
|
|
3546
|
+
"string"
|
|
3547
|
+
],
|
|
3548
|
+
[
|
|
3549
|
+
"--shiki-token-string-expression",
|
|
3550
|
+
"#fe8c2c",
|
|
3551
|
+
"punctuation.section.embedded"
|
|
3552
|
+
],
|
|
3553
|
+
[
|
|
3554
|
+
"--shiki-token-function",
|
|
3555
|
+
"#9d6afb",
|
|
3556
|
+
"entity.name.function"
|
|
3557
|
+
],
|
|
3558
|
+
[
|
|
3559
|
+
"--shiki-token-constant",
|
|
3560
|
+
"#08c0ef",
|
|
3561
|
+
"constant.numeric, constant.language"
|
|
3562
|
+
],
|
|
3563
|
+
[
|
|
3564
|
+
"--shiki-token-parameter",
|
|
3565
|
+
"#737373",
|
|
3566
|
+
"variable.parameter"
|
|
3567
|
+
],
|
|
3568
|
+
[
|
|
3569
|
+
"--shiki-token-punctuation",
|
|
3570
|
+
"#737373",
|
|
3571
|
+
"punctuation"
|
|
3572
|
+
],
|
|
3573
|
+
[
|
|
3574
|
+
"--shiki-token-link",
|
|
3575
|
+
"#ff678d",
|
|
3576
|
+
"markup.underline.link.markdown"
|
|
3577
|
+
]
|
|
3578
|
+
];
|
|
3579
|
+
/** Claude's brand clay (`--cds-hsl-clay`), its inline-code TEXT colour; the
|
|
3580
|
+
* emphasized ramp is the light-theme variant. The chip's fill is deliberately
|
|
3581
|
+
* NOT tinted with it — see {@link INLINE_FILL_DARK}. */
|
|
3582
|
+
const CLAY = "#d97757";
|
|
3583
|
+
const CLAY_EMPHASIZED = "#c8603f";
|
|
3584
|
+
/** Prose colours for the body of a Claude answer.
|
|
3585
|
+
*
|
|
3586
|
+
* These are NOT Pierre: the palette above exists to match Claude's desktop
|
|
3587
|
+
* build inside code blocks, and Claude paints prose in plain body text. This
|
|
3588
|
+
* block is a deliberate departure — headings, emphasis and links get their
|
|
3589
|
+
* own hues so a long answer is scannable, the way a Markdown-highlighting
|
|
3590
|
+
* editor shows it. Values are theme-independent by design (the same six read
|
|
3591
|
+
* acceptably on both surfaces); split them if the light theme ever needs its
|
|
3592
|
+
* own ramp.
|
|
3593
|
+
*
|
|
3594
|
+
* The inline-code entry REPLACES {@link CLAY} rather than sitting beside it:
|
|
3595
|
+
* two colours on the same chip is not a choice a stylesheet can make. */
|
|
3596
|
+
const PROSE = {
|
|
3597
|
+
heading: "#7C9EFF",
|
|
3598
|
+
bold: "#FFB454",
|
|
3599
|
+
italic: "#5BD6C0",
|
|
3600
|
+
inlineCode: "#FF7A93",
|
|
3601
|
+
listMarker: "#F2C94C",
|
|
3602
|
+
quote: "#9AA3B2",
|
|
3603
|
+
link: "#4DA3FF",
|
|
3604
|
+
codeBackground: "#0F1218",
|
|
3605
|
+
codeBorder: "#2E3546"
|
|
3606
|
+
};
|
|
3607
|
+
/** The inline-code chip fill: a neutral 4% wash (Claude's `--t1`), not a tint
|
|
3608
|
+
* of the text colour. A clay-tinted fill reads as a coloured box around every
|
|
3609
|
+
* identifier; the neutral one disappears into the surface and lets the text
|
|
3610
|
+
* carry the accent, which is what the chip is for. */
|
|
3611
|
+
const INLINE_FILL_DARK = "hsl(0 0% 100% / .04)";
|
|
3612
|
+
const INLINE_FILL_LIGHT = "hsl(0 0% 4.3% / .04)";
|
|
3613
|
+
/** Block corner radius (Claude's `--r6`), against the Host's own 12px. */
|
|
3614
|
+
const BLOCK_RADIUS = "8px";
|
|
3615
|
+
/** @param banner - equal to `surface` on purpose: the bar is floated out of
|
|
3616
|
+
* the way below, so this colour is only reached if those chrome selectors
|
|
3617
|
+
* miss, and a bar that matches the block is the neutral fallback. */
|
|
3618
|
+
function palette(entries, surface, banner, inlineFill) {
|
|
3619
|
+
return [
|
|
3620
|
+
...entries.map(([name, value]) => `${name}:${value};`),
|
|
3621
|
+
`--dsw-alias-markdown-code-block:${surface};`,
|
|
3622
|
+
`--dsw-alias-markdown-code-block-banner:${banner};`,
|
|
3623
|
+
`--dsw-alias-markdown-inline-code:${inlineFill}`
|
|
3624
|
+
].join("");
|
|
3625
|
+
}
|
|
3626
|
+
const CLAUDE_MARKDOWN_THEME_CSS = [
|
|
3627
|
+
`.${CLAUDE_MARKDOWN_SCOPE}{display:contents;`,
|
|
3628
|
+
palette(PIERRE_LIGHT, "#ffffff", "#ffffff", INLINE_FILL_LIGHT),
|
|
3629
|
+
"}",
|
|
3630
|
+
`body[data-ds-dark-theme] .${CLAUDE_MARKDOWN_SCOPE}{`,
|
|
3631
|
+
palette(PIERRE_DARK, "#1a1a19", "#1a1a19", INLINE_FILL_DARK),
|
|
3632
|
+
"}",
|
|
3633
|
+
`.${CLAUDE_MARKDOWN_SCOPE} :not(pre)>code{color:${CLAY_EMPHASIZED};border-radius:4px;padding:1px 2px}`,
|
|
3634
|
+
`body[data-ds-dark-theme] .${CLAUDE_MARKDOWN_SCOPE} :not(pre)>code{color:${CLAY}}`,
|
|
3635
|
+
`.${CLAUDE_MARKDOWN_SCOPE} [class*="bannerWrap"]{position:absolute;top:0;right:0;z-index:7;background:transparent;border-radius:0}`,
|
|
3636
|
+
`.${CLAUDE_MARKDOWN_SCOPE} [class*="banner"]:not([class*="bannerWrap"]){background:transparent;padding:6px 8px}`,
|
|
3637
|
+
`.${CLAUDE_MARKDOWN_SCOPE} [class*="infostring"]{display:none}`,
|
|
3638
|
+
`.${CLAUDE_MARKDOWN_SCOPE} pre{white-space:pre;word-break:normal;overflow-x:auto}`,
|
|
3639
|
+
`.${CLAUDE_MARKDOWN_SCOPE} [class*="block"]{--dsl-code-block-border-radius:${BLOCK_RADIUS}}`
|
|
3640
|
+
].join("");
|
|
3641
|
+
/** The class the review-comment card puts on a rendered comment body. Declared
|
|
3642
|
+
* here rather than imported because it is a literal inside `styles.ts`'s CSS
|
|
3643
|
+
* string, not a constant; a rename there makes these rules miss and the
|
|
3644
|
+
* comment keeps its stock colours, which is the same fail-open the chrome
|
|
3645
|
+
* rules above rely on. */
|
|
3646
|
+
const COMMENT_BODY_SCOPE = "dshClaudeDiffCommentBody";
|
|
3647
|
+
/** Everything the `prose: 'enhanced'` setting adds, and nothing the base sheet
|
|
3648
|
+
* needs. Appended AFTER the base sheet so its rules win ties on source order —
|
|
3649
|
+
* see the note on the inline-code selector below. */
|
|
3650
|
+
const CLAUDE_MARKDOWN_ENHANCED_CSS = [
|
|
3651
|
+
`.${CLAUDE_MARKDOWN_SCOPE} :is(h1,h2,h3,h4,h5,h6){color:${PROSE.heading}}`,
|
|
3652
|
+
`.${CLAUDE_MARKDOWN_SCOPE} strong{color:${PROSE.bold}}`,
|
|
3653
|
+
`.${CLAUDE_MARKDOWN_SCOPE} em{color:${PROSE.italic}}`,
|
|
3654
|
+
`.${CLAUDE_MARKDOWN_SCOPE} li::marker{color:${PROSE.listMarker}}`,
|
|
3655
|
+
`.${CLAUDE_MARKDOWN_SCOPE} blockquote{border-left-color:${PROSE.quote};color:${PROSE.quote}}`,
|
|
3656
|
+
`.${CLAUDE_MARKDOWN_SCOPE} a{color:${PROSE.link}}`,
|
|
3657
|
+
`.${CLAUDE_MARKDOWN_SCOPE} :not(pre)>code,body[data-ds-dark-theme] .${CLAUDE_MARKDOWN_SCOPE} :not(pre)>code{color:${PROSE.inlineCode}}`,
|
|
3658
|
+
`.${CLAUDE_MARKDOWN_SCOPE} [class*="block"]{background:${PROSE.codeBackground};box-shadow:0 0 0 1px ${PROSE.codeBorder}}`,
|
|
3659
|
+
`.${CLAUDE_MARKDOWN_SCOPE} pre{background:${PROSE.codeBackground}}`,
|
|
3660
|
+
`.${COMMENT_BODY_SCOPE} :is(h1,h2,h3,h4,h5,h6){color:${PROSE.heading}}`,
|
|
3661
|
+
`.${COMMENT_BODY_SCOPE} strong{color:${PROSE.bold}}`,
|
|
3662
|
+
`.${COMMENT_BODY_SCOPE} em{color:${PROSE.italic}}`,
|
|
3663
|
+
`.${COMMENT_BODY_SCOPE} li::marker{color:${PROSE.listMarker}}`,
|
|
3664
|
+
`.${COMMENT_BODY_SCOPE} blockquote{border-left-color:${PROSE.quote};color:${PROSE.quote}}`,
|
|
3665
|
+
`.${COMMENT_BODY_SCOPE} a{color:${PROSE.link}}`,
|
|
3666
|
+
`.${COMMENT_BODY_SCOPE} :not(pre)>code{color:${PROSE.inlineCode}}`
|
|
3667
|
+
].join("");
|
|
3668
|
+
/** Live reference to this package's sheet, and the mode it currently holds.
|
|
3669
|
+
* The mode is remembered so `ensureClaudeMarkdownTheme` — called on every
|
|
3670
|
+
* Markdown render — cannot undo a choice boot or the settings panel made. */
|
|
3671
|
+
let styleTag = null;
|
|
3672
|
+
let mode = DEFAULT_CLAUDE_PROSE_MODE;
|
|
3673
|
+
function write() {
|
|
3674
|
+
if (typeof document === "undefined") return;
|
|
3675
|
+
if (styleTag === null || styleTag.parentNode === null) {
|
|
3676
|
+
styleTag = document.createElement("style");
|
|
3677
|
+
styleTag.dataset.dshClaudeMarkdownTheme = "";
|
|
3678
|
+
document.head.appendChild(styleTag);
|
|
3679
|
+
}
|
|
3680
|
+
styleTag.textContent = mode === "enhanced" ? CLAUDE_MARKDOWN_THEME_CSS + CLAUDE_MARKDOWN_ENHANCED_CSS : CLAUDE_MARKDOWN_THEME_CSS;
|
|
3681
|
+
}
|
|
3682
|
+
/** Attach the sheet, keeping whatever mode is already set. Idempotent. */
|
|
3683
|
+
function ensureClaudeMarkdownTheme() {
|
|
3684
|
+
write();
|
|
3685
|
+
}
|
|
3686
|
+
/** Switch the prose palette. Rewriting one global sheet repaints every mounted
|
|
3687
|
+
* Markdown block at once, so no render state has to carry the setting. */
|
|
3688
|
+
function applyClaudeMarkdownTheme(next) {
|
|
3689
|
+
mode = next;
|
|
3690
|
+
write();
|
|
3319
3691
|
}
|
|
3320
3692
|
//#endregion
|
|
3321
|
-
//#region src/client/
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3693
|
+
//#region src/client/markdown-labels.tsx
|
|
3694
|
+
/** Stable across renders: MarkdownText keys its streaming renderer on this
|
|
3695
|
+
* object's identity and reparses from scratch whenever it changes. */
|
|
3696
|
+
function useClaudeMarkdownLabels(t) {
|
|
3697
|
+
return useMemo(() => ({
|
|
3698
|
+
code: {
|
|
3699
|
+
copyLabel: t("markdownCopy"),
|
|
3700
|
+
copiedLabel: t("markdownCopied")
|
|
3701
|
+
},
|
|
3702
|
+
footnotes: t("markdownFootnotes")
|
|
3703
|
+
}), [t]);
|
|
3704
|
+
}
|
|
3705
|
+
/** MarkdownText with the labels the running Host demands.
|
|
3706
|
+
*
|
|
3707
|
+
* The published primitives package still types the old `codeLabels` prop, so
|
|
3708
|
+
* the new shape cannot typecheck against it — the cast is confined here rather
|
|
3709
|
+
* than repeated at every call site. Drop it once the installed
|
|
3710
|
+
* @deepseek-ai/dsh-client-ui-primitives matches the Desktop build. */
|
|
3711
|
+
function ClaudeMarkdown({ text, labels, streaming }) {
|
|
3712
|
+
const props = {
|
|
3713
|
+
text,
|
|
3714
|
+
labels,
|
|
3715
|
+
...streaming === void 0 ? {} : { streaming }
|
|
3334
3716
|
};
|
|
3717
|
+
const Renderer = MarkdownText;
|
|
3718
|
+
ensureClaudeMarkdownTheme();
|
|
3719
|
+
return /* @__PURE__ */ jsx("div", {
|
|
3720
|
+
className: CLAUDE_MARKDOWN_SCOPE,
|
|
3721
|
+
children: /* @__PURE__ */ jsx(Renderer, { ...props })
|
|
3722
|
+
});
|
|
3335
3723
|
}
|
|
3336
|
-
|
|
3337
|
-
|
|
3724
|
+
//#endregion
|
|
3725
|
+
//#region src/plugin-budget.ts
|
|
3726
|
+
/**
|
|
3727
|
+
* The plugin's connection budget, in one table.
|
|
3728
|
+
*
|
|
3729
|
+
* A browser opens a small fixed number of connections to one origin — six for
|
|
3730
|
+
* HTTP/1.1 in Chromium — and shares them with the Host's own traffic. Every
|
|
3731
|
+
* response this plugin holds open costs one of them for its lifetime, and a
|
|
3732
|
+
* request that cannot get one waits in the browser's queue where no server-side
|
|
3733
|
+
* deadline can reach it. When the pool is exhausted the panels that would
|
|
3734
|
+
* *diagnose* the problem are the first thing to stop answering, which is how
|
|
3735
|
+
* this failure has always presented: four settings cards timing out at once
|
|
3736
|
+
* against a Host that is demonstrably healthy.
|
|
3737
|
+
*
|
|
3738
|
+
* So the budget is a fixed constant rather than a function of how much work is
|
|
3739
|
+
* in flight. Steady state is one connection (the multiplexed projection
|
|
3740
|
+
* carrier); the peak is `PLUGIN_GLOBAL_PERMITS`, whatever the session count.
|
|
3741
|
+
*
|
|
3742
|
+
* Both halves read this file, which is the point: a route declares a budget
|
|
3743
|
+
* class and the client derives its wait from the same entry, so a server
|
|
3744
|
+
* deadline can never be quietly longer than the client's patience.
|
|
3745
|
+
*/
|
|
3746
|
+
/** Server-side budget classes. A route declares a class, never a number. */
|
|
3747
|
+
const ROUTE_BUDGET_MS = {
|
|
3748
|
+
/** Answers from memory or a single bounded probe. */
|
|
3749
|
+
fast: 5e3,
|
|
3750
|
+
/** Chains local Git work. */
|
|
3751
|
+
git: 45e3,
|
|
3752
|
+
/** Reaches the network: remote Git, `gh`, the npm registry. */
|
|
3753
|
+
remote: 15e4
|
|
3754
|
+
};
|
|
3755
|
+
/** The client waits one round trip longer, so the route's own 504 wins the
|
|
3756
|
+
* race and the caller learns which budget elapsed instead of guessing. */
|
|
3757
|
+
const CLIENT_GRACE_MS = 3e3;
|
|
3758
|
+
function clientBudgetMs(budget) {
|
|
3759
|
+
return ROUTE_BUDGET_MS[budget] + CLIENT_GRACE_MS;
|
|
3760
|
+
}
|
|
3761
|
+
/** A request that never got a permit fails fast and says so, rather than
|
|
3762
|
+
* spending its whole budget queued behind work it cannot see. */
|
|
3763
|
+
const QUEUE_WAIT_BUDGET_MS = 4e3;
|
|
3764
|
+
/** Per-lane ceilings inside the global budget.
|
|
3765
|
+
*
|
|
3766
|
+
* The projection carrier holds a permanently reserved permit outside these,
|
|
3767
|
+
* so three remain. `write + stream <= 2` leaves one permit that only a read
|
|
3768
|
+
* can take: a diagnostic read always has somewhere to go, however many slow
|
|
3769
|
+
* actions are in flight. That invariant is what stops a 150s repository
|
|
3770
|
+
* action from reproducing the original symptom through a new mechanism. */
|
|
3771
|
+
const PLUGIN_LANE_CAPS = {
|
|
3772
|
+
read: 2,
|
|
3773
|
+
write: 1,
|
|
3774
|
+
stream: 1
|
|
3775
|
+
};
|
|
3776
|
+
//#endregion
|
|
3777
|
+
//#region src/rewind.ts
|
|
3778
|
+
function isRewound(ranges, seq) {
|
|
3779
|
+
return ranges.some((range) => seq >= range.start && seq <= range.end);
|
|
3338
3780
|
}
|
|
3339
|
-
|
|
3340
|
-
|
|
3781
|
+
//#endregion
|
|
3782
|
+
//#region src/client/plugin-transport.ts
|
|
3783
|
+
/**
|
|
3784
|
+
* The plugin's only door to the network.
|
|
3785
|
+
*
|
|
3786
|
+
* Every request the Client makes goes through here, and the shape of the
|
|
3787
|
+
* public functions is the point. There is no `signal` property, no `headers`,
|
|
3788
|
+
* no `RequestInit` and no millisecond number in any parameter: a caller cannot
|
|
3789
|
+
* hand this module an options object whose later spread silently overwrites
|
|
3790
|
+
* the deadline sitting above it. That is not a hypothetical — it is exactly
|
|
3791
|
+
* how four call sites lost their deadlines while looking completely ordinary,
|
|
3792
|
+
* because `exactOptionalPropertyTypes` forbids writing `signal: undefined` and
|
|
3793
|
+
* the spread idiom is what people reach for instead. `cancel` is positional
|
|
3794
|
+
* here, so `undefined` is simply passable and the idiom has nothing to do.
|
|
3795
|
+
*
|
|
3796
|
+
* The second seal is arithmetic. The browser shares a small fixed connection
|
|
3797
|
+
* budget between this plugin and the Host, and the plugin used to spend it
|
|
3798
|
+
* proportionally to how many Claude sessions existed. Here every request takes
|
|
3799
|
+
* a permit from a fixed pool first, so more call sites and more sessions
|
|
3800
|
+
* cannot become more sockets — a saturated plugin queues, and says `starved`
|
|
3801
|
+
* within `QUEUE_WAIT_BUDGET_MS` instead of dying silently at its full budget.
|
|
3802
|
+
*
|
|
3803
|
+
* Lane caps guarantee a read always has somewhere to go: the projection
|
|
3804
|
+
* carrier holds a reserved permit, and `write + stream` can occupy at most two
|
|
3805
|
+
* of the remaining three. The panel that diagnoses a saturated pool is
|
|
3806
|
+
* therefore the one request that cannot be starved by it.
|
|
3807
|
+
*/
|
|
3808
|
+
var PluginRequestError = class extends Error {
|
|
3809
|
+
reason;
|
|
3810
|
+
status;
|
|
3811
|
+
code;
|
|
3812
|
+
constructor(reason, message, status, code) {
|
|
3813
|
+
super(message);
|
|
3814
|
+
this.name = "PluginRequestError";
|
|
3815
|
+
this.reason = reason;
|
|
3816
|
+
if (status !== void 0) this.status = status;
|
|
3817
|
+
if (code !== void 0) this.code = code;
|
|
3818
|
+
}
|
|
3819
|
+
};
|
|
3820
|
+
let send = (...args) => fetch(...args);
|
|
3821
|
+
let held = 0;
|
|
3822
|
+
const laneHeld = {
|
|
3823
|
+
read: 0,
|
|
3824
|
+
write: 0,
|
|
3825
|
+
stream: 0
|
|
3826
|
+
};
|
|
3827
|
+
let projectionHeld = false;
|
|
3828
|
+
let queue = [];
|
|
3829
|
+
const inFlight = /* @__PURE__ */ new Map();
|
|
3830
|
+
/** The projection carrier owns a permit of its own, outside the lane caps, so
|
|
3831
|
+
* the transcript stream and the panels never compete for the same slot. */
|
|
3832
|
+
function laneHasRoom(lane) {
|
|
3833
|
+
return held < 4 && laneHeld[lane] < PLUGIN_LANE_CAPS[lane];
|
|
3834
|
+
}
|
|
3835
|
+
function pump() {
|
|
3836
|
+
for (let index = 0; index < queue.length; index += 1) {
|
|
3837
|
+
const waiter = queue[index];
|
|
3838
|
+
if (waiter === void 0 || !laneHasRoom(waiter.lane)) continue;
|
|
3839
|
+
queue.splice(index, 1);
|
|
3840
|
+
index -= 1;
|
|
3841
|
+
clearTimeout(waiter.timer);
|
|
3842
|
+
held += 1;
|
|
3843
|
+
laneHeld[waiter.lane] += 1;
|
|
3844
|
+
waiter.admit();
|
|
3845
|
+
}
|
|
3341
3846
|
}
|
|
3342
|
-
function
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3847
|
+
function release(lane) {
|
|
3848
|
+
held -= 1;
|
|
3849
|
+
laneHeld[lane] -= 1;
|
|
3850
|
+
pump();
|
|
3851
|
+
}
|
|
3852
|
+
function acquire(lane) {
|
|
3853
|
+
let released = false;
|
|
3854
|
+
const releaseOnce = () => {
|
|
3855
|
+
if (released) return;
|
|
3856
|
+
released = true;
|
|
3857
|
+
release(lane);
|
|
3353
3858
|
};
|
|
3859
|
+
if (laneHasRoom(lane)) {
|
|
3860
|
+
held += 1;
|
|
3861
|
+
laneHeld[lane] += 1;
|
|
3862
|
+
return Promise.resolve(releaseOnce);
|
|
3863
|
+
}
|
|
3864
|
+
return new Promise((resolve, reject) => {
|
|
3865
|
+
const waiter = {
|
|
3866
|
+
lane,
|
|
3867
|
+
admit: () => resolve(releaseOnce),
|
|
3868
|
+
reject,
|
|
3869
|
+
timer: setTimeout(() => {
|
|
3870
|
+
queue = queue.filter((item) => item !== waiter);
|
|
3871
|
+
reject(new PluginRequestError("starved", "The plugin is holding every connection it is allowed to open."));
|
|
3872
|
+
}, QUEUE_WAIT_BUDGET_MS)
|
|
3873
|
+
};
|
|
3874
|
+
queue.push(waiter);
|
|
3875
|
+
});
|
|
3354
3876
|
}
|
|
3355
|
-
function
|
|
3356
|
-
if (
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
return "×";
|
|
3877
|
+
function withQuery(path, query) {
|
|
3878
|
+
if (query === void 0) return path;
|
|
3879
|
+
const encoded = new URLSearchParams(query).toString();
|
|
3880
|
+
return encoded.length === 0 ? path : `${path}?${encoded}`;
|
|
3360
3881
|
}
|
|
3361
|
-
function
|
|
3362
|
-
if (
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
return
|
|
3882
|
+
function failureOf(error, cancel) {
|
|
3883
|
+
if (error instanceof PluginRequestError) return error;
|
|
3884
|
+
if (cancel?.aborted === true) return new PluginRequestError("cancelled", "The caller cancelled the request.");
|
|
3885
|
+
const name = error instanceof Error ? error.name : "";
|
|
3886
|
+
if (name === "TimeoutError" || name === "AbortError") return new PluginRequestError("timeout", "The plugin route did not answer inside its budget.");
|
|
3887
|
+
return new PluginRequestError("http", error instanceof Error ? error.message : String(error));
|
|
3367
3888
|
}
|
|
3368
|
-
function
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
return
|
|
3889
|
+
async function decode(response) {
|
|
3890
|
+
let payload;
|
|
3891
|
+
try {
|
|
3892
|
+
payload = await response.json();
|
|
3893
|
+
} catch {
|
|
3894
|
+
if (response.ok) throw new PluginRequestError("shape", "The plugin route answered with a body this build cannot read.");
|
|
3895
|
+
throw new PluginRequestError("http", `HTTP ${response.status}`, response.status);
|
|
3896
|
+
}
|
|
3897
|
+
if (response.ok) return payload;
|
|
3898
|
+
if (response.status === 404) throw new PluginRequestError("route-missing", "The Host is running an older plugin build without this route.", 404);
|
|
3899
|
+
const record = typeof payload === "object" && payload !== null ? payload : void 0;
|
|
3900
|
+
const message = typeof record?.message === "string" ? record.message : typeof record?.error === "string" ? record.error : `HTTP ${response.status}`;
|
|
3901
|
+
const code = typeof record?.error === "string" ? record.error : void 0;
|
|
3902
|
+
throw new PluginRequestError("http", message, response.status, code);
|
|
3903
|
+
}
|
|
3904
|
+
async function dispatch(lane, method, path, budget, cancel, options) {
|
|
3905
|
+
const url = withQuery(path, options?.query);
|
|
3906
|
+
const key = options?.key ?? `${method} ${url}`;
|
|
3907
|
+
const existing = inFlight.get(key);
|
|
3908
|
+
if (existing !== void 0) return await existing;
|
|
3909
|
+
const run = (async () => {
|
|
3910
|
+
const free = await acquire(lane);
|
|
3911
|
+
try {
|
|
3912
|
+
const timeout = AbortSignal.timeout(clientBudgetMs(budget));
|
|
3913
|
+
const signal = cancel === void 0 ? timeout : AbortSignal.any([cancel, timeout]);
|
|
3914
|
+
return await decode(await send(url, {
|
|
3915
|
+
method,
|
|
3916
|
+
credentials: "same-origin",
|
|
3917
|
+
signal,
|
|
3918
|
+
headers: options?.json === void 0 ? { accept: "application/json" } : {
|
|
3919
|
+
accept: "application/json",
|
|
3920
|
+
"content-type": "application/json"
|
|
3921
|
+
},
|
|
3922
|
+
...options?.json === void 0 ? {} : { body: JSON.stringify(options.json) }
|
|
3923
|
+
}));
|
|
3924
|
+
} catch (error) {
|
|
3925
|
+
throw failureOf(error, cancel);
|
|
3926
|
+
} finally {
|
|
3927
|
+
free();
|
|
3928
|
+
}
|
|
3929
|
+
})();
|
|
3930
|
+
inFlight.set(key, run);
|
|
3931
|
+
try {
|
|
3932
|
+
return await run;
|
|
3933
|
+
} finally {
|
|
3934
|
+
if (inFlight.get(key) === run) inFlight.delete(key);
|
|
3935
|
+
}
|
|
3377
3936
|
}
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
style: taskActivityTitle,
|
|
3390
|
-
children: activity.title ?? activity.kind
|
|
3391
|
-
}),
|
|
3392
|
-
activity.summary === void 0 ? null : /* @__PURE__ */ jsx("p", {
|
|
3393
|
-
style: taskActivitySummary,
|
|
3394
|
-
children: activity.summary
|
|
3395
|
-
}),
|
|
3396
|
-
activity.detail === void 0 ? null : /* @__PURE__ */ jsxs("details", {
|
|
3397
|
-
style: taskActivityDetail,
|
|
3398
|
-
children: [/* @__PURE__ */ jsx("summary", {
|
|
3399
|
-
style: taskActivityDetailSummary,
|
|
3400
|
-
children: t("detail")
|
|
3401
|
-
}), /* @__PURE__ */ jsx("pre", {
|
|
3402
|
-
style: detailCode,
|
|
3403
|
-
children: activity.detail
|
|
3404
|
-
})]
|
|
3405
|
-
})
|
|
3406
|
-
]
|
|
3407
|
-
})]
|
|
3937
|
+
/** A bounded read of a plugin route. */
|
|
3938
|
+
function pluginRead(path, budget, cancel, options) {
|
|
3939
|
+
return dispatch("read", "GET", path, budget, cancel, options);
|
|
3940
|
+
}
|
|
3941
|
+
/** A bounded write. Writes never coalesce by default: two of them are two
|
|
3942
|
+
* intents, even when their bodies match. */
|
|
3943
|
+
function pluginWrite(path, budget, cancel, options) {
|
|
3944
|
+
const method = options?.method ?? "POST";
|
|
3945
|
+
return dispatch("write", method, path, budget, cancel, {
|
|
3946
|
+
...options,
|
|
3947
|
+
key: options?.key ?? `${method} ${withQuery(path, options?.query)} #${nextWriteId()}`
|
|
3408
3948
|
});
|
|
3409
3949
|
}
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
const failed = task.status === "failed" || task.status === "killed";
|
|
3415
|
-
const meta = taskMeta(task, t);
|
|
3416
|
-
return /* @__PURE__ */ jsxs("article", {
|
|
3417
|
-
style: {
|
|
3418
|
-
...taskCard,
|
|
3419
|
-
...running ? taskCardRunning : {}
|
|
3420
|
-
},
|
|
3421
|
-
children: [
|
|
3422
|
-
/* @__PURE__ */ jsxs("div", {
|
|
3423
|
-
style: taskCardTop,
|
|
3424
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
3425
|
-
className: running ? "dsh-claude-act-running" : void 0,
|
|
3426
|
-
style: {
|
|
3427
|
-
...taskCardGlyph,
|
|
3428
|
-
...running ? iconChipRunning : {},
|
|
3429
|
-
...failed ? iconChipError : {}
|
|
3430
|
-
},
|
|
3431
|
-
"aria-hidden": "true",
|
|
3432
|
-
children: statusGlyph(task.status)
|
|
3433
|
-
}), /* @__PURE__ */ jsxs("div", {
|
|
3434
|
-
style: taskCardBody,
|
|
3435
|
-
children: [/* @__PURE__ */ jsx("p", {
|
|
3436
|
-
style: {
|
|
3437
|
-
...taskTitle,
|
|
3438
|
-
...failed ? { color: "var(--dsw-alias-state-error-primary)" } : {}
|
|
3439
|
-
},
|
|
3440
|
-
children: task.description
|
|
3441
|
-
}), /* @__PURE__ */ jsxs("p", {
|
|
3442
|
-
style: taskStatusLine,
|
|
3443
|
-
children: [/* @__PURE__ */ jsx("span", { children: t(STATUS_LABEL[task.status] ?? "tasksRunning") }), task.backgrounded === true ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
|
|
3444
|
-
"aria-hidden": "true",
|
|
3445
|
-
children: " · "
|
|
3446
|
-
}), /* @__PURE__ */ jsx("span", { children: t("tasksBackground") })] }) : null]
|
|
3447
|
-
})]
|
|
3448
|
-
})]
|
|
3449
|
-
}),
|
|
3450
|
-
meta.length === 0 ? null : /* @__PURE__ */ jsx("p", {
|
|
3451
|
-
style: taskMeta$1,
|
|
3452
|
-
children: meta.join(" · ")
|
|
3453
|
-
}),
|
|
3454
|
-
task.summary === void 0 || running ? null : /* @__PURE__ */ jsx("p", {
|
|
3455
|
-
style: taskSummary,
|
|
3456
|
-
children: task.summary
|
|
3457
|
-
}),
|
|
3458
|
-
activities.length === 0 ? null : /* @__PURE__ */ jsxs("div", {
|
|
3459
|
-
style: taskActivitySection,
|
|
3460
|
-
children: [/* @__PURE__ */ jsx("button", {
|
|
3461
|
-
type: "button",
|
|
3462
|
-
style: taskTextButton,
|
|
3463
|
-
"aria-expanded": activityOpen,
|
|
3464
|
-
onClick: () => setActivityOpen((value) => !value),
|
|
3465
|
-
children: activityOpen ? t("tasksHideActivity") : t("tasksViewActivity")
|
|
3466
|
-
}), activityOpen ? /* @__PURE__ */ jsx("ul", {
|
|
3467
|
-
style: taskActivityList,
|
|
3468
|
-
children: activities.map((activity) => /* @__PURE__ */ jsx(TaskActivity, {
|
|
3469
|
-
activity,
|
|
3470
|
-
t
|
|
3471
|
-
}, `${activity.turn}:${activity.step}:${activity.ordinal}`))
|
|
3472
|
-
}) : null]
|
|
3473
|
-
})
|
|
3474
|
-
]
|
|
3475
|
-
});
|
|
3476
|
-
}
|
|
3477
|
-
function GroupHeading(props) {
|
|
3478
|
-
const { label, count, collapsed, onToggle, action } = props;
|
|
3479
|
-
const content = /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", { children: label }), /* @__PURE__ */ jsx("span", {
|
|
3480
|
-
style: tasksGroupCount,
|
|
3481
|
-
children: count
|
|
3482
|
-
})] });
|
|
3483
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
3484
|
-
style: tasksGroupHeading,
|
|
3485
|
-
children: [onToggle === void 0 ? /* @__PURE__ */ jsx("div", {
|
|
3486
|
-
style: tasksGroupTitle,
|
|
3487
|
-
children: content
|
|
3488
|
-
}) : /* @__PURE__ */ jsxs("button", {
|
|
3489
|
-
type: "button",
|
|
3490
|
-
style: tasksGroupToggle,
|
|
3491
|
-
"aria-expanded": !collapsed,
|
|
3492
|
-
onClick: onToggle,
|
|
3493
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
3494
|
-
style: {
|
|
3495
|
-
...chevron,
|
|
3496
|
-
...collapsed === true ? {} : chevronOpen
|
|
3497
|
-
},
|
|
3498
|
-
children: "›"
|
|
3499
|
-
}), content]
|
|
3500
|
-
}), action === void 0 ? null : /* @__PURE__ */ jsx("button", {
|
|
3501
|
-
type: "button",
|
|
3502
|
-
style: taskTextButton,
|
|
3503
|
-
onClick: action.onClick,
|
|
3504
|
-
children: action.label
|
|
3505
|
-
})]
|
|
3506
|
-
});
|
|
3507
|
-
}
|
|
3508
|
-
function ClaudeTasksPanel({ useClaudeProjection, t, closeDetails, turn }) {
|
|
3509
|
-
const projection = useClaudeProjection((value) => value);
|
|
3510
|
-
const tasks = useMemo(() => tasksForTurn(projection.tasks?.tasks ?? [], turn), [projection.tasks, turn]);
|
|
3511
|
-
useEffect(() => {
|
|
3512
|
-
if (!projection.owned || tasks.length === 0) closeDetails();
|
|
3513
|
-
}, [
|
|
3514
|
-
closeDetails,
|
|
3515
|
-
projection.owned,
|
|
3516
|
-
tasks.length
|
|
3517
|
-
]);
|
|
3518
|
-
const [finishedCollapsed, setFinishedCollapsed] = useState(false);
|
|
3519
|
-
const [dismissedSettledIds, setDismissedSettledIds] = useState(() => /* @__PURE__ */ new Set());
|
|
3520
|
-
const groups = useMemo(() => visibleTaskGroups(tasks, dismissedSettledIds), [tasks, dismissedSettledIds]);
|
|
3521
|
-
const taskActivities = useMemo(() => new Map(tasks.map((task) => [task.taskId, activitiesForTask(projection.activities, task.taskId)])), [projection.activities, tasks]);
|
|
3522
|
-
const clearFinished = () => setDismissedSettledIds((previous) => /* @__PURE__ */ new Set([...previous, ...tasks.filter((task) => task.status !== "running").map((task) => task.taskId)]));
|
|
3523
|
-
if (!projection.owned) return null;
|
|
3524
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
3525
|
-
className: detailsCardClass,
|
|
3526
|
-
style: tasksPanel,
|
|
3527
|
-
children: [
|
|
3528
|
-
/* @__PURE__ */ jsxs("style", {
|
|
3529
|
-
"data-dsh-claude-panel-icon-styles": true,
|
|
3530
|
-
children: [detailsCardCss, panelIconButtonCss]
|
|
3531
|
-
}),
|
|
3532
|
-
/* @__PURE__ */ jsxs("div", {
|
|
3533
|
-
style: tasksHeader,
|
|
3534
|
-
children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("span", {
|
|
3535
|
-
style: tasksHeading,
|
|
3536
|
-
children: t("tasksPanelTurn")
|
|
3537
|
-
}), /* @__PURE__ */ jsx("span", {
|
|
3538
|
-
style: tasksTurnMeta,
|
|
3539
|
-
children: t("tasksTurnNumber", { turn })
|
|
3540
|
-
})] }), /* @__PURE__ */ jsx("button", {
|
|
3541
|
-
type: "button",
|
|
3542
|
-
className: panelIconButtonClass,
|
|
3543
|
-
"aria-label": t("tasksClose"),
|
|
3544
|
-
onClick: closeDetails,
|
|
3545
|
-
children: /* @__PURE__ */ jsx(IconCloseOutline16, {})
|
|
3546
|
-
})]
|
|
3547
|
-
}),
|
|
3548
|
-
/* @__PURE__ */ jsxs("div", {
|
|
3549
|
-
style: tasksBody,
|
|
3550
|
-
children: [/* @__PURE__ */ jsxs("section", {
|
|
3551
|
-
"aria-label": t("tasksRunning"),
|
|
3552
|
-
children: [/* @__PURE__ */ jsx(GroupHeading, {
|
|
3553
|
-
label: t("tasksRunning"),
|
|
3554
|
-
count: groups.running.length
|
|
3555
|
-
}), groups.running.length === 0 ? /* @__PURE__ */ jsx("p", {
|
|
3556
|
-
style: tasksGroupEmpty,
|
|
3557
|
-
children: t("tasksNoneRunning")
|
|
3558
|
-
}) : /* @__PURE__ */ jsx("div", {
|
|
3559
|
-
style: taskCardList,
|
|
3560
|
-
children: groups.running.map((task) => /* @__PURE__ */ jsx(TaskCard, {
|
|
3561
|
-
task,
|
|
3562
|
-
activities: taskActivities.get(task.taskId) ?? [],
|
|
3563
|
-
t
|
|
3564
|
-
}, task.taskId))
|
|
3565
|
-
})]
|
|
3566
|
-
}), /* @__PURE__ */ jsxs("section", {
|
|
3567
|
-
"aria-label": t("tasksSettled"),
|
|
3568
|
-
style: tasksFinishedSection,
|
|
3569
|
-
children: [/* @__PURE__ */ jsx(GroupHeading, {
|
|
3570
|
-
label: t("tasksSettled"),
|
|
3571
|
-
count: groups.finished.length,
|
|
3572
|
-
collapsed: finishedCollapsed,
|
|
3573
|
-
onToggle: () => setFinishedCollapsed((value) => !value),
|
|
3574
|
-
...groups.finished.length === 0 ? {} : { action: {
|
|
3575
|
-
label: t("tasksClear"),
|
|
3576
|
-
onClick: clearFinished
|
|
3577
|
-
} }
|
|
3578
|
-
}), finishedCollapsed || groups.finished.length === 0 ? null : /* @__PURE__ */ jsx("div", {
|
|
3579
|
-
style: taskCardList,
|
|
3580
|
-
children: groups.finished.map((task) => /* @__PURE__ */ jsx(TaskCard, {
|
|
3581
|
-
task,
|
|
3582
|
-
activities: taskActivities.get(task.taskId) ?? [],
|
|
3583
|
-
t
|
|
3584
|
-
}, task.taskId))
|
|
3585
|
-
})]
|
|
3586
|
-
})]
|
|
3587
|
-
})
|
|
3588
|
-
]
|
|
3589
|
-
});
|
|
3590
|
-
}
|
|
3591
|
-
//#endregion
|
|
3592
|
-
//#region src/client/ClaudeActivityTail.tsx
|
|
3593
|
-
const MAX_HOVER_TASKS = 6;
|
|
3594
|
-
function taskGlyph(status) {
|
|
3595
|
-
if (status === "failed") return {
|
|
3596
|
-
glyph: "×",
|
|
3597
|
-
style: tasksHoverGlyphError
|
|
3598
|
-
};
|
|
3599
|
-
if (status === "completed") return {
|
|
3600
|
-
glyph: "✓",
|
|
3601
|
-
style: tasksHoverGlyphDone
|
|
3602
|
-
};
|
|
3603
|
-
return {
|
|
3604
|
-
glyph: "●",
|
|
3605
|
-
style: tasksHoverGlyphRunning
|
|
3606
|
-
};
|
|
3607
|
-
}
|
|
3608
|
-
function ClaudeTaskLauncher({ turn, tasks, t, openTasks }) {
|
|
3609
|
-
const [hovered, setHovered] = useState(false);
|
|
3610
|
-
const closeTimer = useRef();
|
|
3611
|
-
const turnTasks = useMemo(() => tasksForTurn(tasks, turn), [tasks, turn]);
|
|
3612
|
-
const summary = useMemo(() => summarizeTurnTasks(turnTasks), [turnTasks]);
|
|
3613
|
-
const open = () => {
|
|
3614
|
-
if (closeTimer.current !== void 0) clearTimeout(closeTimer.current);
|
|
3615
|
-
closeTimer.current = void 0;
|
|
3616
|
-
setHovered(true);
|
|
3617
|
-
};
|
|
3618
|
-
const scheduleClose = () => {
|
|
3619
|
-
if (closeTimer.current !== void 0) clearTimeout(closeTimer.current);
|
|
3620
|
-
closeTimer.current = setTimeout(() => {
|
|
3621
|
-
closeTimer.current = void 0;
|
|
3622
|
-
setHovered(false);
|
|
3623
|
-
}, 350);
|
|
3624
|
-
};
|
|
3625
|
-
useEffect(() => () => {
|
|
3626
|
-
if (closeTimer.current !== void 0) clearTimeout(closeTimer.current);
|
|
3627
|
-
}, []);
|
|
3628
|
-
if (summary === void 0) return null;
|
|
3629
|
-
const label = summary.state === "running" ? t("tasksTurnRunning", { count: summary.running }) : summary.state === "failed" ? t("tasksTurnFailed", {
|
|
3630
|
-
failed: summary.failed,
|
|
3631
|
-
completed: summary.completed
|
|
3632
|
-
}) : t("tasksTurnCompleted", { count: summary.completed });
|
|
3633
|
-
const stateStyle = summary.state === "completed" ? tasksBadgeDone : {};
|
|
3634
|
-
const dotStyle = summary.state === "failed" ? tasksBadgeDotError : summary.state === "completed" ? tasksBadgeDotDone : {};
|
|
3635
|
-
return /* @__PURE__ */ jsx("div", {
|
|
3636
|
-
"data-claude-task-launcher": turn,
|
|
3637
|
-
style: tasksBadgeWrap,
|
|
3638
|
-
children: /* @__PURE__ */ jsxs("span", {
|
|
3639
|
-
style: tasksBadgeSeat,
|
|
3640
|
-
onMouseEnter: open,
|
|
3641
|
-
onMouseLeave: scheduleClose,
|
|
3642
|
-
onFocus: open,
|
|
3643
|
-
onBlur: (event) => {
|
|
3644
|
-
if (!event.currentTarget.contains(event.relatedTarget)) scheduleClose();
|
|
3645
|
-
},
|
|
3646
|
-
children: [hovered ? /* @__PURE__ */ jsxs("span", {
|
|
3647
|
-
role: "tooltip",
|
|
3648
|
-
style: tasksHoverCard,
|
|
3649
|
-
onMouseEnter: open,
|
|
3650
|
-
onMouseLeave: scheduleClose,
|
|
3651
|
-
children: [
|
|
3652
|
-
/* @__PURE__ */ jsx("span", {
|
|
3653
|
-
style: tasksHoverHeader,
|
|
3654
|
-
children: label
|
|
3655
|
-
}),
|
|
3656
|
-
turnTasks.slice(0, MAX_HOVER_TASKS).map((task) => {
|
|
3657
|
-
const { glyph, style } = taskGlyph(task.status);
|
|
3658
|
-
return /* @__PURE__ */ jsxs("span", {
|
|
3659
|
-
style: tasksHoverRow,
|
|
3660
|
-
children: [
|
|
3661
|
-
/* @__PURE__ */ jsx("span", {
|
|
3662
|
-
className: task.status === "running" ? "dsh-claude-act-running" : void 0,
|
|
3663
|
-
style: {
|
|
3664
|
-
...tasksHoverGlyph,
|
|
3665
|
-
...style
|
|
3666
|
-
},
|
|
3667
|
-
"aria-hidden": "true",
|
|
3668
|
-
children: glyph
|
|
3669
|
-
}),
|
|
3670
|
-
/* @__PURE__ */ jsx("span", {
|
|
3671
|
-
style: tasksHoverDesc,
|
|
3672
|
-
title: task.description,
|
|
3673
|
-
children: task.description
|
|
3674
|
-
}),
|
|
3675
|
-
task.subagentType === void 0 ? null : /* @__PURE__ */ jsx("span", {
|
|
3676
|
-
style: tasksHoverType,
|
|
3677
|
-
children: task.subagentType
|
|
3678
|
-
})
|
|
3679
|
-
]
|
|
3680
|
-
}, task.taskId);
|
|
3681
|
-
}),
|
|
3682
|
-
turnTasks.length > MAX_HOVER_TASKS ? /* @__PURE__ */ jsxs("span", {
|
|
3683
|
-
style: tasksHoverMore,
|
|
3684
|
-
children: ["+", turnTasks.length - MAX_HOVER_TASKS]
|
|
3685
|
-
}) : null,
|
|
3686
|
-
/* @__PURE__ */ jsx("span", {
|
|
3687
|
-
style: tasksHoverHint,
|
|
3688
|
-
children: t("tasksOpen")
|
|
3689
|
-
})
|
|
3690
|
-
]
|
|
3691
|
-
}) : null, /* @__PURE__ */ jsxs("button", {
|
|
3692
|
-
type: "button",
|
|
3693
|
-
className: "dsh-claude-task-launcher",
|
|
3694
|
-
style: {
|
|
3695
|
-
...tasksTurnBadge,
|
|
3696
|
-
...stateStyle,
|
|
3697
|
-
...hovered ? tasksBadgeHovered : {}
|
|
3698
|
-
},
|
|
3699
|
-
"aria-label": `${label} — ${t("tasksOpen")}`,
|
|
3700
|
-
onClick: () => openTasks(turn),
|
|
3701
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
3702
|
-
className: summary.state === "running" ? "dsh-claude-act-running" : void 0,
|
|
3703
|
-
style: {
|
|
3704
|
-
...tasksBadgeDot,
|
|
3705
|
-
...dotStyle
|
|
3706
|
-
},
|
|
3707
|
-
"aria-hidden": "true"
|
|
3708
|
-
}), /* @__PURE__ */ jsx("span", {
|
|
3709
|
-
style: tasksBadgeLabel,
|
|
3710
|
-
children: label
|
|
3711
|
-
})]
|
|
3712
|
-
})]
|
|
3713
|
-
})
|
|
3714
|
-
});
|
|
3715
|
-
}
|
|
3716
|
-
function ClaudeActivityTail({ matched, useClaudeProjection, t, openTasks }) {
|
|
3717
|
-
const tasks = useClaudeProjection((value) => value.tasks?.tasks ?? []);
|
|
3718
|
-
return /* @__PURE__ */ jsx(ClaudeTaskLauncher, {
|
|
3719
|
-
turn: matched.turn,
|
|
3720
|
-
tasks,
|
|
3721
|
-
t,
|
|
3722
|
-
openTasks
|
|
3723
|
-
});
|
|
3724
|
-
}
|
|
3725
|
-
//#endregion
|
|
3726
|
-
//#region src/client/ClaudeActiveTasksNode.tsx
|
|
3727
|
-
const EMPTY_TASKS$1 = [];
|
|
3728
|
-
/** Render the task launcher while the owning DSH turn is still open. */
|
|
3729
|
-
function ClaudeActiveTasksNode({ node, useClaudeProjection, t, openTasks }) {
|
|
3730
|
-
const tasks = useClaudeProjection((value) => value.tasks?.tasks ?? EMPTY_TASKS$1);
|
|
3731
|
-
return /* @__PURE__ */ jsx(ClaudeTaskLauncher, {
|
|
3732
|
-
turn: node.data.turn,
|
|
3733
|
-
tasks,
|
|
3734
|
-
t,
|
|
3735
|
-
openTasks
|
|
3736
|
-
});
|
|
3737
|
-
}
|
|
3738
|
-
//#endregion
|
|
3739
|
-
//#region src/client/markdown-labels.tsx
|
|
3740
|
-
/** Stable across renders: MarkdownText keys its streaming renderer on this
|
|
3741
|
-
* object's identity and reparses from scratch whenever it changes. */
|
|
3742
|
-
function useClaudeMarkdownLabels(t) {
|
|
3743
|
-
return useMemo(() => ({
|
|
3744
|
-
code: {
|
|
3745
|
-
copyLabel: t("markdownCopy"),
|
|
3746
|
-
copiedLabel: t("markdownCopied")
|
|
3747
|
-
},
|
|
3748
|
-
footnotes: t("markdownFootnotes")
|
|
3749
|
-
}), [t]);
|
|
3950
|
+
let writeId = 0;
|
|
3951
|
+
function nextWriteId() {
|
|
3952
|
+
writeId += 1;
|
|
3953
|
+
return writeId;
|
|
3750
3954
|
}
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3955
|
+
async function openStream(lane, path, cancel, options, reserved) {
|
|
3956
|
+
const url = withQuery(path, options?.query);
|
|
3957
|
+
const free = reserved ? () => {
|
|
3958
|
+
projectionHeld = false;
|
|
3959
|
+
} : await acquire(lane);
|
|
3960
|
+
try {
|
|
3961
|
+
const response = await send(url, {
|
|
3962
|
+
method: options?.method ?? "GET",
|
|
3963
|
+
credentials: "same-origin",
|
|
3964
|
+
signal: cancel,
|
|
3965
|
+
headers: options?.json === void 0 ? { accept: "application/x-ndjson" } : {
|
|
3966
|
+
accept: "application/x-ndjson",
|
|
3967
|
+
"content-type": "application/json"
|
|
3968
|
+
},
|
|
3969
|
+
...options?.json === void 0 ? {} : { body: JSON.stringify(options.json) }
|
|
3970
|
+
});
|
|
3971
|
+
if (!response.ok || response.body === null) {
|
|
3972
|
+
free();
|
|
3973
|
+
if (response.status === 404) throw new PluginRequestError("route-missing", "The Host is running an older plugin build without this route.", 404);
|
|
3974
|
+
throw new PluginRequestError("http", `HTTP ${response.status}`, response.status);
|
|
3975
|
+
}
|
|
3976
|
+
cancel.addEventListener("abort", free, { once: true });
|
|
3977
|
+
return response.body.getReader();
|
|
3978
|
+
} catch (error) {
|
|
3979
|
+
free();
|
|
3980
|
+
throw failureOf(error, cancel);
|
|
3981
|
+
}
|
|
3763
3982
|
}
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3983
|
+
/** A long-lived NDJSON response. No deadline — it is meant to stay open — but
|
|
3984
|
+
* it takes a counted permit for its whole life, which is the bound that
|
|
3985
|
+
* matters. */
|
|
3986
|
+
function pluginNdjson(path, cancel, options) {
|
|
3987
|
+
return openStream("stream", path, cancel, options, false);
|
|
3988
|
+
}
|
|
3989
|
+
/** The reserved projection carrier: exactly one live connection, ever,
|
|
3990
|
+
* whatever the session count. */
|
|
3991
|
+
function pluginProjectionStream(path, cancel, options) {
|
|
3992
|
+
if (projectionHeld) return Promise.reject(new PluginRequestError("starved", "The projection carrier is already open."));
|
|
3993
|
+
projectionHeld = true;
|
|
3994
|
+
return openStream("stream", path, cancel, options, true);
|
|
3995
|
+
}
|
|
3996
|
+
/** Fire-and-forget diagnostics. Dropped rather than queued when saturated:
|
|
3997
|
+
* the channel that reports the plugin's own failures must never be the
|
|
3998
|
+
* traffic that causes them. */
|
|
3999
|
+
function pluginBeacon(path, body) {
|
|
4000
|
+
if (!laneHasRoom("write")) return;
|
|
4001
|
+
pluginWrite(path, "fast", void 0, { json: body }).catch(() => void 0);
|
|
3768
4002
|
}
|
|
3769
4003
|
//#endregion
|
|
3770
4004
|
//#region src/client/projection.ts
|
|
@@ -3776,6 +4010,13 @@ window.__ModuleLoader__.load({
|
|
|
3776
4010
|
activities: []
|
|
3777
4011
|
};
|
|
3778
4012
|
const RETRY_DELAY_MS = 2e3;
|
|
4013
|
+
/** Floor between carrier reopens forced by a desync. A carrier that is losing
|
|
4014
|
+
* lines must not be answered with a reconnect per lost line. */
|
|
4015
|
+
const RESYNC_COOLDOWN_MS = 5e3;
|
|
4016
|
+
/** Wait for the subscribed set to stop moving before reopening the carrier:
|
|
4017
|
+
* mounting a session list changes it once per row. */
|
|
4018
|
+
const SUBSCRIPTION_SETTLE_MS = 250;
|
|
4019
|
+
const NDJSON_SEPARATOR = String.fromCharCode(10);
|
|
3779
4020
|
/** Coalesce stream deltas into at most one React notification per frame. */
|
|
3780
4021
|
const FRAME_MS = 16;
|
|
3781
4022
|
/** Typewriter smoothing: drain newly arrived prose over roughly this window,
|
|
@@ -3899,11 +4140,19 @@ window.__ModuleLoader__.load({
|
|
|
3899
4140
|
setTimeout(resolve, ms).unref?.();
|
|
3900
4141
|
});
|
|
3901
4142
|
}
|
|
3902
|
-
/**
|
|
3903
|
-
*
|
|
3904
|
-
|
|
4143
|
+
/** One session's reducer over the shared carrier's lines.
|
|
4144
|
+
*
|
|
4145
|
+
* A source opens nothing. It used to hold a stream of its own, which made the
|
|
4146
|
+
* plugin's connection count a function of how many sessions existed;
|
|
4147
|
+
* {@link ClaudeProjectionStore} now owns the single carrier and feeds each
|
|
4148
|
+
* session's lines here. `onDemand` reports whether anyone is watching, which
|
|
4149
|
+
* is what the store uses to decide which sessions the carrier subscribes to. */
|
|
4150
|
+
function createClaudeProjectionSource(sessionId, onDemand = () => {}, onDesync = () => {}) {
|
|
3905
4151
|
let snapshot = EMPTY_CLAUDE_PROJECTION;
|
|
3906
4152
|
let revision = 0;
|
|
4153
|
+
/** Last carrier line this reducer applied, by the server's count. Undefined
|
|
4154
|
+
* until a snapshot states where the stream stands. */
|
|
4155
|
+
let seq;
|
|
3907
4156
|
let owned = false;
|
|
3908
4157
|
let commands = [];
|
|
3909
4158
|
let contextUsage;
|
|
@@ -3916,8 +4165,6 @@ window.__ModuleLoader__.load({
|
|
|
3916
4165
|
/** Streaming prose still being revealed: full arrived text plus shown chars. */
|
|
3917
4166
|
const reveal = /* @__PURE__ */ new Map();
|
|
3918
4167
|
let disposed = false;
|
|
3919
|
-
let running = false;
|
|
3920
|
-
let controller;
|
|
3921
4168
|
let frame;
|
|
3922
4169
|
let usedAnimationFrame = false;
|
|
3923
4170
|
const listeners = /* @__PURE__ */ new Set();
|
|
@@ -4016,7 +4263,7 @@ window.__ModuleLoader__.load({
|
|
|
4016
4263
|
}
|
|
4017
4264
|
};
|
|
4018
4265
|
const applyText = (event) => {
|
|
4019
|
-
const { turn, step, ordinal, append, text } = event;
|
|
4266
|
+
const { turn, step, ordinal, append, text, renderer } = event;
|
|
4020
4267
|
if (!nonNegativeInteger(turn) || !nonNegativeInteger(step) || !nonNegativeInteger(ordinal)) return false;
|
|
4021
4268
|
if (append !== void 0 && (typeof append !== "string" || append.length > MAX_TRANSCRIPT_CHARS)) return false;
|
|
4022
4269
|
if (text !== void 0 && (typeof text !== "string" || text.length > MAX_TRANSCRIPT_CHARS)) return false;
|
|
@@ -4033,7 +4280,8 @@ window.__ModuleLoader__.load({
|
|
|
4033
4280
|
step,
|
|
4034
4281
|
ordinal,
|
|
4035
4282
|
kind: "text",
|
|
4036
|
-
phase: "updated"
|
|
4283
|
+
phase: "updated",
|
|
4284
|
+
...isClaudeRenderMode(renderer) ? { renderer } : {}
|
|
4037
4285
|
},
|
|
4038
4286
|
text: fullText
|
|
4039
4287
|
};
|
|
@@ -4053,6 +4301,17 @@ window.__ModuleLoader__.load({
|
|
|
4053
4301
|
});
|
|
4054
4302
|
return true;
|
|
4055
4303
|
};
|
|
4304
|
+
/** This reducer is behind the server and cannot catch up on its own: only a
|
|
4305
|
+
* fresh snapshot can. Reported as well as acted on, because a silent
|
|
4306
|
+
* self-heal hides how often the carrier is losing lines.
|
|
4307
|
+
*
|
|
4308
|
+
* Numbering stops until that snapshot arrives and states it again; every
|
|
4309
|
+
* line in between would only be measured against a count already known to
|
|
4310
|
+
* be wrong. */
|
|
4311
|
+
const desync = (kind, detail) => {
|
|
4312
|
+
seq = void 0;
|
|
4313
|
+
onDesync(kind, `${sessionId}: ${detail}`);
|
|
4314
|
+
};
|
|
4056
4315
|
const applyLine = (line) => {
|
|
4057
4316
|
const trimmed = line.trim();
|
|
4058
4317
|
if (trimmed.length === 0) return;
|
|
@@ -4064,10 +4323,16 @@ window.__ModuleLoader__.load({
|
|
|
4064
4323
|
}
|
|
4065
4324
|
const event = record$7(value);
|
|
4066
4325
|
if (event === void 0 || typeof event.type !== "string") return;
|
|
4326
|
+
if (event.type === "checkpoint") {
|
|
4327
|
+
if (nonNegativeInteger(event.seq) && seq !== void 0 && event.seq !== seq) desync("projection-gap", `checkpoint at ${event.seq}, applied ${seq}`);
|
|
4328
|
+
return;
|
|
4329
|
+
}
|
|
4330
|
+
if (event.type !== "snapshot" && nonNegativeInteger(event.seq) && seq !== void 0 && event.seq !== seq + 1) desync("projection-gap", `expected ${seq + 1}, received ${event.seq}`);
|
|
4067
4331
|
try {
|
|
4068
4332
|
switch (event.type) {
|
|
4069
4333
|
case "snapshot": {
|
|
4070
4334
|
const next = parseClaudeClientProjection(event);
|
|
4335
|
+
seq = nonNegativeInteger(event.seq) ? event.seq : void 0;
|
|
4071
4336
|
revision = next.revision;
|
|
4072
4337
|
owned = next.owned;
|
|
4073
4338
|
commands = next.commands;
|
|
@@ -4080,7 +4345,10 @@ window.__ModuleLoader__.load({
|
|
|
4080
4345
|
break;
|
|
4081
4346
|
}
|
|
4082
4347
|
case "text":
|
|
4083
|
-
if (!applyText(event))
|
|
4348
|
+
if (!applyText(event)) {
|
|
4349
|
+
if (seq !== void 0) desync("projection-delta-rejected", "text not applied");
|
|
4350
|
+
return;
|
|
4351
|
+
}
|
|
4084
4352
|
revision += 1;
|
|
4085
4353
|
break;
|
|
4086
4354
|
case "activity":
|
|
@@ -4114,92 +4382,197 @@ window.__ModuleLoader__.load({
|
|
|
4114
4382
|
default: return;
|
|
4115
4383
|
}
|
|
4116
4384
|
} catch {
|
|
4385
|
+
desync("projection-delta-rejected", `rejected ${event.type}`);
|
|
4117
4386
|
return;
|
|
4118
4387
|
}
|
|
4388
|
+
if (nonNegativeInteger(event.seq)) seq = event.seq;
|
|
4119
4389
|
schedulePublish();
|
|
4120
4390
|
};
|
|
4121
|
-
const run = async () => {
|
|
4122
|
-
if (running) return;
|
|
4123
|
-
running = true;
|
|
4124
|
-
try {
|
|
4125
|
-
while (!disposed && listeners.size > 0) {
|
|
4126
|
-
controller = new AbortController();
|
|
4127
|
-
try {
|
|
4128
|
-
const response = await fetchProjection(`${CLAUDE_PROJECTION_PATH}/${encodeURIComponent(sessionId)}/stream`, {
|
|
4129
|
-
headers: { accept: "application/x-ndjson" },
|
|
4130
|
-
signal: controller.signal
|
|
4131
|
-
});
|
|
4132
|
-
if (!response.ok) throw new Error(`Claude projection stream failed (${response.status})`);
|
|
4133
|
-
if (response.body === null) throw new Error("Claude projection stream is unavailable");
|
|
4134
|
-
const reader = response.body.getReader();
|
|
4135
|
-
const decoder = new TextDecoder();
|
|
4136
|
-
let buffer = "";
|
|
4137
|
-
while (true) {
|
|
4138
|
-
if (disposed || listeners.size === 0) {
|
|
4139
|
-
await reader.cancel().catch(() => void 0);
|
|
4140
|
-
break;
|
|
4141
|
-
}
|
|
4142
|
-
const chunk = await reader.read();
|
|
4143
|
-
buffer += decoder.decode(chunk.value, { stream: !chunk.done });
|
|
4144
|
-
const lines = buffer.split("\n");
|
|
4145
|
-
buffer = lines.pop() ?? "";
|
|
4146
|
-
for (const line of lines) applyLine(line);
|
|
4147
|
-
if (chunk.done) break;
|
|
4148
|
-
}
|
|
4149
|
-
} catch (error) {
|
|
4150
|
-
if (isAbort(error)) return;
|
|
4151
|
-
} finally {
|
|
4152
|
-
controller = void 0;
|
|
4153
|
-
}
|
|
4154
|
-
if (disposed || listeners.size === 0) return;
|
|
4155
|
-
await delay(retryDelayMs);
|
|
4156
|
-
}
|
|
4157
|
-
} finally {
|
|
4158
|
-
running = false;
|
|
4159
|
-
}
|
|
4160
|
-
};
|
|
4161
4391
|
return {
|
|
4162
4392
|
getSnapshot: () => snapshot,
|
|
4393
|
+
feed: applyLine,
|
|
4163
4394
|
subscribe(listener) {
|
|
4164
4395
|
if (disposed) return () => {};
|
|
4165
4396
|
const wasIdle = listeners.size === 0;
|
|
4166
4397
|
listeners.add(listener);
|
|
4167
|
-
if (wasIdle)
|
|
4398
|
+
if (wasIdle) onDemand(true);
|
|
4168
4399
|
return () => {
|
|
4169
4400
|
listeners.delete(listener);
|
|
4170
4401
|
if (listeners.size !== 0) return;
|
|
4171
|
-
controller?.abort();
|
|
4172
|
-
controller = void 0;
|
|
4173
4402
|
cancelFrame();
|
|
4403
|
+
onDemand(false);
|
|
4174
4404
|
};
|
|
4175
4405
|
},
|
|
4176
4406
|
dispose() {
|
|
4177
4407
|
disposed = true;
|
|
4178
4408
|
listeners.clear();
|
|
4179
|
-
controller?.abort();
|
|
4180
|
-
controller = void 0;
|
|
4181
4409
|
cancelFrame();
|
|
4410
|
+
onDemand(false);
|
|
4182
4411
|
}
|
|
4183
4412
|
};
|
|
4184
4413
|
}
|
|
4414
|
+
/**
|
|
4415
|
+
* Every session's projection over ONE connection.
|
|
4416
|
+
*
|
|
4417
|
+
* The plugin used to open an NDJSON stream per session, so its share of the
|
|
4418
|
+
* browser's small per-origin connection budget grew with the number of Claude
|
|
4419
|
+
* sessions — and the overview panel subscribes one per LISTED session, not per
|
|
4420
|
+
* open one. Past a handful of sessions the plugin's own settings panel could no
|
|
4421
|
+
* longer get a connection at all, which is the failure this class exists to
|
|
4422
|
+
* make impossible: the carrier is one connection whatever the session count.
|
|
4423
|
+
*
|
|
4424
|
+
* `source(sessionId)` keeps its shape, so consumers are unaware of any of this.
|
|
4425
|
+
*/
|
|
4185
4426
|
var ClaudeProjectionStore = class {
|
|
4186
4427
|
#sources = /* @__PURE__ */ new Map();
|
|
4428
|
+
/** Sessions with at least one live subscriber, newest interest last. */
|
|
4429
|
+
#wanted = /* @__PURE__ */ new Set();
|
|
4430
|
+
#open;
|
|
4431
|
+
#retryDelayMs;
|
|
4432
|
+
#settleMs;
|
|
4433
|
+
#report;
|
|
4434
|
+
#resyncCooldownMs;
|
|
4435
|
+
#resyncedAt = 0;
|
|
4436
|
+
#controller;
|
|
4437
|
+
#settle;
|
|
4438
|
+
#running = false;
|
|
4439
|
+
#disposed = false;
|
|
4440
|
+
constructor(options = {}) {
|
|
4441
|
+
this.#open = options.open ?? ((path, cancel) => pluginProjectionStream(path, cancel));
|
|
4442
|
+
this.#retryDelayMs = options.retryDelayMs ?? RETRY_DELAY_MS;
|
|
4443
|
+
this.#settleMs = options.settleMs ?? SUBSCRIPTION_SETTLE_MS;
|
|
4444
|
+
this.#report = options.report ?? (() => {});
|
|
4445
|
+
this.#resyncCooldownMs = options.resyncCooldownMs ?? RESYNC_COOLDOWN_MS;
|
|
4446
|
+
}
|
|
4187
4447
|
source(sessionId) {
|
|
4188
4448
|
let source = this.#sources.get(sessionId);
|
|
4189
4449
|
if (source === void 0) {
|
|
4190
|
-
source = createClaudeProjectionSource(sessionId)
|
|
4450
|
+
source = createClaudeProjectionSource(sessionId, (active) => {
|
|
4451
|
+
this.#demand(sessionId, active);
|
|
4452
|
+
}, (kind, detail) => {
|
|
4453
|
+
this.#resync(kind, detail);
|
|
4454
|
+
});
|
|
4191
4455
|
this.#sources.set(sessionId, source);
|
|
4192
4456
|
}
|
|
4193
4457
|
return source;
|
|
4194
4458
|
}
|
|
4459
|
+
/** Reopen the carrier so every lane is restated from a fresh snapshot.
|
|
4460
|
+
*
|
|
4461
|
+
* One session noticed the hole, but the carrier is shared and a dropped
|
|
4462
|
+
* line is a property of the carrier, so the others are suspect too --
|
|
4463
|
+
* reopening restates all of them for the price of the one reconnect. */
|
|
4464
|
+
#resync(kind, detail) {
|
|
4465
|
+
if (this.#disposed) return;
|
|
4466
|
+
this.#report(kind, detail);
|
|
4467
|
+
const now = Date.now();
|
|
4468
|
+
if (now - this.#resyncedAt < this.#resyncCooldownMs) return;
|
|
4469
|
+
this.#resyncedAt = now;
|
|
4470
|
+
this.#reopen();
|
|
4471
|
+
}
|
|
4195
4472
|
dispose() {
|
|
4473
|
+
this.#disposed = true;
|
|
4474
|
+
if (this.#settle !== void 0) clearTimeout(this.#settle);
|
|
4475
|
+
this.#settle = void 0;
|
|
4476
|
+
this.#controller?.abort();
|
|
4477
|
+
this.#controller = void 0;
|
|
4196
4478
|
for (const source of this.#sources.values()) source.dispose();
|
|
4197
4479
|
this.#sources.clear();
|
|
4480
|
+
this.#wanted.clear();
|
|
4481
|
+
}
|
|
4482
|
+
/** Note interest and reopen the carrier once the set stops moving. Mounting
|
|
4483
|
+
* a session list would otherwise reopen it once per row. */
|
|
4484
|
+
#demand(sessionId, active) {
|
|
4485
|
+
if (this.#disposed) return;
|
|
4486
|
+
if (active) {
|
|
4487
|
+
this.#wanted.delete(sessionId);
|
|
4488
|
+
this.#wanted.add(sessionId);
|
|
4489
|
+
} else if (!this.#wanted.delete(sessionId)) return;
|
|
4490
|
+
if (this.#settle !== void 0) clearTimeout(this.#settle);
|
|
4491
|
+
const timer = setTimeout(() => {
|
|
4492
|
+
this.#settle = void 0;
|
|
4493
|
+
this.#reopen();
|
|
4494
|
+
}, this.#settleMs);
|
|
4495
|
+
timer.unref?.();
|
|
4496
|
+
this.#settle = timer;
|
|
4497
|
+
}
|
|
4498
|
+
#lanes() {
|
|
4499
|
+
const wanted = [...this.#wanted];
|
|
4500
|
+
return wanted.slice(Math.max(0, wanted.length - 16));
|
|
4501
|
+
}
|
|
4502
|
+
#reopen() {
|
|
4503
|
+
this.#controller?.abort();
|
|
4504
|
+
this.#controller = void 0;
|
|
4505
|
+
if (this.#disposed || this.#wanted.size === 0) return;
|
|
4506
|
+
this.#run();
|
|
4507
|
+
}
|
|
4508
|
+
async #run() {
|
|
4509
|
+
if (this.#running) return;
|
|
4510
|
+
this.#running = true;
|
|
4511
|
+
try {
|
|
4512
|
+
while (!this.#disposed && this.#wanted.size > 0) {
|
|
4513
|
+
const controller = new AbortController();
|
|
4514
|
+
this.#controller = controller;
|
|
4515
|
+
const lanes = this.#lanes();
|
|
4516
|
+
let superseded = false;
|
|
4517
|
+
try {
|
|
4518
|
+
const reader = await this.#open(`${CLAUDE_PROJECTION_PATH}/multi?sessions=${lanes.map(encodeURIComponent).join(",")}`, controller.signal);
|
|
4519
|
+
const stop = () => {
|
|
4520
|
+
superseded = true;
|
|
4521
|
+
reader.cancel().catch(() => void 0);
|
|
4522
|
+
};
|
|
4523
|
+
controller.signal.addEventListener("abort", stop, { once: true });
|
|
4524
|
+
const decoder = new TextDecoder();
|
|
4525
|
+
let buffer = "";
|
|
4526
|
+
while (!controller.signal.aborted) {
|
|
4527
|
+
const chunk = await reader.read();
|
|
4528
|
+
buffer += decoder.decode(chunk.value, { stream: !chunk.done });
|
|
4529
|
+
const lines = buffer.split(NDJSON_SEPARATOR);
|
|
4530
|
+
buffer = lines.pop() ?? "";
|
|
4531
|
+
for (const line of lines) this.#dispatch(line);
|
|
4532
|
+
if (chunk.done) break;
|
|
4533
|
+
}
|
|
4534
|
+
controller.signal.removeEventListener("abort", stop);
|
|
4535
|
+
await reader.cancel().catch(() => void 0);
|
|
4536
|
+
} catch (error) {
|
|
4537
|
+
if (isAbort(error)) {
|
|
4538
|
+
if (this.#controller !== controller) continue;
|
|
4539
|
+
return;
|
|
4540
|
+
}
|
|
4541
|
+
} finally {
|
|
4542
|
+
if (this.#controller === controller) this.#controller = void 0;
|
|
4543
|
+
}
|
|
4544
|
+
if (this.#disposed || this.#wanted.size === 0) return;
|
|
4545
|
+
if (superseded) continue;
|
|
4546
|
+
await delay(this.#retryDelayMs);
|
|
4547
|
+
}
|
|
4548
|
+
} finally {
|
|
4549
|
+
this.#running = false;
|
|
4550
|
+
}
|
|
4551
|
+
}
|
|
4552
|
+
/** Route one carrier line to the session that owns it. A line for a session
|
|
4553
|
+
* nobody is watching any more is dropped rather than reviving its lane. */
|
|
4554
|
+
#dispatch(line) {
|
|
4555
|
+
if (line.length === 0) return;
|
|
4556
|
+
let session;
|
|
4557
|
+
try {
|
|
4558
|
+
session = JSON.parse(line).session;
|
|
4559
|
+
} catch {
|
|
4560
|
+
return;
|
|
4561
|
+
}
|
|
4562
|
+
if (typeof session !== "string") return;
|
|
4563
|
+
this.#sources.get(session)?.feed(line);
|
|
4198
4564
|
}
|
|
4199
4565
|
};
|
|
4200
4566
|
//#endregion
|
|
4567
|
+
//#region src/client/token-format.ts
|
|
4568
|
+
function formatTokenCount(tokens) {
|
|
4569
|
+
if (tokens >= 1e6) return `${Number((tokens / 1e6).toFixed(tokens >= 1e7 ? 0 : 1))}M`;
|
|
4570
|
+
if (tokens >= 1e3) return `${Number((tokens / 1e3).toFixed(tokens >= 1e5 ? 0 : 1))}K`;
|
|
4571
|
+
return String(tokens);
|
|
4572
|
+
}
|
|
4573
|
+
//#endregion
|
|
4201
4574
|
//#region src/client/ClaudeActivityNode.tsx
|
|
4202
|
-
const EMPTY_TASKS = [];
|
|
4575
|
+
const EMPTY_TASKS$1 = [];
|
|
4203
4576
|
const ACTIVITY_CSS = [
|
|
4204
4577
|
".dsh-claude-flow{display:flex;flex-direction:column;gap:10px}",
|
|
4205
4578
|
".dsh-claude-transcript-text{color:var(--dsw-alias-label-primary);font-size:15px;line-height:24px;overflow-wrap:anywhere}",
|
|
@@ -4232,6 +4605,13 @@ window.__ModuleLoader__.load({
|
|
|
4232
4605
|
".dsh-claude-diff-delete{color:var(--dsw-alias-state-error-primary)}",
|
|
4233
4606
|
".dsh-claude-tool-name{font-size:14px;line-height:22px;color:var(--dsw-alias-label-primary)}",
|
|
4234
4607
|
".dsh-claude-tool-summary{margin-left:8px;color:var(--dsw-alias-label-tertiary)}",
|
|
4608
|
+
".dsh-claude-turn-usage{display:flex;flex-wrap:wrap;align-items:center;gap:5px;margin-top:10px;",
|
|
4609
|
+
"color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}",
|
|
4610
|
+
".dsh-claude-turn-usage-label{color:var(--dsw-alias-label-caption)}",
|
|
4611
|
+
".dsh-claude-tool-terminal{display:flex;gap:8px;margin:6px 0 0;padding:8px 10px;max-height:220px;overflow:auto;",
|
|
4612
|
+
"border-radius:8px;background:var(--dsw-alias-markdown-code-block);font:var(--dsw-font-markdown-code-block-small)}",
|
|
4613
|
+
".dsh-claude-tool-prompt{flex:none;user-select:none;color:var(--dsw-alias-label-caption)}",
|
|
4614
|
+
".dsh-claude-tool-command{min-width:0;margin:0;color:var(--dsw-alias-label-primary);white-space:pre-wrap;overflow-wrap:anywhere}",
|
|
4235
4615
|
".dsh-claude-tool-detail{margin:6px 0 0;padding:8px 10px;max-height:220px;overflow:auto;border-radius:8px;background:var(--dsw-alias-markdown-code-block);font:var(--dsw-font-markdown-code-block-small);white-space:pre-wrap;overflow-wrap:anywhere}",
|
|
4236
4616
|
".dsh-claude-flow-row{position:relative;overflow:hidden}",
|
|
4237
4617
|
".dsh-claude-flow-leading{flex-shrink:0}",
|
|
@@ -4407,6 +4787,21 @@ window.__ModuleLoader__.load({
|
|
|
4407
4787
|
}, index))
|
|
4408
4788
|
});
|
|
4409
4789
|
}
|
|
4790
|
+
/** The command as a shell prompt rather than a labelled field: it was typed
|
|
4791
|
+
* at one, and the prompt is what tells a reader that at a glance. */
|
|
4792
|
+
function Terminal({ command }) {
|
|
4793
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
4794
|
+
className: "dsh-claude-tool-terminal",
|
|
4795
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
4796
|
+
className: "dsh-claude-tool-prompt",
|
|
4797
|
+
"aria-hidden": "true",
|
|
4798
|
+
children: "$"
|
|
4799
|
+
}), /* @__PURE__ */ jsx("pre", {
|
|
4800
|
+
className: "dsh-claude-tool-command",
|
|
4801
|
+
children: command
|
|
4802
|
+
})]
|
|
4803
|
+
});
|
|
4804
|
+
}
|
|
4410
4805
|
function TextDetail({ title, value }) {
|
|
4411
4806
|
if (value === void 0 || value.length === 0) return null;
|
|
4412
4807
|
return /* @__PURE__ */ jsx(Section, {
|
|
@@ -4479,176 +4874,633 @@ window.__ModuleLoader__.load({
|
|
|
4479
4874
|
if (tool.toolName === "Bash" || tool.toolName === "PowerShell") {
|
|
4480
4875
|
const command = text$1(input?.command);
|
|
4481
4876
|
const terminal = [text$1(output?.stdout), text$1(output?.stderr)].filter((value) => value !== void 0).join("\n");
|
|
4877
|
+
const typed = command ?? (typeof inputValue === "string" ? inputValue : void 0);
|
|
4482
4878
|
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
4483
|
-
/* @__PURE__ */ jsx(
|
|
4484
|
-
title: "Command",
|
|
4485
|
-
value: command ?? (typeof inputValue === "string" ? inputValue : void 0)
|
|
4486
|
-
}),
|
|
4879
|
+
typed === void 0 ? null : /* @__PURE__ */ jsx(Terminal, { command: typed }),
|
|
4487
4880
|
input === void 0 ? null : /* @__PURE__ */ jsx(Section, {
|
|
4488
4881
|
title: t("toolInput"),
|
|
4489
4882
|
children: /* @__PURE__ */ jsx(Fields, {
|
|
4490
4883
|
value: input,
|
|
4491
|
-
omit: ["command"]
|
|
4884
|
+
omit: ["command", "description"]
|
|
4492
4885
|
})
|
|
4493
4886
|
}),
|
|
4494
4887
|
/* @__PURE__ */ jsx(TextDetail, {
|
|
4495
4888
|
title: outputTitle,
|
|
4496
4889
|
value: terminal || (typeof outputValue === "string" ? outputValue : void 0)
|
|
4497
4890
|
})
|
|
4498
|
-
] });
|
|
4499
|
-
}
|
|
4500
|
-
return /* @__PURE__ */ jsxs(Fragment$1, { children: [input === void 0 ? /* @__PURE__ */ jsx(TextDetail, {
|
|
4501
|
-
title: t("toolInput"),
|
|
4502
|
-
value: typeof inputValue === "string" ? inputValue : void 0
|
|
4503
|
-
}) : /* @__PURE__ */ jsx(Section, {
|
|
4504
|
-
title: t("toolInput"),
|
|
4505
|
-
children: /* @__PURE__ */ jsx(Fields, { value: input })
|
|
4506
|
-
}), output === void 0 ? /* @__PURE__ */ jsx(TextDetail, {
|
|
4507
|
-
title: outputTitle,
|
|
4508
|
-
value: typeof outputValue === "string" ? outputValue : void 0
|
|
4509
|
-
}) : /* @__PURE__ */ jsx(Section, {
|
|
4510
|
-
title: outputTitle,
|
|
4511
|
-
children: /* @__PURE__ */ jsx(Fields, { value: output })
|
|
4512
|
-
})] });
|
|
4891
|
+
] });
|
|
4892
|
+
}
|
|
4893
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [input === void 0 ? /* @__PURE__ */ jsx(TextDetail, {
|
|
4894
|
+
title: t("toolInput"),
|
|
4895
|
+
value: typeof inputValue === "string" ? inputValue : void 0
|
|
4896
|
+
}) : /* @__PURE__ */ jsx(Section, {
|
|
4897
|
+
title: t("toolInput"),
|
|
4898
|
+
children: /* @__PURE__ */ jsx(Fields, { value: input })
|
|
4899
|
+
}), output === void 0 ? /* @__PURE__ */ jsx(TextDetail, {
|
|
4900
|
+
title: outputTitle,
|
|
4901
|
+
value: typeof outputValue === "string" ? outputValue : void 0
|
|
4902
|
+
}) : /* @__PURE__ */ jsx(Section, {
|
|
4903
|
+
title: outputTitle,
|
|
4904
|
+
children: /* @__PURE__ */ jsx(Fields, { value: output })
|
|
4905
|
+
})] });
|
|
4906
|
+
}
|
|
4907
|
+
function ClaudeTranscriptToolItem({ tool, t }) {
|
|
4908
|
+
return /* @__PURE__ */ jsxs("details", {
|
|
4909
|
+
className: "dsh-claude-tool-item",
|
|
4910
|
+
children: [/* @__PURE__ */ jsxs("summary", {
|
|
4911
|
+
className: "dsh-claude-tool-summary-row",
|
|
4912
|
+
children: [
|
|
4913
|
+
/* @__PURE__ */ jsx("span", {
|
|
4914
|
+
className: "dsh-claude-tool-label",
|
|
4915
|
+
children: tool.toolName
|
|
4916
|
+
}),
|
|
4917
|
+
/* @__PURE__ */ jsx("span", {
|
|
4918
|
+
className: "dsh-claude-tool-description",
|
|
4919
|
+
children: tool.description
|
|
4920
|
+
}),
|
|
4921
|
+
tool.additions === void 0 && tool.deletions === void 0 ? null : /* @__PURE__ */ jsxs("span", {
|
|
4922
|
+
className: "dsh-claude-tool-stats",
|
|
4923
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
4924
|
+
className: "dsh-claude-diff-add",
|
|
4925
|
+
children: ["+", tool.additions ?? 0]
|
|
4926
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
4927
|
+
className: "dsh-claude-diff-delete",
|
|
4928
|
+
children: ["−", tool.deletions ?? 0]
|
|
4929
|
+
})]
|
|
4930
|
+
})
|
|
4931
|
+
]
|
|
4932
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
4933
|
+
className: "dsh-claude-tool-content",
|
|
4934
|
+
children: [tool.subcalls.length === 0 ? null : /* @__PURE__ */ jsx("div", {
|
|
4935
|
+
className: "dsh-claude-flow-subcalls",
|
|
4936
|
+
children: tool.subcalls.map((subcall) => /* @__PURE__ */ jsxs("div", { children: [
|
|
4937
|
+
subcallGlyph(subcall),
|
|
4938
|
+
" ",
|
|
4939
|
+
subcall.toolName ?? t("subagent"),
|
|
4940
|
+
subcall.summary === void 0 ? "" : ` · ${subcall.summary}`
|
|
4941
|
+
] }, subcall.toolUseId))
|
|
4942
|
+
}), /* @__PURE__ */ jsx(ToolPresentation, {
|
|
4943
|
+
tool,
|
|
4944
|
+
t
|
|
4945
|
+
})]
|
|
4946
|
+
})]
|
|
4947
|
+
});
|
|
4948
|
+
}
|
|
4949
|
+
function ClaudeTranscriptToolGroup({ tools, additions, deletions, files: _files, t }) {
|
|
4950
|
+
const [open, setOpen] = useState(false);
|
|
4951
|
+
const failed = tools.some((tool) => tool.isError === true || tool.phase === "failed");
|
|
4952
|
+
const running = tools.some((tool) => tool.phase === "started" || tool.phase === "updated");
|
|
4953
|
+
const summary = tools.length === 1 ? t("usedTool") : t("usedTools", { count: tools.length });
|
|
4954
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
4955
|
+
className: "dsh-claude-tool-group-native",
|
|
4956
|
+
children: [/* @__PURE__ */ jsx(DisclosureRow, {
|
|
4957
|
+
rowClassName: "dsh-claude-flow-row",
|
|
4958
|
+
leadingClassName: "dsh-claude-flow-leading",
|
|
4959
|
+
titleClassName: "dsh-claude-flow-title",
|
|
4960
|
+
chevronClassName: "dsh-claude-flow-chevron",
|
|
4961
|
+
icon: failed ? /* @__PURE__ */ jsx(StateDot, { state: "error" }) : running ? /* @__PURE__ */ jsx(StateDot, { state: "ongoing" }) : /* @__PURE__ */ jsx(IconApiOutline14, { size: 14 }),
|
|
4962
|
+
title: summary,
|
|
4963
|
+
open,
|
|
4964
|
+
expandable: true,
|
|
4965
|
+
expandOnRowClick: true,
|
|
4966
|
+
keepContentWhenOpen: true,
|
|
4967
|
+
onToggle: () => setOpen((value) => !value),
|
|
4968
|
+
collapsedContent: additions !== void 0 || deletions !== void 0 ? /* @__PURE__ */ jsxs("span", {
|
|
4969
|
+
className: "dsh-claude-tool-stats",
|
|
4970
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
4971
|
+
className: "dsh-claude-diff-add",
|
|
4972
|
+
children: ["+", additions ?? 0]
|
|
4973
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
4974
|
+
className: "dsh-claude-diff-delete",
|
|
4975
|
+
children: ["−", deletions ?? 0]
|
|
4976
|
+
})]
|
|
4977
|
+
}) : void 0
|
|
4978
|
+
}), open ? /* @__PURE__ */ jsx("div", {
|
|
4979
|
+
className: "dsh-claude-tool-list",
|
|
4980
|
+
children: tools.map((tool) => /* @__PURE__ */ jsx(ClaudeTranscriptToolItem, {
|
|
4981
|
+
tool,
|
|
4982
|
+
t
|
|
4983
|
+
}, tool.toolUseId))
|
|
4984
|
+
}) : null]
|
|
4985
|
+
});
|
|
4986
|
+
}
|
|
4987
|
+
/** Round a duration the way a reader reads one: no more precision than the
|
|
4988
|
+
* number deserves. */
|
|
4989
|
+
function formatTurnDuration(ms) {
|
|
4990
|
+
if (ms < 1e3) return `${Math.max(1, Math.round(ms))}ms`;
|
|
4991
|
+
const seconds = ms / 1e3;
|
|
4992
|
+
if (seconds < 60) return `${seconds < 10 ? seconds.toFixed(1) : String(Math.round(seconds))}s`;
|
|
4993
|
+
const whole = Math.round(seconds);
|
|
4994
|
+
return `${Math.floor(whole / 60)}m ${String(whole % 60).padStart(2, "0")}s`;
|
|
4995
|
+
}
|
|
4996
|
+
/** Share of the prompt that was served from cache.
|
|
4997
|
+
*
|
|
4998
|
+
* Cache reads are counted against everything the prompt cost to assemble --
|
|
4999
|
+
* fresh input and cache writes included -- so a turn that read nothing scores
|
|
5000
|
+
* zero rather than dividing by nothing. */
|
|
5001
|
+
function cacheHitRate(usage) {
|
|
5002
|
+
const read = usage.cacheReadTokens ?? 0;
|
|
5003
|
+
const total = read + (usage.cacheCreationTokens ?? 0) + (usage.inputTokens ?? 0);
|
|
5004
|
+
return total === 0 ? void 0 : read / total;
|
|
5005
|
+
}
|
|
5006
|
+
/** The turn's accounting, in the order a reader wants it: size, then cost of
|
|
5007
|
+
* assembling it, then how long it took, then money. */
|
|
5008
|
+
function turnUsageParts(usage, t) {
|
|
5009
|
+
const tokens = (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0) + (usage.cacheReadTokens ?? 0) + (usage.cacheCreationTokens ?? 0);
|
|
5010
|
+
const cached = cacheHitRate(usage);
|
|
5011
|
+
const parts = [];
|
|
5012
|
+
if (tokens > 0) parts.push(t("turnUsageTokens", { count: formatTokenCount(tokens) }));
|
|
5013
|
+
if (cached !== void 0) parts.push(t("turnUsageCache", { percent: (cached * 100).toFixed(1) }));
|
|
5014
|
+
if (usage.durationMs !== void 0) parts.push(formatTurnDuration(usage.durationMs));
|
|
5015
|
+
if (usage.ttftMs !== void 0) parts.push(t("turnUsageTtft", { duration: formatTurnDuration(usage.ttftMs) }));
|
|
5016
|
+
if (usage.cumulativeCostUsd !== void 0) parts.push(t("turnUsageCost", { cost: usage.cumulativeCostUsd.toFixed(2) }));
|
|
5017
|
+
return parts;
|
|
5018
|
+
}
|
|
5019
|
+
/** The footer the Host draws under its own assistant message, drawn here for
|
|
5020
|
+
* the steps the Host never had a message for. */
|
|
5021
|
+
function ClaudeTurnUsage({ usage, t }) {
|
|
5022
|
+
const parts = turnUsageParts(usage, t);
|
|
5023
|
+
if (parts.length === 0) return null;
|
|
5024
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
5025
|
+
className: "dsh-claude-turn-usage",
|
|
5026
|
+
children: [
|
|
5027
|
+
/* @__PURE__ */ jsx("span", {
|
|
5028
|
+
className: "dsh-claude-turn-usage-label",
|
|
5029
|
+
children: t("turnUsage")
|
|
5030
|
+
}),
|
|
5031
|
+
/* @__PURE__ */ jsx("span", {
|
|
5032
|
+
"aria-hidden": "true",
|
|
5033
|
+
children: "·"
|
|
5034
|
+
}),
|
|
5035
|
+
/* @__PURE__ */ jsx("span", { children: parts.join(" · ") })
|
|
5036
|
+
]
|
|
5037
|
+
});
|
|
5038
|
+
}
|
|
5039
|
+
function ClaudeActivityNode({ node, useClaudeProjection, t }) {
|
|
5040
|
+
ensureCss$3();
|
|
5041
|
+
const marker = node.data;
|
|
5042
|
+
const activities = useClaudeProjection((value) => selectStepActivities(value, marker.turn, marker.step));
|
|
5043
|
+
const tasks = useClaudeProjection((value) => value.tasks?.tasks ?? EMPTY_TASKS$1);
|
|
5044
|
+
const items = useMemo(() => transcriptItemsForStep(activities, marker.turn, marker.step, tasks), [
|
|
5045
|
+
activities,
|
|
5046
|
+
marker.step,
|
|
5047
|
+
marker.turn,
|
|
5048
|
+
tasks
|
|
5049
|
+
]);
|
|
5050
|
+
const markdownLabels = useClaudeMarkdownLabels(t);
|
|
5051
|
+
if (items.length === 0) return null;
|
|
5052
|
+
return /* @__PURE__ */ jsx("div", {
|
|
5053
|
+
className: "dsh-claude-flow",
|
|
5054
|
+
children: items.map((item) => item.kind === "text" ? /* @__PURE__ */ jsx("div", {
|
|
5055
|
+
className: "dsh-claude-transcript-text",
|
|
5056
|
+
children: /* @__PURE__ */ jsx(ClaudeMarkdown, {
|
|
5057
|
+
text: item.text,
|
|
5058
|
+
labels: markdownLabels
|
|
5059
|
+
})
|
|
5060
|
+
}, `text:${item.ordinal}`) : item.kind === "compaction" ? /* @__PURE__ */ jsx(ClaudeCompactionDivider, {
|
|
5061
|
+
compaction: item.compaction,
|
|
5062
|
+
t
|
|
5063
|
+
}, `compaction:${item.ordinal}`) : item.kind === "usage" ? /* @__PURE__ */ jsx(ClaudeTurnUsage, {
|
|
5064
|
+
usage: item.usage,
|
|
5065
|
+
t
|
|
5066
|
+
}, `usage:${item.ordinal}`) : item.kind === "tools" ? /* @__PURE__ */ jsx(ClaudeTranscriptToolGroup, {
|
|
5067
|
+
tools: item.tools,
|
|
5068
|
+
...item.additions === void 0 ? {} : { additions: item.additions },
|
|
5069
|
+
...item.deletions === void 0 ? {} : { deletions: item.deletions },
|
|
5070
|
+
...item.files === void 0 ? {} : { files: item.files },
|
|
5071
|
+
t
|
|
5072
|
+
}, `tools:${item.ordinal}`) : /* @__PURE__ */ jsx(ActivityRow, {
|
|
5073
|
+
row: item.row,
|
|
5074
|
+
t
|
|
5075
|
+
}, `activity:${item.ordinal}`))
|
|
5076
|
+
});
|
|
4513
5077
|
}
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
5078
|
+
//#endregion
|
|
5079
|
+
//#region src/client/ClaudeTasksPanel.tsx
|
|
5080
|
+
const STATUS_LABEL = {
|
|
5081
|
+
running: "tasksRunning",
|
|
5082
|
+
completed: "tasksCompleted",
|
|
5083
|
+
failed: "tasksFailed",
|
|
5084
|
+
stopped: "tasksStopped",
|
|
5085
|
+
killed: "tasksKilled"
|
|
5086
|
+
};
|
|
5087
|
+
function visibleTaskGroups(tasks, dismissedSettledIds) {
|
|
5088
|
+
const projected = tasks.filter(isProjectedTask);
|
|
5089
|
+
return {
|
|
5090
|
+
running: projected.filter((task) => task.status === "running"),
|
|
5091
|
+
finished: projected.filter((task) => task.status !== "running" && !dismissedSettledIds.has(task.taskId))
|
|
5092
|
+
};
|
|
5093
|
+
}
|
|
5094
|
+
function activitiesForTask(activities, taskId) {
|
|
5095
|
+
return activities.filter((activity) => activity.taskId === taskId);
|
|
5096
|
+
}
|
|
5097
|
+
function tasksForTurn(tasks, turn) {
|
|
5098
|
+
return tasks.filter((task) => task.originTurn === turn && isProjectedTask(task));
|
|
5099
|
+
}
|
|
5100
|
+
function summarizeTurnTasks(tasks) {
|
|
5101
|
+
if (tasks.length === 0) return void 0;
|
|
5102
|
+
const running = tasks.filter((task) => task.status === "running").length;
|
|
5103
|
+
const failed = tasks.filter((task) => task.status === "failed" || task.status === "stopped" || task.status === "killed").length;
|
|
5104
|
+
const completed = tasks.filter((task) => task.status === "completed").length;
|
|
5105
|
+
return {
|
|
5106
|
+
state: running > 0 ? "running" : failed > 0 ? "failed" : "completed",
|
|
5107
|
+
count: tasks.length,
|
|
5108
|
+
running,
|
|
5109
|
+
failed,
|
|
5110
|
+
completed
|
|
5111
|
+
};
|
|
5112
|
+
}
|
|
5113
|
+
function statusGlyph(status) {
|
|
5114
|
+
if (status === "running") return "●";
|
|
5115
|
+
if (status === "completed") return "✓";
|
|
5116
|
+
if (status === "stopped") return "–";
|
|
5117
|
+
return "×";
|
|
5118
|
+
}
|
|
5119
|
+
function formatDuration(ms) {
|
|
5120
|
+
if (ms < 1e3) return String(Math.max(1, Math.round(ms))) + "ms";
|
|
5121
|
+
const seconds = Math.round(ms / 1e3);
|
|
5122
|
+
if (seconds < 60) return String(seconds) + "s";
|
|
5123
|
+
const minutes = Math.floor(seconds / 60);
|
|
5124
|
+
return String(minutes) + "m " + String(seconds % 60) + "s";
|
|
5125
|
+
}
|
|
5126
|
+
function taskMeta(task, t) {
|
|
5127
|
+
const parts = [];
|
|
5128
|
+
if (task.subagentType !== void 0) parts.push(task.subagentType);
|
|
5129
|
+
else if (task.taskType !== void 0) parts.push(task.taskType);
|
|
5130
|
+
if (task.usage?.durationMs !== void 0) parts.push(formatDuration(task.usage.durationMs));
|
|
5131
|
+
if (task.usage?.totalTokens !== void 0) parts.push(t("tokens", { count: formatTokenCount(task.usage.totalTokens) }));
|
|
5132
|
+
if (task.usage?.toolUses !== void 0) parts.push(t("tasksToolUses", { count: task.usage.toolUses }));
|
|
5133
|
+
if (task.lastToolName !== void 0) parts.push(t("tasksLastTool", { tool: task.lastToolName }));
|
|
5134
|
+
return parts;
|
|
5135
|
+
}
|
|
5136
|
+
function TaskActivity({ activity, t }) {
|
|
5137
|
+
return /* @__PURE__ */ jsxs("li", {
|
|
5138
|
+
style: taskActivityItem,
|
|
5139
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
5140
|
+
style: taskActivityGlyph,
|
|
5141
|
+
"aria-hidden": "true",
|
|
5142
|
+
children: activity.isError === true ? "×" : "›"
|
|
5143
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
5144
|
+
style: taskActivityBody,
|
|
4519
5145
|
children: [
|
|
4520
|
-
/* @__PURE__ */ jsx("
|
|
4521
|
-
|
|
4522
|
-
children:
|
|
5146
|
+
/* @__PURE__ */ jsx("p", {
|
|
5147
|
+
style: taskActivityTitle,
|
|
5148
|
+
children: activity.title ?? activity.kind
|
|
4523
5149
|
}),
|
|
4524
|
-
/* @__PURE__ */ jsx("
|
|
4525
|
-
|
|
4526
|
-
children:
|
|
5150
|
+
activity.summary === void 0 ? null : /* @__PURE__ */ jsx("p", {
|
|
5151
|
+
style: taskActivitySummary,
|
|
5152
|
+
children: activity.summary
|
|
4527
5153
|
}),
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
children: [/* @__PURE__ */
|
|
4531
|
-
|
|
4532
|
-
children:
|
|
4533
|
-
}), /* @__PURE__ */
|
|
4534
|
-
|
|
4535
|
-
children:
|
|
5154
|
+
activity.detail === void 0 ? null : /* @__PURE__ */ jsxs("details", {
|
|
5155
|
+
style: taskActivityDetail,
|
|
5156
|
+
children: [/* @__PURE__ */ jsx("summary", {
|
|
5157
|
+
style: taskActivityDetailSummary,
|
|
5158
|
+
children: t("detail")
|
|
5159
|
+
}), /* @__PURE__ */ jsx("pre", {
|
|
5160
|
+
style: detailCode,
|
|
5161
|
+
children: activity.detail
|
|
4536
5162
|
})]
|
|
4537
5163
|
})
|
|
4538
5164
|
]
|
|
4539
|
-
}), /* @__PURE__ */ jsxs("div", {
|
|
4540
|
-
className: "dsh-claude-tool-content",
|
|
4541
|
-
children: [tool.subcalls.length === 0 ? null : /* @__PURE__ */ jsx("div", {
|
|
4542
|
-
className: "dsh-claude-flow-subcalls",
|
|
4543
|
-
children: tool.subcalls.map((subcall) => /* @__PURE__ */ jsxs("div", { children: [
|
|
4544
|
-
subcallGlyph(subcall),
|
|
4545
|
-
" ",
|
|
4546
|
-
subcall.toolName ?? t("subagent"),
|
|
4547
|
-
subcall.summary === void 0 ? "" : ` · ${subcall.summary}`
|
|
4548
|
-
] }, subcall.toolUseId))
|
|
4549
|
-
}), /* @__PURE__ */ jsx(ToolPresentation, {
|
|
4550
|
-
tool,
|
|
4551
|
-
t
|
|
4552
|
-
})]
|
|
4553
5165
|
})]
|
|
4554
5166
|
});
|
|
4555
5167
|
}
|
|
4556
|
-
function
|
|
4557
|
-
const
|
|
4558
|
-
const
|
|
4559
|
-
const
|
|
4560
|
-
const
|
|
5168
|
+
function TaskCard(props) {
|
|
5169
|
+
const { task, activities, allActivities, t } = props;
|
|
5170
|
+
const tools = useMemo(() => taskTools(allActivities, task.taskId), [allActivities, task.taskId]);
|
|
5171
|
+
const [activityOpen, setActivityOpen] = useState(false);
|
|
5172
|
+
const running = task.status === "running";
|
|
5173
|
+
const failed = task.status === "failed" || task.status === "killed";
|
|
5174
|
+
const meta = taskMeta(task, t);
|
|
5175
|
+
return /* @__PURE__ */ jsxs("article", {
|
|
5176
|
+
style: {
|
|
5177
|
+
...taskCard,
|
|
5178
|
+
...running ? taskCardRunning : {}
|
|
5179
|
+
},
|
|
5180
|
+
children: [
|
|
5181
|
+
/* @__PURE__ */ jsxs("div", {
|
|
5182
|
+
style: taskCardTop,
|
|
5183
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
5184
|
+
className: running ? "dsh-claude-act-running" : void 0,
|
|
5185
|
+
style: {
|
|
5186
|
+
...taskCardGlyph,
|
|
5187
|
+
...running ? iconChipRunning : {},
|
|
5188
|
+
...failed ? iconChipError : {}
|
|
5189
|
+
},
|
|
5190
|
+
"aria-hidden": "true",
|
|
5191
|
+
children: statusGlyph(task.status)
|
|
5192
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
5193
|
+
style: taskCardBody,
|
|
5194
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
5195
|
+
style: {
|
|
5196
|
+
...taskTitle,
|
|
5197
|
+
...failed ? { color: "var(--dsw-alias-state-error-primary)" } : {}
|
|
5198
|
+
},
|
|
5199
|
+
children: task.description
|
|
5200
|
+
}), /* @__PURE__ */ jsxs("p", {
|
|
5201
|
+
style: taskStatusLine,
|
|
5202
|
+
children: [/* @__PURE__ */ jsx("span", { children: t(STATUS_LABEL[task.status] ?? "tasksRunning") }), task.backgrounded === true ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
|
|
5203
|
+
"aria-hidden": "true",
|
|
5204
|
+
children: " · "
|
|
5205
|
+
}), /* @__PURE__ */ jsx("span", { children: t("tasksBackground") })] }) : null]
|
|
5206
|
+
})]
|
|
5207
|
+
})]
|
|
5208
|
+
}),
|
|
5209
|
+
meta.length === 0 ? null : /* @__PURE__ */ jsx("p", {
|
|
5210
|
+
style: taskMeta$1,
|
|
5211
|
+
children: meta.join(" · ")
|
|
5212
|
+
}),
|
|
5213
|
+
task.summary === void 0 || running ? null : /* @__PURE__ */ jsx("p", {
|
|
5214
|
+
style: taskSummary,
|
|
5215
|
+
children: task.summary
|
|
5216
|
+
}),
|
|
5217
|
+
activities.length === 0 && tools.length === 0 ? null : /* @__PURE__ */ jsxs("div", {
|
|
5218
|
+
style: taskActivitySection,
|
|
5219
|
+
children: [/* @__PURE__ */ jsx("button", {
|
|
5220
|
+
type: "button",
|
|
5221
|
+
style: taskTextButton,
|
|
5222
|
+
"aria-expanded": activityOpen,
|
|
5223
|
+
onClick: () => setActivityOpen((value) => !value),
|
|
5224
|
+
children: activityOpen ? t("tasksHideActivity") : t("tasksViewActivity")
|
|
5225
|
+
}), !activityOpen ? null : tools.length > 0 ? /* @__PURE__ */ jsx("div", {
|
|
5226
|
+
style: taskToolList,
|
|
5227
|
+
children: tools.map((tool) => /* @__PURE__ */ jsx(ClaudeTranscriptToolItem, {
|
|
5228
|
+
tool,
|
|
5229
|
+
t
|
|
5230
|
+
}, tool.toolUseId))
|
|
5231
|
+
}) : /* @__PURE__ */ jsx("ul", {
|
|
5232
|
+
style: taskActivityList,
|
|
5233
|
+
children: activities.map((activity) => /* @__PURE__ */ jsx(TaskActivity, {
|
|
5234
|
+
activity,
|
|
5235
|
+
t
|
|
5236
|
+
}, `${activity.turn}:${activity.step}:${activity.ordinal}`))
|
|
5237
|
+
})]
|
|
5238
|
+
})
|
|
5239
|
+
]
|
|
5240
|
+
});
|
|
5241
|
+
}
|
|
5242
|
+
function GroupHeading(props) {
|
|
5243
|
+
const { label, count, collapsed, onToggle, action } = props;
|
|
5244
|
+
const content = /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", { children: label }), /* @__PURE__ */ jsx("span", {
|
|
5245
|
+
style: tasksGroupCount,
|
|
5246
|
+
children: count
|
|
5247
|
+
})] });
|
|
5248
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
5249
|
+
style: tasksGroupHeading,
|
|
5250
|
+
children: [onToggle === void 0 ? /* @__PURE__ */ jsx("div", {
|
|
5251
|
+
style: tasksGroupTitle,
|
|
5252
|
+
children: content
|
|
5253
|
+
}) : /* @__PURE__ */ jsxs("button", {
|
|
5254
|
+
type: "button",
|
|
5255
|
+
style: tasksGroupToggle,
|
|
5256
|
+
"aria-expanded": !collapsed,
|
|
5257
|
+
onClick: onToggle,
|
|
5258
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
5259
|
+
style: {
|
|
5260
|
+
...chevron,
|
|
5261
|
+
...collapsed === true ? {} : chevronOpen
|
|
5262
|
+
},
|
|
5263
|
+
children: "›"
|
|
5264
|
+
}), content]
|
|
5265
|
+
}), action === void 0 ? null : /* @__PURE__ */ jsx("button", {
|
|
5266
|
+
type: "button",
|
|
5267
|
+
style: taskTextButton,
|
|
5268
|
+
onClick: action.onClick,
|
|
5269
|
+
children: action.label
|
|
5270
|
+
})]
|
|
5271
|
+
});
|
|
5272
|
+
}
|
|
5273
|
+
function ClaudeTasksPanel({ useClaudeProjection, t, closeDetails, turn }) {
|
|
5274
|
+
const projection = useClaudeProjection((value) => value);
|
|
5275
|
+
const tasks = useMemo(() => tasksForTurn(projection.tasks?.tasks ?? [], turn), [projection.tasks, turn]);
|
|
5276
|
+
useEffect(() => {
|
|
5277
|
+
if (!projection.owned || tasks.length === 0) closeDetails();
|
|
5278
|
+
}, [
|
|
5279
|
+
closeDetails,
|
|
5280
|
+
projection.owned,
|
|
5281
|
+
tasks.length
|
|
5282
|
+
]);
|
|
5283
|
+
const [finishedCollapsed, setFinishedCollapsed] = useState(false);
|
|
5284
|
+
const [dismissedSettledIds, setDismissedSettledIds] = useState(() => /* @__PURE__ */ new Set());
|
|
5285
|
+
const groups = useMemo(() => visibleTaskGroups(tasks, dismissedSettledIds), [tasks, dismissedSettledIds]);
|
|
5286
|
+
const taskActivities = useMemo(() => new Map(tasks.map((task) => [task.taskId, activitiesForTask(projection.activities, task.taskId)])), [projection.activities, tasks]);
|
|
5287
|
+
const clearFinished = () => setDismissedSettledIds((previous) => /* @__PURE__ */ new Set([...previous, ...tasks.filter((task) => task.status !== "running").map((task) => task.taskId)]));
|
|
5288
|
+
if (!projection.owned) return null;
|
|
4561
5289
|
return /* @__PURE__ */ jsxs("div", {
|
|
4562
|
-
className:
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
children:
|
|
5290
|
+
className: detailsCardClass,
|
|
5291
|
+
style: tasksPanel,
|
|
5292
|
+
children: [
|
|
5293
|
+
/* @__PURE__ */ jsxs("style", {
|
|
5294
|
+
"data-dsh-claude-panel-icon-styles": true,
|
|
5295
|
+
children: [detailsCardCss, panelIconButtonCss]
|
|
5296
|
+
}),
|
|
5297
|
+
/* @__PURE__ */ jsxs("div", {
|
|
5298
|
+
style: tasksHeader,
|
|
5299
|
+
children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("span", {
|
|
5300
|
+
style: tasksHeading,
|
|
5301
|
+
children: t("tasksPanelTurn")
|
|
5302
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
5303
|
+
style: tasksTurnMeta,
|
|
5304
|
+
children: t("tasksTurnNumber", { turn })
|
|
5305
|
+
})] }), /* @__PURE__ */ jsx("button", {
|
|
5306
|
+
type: "button",
|
|
5307
|
+
className: panelIconButtonClass,
|
|
5308
|
+
"aria-label": t("tasksClose"),
|
|
5309
|
+
onClick: closeDetails,
|
|
5310
|
+
children: /* @__PURE__ */ jsx(IconCloseOutline16, {})
|
|
4583
5311
|
})]
|
|
4584
|
-
})
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
5312
|
+
}),
|
|
5313
|
+
/* @__PURE__ */ jsxs("div", {
|
|
5314
|
+
style: tasksBody,
|
|
5315
|
+
children: [/* @__PURE__ */ jsxs("section", {
|
|
5316
|
+
"aria-label": t("tasksRunning"),
|
|
5317
|
+
children: [/* @__PURE__ */ jsx(GroupHeading, {
|
|
5318
|
+
label: t("tasksRunning"),
|
|
5319
|
+
count: groups.running.length
|
|
5320
|
+
}), groups.running.length === 0 ? /* @__PURE__ */ jsx("p", {
|
|
5321
|
+
style: tasksGroupEmpty,
|
|
5322
|
+
children: t("tasksNoneRunning")
|
|
5323
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
5324
|
+
style: taskCardList,
|
|
5325
|
+
children: groups.running.map((task) => /* @__PURE__ */ jsx(TaskCard, {
|
|
5326
|
+
task,
|
|
5327
|
+
activities: taskActivities.get(task.taskId) ?? [],
|
|
5328
|
+
allActivities: projection.activities,
|
|
5329
|
+
t
|
|
5330
|
+
}, task.taskId))
|
|
5331
|
+
})]
|
|
5332
|
+
}), /* @__PURE__ */ jsxs("section", {
|
|
5333
|
+
"aria-label": t("tasksSettled"),
|
|
5334
|
+
style: tasksFinishedSection,
|
|
5335
|
+
children: [/* @__PURE__ */ jsx(GroupHeading, {
|
|
5336
|
+
label: t("tasksSettled"),
|
|
5337
|
+
count: groups.finished.length,
|
|
5338
|
+
collapsed: finishedCollapsed,
|
|
5339
|
+
onToggle: () => setFinishedCollapsed((value) => !value),
|
|
5340
|
+
...groups.finished.length === 0 ? {} : { action: {
|
|
5341
|
+
label: t("tasksClear"),
|
|
5342
|
+
onClick: clearFinished
|
|
5343
|
+
} }
|
|
5344
|
+
}), finishedCollapsed || groups.finished.length === 0 ? null : /* @__PURE__ */ jsx("div", {
|
|
5345
|
+
style: taskCardList,
|
|
5346
|
+
children: groups.finished.map((task) => /* @__PURE__ */ jsx(TaskCard, {
|
|
5347
|
+
task,
|
|
5348
|
+
activities: taskActivities.get(task.taskId) ?? [],
|
|
5349
|
+
allActivities: projection.activities,
|
|
5350
|
+
t
|
|
5351
|
+
}, task.taskId))
|
|
5352
|
+
})]
|
|
5353
|
+
})]
|
|
5354
|
+
})
|
|
5355
|
+
]
|
|
4592
5356
|
});
|
|
4593
5357
|
}
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
5358
|
+
//#endregion
|
|
5359
|
+
//#region src/client/ClaudeActivityTail.tsx
|
|
5360
|
+
const MAX_HOVER_TASKS = 6;
|
|
5361
|
+
function taskGlyph(status) {
|
|
5362
|
+
if (status === "failed") return {
|
|
5363
|
+
glyph: "×",
|
|
5364
|
+
style: tasksHoverGlyphError
|
|
5365
|
+
};
|
|
5366
|
+
if (status === "completed") return {
|
|
5367
|
+
glyph: "✓",
|
|
5368
|
+
style: tasksHoverGlyphDone
|
|
5369
|
+
};
|
|
5370
|
+
return {
|
|
5371
|
+
glyph: "●",
|
|
5372
|
+
style: tasksHoverGlyphRunning
|
|
5373
|
+
};
|
|
5374
|
+
}
|
|
5375
|
+
function ClaudeTaskLauncher({ turn, tasks, t, openTasks }) {
|
|
5376
|
+
const [hovered, setHovered] = useState(false);
|
|
5377
|
+
const closeTimer = useRef();
|
|
5378
|
+
const turnTasks = useMemo(() => tasksForTurn(tasks, turn), [tasks, turn]);
|
|
5379
|
+
const summary = useMemo(() => summarizeTurnTasks(turnTasks), [turnTasks]);
|
|
5380
|
+
const open = () => {
|
|
5381
|
+
if (closeTimer.current !== void 0) clearTimeout(closeTimer.current);
|
|
5382
|
+
closeTimer.current = void 0;
|
|
5383
|
+
setHovered(true);
|
|
5384
|
+
};
|
|
5385
|
+
const scheduleClose = () => {
|
|
5386
|
+
if (closeTimer.current !== void 0) clearTimeout(closeTimer.current);
|
|
5387
|
+
closeTimer.current = setTimeout(() => {
|
|
5388
|
+
closeTimer.current = void 0;
|
|
5389
|
+
setHovered(false);
|
|
5390
|
+
}, 350);
|
|
5391
|
+
};
|
|
5392
|
+
useEffect(() => () => {
|
|
5393
|
+
if (closeTimer.current !== void 0) clearTimeout(closeTimer.current);
|
|
5394
|
+
}, []);
|
|
5395
|
+
if (summary === void 0) return null;
|
|
5396
|
+
const label = summary.state === "running" ? t("tasksTurnRunning", { count: summary.running }) : summary.state === "failed" ? t("tasksTurnFailed", {
|
|
5397
|
+
failed: summary.failed,
|
|
5398
|
+
completed: summary.completed
|
|
5399
|
+
}) : t("tasksTurnCompleted", { count: summary.completed });
|
|
5400
|
+
const stateStyle = summary.state === "completed" ? tasksBadgeDone : {};
|
|
5401
|
+
const dotStyle = summary.state === "failed" ? tasksBadgeDotError : summary.state === "completed" ? tasksBadgeDotDone : {};
|
|
4607
5402
|
return /* @__PURE__ */ jsx("div", {
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
5403
|
+
"data-claude-task-launcher": turn,
|
|
5404
|
+
style: tasksBadgeWrap,
|
|
5405
|
+
children: /* @__PURE__ */ jsxs("span", {
|
|
5406
|
+
style: tasksBadgeSeat,
|
|
5407
|
+
onMouseEnter: open,
|
|
5408
|
+
onMouseLeave: scheduleClose,
|
|
5409
|
+
onFocus: open,
|
|
5410
|
+
onBlur: (event) => {
|
|
5411
|
+
if (!event.currentTarget.contains(event.relatedTarget)) scheduleClose();
|
|
5412
|
+
},
|
|
5413
|
+
children: [hovered ? /* @__PURE__ */ jsxs("span", {
|
|
5414
|
+
role: "tooltip",
|
|
5415
|
+
style: tasksHoverCard,
|
|
5416
|
+
onMouseEnter: open,
|
|
5417
|
+
onMouseLeave: scheduleClose,
|
|
5418
|
+
children: [
|
|
5419
|
+
/* @__PURE__ */ jsx("span", {
|
|
5420
|
+
style: tasksHoverHeader,
|
|
5421
|
+
children: label
|
|
5422
|
+
}),
|
|
5423
|
+
turnTasks.slice(0, MAX_HOVER_TASKS).map((task) => {
|
|
5424
|
+
const { glyph, style } = taskGlyph(task.status);
|
|
5425
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
5426
|
+
style: tasksHoverRow,
|
|
5427
|
+
children: [
|
|
5428
|
+
/* @__PURE__ */ jsx("span", {
|
|
5429
|
+
className: task.status === "running" ? "dsh-claude-act-running" : void 0,
|
|
5430
|
+
style: {
|
|
5431
|
+
...tasksHoverGlyph,
|
|
5432
|
+
...style
|
|
5433
|
+
},
|
|
5434
|
+
"aria-hidden": "true",
|
|
5435
|
+
children: glyph
|
|
5436
|
+
}),
|
|
5437
|
+
/* @__PURE__ */ jsx("span", {
|
|
5438
|
+
style: tasksHoverDesc,
|
|
5439
|
+
title: task.description,
|
|
5440
|
+
children: task.description
|
|
5441
|
+
}),
|
|
5442
|
+
task.subagentType === void 0 ? null : /* @__PURE__ */ jsx("span", {
|
|
5443
|
+
style: tasksHoverType,
|
|
5444
|
+
children: task.subagentType
|
|
5445
|
+
})
|
|
5446
|
+
]
|
|
5447
|
+
}, task.taskId);
|
|
5448
|
+
}),
|
|
5449
|
+
turnTasks.length > MAX_HOVER_TASKS ? /* @__PURE__ */ jsxs("span", {
|
|
5450
|
+
style: tasksHoverMore,
|
|
5451
|
+
children: ["+", turnTasks.length - MAX_HOVER_TASKS]
|
|
5452
|
+
}) : null,
|
|
5453
|
+
/* @__PURE__ */ jsx("span", {
|
|
5454
|
+
style: tasksHoverHint,
|
|
5455
|
+
children: t("tasksOpen")
|
|
5456
|
+
})
|
|
5457
|
+
]
|
|
5458
|
+
}) : null, /* @__PURE__ */ jsxs("button", {
|
|
5459
|
+
type: "button",
|
|
5460
|
+
className: "dsh-claude-task-launcher",
|
|
5461
|
+
style: {
|
|
5462
|
+
...tasksTurnBadge,
|
|
5463
|
+
...stateStyle,
|
|
5464
|
+
...hovered ? tasksBadgeHovered : {}
|
|
5465
|
+
},
|
|
5466
|
+
"aria-label": `${label} — ${t("tasksOpen")}`,
|
|
5467
|
+
onClick: () => openTasks(turn),
|
|
5468
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
5469
|
+
className: summary.state === "running" ? "dsh-claude-act-running" : void 0,
|
|
5470
|
+
style: {
|
|
5471
|
+
...tasksBadgeDot,
|
|
5472
|
+
...dotStyle
|
|
5473
|
+
},
|
|
5474
|
+
"aria-hidden": "true"
|
|
5475
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
5476
|
+
style: tasksBadgeLabel,
|
|
5477
|
+
children: label
|
|
5478
|
+
})]
|
|
5479
|
+
})]
|
|
5480
|
+
})
|
|
5481
|
+
});
|
|
5482
|
+
}
|
|
5483
|
+
function ClaudeActivityTail({ matched, useClaudeProjection, t, openTasks }) {
|
|
5484
|
+
const tasks = useClaudeProjection((value) => value.tasks?.tasks ?? []);
|
|
5485
|
+
return /* @__PURE__ */ jsx(ClaudeTaskLauncher, {
|
|
5486
|
+
turn: matched.turn,
|
|
5487
|
+
tasks,
|
|
5488
|
+
t,
|
|
5489
|
+
openTasks
|
|
4628
5490
|
});
|
|
4629
5491
|
}
|
|
4630
5492
|
//#endregion
|
|
4631
|
-
//#region src/client/
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
* Streaming routes (projection, ask, repository setup) are legitimately
|
|
4643
|
-
* long-lived and carry their own cancellation instead. */
|
|
4644
|
-
/** Reads: the route answers from cache or a short-timeout Git call. */
|
|
4645
|
-
const PLUGIN_READ_TIMEOUT_MS = 3e4;
|
|
4646
|
-
/** Writes: the route may chain several remote Git/gh calls of its own. */
|
|
4647
|
-
const PLUGIN_ACTION_TIMEOUT_MS = 18e4;
|
|
4648
|
-
/** Combine a caller's cancellation with this deadline. */
|
|
4649
|
-
function pluginRequestSignal(timeoutMs, signal) {
|
|
4650
|
-
const timeout = AbortSignal.timeout(timeoutMs);
|
|
4651
|
-
return signal === void 0 ? timeout : AbortSignal.any([signal, timeout]);
|
|
5493
|
+
//#region src/client/ClaudeActiveTasksNode.tsx
|
|
5494
|
+
const EMPTY_TASKS = [];
|
|
5495
|
+
/** Render the task launcher while the owning DSH turn is still open. */
|
|
5496
|
+
function ClaudeActiveTasksNode({ node, useClaudeProjection, t, openTasks }) {
|
|
5497
|
+
const tasks = useClaudeProjection((value) => value.tasks?.tasks ?? EMPTY_TASKS);
|
|
5498
|
+
return /* @__PURE__ */ jsx(ClaudeTaskLauncher, {
|
|
5499
|
+
turn: node.data.turn,
|
|
5500
|
+
tasks,
|
|
5501
|
+
t,
|
|
5502
|
+
openTasks
|
|
5503
|
+
});
|
|
4652
5504
|
}
|
|
4653
5505
|
//#endregion
|
|
4654
5506
|
//#region src/client/jira-api.ts
|
|
@@ -4663,68 +5515,75 @@ window.__ModuleLoader__.load({
|
|
|
4663
5515
|
function record$5(value) {
|
|
4664
5516
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
4665
5517
|
}
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
5518
|
+
/**
|
|
5519
|
+
* Every Jira failure the panels catch is a `JiraClientError`, whatever the
|
|
5520
|
+
* transport threw: `ClaudeHeroRepositoryControls` branches on `code` to tell
|
|
5521
|
+
* 'not-connected' apart from a real outage, and the settings card renders the
|
|
5522
|
+
* message verbatim.
|
|
5523
|
+
*
|
|
5524
|
+
* The routes answer `{ error, message }`, so `message` is already the sentence
|
|
5525
|
+
* to show. A body carrying only a code — a 405, a bad JSON body — used to read
|
|
5526
|
+
* 'Jira is unavailable.' rather than leaking the code as prose, and it still
|
|
5527
|
+
* does. Transport failures (a starved pool, an elapsed budget, an older Host
|
|
5528
|
+
* without the route) carry their own wording and keep it.
|
|
5529
|
+
*/
|
|
5530
|
+
function jiraFailure(cause) {
|
|
5531
|
+
if (cause instanceof JiraClientError) return cause;
|
|
5532
|
+
if (!(cause instanceof PluginRequestError)) return new JiraClientError(cause instanceof Error ? cause.message : String(cause));
|
|
5533
|
+
return new JiraClientError(cause.message === cause.code ? "Jira is unavailable." : cause.message, cause.code);
|
|
5534
|
+
}
|
|
5535
|
+
function payload(value) {
|
|
5536
|
+
const body = record$5(value);
|
|
4674
5537
|
if (body === void 0) throw new JiraClientError("Invalid Jira response.");
|
|
4675
5538
|
return body;
|
|
4676
5539
|
}
|
|
4677
|
-
function status(
|
|
5540
|
+
function status(value) {
|
|
5541
|
+
const body = payload(value);
|
|
4678
5542
|
if (typeof body.connected !== "boolean") throw new JiraClientError("Invalid Jira status response.");
|
|
4679
5543
|
return body;
|
|
4680
5544
|
}
|
|
4681
5545
|
async function loadJiraStatus(signal) {
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
}
|
|
5546
|
+
try {
|
|
5547
|
+
return status(await pluginRead(`${CLAUDE_JIRA_PATH}/status`, "remote", signal));
|
|
5548
|
+
} catch (cause) {
|
|
5549
|
+
throw jiraFailure(cause);
|
|
5550
|
+
}
|
|
4687
5551
|
}
|
|
4688
5552
|
async function connectJira(input) {
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
},
|
|
4695
|
-
body: JSON.stringify(input)
|
|
4696
|
-
}));
|
|
5553
|
+
try {
|
|
5554
|
+
return status(await pluginWrite(`${CLAUDE_JIRA_PATH}/connect`, "remote", void 0, { json: input }));
|
|
5555
|
+
} catch (cause) {
|
|
5556
|
+
throw jiraFailure(cause);
|
|
5557
|
+
}
|
|
4697
5558
|
}
|
|
4698
5559
|
async function disconnectJira() {
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
5560
|
+
try {
|
|
5561
|
+
await pluginWrite(`${CLAUDE_JIRA_PATH}/disconnect`, "remote");
|
|
5562
|
+
} catch (cause) {
|
|
5563
|
+
throw jiraFailure(cause);
|
|
5564
|
+
}
|
|
4703
5565
|
}
|
|
4704
5566
|
async function searchJiraTickets(query, signal) {
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
5567
|
+
try {
|
|
5568
|
+
const body = payload(await pluginRead(`${CLAUDE_JIRA_PATH}/search`, "remote", signal, { query: { query } }));
|
|
5569
|
+
if (!Array.isArray(body.tickets)) throw new JiraClientError("Invalid Jira search response.");
|
|
5570
|
+
const tickets = [];
|
|
5571
|
+
for (const item of body.tickets) {
|
|
5572
|
+
const ticket = record$5(item);
|
|
5573
|
+
if (ticket === void 0 || typeof ticket.key !== "string" || typeof ticket.summary !== "string" || typeof ticket.url !== "string") continue;
|
|
5574
|
+
tickets.push(ticket);
|
|
5575
|
+
}
|
|
5576
|
+
return tickets;
|
|
5577
|
+
} catch (cause) {
|
|
5578
|
+
throw jiraFailure(cause);
|
|
4716
5579
|
}
|
|
4717
|
-
return tickets;
|
|
4718
5580
|
}
|
|
4719
5581
|
async function assignJiraTicket(key) {
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
},
|
|
4726
|
-
body: JSON.stringify({ key })
|
|
4727
|
-
});
|
|
5582
|
+
try {
|
|
5583
|
+
await pluginWrite(`${CLAUDE_JIRA_PATH}/assign`, "remote", void 0, { json: { key } });
|
|
5584
|
+
} catch (cause) {
|
|
5585
|
+
throw jiraFailure(cause);
|
|
5586
|
+
}
|
|
4728
5587
|
}
|
|
4729
5588
|
/** Draft seeded into the composer when a session starts from a ticket. */
|
|
4730
5589
|
function ticketPrompt(ticket) {
|
|
@@ -4760,7 +5619,9 @@ window.__ModuleLoader__.load({
|
|
|
4760
5619
|
if (typeof setting !== "object" || setting === null || Array.isArray(setting)) return false;
|
|
4761
5620
|
const item = setting;
|
|
4762
5621
|
if (typeof item.key !== "string" || typeof item.value !== "string" || ![
|
|
5622
|
+
"immediate",
|
|
4763
5623
|
"new-session",
|
|
5624
|
+
"next-turn",
|
|
4764
5625
|
"next-worktree",
|
|
4765
5626
|
"restart"
|
|
4766
5627
|
].includes(String(item.effect))) return false;
|
|
@@ -4775,6 +5636,27 @@ window.__ModuleLoader__.load({
|
|
|
4775
5636
|
function value(status, detail) {
|
|
4776
5637
|
return detail === void 0 ? status : `${status} · ${detail}`;
|
|
4777
5638
|
}
|
|
5639
|
+
/** The trigger's disclosure chevron.
|
|
5640
|
+
*
|
|
5641
|
+
* Geometry rather than a character: the ink spans y 6 to 10 in a 16-unit box,
|
|
5642
|
+
* so it is centred on the box's own centre and the open state's 180-degree
|
|
5643
|
+
* flip lands exactly where the closed state sat. A text arrowhead carries its
|
|
5644
|
+
* ink below the centre of the em box, which is why this needed a hand-tuned
|
|
5645
|
+
* nudge that could only be right in one of the two states. */
|
|
5646
|
+
function SelectChevron() {
|
|
5647
|
+
return /* @__PURE__ */ jsx("svg", {
|
|
5648
|
+
width: "16",
|
|
5649
|
+
height: "16",
|
|
5650
|
+
viewBox: "0 0 16 16",
|
|
5651
|
+
fill: "none",
|
|
5652
|
+
stroke: "currentColor",
|
|
5653
|
+
strokeWidth: "1.8",
|
|
5654
|
+
strokeLinecap: "round",
|
|
5655
|
+
strokeLinejoin: "round",
|
|
5656
|
+
"aria-hidden": "true",
|
|
5657
|
+
children: /* @__PURE__ */ jsx("path", { d: "M4 6l4 4 4-4" })
|
|
5658
|
+
});
|
|
5659
|
+
}
|
|
4778
5660
|
function GlobalSettingText({ setting, disabled, onChange }) {
|
|
4779
5661
|
const [draft, setDraft] = useState(setting.value);
|
|
4780
5662
|
useEffect(() => {
|
|
@@ -4805,7 +5687,7 @@ window.__ModuleLoader__.load({
|
|
|
4805
5687
|
}
|
|
4806
5688
|
});
|
|
4807
5689
|
}
|
|
4808
|
-
function GlobalSettingSelect({ setting, disabled, onChange }) {
|
|
5690
|
+
function GlobalSettingSelect({ setting, disabled, onChange, labelFor = (option) => option.label }) {
|
|
4809
5691
|
const [open, setOpen] = useState(false);
|
|
4810
5692
|
const [activeIndex, setActiveIndex] = useState(0);
|
|
4811
5693
|
const rootRef = useRef(null);
|
|
@@ -4843,80 +5725,81 @@ window.__ModuleLoader__.load({
|
|
|
4843
5725
|
onBlur: (event) => {
|
|
4844
5726
|
if (!event.currentTarget.contains(event.relatedTarget)) setOpen(false);
|
|
4845
5727
|
},
|
|
4846
|
-
children: [
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
|
4864
|
-
event.preventDefault();
|
|
4865
|
-
if (!open) openMenu(event.key === "ArrowDown" ? selectedIndex : Math.max(0, setting.options.length - 1));
|
|
4866
|
-
else move(event.key === "ArrowDown" ? 1 : -1);
|
|
4867
|
-
} else if (event.key === "Home" && open) {
|
|
4868
|
-
event.preventDefault();
|
|
4869
|
-
setActiveIndex(0);
|
|
4870
|
-
} else if (event.key === "End" && open) {
|
|
4871
|
-
event.preventDefault();
|
|
4872
|
-
setActiveIndex(Math.max(0, setting.options.length - 1));
|
|
4873
|
-
} else if ((event.key === "Enter" || event.key === " ") && open) {
|
|
4874
|
-
event.preventDefault();
|
|
4875
|
-
choose(activeIndex);
|
|
4876
|
-
} else if (event.key === "Escape" && open) {
|
|
4877
|
-
event.preventDefault();
|
|
4878
|
-
setOpen(false);
|
|
4879
|
-
}
|
|
4880
|
-
},
|
|
4881
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
4882
|
-
style: settingSelectValue,
|
|
4883
|
-
children: selectedOption?.label ?? setting.value
|
|
4884
|
-
}), /* @__PURE__ */ jsx("span", {
|
|
4885
|
-
"aria-hidden": "true",
|
|
4886
|
-
style: {
|
|
4887
|
-
...settingSelectChevron,
|
|
4888
|
-
...open ? settingSelectChevronOpen : {}
|
|
5728
|
+
children: [
|
|
5729
|
+
/* @__PURE__ */ jsx("style", {
|
|
5730
|
+
"data-dsh-claude-setting-select-styles": true,
|
|
5731
|
+
children: settingSelectCss
|
|
5732
|
+
}),
|
|
5733
|
+
/* @__PURE__ */ jsxs("button", {
|
|
5734
|
+
ref: triggerRef,
|
|
5735
|
+
type: "button",
|
|
5736
|
+
"aria-haspopup": "listbox",
|
|
5737
|
+
"aria-expanded": open,
|
|
5738
|
+
"aria-controls": open ? listboxId : void 0,
|
|
5739
|
+
"aria-activedescendant": open ? `${listboxId}-${activeIndex}` : void 0,
|
|
5740
|
+
disabled: disabled || setting.options.length === 0,
|
|
5741
|
+
className: settingSelectTriggerClass,
|
|
5742
|
+
onClick: () => {
|
|
5743
|
+
if (open) setOpen(false);
|
|
5744
|
+
else openMenu();
|
|
4889
5745
|
},
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
"
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
5746
|
+
onKeyDown: (event) => {
|
|
5747
|
+
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
|
5748
|
+
event.preventDefault();
|
|
5749
|
+
if (!open) openMenu(event.key === "ArrowDown" ? selectedIndex : Math.max(0, setting.options.length - 1));
|
|
5750
|
+
else move(event.key === "ArrowDown" ? 1 : -1);
|
|
5751
|
+
} else if (event.key === "Home" && open) {
|
|
5752
|
+
event.preventDefault();
|
|
5753
|
+
setActiveIndex(0);
|
|
5754
|
+
} else if (event.key === "End" && open) {
|
|
5755
|
+
event.preventDefault();
|
|
5756
|
+
setActiveIndex(Math.max(0, setting.options.length - 1));
|
|
5757
|
+
} else if ((event.key === "Enter" || event.key === " ") && open) {
|
|
5758
|
+
event.preventDefault();
|
|
5759
|
+
choose(activeIndex);
|
|
5760
|
+
} else if (event.key === "Escape" && open) {
|
|
5761
|
+
event.preventDefault();
|
|
5762
|
+
setOpen(false);
|
|
5763
|
+
}
|
|
5764
|
+
},
|
|
5765
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
5766
|
+
style: settingSelectValue,
|
|
5767
|
+
children: selectedOption === void 0 ? setting.value : labelFor(selectedOption)
|
|
5768
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
5769
|
+
"aria-hidden": "true",
|
|
5770
|
+
className: settingSelectChevronClass,
|
|
5771
|
+
children: /* @__PURE__ */ jsx(SelectChevron, {})
|
|
5772
|
+
})]
|
|
5773
|
+
}),
|
|
5774
|
+
open ? /* @__PURE__ */ jsx("div", {
|
|
5775
|
+
id: listboxId,
|
|
5776
|
+
role: "listbox",
|
|
5777
|
+
"aria-activedescendant": `${listboxId}-${activeIndex}`,
|
|
5778
|
+
style: settingSelectMenu,
|
|
5779
|
+
children: setting.options.map((option, index) => {
|
|
5780
|
+
const selected = option.value === setting.value;
|
|
5781
|
+
const active = index === activeIndex;
|
|
5782
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
5783
|
+
id: `${listboxId}-${index}`,
|
|
5784
|
+
type: "button",
|
|
5785
|
+
role: "option",
|
|
5786
|
+
"aria-selected": selected,
|
|
5787
|
+
style: {
|
|
5788
|
+
...settingSelectOption,
|
|
5789
|
+
...active ? settingSelectOptionActive : {}
|
|
5790
|
+
},
|
|
5791
|
+
onMouseEnter: () => setActiveIndex(index),
|
|
5792
|
+
onMouseDown: (event) => event.preventDefault(),
|
|
5793
|
+
onClick: () => choose(index),
|
|
5794
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
5795
|
+
style: settingSelectCheck,
|
|
5796
|
+
"aria-hidden": "true",
|
|
5797
|
+
children: selected ? "✓" : ""
|
|
5798
|
+
}), /* @__PURE__ */ jsx("span", { children: labelFor(option) })]
|
|
5799
|
+
}, `${option.source}:${option.value}`);
|
|
5800
|
+
})
|
|
5801
|
+
}) : null
|
|
5802
|
+
]
|
|
4920
5803
|
});
|
|
4921
5804
|
}
|
|
4922
5805
|
/** Fixed windows carry a translated label; server-named model buckets (e.g.
|
|
@@ -4927,6 +5810,23 @@ window.__ModuleLoader__.load({
|
|
|
4927
5810
|
"seven_day_opus",
|
|
4928
5811
|
"seven_day_sonnet"
|
|
4929
5812
|
]);
|
|
5813
|
+
/** The prose mode a settings payload carries, or the default when it carries
|
|
5814
|
+
* none — an older Host, or a response this Client does not fully understand,
|
|
5815
|
+
* leaves the palette alone rather than guessing. */
|
|
5816
|
+
function proseModeOf(settings) {
|
|
5817
|
+
const value = settings.find((setting) => setting.key === "prose")?.value;
|
|
5818
|
+
return isClaudeProseMode(value) ? value : DEFAULT_CLAUDE_PROSE_MODE;
|
|
5819
|
+
}
|
|
5820
|
+
/** Settings whose row only makes sense under a particular value of another.
|
|
5821
|
+
* Filtering here rather than server-side keeps the descriptor list flat: the
|
|
5822
|
+
* server has no view of what the Client can paint. Fails OPEN — a payload
|
|
5823
|
+
* missing the setting a row depends on shows the row rather than hiding it,
|
|
5824
|
+
* so an older Host cannot make a setting unreachable. */
|
|
5825
|
+
function visibleGlobalSettings(settings) {
|
|
5826
|
+
const renderer = settings.find((setting) => setting.key === "renderer");
|
|
5827
|
+
if (renderer === void 0 || renderer.value !== "native") return settings;
|
|
5828
|
+
return settings.filter((setting) => setting.key !== "prose");
|
|
5829
|
+
}
|
|
4930
5830
|
/** Per-setting label and the effect note that used to sit as a standalone
|
|
4931
5831
|
* paragraph under the card; it now hangs off the label as a hover hint. */
|
|
4932
5832
|
const SETTING_COPY = {
|
|
@@ -4934,6 +5834,14 @@ window.__ModuleLoader__.load({
|
|
|
4934
5834
|
label: "outputStyle",
|
|
4935
5835
|
hint: "globalSettingsNewSession"
|
|
4936
5836
|
},
|
|
5837
|
+
renderer: {
|
|
5838
|
+
label: "renderer",
|
|
5839
|
+
hint: "rendererEffect"
|
|
5840
|
+
},
|
|
5841
|
+
prose: {
|
|
5842
|
+
label: "prose",
|
|
5843
|
+
hint: "proseEffect"
|
|
5844
|
+
},
|
|
4937
5845
|
worktreeBranchPrefix: {
|
|
4938
5846
|
label: "worktreeBranchPrefix",
|
|
4939
5847
|
hint: "worktreeBranchPrefixEffect"
|
|
@@ -4947,6 +5855,19 @@ window.__ModuleLoader__.load({
|
|
|
4947
5855
|
hint: "idleTimeoutEffect"
|
|
4948
5856
|
}
|
|
4949
5857
|
};
|
|
5858
|
+
/** Translated display text for the option vocabularies this plugin owns,
|
|
5859
|
+
* keyed `<setting>:<option>`. Options discovered on the machine (output style
|
|
5860
|
+
* names) carry no entry and keep the label the route reported. */
|
|
5861
|
+
const SETTING_OPTION_COPY = {
|
|
5862
|
+
"renderer:plugin": "rendererPlugin",
|
|
5863
|
+
"renderer:native": "rendererNative",
|
|
5864
|
+
"prose:plain": "prosePlain",
|
|
5865
|
+
"prose:enhanced": "proseEnhanced"
|
|
5866
|
+
};
|
|
5867
|
+
function settingOptionLabel(settingKey, option, t) {
|
|
5868
|
+
const key = SETTING_OPTION_COPY[`${settingKey}:${option.value}`];
|
|
5869
|
+
return key === void 0 ? option.label : t(key);
|
|
5870
|
+
}
|
|
4950
5871
|
/** '?' badge that reveals a setting's effect note on hover or keyboard focus. */
|
|
4951
5872
|
function SettingHint({ text, label }) {
|
|
4952
5873
|
return /* @__PURE__ */ jsx(Tooltip, {
|
|
@@ -4968,8 +5889,9 @@ window.__ModuleLoader__.load({
|
|
|
4968
5889
|
const report = value;
|
|
4969
5890
|
return typeof report.available === "boolean" && typeof report.fetchedAt === "number" && Array.isArray(report.windows) && report.windows.every((entry) => typeof entry === "object" && entry !== null && typeof entry.id === "string");
|
|
4970
5891
|
}
|
|
4971
|
-
/** Coarse duration for a reset countdown or a
|
|
4972
|
-
* hour, then hours, then days. Never negative — a passed reset
|
|
5892
|
+
/** Coarse duration for a reset countdown or the age of a reading: minutes
|
|
5893
|
+
* below an hour, then hours, then days. Never negative — a passed reset
|
|
5894
|
+
* reads '0m'. */
|
|
4973
5895
|
function durationLabel(milliseconds) {
|
|
4974
5896
|
const minutes = Math.max(0, Math.round(milliseconds / 6e4));
|
|
4975
5897
|
if (minutes < 60) return `${minutes}m`;
|
|
@@ -4999,23 +5921,31 @@ window.__ModuleLoader__.load({
|
|
|
4999
5921
|
})]
|
|
5000
5922
|
});
|
|
5001
5923
|
}
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5924
|
+
function cardFailure(cause) {
|
|
5925
|
+
return {
|
|
5926
|
+
detail: cause instanceof Error ? cause.message : String(cause),
|
|
5927
|
+
starved: cause instanceof PluginRequestError && cause.reason === "starved"
|
|
5928
|
+
};
|
|
5929
|
+
}
|
|
5930
|
+
function FailureNotice({ label, failure }) {
|
|
5931
|
+
return /* @__PURE__ */ jsxs("p", {
|
|
5932
|
+
role: "alert",
|
|
5933
|
+
style: {
|
|
5934
|
+
...notice,
|
|
5935
|
+
color: failure.starved ? "var(--dsw-alias-state-warning-primary, #d69e2e)" : "var(--dsw-alias-state-error-primary)"
|
|
5936
|
+
},
|
|
5937
|
+
children: [
|
|
5938
|
+
label,
|
|
5939
|
+
": ",
|
|
5940
|
+
failure.detail
|
|
5941
|
+
]
|
|
5013
5942
|
});
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5943
|
+
}
|
|
5944
|
+
/** The plan usage report. A plain read serves the cached reading from memory;
|
|
5945
|
+
* the refresh spawns a probe process, which is why only it pays the remote
|
|
5946
|
+
* budget. */
|
|
5947
|
+
async function loadPlanUsage(refresh) {
|
|
5948
|
+
const payload = refresh ? await pluginWrite(CLAUDE_USAGE_PATH, "remote") : await pluginRead(CLAUDE_USAGE_PATH, "fast");
|
|
5019
5949
|
if (!isPlanUsageReport(payload)) throw new Error("Invalid plan usage response");
|
|
5020
5950
|
return payload;
|
|
5021
5951
|
}
|
|
@@ -5029,7 +5959,7 @@ window.__ModuleLoader__.load({
|
|
|
5029
5959
|
try {
|
|
5030
5960
|
setReport(await load(refresh));
|
|
5031
5961
|
} catch (cause) {
|
|
5032
|
-
setError(
|
|
5962
|
+
setError(cardFailure(cause));
|
|
5033
5963
|
} finally {
|
|
5034
5964
|
setBusy(false);
|
|
5035
5965
|
}
|
|
@@ -5086,17 +6016,9 @@ window.__ModuleLoader__.load({
|
|
|
5086
6016
|
style: notice,
|
|
5087
6017
|
children: t("planUsageUpdated", { age: durationLabel(now - report.fetchedAt) })
|
|
5088
6018
|
}) : null,
|
|
5089
|
-
error === void 0 ? null : /* @__PURE__ */
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
...notice,
|
|
5093
|
-
color: "var(--dsw-alias-state-error-primary)"
|
|
5094
|
-
},
|
|
5095
|
-
children: [
|
|
5096
|
-
t("planUsageError"),
|
|
5097
|
-
": ",
|
|
5098
|
-
error
|
|
5099
|
-
]
|
|
6019
|
+
error === void 0 ? null : /* @__PURE__ */ jsx(FailureNotice, {
|
|
6020
|
+
label: t("planUsageError"),
|
|
6021
|
+
failure: error
|
|
5100
6022
|
})
|
|
5101
6023
|
]
|
|
5102
6024
|
});
|
|
@@ -5120,7 +6042,7 @@ window.__ModuleLoader__.load({
|
|
|
5120
6042
|
useEffect(() => {
|
|
5121
6043
|
const controller = new AbortController();
|
|
5122
6044
|
loadJiraStatus(controller.signal).then(setJiraStatus, (reason) => {
|
|
5123
|
-
if (!controller.signal.aborted) setJiraError(
|
|
6045
|
+
if (!controller.signal.aborted) setJiraError(cardFailure(reason));
|
|
5124
6046
|
});
|
|
5125
6047
|
return () => {
|
|
5126
6048
|
controller.abort();
|
|
@@ -5137,7 +6059,7 @@ window.__ModuleLoader__.load({
|
|
|
5137
6059
|
}));
|
|
5138
6060
|
setJiraToken("");
|
|
5139
6061
|
} catch (cause) {
|
|
5140
|
-
setJiraError(
|
|
6062
|
+
setJiraError(cardFailure(cause));
|
|
5141
6063
|
} finally {
|
|
5142
6064
|
setJiraBusy(false);
|
|
5143
6065
|
}
|
|
@@ -5149,7 +6071,7 @@ window.__ModuleLoader__.load({
|
|
|
5149
6071
|
await disconnectJira();
|
|
5150
6072
|
setJiraStatus({ connected: false });
|
|
5151
6073
|
} catch (cause) {
|
|
5152
|
-
setJiraError(
|
|
6074
|
+
setJiraError(cardFailure(cause));
|
|
5153
6075
|
} finally {
|
|
5154
6076
|
setJiraBusy(false);
|
|
5155
6077
|
}
|
|
@@ -5159,16 +6081,9 @@ window.__ModuleLoader__.load({
|
|
|
5159
6081
|
setError(void 0);
|
|
5160
6082
|
setReport(void 0);
|
|
5161
6083
|
try {
|
|
5162
|
-
|
|
5163
|
-
credentials: "same-origin",
|
|
5164
|
-
signal: AbortSignal.timeout(SETTINGS_REQUEST_TIMEOUT_MS),
|
|
5165
|
-
headers: { accept: "application/json" }
|
|
5166
|
-
});
|
|
5167
|
-
const payload = await response.json();
|
|
5168
|
-
if (!response.ok) throw new Error("error" in payload ? payload.error : `HTTP ${response.status}`);
|
|
5169
|
-
setReport(payload);
|
|
6084
|
+
setReport(await pluginRead(CLAUDE_DOCTOR_PATH, "fast"));
|
|
5170
6085
|
} catch (cause) {
|
|
5171
|
-
setError(
|
|
6086
|
+
setError(cardFailure(cause));
|
|
5172
6087
|
} finally {
|
|
5173
6088
|
setBusy(false);
|
|
5174
6089
|
}
|
|
@@ -5180,25 +6095,15 @@ window.__ModuleLoader__.load({
|
|
|
5180
6095
|
setGlobalSettingsBusy(true);
|
|
5181
6096
|
setGlobalSettingsError(void 0);
|
|
5182
6097
|
try {
|
|
5183
|
-
const
|
|
5184
|
-
method:
|
|
5185
|
-
|
|
5186
|
-
signal: AbortSignal.timeout(SETTINGS_REQUEST_TIMEOUT_MS),
|
|
5187
|
-
headers: {
|
|
5188
|
-
accept: "application/json",
|
|
5189
|
-
...changes === void 0 ? {} : { "content-type": "application/json" }
|
|
5190
|
-
},
|
|
5191
|
-
...changes === void 0 ? {} : { body: JSON.stringify({ changes }) }
|
|
6098
|
+
const payload = changes === void 0 ? await pluginRead(CLAUDE_GLOBAL_SETTINGS_PATH, "fast") : await pluginWrite(CLAUDE_GLOBAL_SETTINGS_PATH, "fast", void 0, {
|
|
6099
|
+
method: "PATCH",
|
|
6100
|
+
json: { changes }
|
|
5192
6101
|
});
|
|
5193
|
-
const payload = await response.json();
|
|
5194
|
-
if (!response.ok) {
|
|
5195
|
-
const message = typeof payload === "object" && payload !== null && "error" in payload && typeof payload.error === "string" ? payload.error : `HTTP ${response.status}`;
|
|
5196
|
-
throw new Error(message);
|
|
5197
|
-
}
|
|
5198
6102
|
if (!isGlobalSettingsView(payload)) throw new Error("Invalid global settings response");
|
|
5199
6103
|
setGlobalSettings(payload);
|
|
6104
|
+
applyClaudeMarkdownTheme(proseModeOf(payload.settings));
|
|
5200
6105
|
} catch (cause) {
|
|
5201
|
-
setGlobalSettingsError(
|
|
6106
|
+
setGlobalSettingsError(cardFailure(cause));
|
|
5202
6107
|
} finally {
|
|
5203
6108
|
setGlobalSettingsBusy(false);
|
|
5204
6109
|
}
|
|
@@ -5210,20 +6115,11 @@ window.__ModuleLoader__.load({
|
|
|
5210
6115
|
setUpdateBusy(action);
|
|
5211
6116
|
setUpdateError(void 0);
|
|
5212
6117
|
try {
|
|
5213
|
-
const
|
|
5214
|
-
method: action === "check" ? "GET" : "POST",
|
|
5215
|
-
credentials: "same-origin",
|
|
5216
|
-
headers: { accept: "application/json" }
|
|
5217
|
-
});
|
|
5218
|
-
const payload = await response.json();
|
|
5219
|
-
if (!response.ok) {
|
|
5220
|
-
const message = typeof payload === "object" && payload !== null && "error" in payload && typeof payload.error === "string" ? payload.error : `HTTP ${response.status}`;
|
|
5221
|
-
throw new Error(message);
|
|
5222
|
-
}
|
|
6118
|
+
const payload = action === "check" ? await pluginRead(CLAUDE_UPDATE_CHECK_PATH, "remote") : await pluginWrite(CLAUDE_UPDATE_PATH, "remote");
|
|
5223
6119
|
if (!isPluginUpdateStatus(payload)) throw new Error("Invalid update response");
|
|
5224
6120
|
setUpdateStatus(payload);
|
|
5225
6121
|
} catch (cause) {
|
|
5226
|
-
setUpdateError(
|
|
6122
|
+
setUpdateError(cardFailure(cause));
|
|
5227
6123
|
} finally {
|
|
5228
6124
|
setUpdateBusy(void 0);
|
|
5229
6125
|
}
|
|
@@ -5293,17 +6189,9 @@ window.__ModuleLoader__.load({
|
|
|
5293
6189
|
children: rowValue
|
|
5294
6190
|
}, `${label}-value`)])
|
|
5295
6191
|
}),
|
|
5296
|
-
error === void 0 ? null : /* @__PURE__ */
|
|
5297
|
-
|
|
5298
|
-
|
|
5299
|
-
...notice,
|
|
5300
|
-
color: "var(--dsw-alias-state-error-primary)"
|
|
5301
|
-
},
|
|
5302
|
-
children: [
|
|
5303
|
-
t("error"),
|
|
5304
|
-
": ",
|
|
5305
|
-
error
|
|
5306
|
-
]
|
|
6192
|
+
error === void 0 ? null : /* @__PURE__ */ jsx(FailureNotice, {
|
|
6193
|
+
label: t("error"),
|
|
6194
|
+
failure: error
|
|
5307
6195
|
})
|
|
5308
6196
|
]
|
|
5309
6197
|
}),
|
|
@@ -5324,7 +6212,7 @@ window.__ModuleLoader__.load({
|
|
|
5324
6212
|
globalSettings === void 0 ? /* @__PURE__ */ jsx("p", {
|
|
5325
6213
|
style: notice,
|
|
5326
6214
|
children: t("globalSettingsLoading")
|
|
5327
|
-
}) : globalSettings.settings.map((setting) => {
|
|
6215
|
+
}) : visibleGlobalSettings(globalSettings.settings).map((setting) => {
|
|
5328
6216
|
const copy = SETTING_COPY[setting.key];
|
|
5329
6217
|
return /* @__PURE__ */ jsxs("div", {
|
|
5330
6218
|
style: diagnosticGrid,
|
|
@@ -5337,6 +6225,7 @@ window.__ModuleLoader__.load({
|
|
|
5337
6225
|
}), setting.kind === "select" ? /* @__PURE__ */ jsx(GlobalSettingSelect, {
|
|
5338
6226
|
setting,
|
|
5339
6227
|
disabled: globalSettingsBusy,
|
|
6228
|
+
labelFor: (option) => settingOptionLabel(setting.key, option, t),
|
|
5340
6229
|
onChange: (nextValue) => {
|
|
5341
6230
|
requestGlobalSettings({ [setting.key]: nextValue });
|
|
5342
6231
|
}
|
|
@@ -5349,17 +6238,9 @@ window.__ModuleLoader__.load({
|
|
|
5349
6238
|
})]
|
|
5350
6239
|
}, setting.key);
|
|
5351
6240
|
}),
|
|
5352
|
-
globalSettingsError === void 0 ? null : /* @__PURE__ */
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
...notice,
|
|
5356
|
-
color: "var(--dsw-alias-state-error-primary)"
|
|
5357
|
-
},
|
|
5358
|
-
children: [
|
|
5359
|
-
t("globalSettingsError"),
|
|
5360
|
-
": ",
|
|
5361
|
-
globalSettingsError
|
|
5362
|
-
]
|
|
6241
|
+
globalSettingsError === void 0 ? null : /* @__PURE__ */ jsx(FailureNotice, {
|
|
6242
|
+
label: t("globalSettingsError"),
|
|
6243
|
+
failure: globalSettingsError
|
|
5363
6244
|
})
|
|
5364
6245
|
]
|
|
5365
6246
|
}),
|
|
@@ -5465,17 +6346,9 @@ window.__ModuleLoader__.load({
|
|
|
5465
6346
|
})]
|
|
5466
6347
|
})
|
|
5467
6348
|
] }),
|
|
5468
|
-
jiraError === void 0 ? null : /* @__PURE__ */
|
|
5469
|
-
|
|
5470
|
-
|
|
5471
|
-
...notice,
|
|
5472
|
-
color: "var(--dsw-alias-state-error-primary)"
|
|
5473
|
-
},
|
|
5474
|
-
children: [
|
|
5475
|
-
t("jiraError"),
|
|
5476
|
-
": ",
|
|
5477
|
-
jiraError
|
|
5478
|
-
]
|
|
6349
|
+
jiraError === void 0 ? null : /* @__PURE__ */ jsx(FailureNotice, {
|
|
6350
|
+
label: t("jiraError"),
|
|
6351
|
+
failure: jiraError
|
|
5479
6352
|
})
|
|
5480
6353
|
]
|
|
5481
6354
|
}),
|
|
@@ -5536,17 +6409,9 @@ window.__ModuleLoader__.load({
|
|
|
5536
6409
|
style: notice,
|
|
5537
6410
|
children: t("restartRequired")
|
|
5538
6411
|
}),
|
|
5539
|
-
updateError === void 0 ? null : /* @__PURE__ */
|
|
5540
|
-
|
|
5541
|
-
|
|
5542
|
-
...notice,
|
|
5543
|
-
color: "var(--dsw-alias-state-error-primary)"
|
|
5544
|
-
},
|
|
5545
|
-
children: [
|
|
5546
|
-
t("updateError"),
|
|
5547
|
-
": ",
|
|
5548
|
-
updateError
|
|
5549
|
-
]
|
|
6412
|
+
updateError === void 0 ? null : /* @__PURE__ */ jsx(FailureNotice, {
|
|
6413
|
+
label: t("updateError"),
|
|
6414
|
+
failure: updateError
|
|
5550
6415
|
}),
|
|
5551
6416
|
/* @__PURE__ */ jsxs("div", {
|
|
5552
6417
|
style: settingsActions,
|
|
@@ -5598,14 +6463,14 @@ window.__ModuleLoader__.load({
|
|
|
5598
6463
|
function record$4(value) {
|
|
5599
6464
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
5600
6465
|
}
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
return
|
|
6466
|
+
/** The dialog branches on `code`, so a route refusal keeps arriving as this
|
|
6467
|
+
* class rather than as the transport's own error.
|
|
6468
|
+
*
|
|
6469
|
+
* `commit` cannot be carried across: the transport forwards a failed route's
|
|
6470
|
+
* message and error code, not the rest of its body, so a commit that survived
|
|
6471
|
+
* a failed push no longer reaches the dialog that offers its hash. */
|
|
6472
|
+
function actionError(error) {
|
|
6473
|
+
return error instanceof PluginRequestError ? new RepositoryActionClientError(error.message, error.code) : error;
|
|
5609
6474
|
}
|
|
5610
6475
|
function preview(value) {
|
|
5611
6476
|
const input = record$4(value);
|
|
@@ -5625,42 +6490,35 @@ window.__ModuleLoader__.load({
|
|
|
5625
6490
|
if (input === void 0 || typeof input.commit !== "string" || typeof input.pushed !== "boolean" || input.pullRequestUrl !== void 0 && typeof input.pullRequestUrl !== "string" || input.conflicts !== void 0 && (!Array.isArray(input.conflicts) || input.conflicts.some((item) => typeof item !== "string"))) throw new Error("Invalid repository action result.");
|
|
5626
6491
|
return input;
|
|
5627
6492
|
}
|
|
5628
|
-
|
|
5629
|
-
return `${CLAUDE_REPOSITORY_ACTION_PATH}${path}?sessionId=${encodeURIComponent(sessionId)}`;
|
|
5630
|
-
}
|
|
6493
|
+
/** The preview only chains local Git; everything that writes may reach a remote. */
|
|
5631
6494
|
async function loadRepositoryActionPreview(sessionId, signal) {
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
|
|
5635
|
-
|
|
5636
|
-
|
|
5637
|
-
})));
|
|
6495
|
+
try {
|
|
6496
|
+
return preview(await pluginRead(`${CLAUDE_REPOSITORY_ACTION_PATH}/preview`, "git", signal, { query: { sessionId } }));
|
|
6497
|
+
} catch (error) {
|
|
6498
|
+
throw actionError(error);
|
|
6499
|
+
}
|
|
5638
6500
|
}
|
|
5639
6501
|
async function generateCommitMessage(sessionId, fingerprint, signal) {
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
5649
|
-
}
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
},
|
|
5661
|
-
body: JSON.stringify(request),
|
|
5662
|
-
signal: pluginRequestSignal(PLUGIN_ACTION_TIMEOUT_MS)
|
|
5663
|
-
})).then(result);
|
|
6502
|
+
try {
|
|
6503
|
+
const value = record$4(await pluginWrite(`${CLAUDE_REPOSITORY_ACTION_PATH}/message`, "remote", signal, {
|
|
6504
|
+
query: { sessionId },
|
|
6505
|
+
json: { fingerprint }
|
|
6506
|
+
}));
|
|
6507
|
+
if (typeof value?.message !== "string") throw new Error("Invalid generated commit message.");
|
|
6508
|
+
return value.message;
|
|
6509
|
+
} catch (error) {
|
|
6510
|
+
throw actionError(error);
|
|
6511
|
+
}
|
|
6512
|
+
}
|
|
6513
|
+
async function executeRepositoryAction(sessionId, request) {
|
|
6514
|
+
try {
|
|
6515
|
+
return result(await pluginWrite(CLAUDE_REPOSITORY_ACTION_PATH, "remote", void 0, {
|
|
6516
|
+
query: { sessionId },
|
|
6517
|
+
json: request
|
|
6518
|
+
}));
|
|
6519
|
+
} catch (error) {
|
|
6520
|
+
throw actionError(error);
|
|
6521
|
+
}
|
|
5664
6522
|
}
|
|
5665
6523
|
//#endregion
|
|
5666
6524
|
//#region src/client/action-toast.tsx
|
|
@@ -5722,130 +6580,79 @@ window.__ModuleLoader__.load({
|
|
|
5722
6580
|
if (event?.type === "error" && typeof event.message === "string") throw new Error(event.message);
|
|
5723
6581
|
throw new Error("Invalid repository setup progress response.");
|
|
5724
6582
|
}
|
|
5725
|
-
async function response(pending) {
|
|
5726
|
-
const result = await pending;
|
|
5727
|
-
const body = await result.json();
|
|
5728
|
-
if (!result.ok) {
|
|
5729
|
-
const error = body;
|
|
5730
|
-
throw new Error(error.message ?? error.error ?? "Repository setup failed.");
|
|
5731
|
-
}
|
|
5732
|
-
return body;
|
|
5733
|
-
}
|
|
5734
|
-
/** A wedged host must surface as an error; the hero has no other way out of its loading state. */
|
|
5735
|
-
const BRANCH_LOAD_TIMEOUT_MS = 15e3;
|
|
5736
6583
|
function loadRepositoryBranches(cwd, signal) {
|
|
5737
|
-
return
|
|
5738
|
-
method: "GET",
|
|
5739
|
-
credentials: "same-origin",
|
|
5740
|
-
headers: { accept: "application/json" },
|
|
5741
|
-
signal: pluginRequestSignal(BRANCH_LOAD_TIMEOUT_MS, signal)
|
|
5742
|
-
}));
|
|
6584
|
+
return pluginRead(`${CLAUDE_REPOSITORY_SETUP_PATH}/branches`, "git", signal, { query: { cwd } });
|
|
5743
6585
|
}
|
|
5744
6586
|
/** Pull remote refs down first, then list: a POST because `--prune` rewrites
|
|
5745
|
-
* this checkout's remote-tracking refs, and on the
|
|
6587
|
+
* this checkout's remote-tracking refs, and on the remote budget because the
|
|
5746
6588
|
* host's own `git fetch` runs for up to a minute. */
|
|
5747
6589
|
async function refreshRepositoryBranches(cwd, signal) {
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
},
|
|
5755
|
-
signal: pluginRequestSignal(PLUGIN_ACTION_TIMEOUT_MS, signal),
|
|
5756
|
-
body: JSON.stringify({ cwd })
|
|
5757
|
-
});
|
|
5758
|
-
if (result.status === 404) throw new Error("route-missing");
|
|
5759
|
-
return response(Promise.resolve(result));
|
|
6590
|
+
try {
|
|
6591
|
+
return await pluginWrite(`${CLAUDE_REPOSITORY_SETUP_PATH}/branches/refresh`, "remote", signal, { json: { cwd } });
|
|
6592
|
+
} catch (error) {
|
|
6593
|
+
if (error instanceof PluginRequestError && error.reason === "route-missing") throw new Error("route-missing");
|
|
6594
|
+
throw error;
|
|
6595
|
+
}
|
|
5760
6596
|
}
|
|
5761
6597
|
async function prepareRepository(cwd, branch, worktree, branchName, onProgress = () => {}) {
|
|
5762
|
-
const
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
5776
|
-
|
|
5777
|
-
|
|
5778
|
-
|
|
5779
|
-
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
for (const line of lines) {
|
|
5791
|
-
if (line.trim().length === 0) continue;
|
|
5792
|
-
const value = parseRepositorySetupEvent(line, onProgress);
|
|
6598
|
+
const carrier = new AbortController();
|
|
6599
|
+
try {
|
|
6600
|
+
const reader = await pluginNdjson(CLAUDE_REPOSITORY_SETUP_PATH, carrier.signal, {
|
|
6601
|
+
method: "POST",
|
|
6602
|
+
json: {
|
|
6603
|
+
cwd,
|
|
6604
|
+
branch,
|
|
6605
|
+
worktree,
|
|
6606
|
+
...branchName === void 0 ? {} : { branchName }
|
|
6607
|
+
}
|
|
6608
|
+
});
|
|
6609
|
+
const decoder = new TextDecoder();
|
|
6610
|
+
let buffer = "";
|
|
6611
|
+
let completed;
|
|
6612
|
+
while (true) {
|
|
6613
|
+
const chunk = await reader.read();
|
|
6614
|
+
buffer += decoder.decode(chunk.value, { stream: !chunk.done });
|
|
6615
|
+
const lines = buffer.split("\n");
|
|
6616
|
+
buffer = lines.pop() ?? "";
|
|
6617
|
+
for (const line of lines) {
|
|
6618
|
+
if (line.trim().length === 0) continue;
|
|
6619
|
+
const value = parseRepositorySetupEvent(line, onProgress);
|
|
6620
|
+
if (value !== void 0) completed = value;
|
|
6621
|
+
}
|
|
6622
|
+
if (chunk.done) break;
|
|
6623
|
+
}
|
|
6624
|
+
if (buffer.trim().length > 0) {
|
|
6625
|
+
const value = parseRepositorySetupEvent(buffer, onProgress);
|
|
5793
6626
|
if (value !== void 0) completed = value;
|
|
5794
6627
|
}
|
|
5795
|
-
if (
|
|
6628
|
+
if (completed === void 0) throw new Error("Repository setup progress ended before completion.");
|
|
6629
|
+
return completed;
|
|
6630
|
+
} finally {
|
|
6631
|
+
carrier.abort();
|
|
5796
6632
|
}
|
|
5797
|
-
if (buffer.trim().length > 0) {
|
|
5798
|
-
const value = parseRepositorySetupEvent(buffer, onProgress);
|
|
5799
|
-
if (value !== void 0) completed = value;
|
|
5800
|
-
}
|
|
5801
|
-
if (completed === void 0) throw new Error("Repository setup progress ended before completion.");
|
|
5802
|
-
return completed;
|
|
5803
6633
|
}
|
|
5804
|
-
/** Bookkeeping, not a gate: a wedged host must fail this instead of hanging on
|
|
5805
|
-
* a connection the rest of the flow is waiting behind. */
|
|
5806
|
-
const LEASE_BIND_TIMEOUT_MS = 1e4;
|
|
5807
6634
|
async function bindRepositoryLease(leaseId, sessionId) {
|
|
5808
|
-
await
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
headers: {
|
|
5813
|
-
accept: "application/json",
|
|
5814
|
-
"content-type": "application/json"
|
|
5815
|
-
},
|
|
5816
|
-
body: JSON.stringify({
|
|
5817
|
-
leaseId,
|
|
5818
|
-
sessionId
|
|
5819
|
-
})
|
|
5820
|
-
}));
|
|
6635
|
+
await pluginWrite(`${CLAUDE_REPOSITORY_SETUP_PATH}/bind`, "remote", void 0, { json: {
|
|
6636
|
+
leaseId,
|
|
6637
|
+
sessionId
|
|
6638
|
+
} });
|
|
5821
6639
|
}
|
|
5822
6640
|
async function cleanupMergedRepository(path, baseBranch) {
|
|
5823
|
-
const body = await
|
|
5824
|
-
|
|
5825
|
-
|
|
5826
|
-
|
|
5827
|
-
headers: {
|
|
5828
|
-
accept: "application/json",
|
|
5829
|
-
"content-type": "application/json"
|
|
5830
|
-
},
|
|
5831
|
-
body: JSON.stringify({
|
|
5832
|
-
path,
|
|
5833
|
-
baseBranch
|
|
5834
|
-
})
|
|
5835
|
-
}));
|
|
6641
|
+
const body = await pluginWrite(`${CLAUDE_REPOSITORY_SETUP_PATH}/cleanup`, "remote", void 0, { json: {
|
|
6642
|
+
path,
|
|
6643
|
+
baseBranch
|
|
6644
|
+
} });
|
|
5836
6645
|
if (body.mode !== "worktree" && body.mode !== "checkout" || typeof body.root !== "string" || typeof body.branch !== "string") throw new Error("Invalid repository cleanup response.");
|
|
5837
6646
|
return body;
|
|
5838
6647
|
}
|
|
5839
6648
|
/** Lines [from, to] of a working-tree file plus its total line count, for expanding unmodified diff context. */
|
|
5840
6649
|
async function loadRepositoryFileLines(cwd, path, from, to, signal) {
|
|
5841
|
-
const
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
...signal === void 0 ? {} : { signal }
|
|
5848
|
-
}));
|
|
6650
|
+
const body = await pluginRead(CLAUDE_REPOSITORY_FILE_PATH, "git", signal, { query: {
|
|
6651
|
+
cwd,
|
|
6652
|
+
path,
|
|
6653
|
+
from: String(from),
|
|
6654
|
+
to: String(to)
|
|
6655
|
+
} });
|
|
5849
6656
|
if (!Array.isArray(body.lines) || typeof body.total !== "number") throw new Error("Invalid repository file response.");
|
|
5850
6657
|
return {
|
|
5851
6658
|
lines: body.lines.map(String),
|
|
@@ -5853,13 +6660,7 @@ window.__ModuleLoader__.load({
|
|
|
5853
6660
|
};
|
|
5854
6661
|
}
|
|
5855
6662
|
async function loadRepositoryStatusFor(cwd, signal) {
|
|
5856
|
-
const body = await
|
|
5857
|
-
signal: pluginRequestSignal(PLUGIN_READ_TIMEOUT_MS),
|
|
5858
|
-
method: "GET",
|
|
5859
|
-
credentials: "same-origin",
|
|
5860
|
-
headers: { accept: "application/json" },
|
|
5861
|
-
...signal === void 0 ? {} : { signal }
|
|
5862
|
-
}));
|
|
6663
|
+
const body = await pluginRead(CLAUDE_REPOSITORY_STATUS_PATH, "git", signal, { query: { cwd } });
|
|
5863
6664
|
if (typeof body.status !== "string" || typeof body.cwd !== "string") throw new Error("Invalid repository status response.");
|
|
5864
6665
|
return body;
|
|
5865
6666
|
}
|
|
@@ -5896,34 +6697,27 @@ window.__ModuleLoader__.load({
|
|
|
5896
6697
|
function record$2(value) {
|
|
5897
6698
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
5898
6699
|
}
|
|
5899
|
-
function
|
|
5900
|
-
return
|
|
6700
|
+
function feedbackQuery(sessionId, pullNumber, extra) {
|
|
6701
|
+
return {
|
|
6702
|
+
sessionId,
|
|
6703
|
+
number: String(pullNumber),
|
|
6704
|
+
...extra
|
|
6705
|
+
};
|
|
5901
6706
|
}
|
|
5902
|
-
|
|
5903
|
-
const body = record$2(
|
|
5904
|
-
if (!response.ok) throw new Error(typeof body?.message === "string" ? body.message : "Pull request feedback is unavailable.");
|
|
6707
|
+
function answer(value) {
|
|
6708
|
+
const body = record$2(value);
|
|
5905
6709
|
if (body === void 0) throw new Error("Invalid pull request feedback response.");
|
|
5906
6710
|
return body;
|
|
5907
6711
|
}
|
|
5908
|
-
|
|
5909
|
-
|
|
5910
|
-
|
|
5911
|
-
|
|
5912
|
-
headers: { accept: "application/json" },
|
|
5913
|
-
signal: pluginRequestSignal(PLUGIN_READ_TIMEOUT_MS, signal)
|
|
5914
|
-
}));
|
|
6712
|
+
/** Every arm of this route shells out to `gh`, so reads and writes alike take
|
|
6713
|
+
* the remote budget. */
|
|
6714
|
+
async function loadJson(path, sessionId, pullNumber, signal, extra) {
|
|
6715
|
+
return answer(await pluginRead(`${CLAUDE_REPOSITORY_FEEDBACK_PATH}${path}`, "remote", signal, { query: feedbackQuery(sessionId, pullNumber, extra) }));
|
|
5915
6716
|
}
|
|
5916
|
-
/** Writes reach GitHub through `gh`, so they get the action deadline. */
|
|
5917
6717
|
async function postJson(path, sessionId, pullNumber, input) {
|
|
5918
|
-
return answer(await
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
headers: {
|
|
5922
|
-
accept: "application/json",
|
|
5923
|
-
"content-type": "application/json"
|
|
5924
|
-
},
|
|
5925
|
-
signal: pluginRequestSignal(PLUGIN_ACTION_TIMEOUT_MS),
|
|
5926
|
-
body: JSON.stringify(input)
|
|
6718
|
+
return answer(await pluginWrite(`${CLAUDE_REPOSITORY_FEEDBACK_PATH}${path}`, "remote", void 0, {
|
|
6719
|
+
query: feedbackQuery(sessionId, pullNumber),
|
|
6720
|
+
json: input
|
|
5927
6721
|
}));
|
|
5928
6722
|
}
|
|
5929
6723
|
function reviewComment(value) {
|
|
@@ -5972,7 +6766,7 @@ window.__ModuleLoader__.load({
|
|
|
5972
6766
|
}
|
|
5973
6767
|
/** Logins GitHub would notify, for the reply composer's `@` completion. */
|
|
5974
6768
|
async function loadMentionableUsers(sessionId, pullNumber, query, signal) {
|
|
5975
|
-
const body = await loadJson("/mentionables", sessionId, pullNumber, signal,
|
|
6769
|
+
const body = await loadJson("/mentionables", sessionId, pullNumber, signal, { q: query });
|
|
5976
6770
|
if (!Array.isArray(body.users)) return [];
|
|
5977
6771
|
const users = [];
|
|
5978
6772
|
for (const item of body.users) {
|
|
@@ -7128,22 +7922,10 @@ window.__ModuleLoader__.load({
|
|
|
7128
7922
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
7129
7923
|
}
|
|
7130
7924
|
async function post(path, sessionId, body) {
|
|
7131
|
-
|
|
7132
|
-
|
|
7133
|
-
|
|
7134
|
-
headers: {
|
|
7135
|
-
accept: "application/json",
|
|
7136
|
-
"content-type": "application/json"
|
|
7137
|
-
},
|
|
7138
|
-
body: JSON.stringify(body),
|
|
7139
|
-
signal: pluginRequestSignal(PLUGIN_READ_TIMEOUT_MS)
|
|
7925
|
+
return await pluginWrite(`${CLAUDE_REVIEW_COMMENT_PATH}${path}`, "fast", void 0, {
|
|
7926
|
+
query: { sessionId },
|
|
7927
|
+
json: body
|
|
7140
7928
|
});
|
|
7141
|
-
const value = await response.json();
|
|
7142
|
-
if (!response.ok) {
|
|
7143
|
-
const error = record$1(value);
|
|
7144
|
-
throw new Error(typeof error?.message === "string" ? error.message : "Review comment request failed.");
|
|
7145
|
-
}
|
|
7146
|
-
return value;
|
|
7147
7929
|
}
|
|
7148
7930
|
async function addReviewComment(sessionId, comment) {
|
|
7149
7931
|
const created = record$1(record$1(await post("", sessionId, comment))?.comment);
|
|
@@ -13302,6 +14084,16 @@ window.__ModuleLoader__.load({
|
|
|
13302
14084
|
});
|
|
13303
14085
|
}
|
|
13304
14086
|
//#endregion
|
|
14087
|
+
//#region src/client/session-preset.ts
|
|
14088
|
+
/**
|
|
14089
|
+
* Resolve one row's preset id, newest seat first.
|
|
14090
|
+
* @param row - a session-list row, or undefined when the id is not listed.
|
|
14091
|
+
* @returns the preset id, or undefined when neither source carries one.
|
|
14092
|
+
*/
|
|
14093
|
+
function sessionRowPreset(row) {
|
|
14094
|
+
return row?.agentPreset ?? row?.projectionValues?.agentPreset;
|
|
14095
|
+
}
|
|
14096
|
+
//#endregion
|
|
13305
14097
|
//#region src/client/ClaudePullRequestsPanel.tsx
|
|
13306
14098
|
const NO_WORKSPACE_STATE = {};
|
|
13307
14099
|
const NO_WORKSPACES = {
|
|
@@ -13324,7 +14116,7 @@ window.__ModuleLoader__.load({
|
|
|
13324
14116
|
function claudeSessionRows(state, archivedSessionIds = []) {
|
|
13325
14117
|
const rows = state.ids === void 0 ? Object.values(state.byId) : state.ids.map((id) => state.byId[id]);
|
|
13326
14118
|
const archived = new Set(archivedSessionIds);
|
|
13327
|
-
return rows.filter((row) => row !== void 0 && row
|
|
14119
|
+
return rows.filter((row) => row !== void 0 && sessionRowPreset(row) === "claude" && row.blank !== true && row.origin !== "subagent" && !archived.has(row.id) && typeof row.cwd === "string").sort((left, right) => Number(right.running === true) - Number(left.running === true) || (left.displayTitle ?? left.id).localeCompare(right.displayTitle ?? right.id));
|
|
13328
14120
|
}
|
|
13329
14121
|
function Badge({ label, tone = "neutral" }) {
|
|
13330
14122
|
const toneStyle = tone === "success" ? repositoryItemSuccess : tone === "warning" ? repositoryItemWarning : tone === "error" ? repositoryItemError : tone === "merged" ? { color: "#a78bfa" } : {};
|
|
@@ -13519,56 +14311,64 @@ window.__ModuleLoader__.load({
|
|
|
13519
14311
|
};
|
|
13520
14312
|
throw new Error("Invalid ask stream event.");
|
|
13521
14313
|
}
|
|
14314
|
+
async function openAnswer(sessionId, request, cancel) {
|
|
14315
|
+
try {
|
|
14316
|
+
return await pluginNdjson(CLAUDE_ASK_PATH, cancel, {
|
|
14317
|
+
method: "POST",
|
|
14318
|
+
query: { sessionId },
|
|
14319
|
+
json: request
|
|
14320
|
+
});
|
|
14321
|
+
} catch (error) {
|
|
14322
|
+
if (error instanceof PluginRequestError && error.reason === "http") throw new Error("The question could not be sent.");
|
|
14323
|
+
throw error;
|
|
14324
|
+
}
|
|
14325
|
+
}
|
|
13522
14326
|
/** Stream an answer about selected reply text; resolves when the answer completes. */
|
|
13523
14327
|
async function askAboutSelection(sessionId, request, onProgress, signal) {
|
|
13524
|
-
const
|
|
13525
|
-
|
|
13526
|
-
|
|
13527
|
-
headers: {
|
|
13528
|
-
accept: "application/x-ndjson",
|
|
13529
|
-
"content-type": "application/json"
|
|
13530
|
-
},
|
|
13531
|
-
body: JSON.stringify(request),
|
|
13532
|
-
...signal === void 0 ? {} : { signal }
|
|
13533
|
-
});
|
|
13534
|
-
if (!response.ok) {
|
|
13535
|
-
const body = record(await response.json().catch(() => void 0));
|
|
13536
|
-
throw new Error(typeof body?.message === "string" ? body.message : "The question could not be sent.");
|
|
13537
|
-
}
|
|
13538
|
-
if (response.body === null) throw new Error("The answer stream is unavailable.");
|
|
13539
|
-
const reader = response.body.getReader();
|
|
13540
|
-
const decoder = new TextDecoder();
|
|
13541
|
-
let buffer = "";
|
|
13542
|
-
let finished = false;
|
|
13543
|
-
const handle = (line) => {
|
|
13544
|
-
if (line.trim().length === 0) return;
|
|
13545
|
-
const event = parseAskEvent(line);
|
|
13546
|
-
if (event.type === "delta") onProgress({
|
|
13547
|
-
type: "text",
|
|
13548
|
-
text: event.text
|
|
13549
|
-
});
|
|
13550
|
-
else if (event.type === "thinking") onProgress({
|
|
13551
|
-
type: "thinking",
|
|
13552
|
-
text: event.text
|
|
13553
|
-
});
|
|
13554
|
-
else if (event.type === "status") onProgress({
|
|
13555
|
-
type: "status",
|
|
13556
|
-
text: event.text
|
|
13557
|
-
});
|
|
13558
|
-
else if (event.type === "tool") onProgress(event);
|
|
13559
|
-
else if (event.type === "done") finished = true;
|
|
13560
|
-
else throw new Error(event.message);
|
|
14328
|
+
const carrier = new AbortController();
|
|
14329
|
+
const stop = () => {
|
|
14330
|
+
carrier.abort();
|
|
13561
14331
|
};
|
|
13562
|
-
|
|
13563
|
-
|
|
13564
|
-
|
|
13565
|
-
const
|
|
13566
|
-
|
|
13567
|
-
|
|
13568
|
-
|
|
14332
|
+
signal?.addEventListener("abort", stop, { once: true });
|
|
14333
|
+
if (signal?.aborted === true) carrier.abort();
|
|
14334
|
+
try {
|
|
14335
|
+
const reader = await openAnswer(sessionId, request, carrier.signal);
|
|
14336
|
+
const decoder = new TextDecoder();
|
|
14337
|
+
let buffer = "";
|
|
14338
|
+
let finished = false;
|
|
14339
|
+
const handle = (line) => {
|
|
14340
|
+
if (line.trim().length === 0) return;
|
|
14341
|
+
const event = parseAskEvent(line);
|
|
14342
|
+
if (event.type === "delta") onProgress({
|
|
14343
|
+
type: "text",
|
|
14344
|
+
text: event.text
|
|
14345
|
+
});
|
|
14346
|
+
else if (event.type === "thinking") onProgress({
|
|
14347
|
+
type: "thinking",
|
|
14348
|
+
text: event.text
|
|
14349
|
+
});
|
|
14350
|
+
else if (event.type === "status") onProgress({
|
|
14351
|
+
type: "status",
|
|
14352
|
+
text: event.text
|
|
14353
|
+
});
|
|
14354
|
+
else if (event.type === "tool") onProgress(event);
|
|
14355
|
+
else if (event.type === "done") finished = true;
|
|
14356
|
+
else throw new Error(event.message);
|
|
14357
|
+
};
|
|
14358
|
+
while (true) {
|
|
14359
|
+
const chunk = await reader.read();
|
|
14360
|
+
buffer += decoder.decode(chunk.value, { stream: !chunk.done });
|
|
14361
|
+
const lines = buffer.split("\n");
|
|
14362
|
+
buffer = lines.pop() ?? "";
|
|
14363
|
+
for (const line of lines) handle(line);
|
|
14364
|
+
if (chunk.done) break;
|
|
14365
|
+
}
|
|
14366
|
+
handle(buffer);
|
|
14367
|
+
if (!finished) throw new Error("The answer ended unexpectedly.");
|
|
14368
|
+
} finally {
|
|
14369
|
+
signal?.removeEventListener("abort", stop);
|
|
14370
|
+
carrier.abort();
|
|
13569
14371
|
}
|
|
13570
|
-
handle(buffer);
|
|
13571
|
-
if (!finished) throw new Error("The answer ended unexpectedly.");
|
|
13572
14372
|
}
|
|
13573
14373
|
//#endregion
|
|
13574
14374
|
//#region src/client/ClaudeSelectionAsk.tsx
|
|
@@ -14124,13 +14924,12 @@ window.__ModuleLoader__.load({
|
|
|
14124
14924
|
});
|
|
14125
14925
|
return stop;
|
|
14126
14926
|
}
|
|
14927
|
+
/** A beacon rather than a request: a finding is dropped when the connection
|
|
14928
|
+
* budget is spent, because the channel that reports the plugin's own failures
|
|
14929
|
+
* must never be the traffic that causes them — and must never queue behind
|
|
14930
|
+
* them either. */
|
|
14127
14931
|
function postToHost(report) {
|
|
14128
|
-
|
|
14129
|
-
method: "POST",
|
|
14130
|
-
credentials: "same-origin",
|
|
14131
|
-
headers: { "content-type": "application/json" },
|
|
14132
|
-
body: JSON.stringify(report)
|
|
14133
|
-
}).catch(() => {});
|
|
14932
|
+
pluginBeacon(CLAUDE_CLIENT_DIAGNOSTICS_PATH, report);
|
|
14134
14933
|
}
|
|
14135
14934
|
/** Renderer-side findings reach the Host log through here.
|
|
14136
14935
|
*
|
|
@@ -14217,23 +15016,15 @@ window.__ModuleLoader__.load({
|
|
|
14217
15016
|
/** Drop one user message and everything after it: the rows are hidden from
|
|
14218
15017
|
* this session's transcript and Claude resumes before that turn. */
|
|
14219
15018
|
async function rewindSession(sessionId, seq) {
|
|
14220
|
-
|
|
14221
|
-
|
|
14222
|
-
credentials: "same-origin",
|
|
14223
|
-
signal: pluginRequestSignal(PLUGIN_READ_TIMEOUT_MS),
|
|
14224
|
-
headers: {
|
|
14225
|
-
"content-type": "application/json",
|
|
14226
|
-
accept: "application/json"
|
|
14227
|
-
},
|
|
14228
|
-
body: JSON.stringify({
|
|
15019
|
+
try {
|
|
15020
|
+
await pluginWrite(CLAUDE_REWIND_PATH, "fast", void 0, { json: {
|
|
14229
15021
|
sessionId,
|
|
14230
15022
|
seq
|
|
14231
|
-
})
|
|
14232
|
-
})
|
|
14233
|
-
|
|
14234
|
-
|
|
14235
|
-
|
|
14236
|
-
throw new Error(typeof body?.error === "string" ? body.error : `http-${result.status}`);
|
|
15023
|
+
} });
|
|
15024
|
+
} catch (error) {
|
|
15025
|
+
if (!(error instanceof PluginRequestError)) throw error;
|
|
15026
|
+
throw new Error(error.code ?? error.reason);
|
|
15027
|
+
}
|
|
14237
15028
|
}
|
|
14238
15029
|
//#endregion
|
|
14239
15030
|
//#region src/client/ClaudeRewind.tsx
|
|
@@ -15449,15 +16240,10 @@ window.__ModuleLoader__.load({
|
|
|
15449
16240
|
//#region src/client/editor-open-api.ts
|
|
15450
16241
|
/** Launch the session's project in a desktop editor on the host machine. */
|
|
15451
16242
|
async function openProjectInEditor(sessionId, editor) {
|
|
15452
|
-
|
|
15453
|
-
|
|
15454
|
-
|
|
15455
|
-
|
|
15456
|
-
headers: { accept: "application/json" }
|
|
15457
|
-
});
|
|
15458
|
-
if (result.ok) return;
|
|
15459
|
-
const body = await result.json().catch(() => void 0);
|
|
15460
|
-
throw new Error(typeof body?.message === "string" ? body.message : "The editor could not be launched.");
|
|
16243
|
+
await pluginWrite(CLAUDE_EDITOR_OPEN_PATH, "fast", void 0, { query: {
|
|
16244
|
+
sessionId,
|
|
16245
|
+
editor
|
|
16246
|
+
} });
|
|
15461
16247
|
}
|
|
15462
16248
|
//#endregion
|
|
15463
16249
|
//#region src/client/ClaudeSessionMenu.tsx
|
|
@@ -15729,7 +16515,7 @@ window.__ModuleLoader__.load({
|
|
|
15729
16515
|
document.head.appendChild(element);
|
|
15730
16516
|
}
|
|
15731
16517
|
function ClaudeAgentPresetLabel({ t, hostT, roster, sessionId, useSessions }) {
|
|
15732
|
-
const preset = useSessions((state) => state.byId[sessionId]
|
|
16518
|
+
const preset = useSessions((state) => sessionRowPreset(state.byId[sessionId]));
|
|
15733
16519
|
const rows = useSyncExternalStore(roster.subscribe, roster.getSnapshot, roster.getSnapshot);
|
|
15734
16520
|
const { load } = roster;
|
|
15735
16521
|
useEffect(() => {
|
|
@@ -16162,6 +16948,14 @@ window.__ModuleLoader__.load({
|
|
|
16162
16948
|
globalSettingsNewSession: "修改仅对新建 Claude 会话生效。",
|
|
16163
16949
|
globalSettingsError: "全局设置保存失败",
|
|
16164
16950
|
outputStyle: "Output Style",
|
|
16951
|
+
renderer: "AI 输出渲染器",
|
|
16952
|
+
rendererPlugin: "插件渲染器",
|
|
16953
|
+
rendererNative: "DSH 原生渲染器",
|
|
16954
|
+
rendererEffect: "插件渲染器沿用本插件自带的转录视图:交错的正文、成组的工具卡片与活动行。DSH 原生渲染器改由 DSH 自身绘制:正文作为普通助手文本块,思考作为推理块,Claude 的顶层工具会镜像成原生工具卡片。修改从下一个回合起生效;已经产生的回合仍按记录时的渲染器显示,不会重绘。",
|
|
16955
|
+
prose: "Markdown 彩色高亮",
|
|
16956
|
+
prosePlain: "关闭",
|
|
16957
|
+
proseEnhanced: "开启",
|
|
16958
|
+
proseEffect: "开启后,正文的标题、加粗、斜体、行内代码、列表符号、引用与链接各自着色,代码块换成深色描边底。这会覆盖本插件默认对齐 Claude 桌面版的配色。仅在使用插件渲染器时有效,改动立即生效,无需等待下一回合。",
|
|
16165
16959
|
worktreeBranchPrefix: "Worktree 分支前缀",
|
|
16166
16960
|
worktreeBranchPrefixEffect: "分支前缀修改会应用于之后自动创建的 Worktree 分支;显式指定的完整分支名不会被修改。",
|
|
16167
16961
|
maxProcessesSetting: "Claude 进程上限",
|
|
@@ -16474,7 +17268,12 @@ window.__ModuleLoader__.load({
|
|
|
16474
17268
|
rewindSubmitting: "回退中…",
|
|
16475
17269
|
rewindFailed: "回退失败({code})。",
|
|
16476
17270
|
rewindBusy: "这个会话正在运行,请等本轮结束后再回退。",
|
|
16477
|
-
rewindStale: "宿主进程里的插件还是旧版本(没有回退接口)。重启 DSH 后再试。"
|
|
17271
|
+
rewindStale: "宿主进程里的插件还是旧版本(没有回退接口)。重启 DSH 后再试。",
|
|
17272
|
+
turnUsage: "本回合用量",
|
|
17273
|
+
turnUsageTokens: "{count} tok",
|
|
17274
|
+
turnUsageCache: "缓存命中 {percent}%",
|
|
17275
|
+
turnUsageTtft: "首字 {duration}",
|
|
17276
|
+
turnUsageCost: "累计 ${cost}"
|
|
16478
17277
|
};
|
|
16479
17278
|
const en = {
|
|
16480
17279
|
nav: "Claude Code",
|
|
@@ -16522,6 +17321,14 @@ window.__ModuleLoader__.load({
|
|
|
16522
17321
|
globalSettingsNewSession: "Changes apply only to new Claude sessions.",
|
|
16523
17322
|
globalSettingsError: "Global settings save failed",
|
|
16524
17323
|
outputStyle: "Output Style",
|
|
17324
|
+
renderer: "AI output renderer",
|
|
17325
|
+
rendererPlugin: "Plugin renderer",
|
|
17326
|
+
rendererNative: "DSH native renderer",
|
|
17327
|
+
rendererEffect: "The plugin renderer keeps this package’s own transcript: interleaved prose, grouped tool cards, and activity rows. The DSH native renderer hands the same turn to DSH itself — prose as ordinary assistant text blocks, thinking as reasoning blocks, and root Claude tools mirrored into native tool cards. A change applies from the next turn; turns already recorded keep the renderer they were recorded with and are not redrawn.",
|
|
17328
|
+
prose: "Markdown colour highlighting",
|
|
17329
|
+
prosePlain: "Off",
|
|
17330
|
+
proseEnhanced: "On",
|
|
17331
|
+
proseEffect: "Gives headings, bold, italics, inline code, list markers, quotes and links their own colours, and paints the code block on a dark outlined surface. This overrides the Claude-desktop palette this package matches by default. Only applies under the plugin renderer, and takes effect immediately rather than on the next turn.",
|
|
16525
17332
|
worktreeBranchPrefix: "Worktree branch prefix",
|
|
16526
17333
|
worktreeBranchPrefixEffect: "Prefix changes apply to subsequently generated Worktree branches. Explicit full branch names remain unchanged.",
|
|
16527
17334
|
maxProcessesSetting: "Claude process limit",
|
|
@@ -16834,7 +17641,12 @@ window.__ModuleLoader__.load({
|
|
|
16834
17641
|
rewindSubmitting: "Rewinding…",
|
|
16835
17642
|
rewindFailed: "The rewind failed ({code}).",
|
|
16836
17643
|
rewindBusy: "This session is running; wait for the turn to finish before rewinding.",
|
|
16837
|
-
rewindStale: "The Host process is still running the previous plugin build, which has no rewind route. Restart DSH and try again."
|
|
17644
|
+
rewindStale: "The Host process is still running the previous plugin build, which has no rewind route. Restart DSH and try again.",
|
|
17645
|
+
turnUsage: "Turn usage",
|
|
17646
|
+
turnUsageTokens: "{count} tok",
|
|
17647
|
+
turnUsageCache: "Cache hit {percent}%",
|
|
17648
|
+
turnUsageTtft: "TTFT {duration}",
|
|
17649
|
+
turnUsageCost: "${cost} total"
|
|
16838
17650
|
};
|
|
16839
17651
|
//#endregion
|
|
16840
17652
|
//#region src/client/index.tsx
|
|
@@ -16884,7 +17696,12 @@ window.__ModuleLoader__.load({
|
|
|
16884
17696
|
}), "dsh-claude: client copy");
|
|
16885
17697
|
const t = ctx.locale.bind(namespace);
|
|
16886
17698
|
ctx.effect(() => restyleHostChrome(), "dsh-claude: Host chrome restyling");
|
|
16887
|
-
|
|
17699
|
+
pluginRead(CLAUDE_GLOBAL_SETTINGS_PATH, "fast").then((payload) => {
|
|
17700
|
+
if (isGlobalSettingsView(payload)) applyClaudeMarkdownTheme(proseModeOf(payload.settings));
|
|
17701
|
+
}).catch(() => {});
|
|
17702
|
+
const projections = new ClaudeProjectionStore({ report: (kind, detail) => {
|
|
17703
|
+
diagnostics.report(kind, detail);
|
|
17704
|
+
} });
|
|
16888
17705
|
ctx.effect(() => ctx.inputTriggers.registerSource(createClaudeCommandSource(ctx, projections)), "dsh-claude: Claude slash source");
|
|
16889
17706
|
const sessions = ctx.get("sessions");
|
|
16890
17707
|
const workspaces = ctx.get("workspaces");
|