dsh-zen-remote 2.3.4 → 2.3.5
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 +1 -1
- package/README.zh-CN.md +1 -1
- package/lib/client.js +101 -29
- package/lib/types/client/effects/turn-fold.d.ts +42 -0
- package/lib/types/client/effects/turn-fold.d.ts.map +1 -1
- package/lib/types/client/styles/turn-fold.css.d.ts.map +1 -1
- package/package.json +37 -37
- package/src/client/effects/turn-fold.ts +97 -26
- package/src/client/styles/turn-fold.css.ts +9 -0
package/README.md
CHANGED
|
@@ -66,7 +66,7 @@ dsh plugin --profile web add dsh-zen-remote
|
|
|
66
66
|
// ~/.dsh/profiles/desktop/package.json
|
|
67
67
|
{
|
|
68
68
|
"dependencies": {
|
|
69
|
-
"dsh-zen-remote": "^2.3.
|
|
69
|
+
"dsh-zen-remote": "^2.3.5" // for local development: "link:/path/to/dsh-zen-remote"
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
```
|
package/README.zh-CN.md
CHANGED
|
@@ -66,7 +66,7 @@ dsh plugin --profile web add dsh-zen-remote
|
|
|
66
66
|
// ~/.dsh/profiles/desktop/package.json
|
|
67
67
|
{
|
|
68
68
|
"dependencies": {
|
|
69
|
-
"dsh-zen-remote": "^2.3.
|
|
69
|
+
"dsh-zen-remote": "^2.3.5" // 本地开发换成 "link:/path/to/dsh-zen-remote"
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
```
|
package/lib/client.js
CHANGED
|
@@ -12363,8 +12363,11 @@ function clientConfig() {
|
|
|
12363
12363
|
__modules["client/effects/turn-fold.js"] = function (require, module, exports) {
|
|
12364
12364
|
"use strict";
|
|
12365
12365
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12366
|
-
exports.OPEN = exports.THINK = exports.PROCESS_KINDS = void 0;
|
|
12366
|
+
exports.BLANK = exports.OPEN = exports.THINK = exports.PROCESS_KINDS = void 0;
|
|
12367
12367
|
exports.foldSuppressionTransition = foldSuppressionTransition;
|
|
12368
|
+
exports.hasContentOutside = hasContentOutside;
|
|
12369
|
+
exports.foldsWholeRow = foldsWholeRow;
|
|
12370
|
+
exports.isBlankStep = isBlankStep;
|
|
12368
12371
|
exports.installTurnFold = installTurnFold;
|
|
12369
12372
|
const client_config_ts_1 = require("./client/client-config.js");
|
|
12370
12373
|
const locales_ts_1 = require("./client/locales.js");
|
|
@@ -12464,46 +12467,93 @@ exports.OPEN = 'data-mnav-fold-open';
|
|
|
12464
12467
|
const ACTIVE_ATTR = 'data-mnav-fold-on';
|
|
12465
12468
|
/** Selector of the one row kind that can fold whole — see {@link foldsWholeRow}. */
|
|
12466
12469
|
const STEP_SELECTOR = '[data-chat-flow-kind="assistant-step"]';
|
|
12470
|
+
/** Elements that show something without a text node of their own. Every other
|
|
12471
|
+
* element is a wrapper: it is content only if something inside it is. */
|
|
12472
|
+
const SELF_VISIBLE = new Set([
|
|
12473
|
+
'AUDIO', 'CANVAS', 'EMBED', 'HR', 'IFRAME', 'IMG', 'INPUT', 'OBJECT',
|
|
12474
|
+
'PICTURE', 'SELECT', 'SVG', 'TEXTAREA', 'VIDEO',
|
|
12475
|
+
]);
|
|
12476
|
+
/**
|
|
12477
|
+
* True when `node` renders anything a reader could see outside the subtrees
|
|
12478
|
+
* that match `skip`: a non-blank text node, or a {@link SELF_VISIBLE} element.
|
|
12479
|
+
* Structural, no class names — same discipline as the rest of this effect.
|
|
12480
|
+
*/
|
|
12481
|
+
function hasContentOutside(node, skip) {
|
|
12482
|
+
if (node.nodeType === 3)
|
|
12483
|
+
return (node.textContent ?? '').trim() !== '';
|
|
12484
|
+
if (node.nodeType !== 1)
|
|
12485
|
+
return false;
|
|
12486
|
+
if (skip !== null && node.matches?.(skip) === true)
|
|
12487
|
+
return false;
|
|
12488
|
+
if (SELF_VISIBLE.has((node.tagName ?? '').toUpperCase()))
|
|
12489
|
+
return true;
|
|
12490
|
+
const children = node.childNodes;
|
|
12491
|
+
for (let i = 0; i < children.length; i++) {
|
|
12492
|
+
const child = children[i];
|
|
12493
|
+
if (child !== undefined && hasContentOutside(child, skip))
|
|
12494
|
+
return true;
|
|
12495
|
+
}
|
|
12496
|
+
return false;
|
|
12497
|
+
}
|
|
12467
12498
|
/**
|
|
12468
12499
|
* True when the reasoning rows are the ONLY thing this row renders, so the
|
|
12469
12500
|
* whole flow item has to go: the flow column is `display: flex` with
|
|
12470
12501
|
* `gap: 16px`, and an emptied-out flex item still claims its gap — the
|
|
12471
12502
|
* reader would see the folded steps as a column of blank bands.
|
|
12472
|
-
* Structural test, no class names:
|
|
12473
|
-
*
|
|
12503
|
+
* Structural test, no class names: outside the reasoning rows the row holds
|
|
12504
|
+
* nothing visible — only wrappers. Each reasoning block sits in its own
|
|
12505
|
+
* ProcessReasoning wrapper, and since DSH 0.2.1-rc.5 a block whose display
|
|
12506
|
+
* text is empty once a leading `<signature>…</signature>` is stripped renders
|
|
12507
|
+
* its ReasoningRow as null and leaves that wrapper empty; an empty wrapper
|
|
12508
|
+
* beside a Think must not keep the row unfolded.
|
|
12474
12509
|
*/
|
|
12475
12510
|
function foldsWholeRow(row, thinks) {
|
|
12476
|
-
|
|
12477
|
-
if (first === undefined)
|
|
12478
|
-
return false;
|
|
12479
|
-
const body = first.parentElement;
|
|
12480
|
-
if (body === null || body.childElementCount !== thinks.length)
|
|
12511
|
+
if (thinks.length === 0)
|
|
12481
12512
|
return false;
|
|
12482
|
-
|
|
12483
|
-
|
|
12484
|
-
|
|
12485
|
-
|
|
12486
|
-
|
|
12487
|
-
|
|
12488
|
-
|
|
12489
|
-
|
|
12490
|
-
|
|
12491
|
-
|
|
12492
|
-
|
|
12493
|
-
|
|
12494
|
-
|
|
12513
|
+
return !hasContentOutside(row, exports.THINK);
|
|
12514
|
+
}
|
|
12515
|
+
/**
|
|
12516
|
+
* True for an assistant-step row that renders nothing at all — no Think, no
|
|
12517
|
+
* prose, no image, no notice. DSH 0.2.1-rc.5 produces one for a step whose
|
|
12518
|
+
* only reasoning is a `<signature>…</signature>` (ReasoningRow hides itself,
|
|
12519
|
+
* the wrappers stay). It is not process to count in the chip, and it is not
|
|
12520
|
+
* content either: left alone it is a blank band in the flow (the gap again),
|
|
12521
|
+
* so it is hidden on its own marker ({@link BLANK}) that the chip's open
|
|
12522
|
+
* override never releases. On 0.2.1-rc.2, which renders such a block as an
|
|
12523
|
+
* ordinary Think, the row is not blank and folds exactly as before.
|
|
12524
|
+
*/
|
|
12525
|
+
function isBlankStep(row) {
|
|
12526
|
+
return !hasContentOutside(row, null);
|
|
12527
|
+
}
|
|
12528
|
+
/** Marker of a {@link isBlankStep} row; the stylesheet hides it while the
|
|
12529
|
+
* fold is live. Never counted, never opened. */
|
|
12530
|
+
exports.BLANK = 'data-mnav-blank';
|
|
12531
|
+
/** Hold {@link BLANK} on an assistant-step row exactly while it is
|
|
12532
|
+
* {@link isBlankStep} — set the moment it empties, dropped the moment
|
|
12533
|
+
* anything (a Think, prose, an image) arrives in it. */
|
|
12534
|
+
function markBlank(row, thinks) {
|
|
12535
|
+
if (thinks.length === 0 && isBlankStep(row))
|
|
12536
|
+
row.setAttribute(exports.BLANK, '');
|
|
12537
|
+
else
|
|
12538
|
+
row.removeAttribute(exports.BLANK);
|
|
12539
|
+
}
|
|
12540
|
+
/** The process items one flow row contributes — the row itself, its reasoning
|
|
12541
|
+
* rows, or nothing at all — and how many steps they are on the chip: a row
|
|
12542
|
+
* folded whole still counts each reasoning block it holds, so the count does
|
|
12543
|
+
* not depend on how the row had to be hidden. */
|
|
12495
12544
|
function processItems(row) {
|
|
12496
12545
|
const kind = row.getAttribute('data-chat-flow-kind');
|
|
12497
12546
|
if (kind === null)
|
|
12498
|
-
return [];
|
|
12547
|
+
return { items: [], steps: 0 };
|
|
12499
12548
|
if (PROCESS_KIND_SET.has(kind))
|
|
12500
|
-
return [row];
|
|
12549
|
+
return { items: [row], steps: 1 };
|
|
12501
12550
|
if (kind !== 'assistant-step')
|
|
12502
|
-
return [];
|
|
12551
|
+
return { items: [], steps: 0 };
|
|
12503
12552
|
const thinks = [...row.querySelectorAll(exports.THINK)];
|
|
12553
|
+
markBlank(row, thinks);
|
|
12504
12554
|
if (thinks.length === 0)
|
|
12505
|
-
return [];
|
|
12506
|
-
return foldsWholeRow(row, thinks) ? [row] : thinks;
|
|
12555
|
+
return { items: [], steps: 0 };
|
|
12556
|
+
return { items: foldsWholeRow(row, thinks) ? [row] : thinks, steps: thinks.length };
|
|
12507
12557
|
}
|
|
12508
12558
|
/**
|
|
12509
12559
|
* The summary row sitting immediately before `anchor`, created if absent.
|
|
@@ -12536,10 +12586,12 @@ function groupsOf(flow) {
|
|
|
12536
12586
|
if (kind === null)
|
|
12537
12587
|
continue;
|
|
12538
12588
|
if (current === undefined || TURN_HEAD_KINDS.has(kind)) {
|
|
12539
|
-
current = { key: row.getAttribute('data-chat-flow-key') ?? `pos${groups.length}`, items: [] };
|
|
12589
|
+
current = { key: row.getAttribute('data-chat-flow-key') ?? `pos${groups.length}`, items: [], steps: 0 };
|
|
12540
12590
|
groups.push(current);
|
|
12541
12591
|
}
|
|
12542
|
-
|
|
12592
|
+
const { items, steps } = processItems(row);
|
|
12593
|
+
current.items.push(...items);
|
|
12594
|
+
current.steps += steps;
|
|
12543
12595
|
}
|
|
12544
12596
|
return groups;
|
|
12545
12597
|
}
|
|
@@ -12636,7 +12688,7 @@ function installTurnFold(ctx) {
|
|
|
12636
12688
|
chip.dataset['running'] = '';
|
|
12637
12689
|
else
|
|
12638
12690
|
delete chip.dataset['running'];
|
|
12639
|
-
label(chip, group.
|
|
12691
|
+
label(chip, group.steps, groupRunning);
|
|
12640
12692
|
live.add(group.key);
|
|
12641
12693
|
});
|
|
12642
12694
|
for (const chip of flow.querySelectorAll(CHIP_SELECTOR)) {
|
|
@@ -12659,6 +12711,12 @@ function installTurnFold(ctx) {
|
|
|
12659
12711
|
stale.removeAttribute(FOLD);
|
|
12660
12712
|
stale.removeAttribute(exports.OPEN);
|
|
12661
12713
|
}
|
|
12714
|
+
// {@link BLANK} is held by groupsOf on every assistant-step row of this
|
|
12715
|
+
// column; anything else carrying it left the column (or changed kind).
|
|
12716
|
+
for (const stale of flow.querySelectorAll(`[${exports.BLANK}]`)) {
|
|
12717
|
+
if (stale.parentElement !== flow || !stale.matches(STEP_SELECTOR))
|
|
12718
|
+
stale.removeAttribute(exports.BLANK);
|
|
12719
|
+
}
|
|
12662
12720
|
};
|
|
12663
12721
|
/**
|
|
12664
12722
|
* Fold (or release) the reasoning-only rows this mutation batch touched,
|
|
@@ -12724,6 +12782,9 @@ function installTurnFold(ctx) {
|
|
|
12724
12782
|
row.setAttribute(FOLD, '');
|
|
12725
12783
|
else
|
|
12726
12784
|
row.removeAttribute(FOLD);
|
|
12785
|
+
// Same frame for the blank row (see {@link isBlankStep}): hidden
|
|
12786
|
+
// before it paints its band, shown before its first content paints.
|
|
12787
|
+
markBlank(row, thinks);
|
|
12727
12788
|
}
|
|
12728
12789
|
};
|
|
12729
12790
|
const schedule = () => {
|
|
@@ -12765,6 +12826,8 @@ function installTurnFold(ctx) {
|
|
|
12765
12826
|
item.removeAttribute(FOLD);
|
|
12766
12827
|
item.removeAttribute(exports.OPEN);
|
|
12767
12828
|
}
|
|
12829
|
+
for (const item of document.querySelectorAll(`[${exports.BLANK}]`))
|
|
12830
|
+
item.removeAttribute(exports.BLANK);
|
|
12768
12831
|
};
|
|
12769
12832
|
const attach = () => {
|
|
12770
12833
|
if (observer !== null)
|
|
@@ -12919,6 +12982,15 @@ const rules = (scope) => `
|
|
|
12919
12982
|
display: none !important;
|
|
12920
12983
|
}
|
|
12921
12984
|
|
|
12985
|
+
/* An assistant-step row that renders nothing at all (effects/turn-fold.ts
|
|
12986
|
+
isBlankStep — DSH 0.2.1-rc.5 leaves one behind for a step whose only
|
|
12987
|
+
reasoning is a <signature>…</signature>). Not a step: never counted on
|
|
12988
|
+
the chip and never released by the open override, it would only ever
|
|
12989
|
+
paint the flow column's 16px gap as a blank band. Literal mirror of BLANK in effects/turn-fold.ts. */
|
|
12990
|
+
${scope} [data-chat-flow] > [data-mnav-blank] {
|
|
12991
|
+
display: none !important;
|
|
12992
|
+
}
|
|
12993
|
+
|
|
12922
12994
|
/* Compact chip, sized like the composer's own pills rather than a button:
|
|
12923
12995
|
it is a reading affordance in the middle of the message flow, so it has
|
|
12924
12996
|
to stay quieter than the content around it. The flow column is a flex
|
|
@@ -22,6 +22,48 @@ export declare const PROCESS_KINDS: readonly ["context", "tool-call", "command"]
|
|
|
22
22
|
export declare const THINK = "[data-variant=\"think\"]";
|
|
23
23
|
/** Per-item override written while its turn is expanded. */
|
|
24
24
|
export declare const OPEN = "data-mnav-fold-open";
|
|
25
|
+
/** The DOM surface {@link hasContentOutside} reads — kept this narrow so the
|
|
26
|
+
* verdict is testable off-DOM (test/turn-fold-blank-step.test.cjs). */
|
|
27
|
+
export interface ContentNode {
|
|
28
|
+
readonly nodeType: number;
|
|
29
|
+
readonly textContent: string | null;
|
|
30
|
+
readonly childNodes: ArrayLike<ContentNode>;
|
|
31
|
+
readonly tagName?: string;
|
|
32
|
+
matches?(selector: string): boolean;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* True when `node` renders anything a reader could see outside the subtrees
|
|
36
|
+
* that match `skip`: a non-blank text node, or a {@link SELF_VISIBLE} element.
|
|
37
|
+
* Structural, no class names — same discipline as the rest of this effect.
|
|
38
|
+
*/
|
|
39
|
+
export declare function hasContentOutside(node: ContentNode, skip: string | null): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* True when the reasoning rows are the ONLY thing this row renders, so the
|
|
42
|
+
* whole flow item has to go: the flow column is `display: flex` with
|
|
43
|
+
* `gap: 16px`, and an emptied-out flex item still claims its gap — the
|
|
44
|
+
* reader would see the folded steps as a column of blank bands.
|
|
45
|
+
* Structural test, no class names: outside the reasoning rows the row holds
|
|
46
|
+
* nothing visible — only wrappers. Each reasoning block sits in its own
|
|
47
|
+
* ProcessReasoning wrapper, and since DSH 0.2.1-rc.5 a block whose display
|
|
48
|
+
* text is empty once a leading `<signature>…</signature>` is stripped renders
|
|
49
|
+
* its ReasoningRow as null and leaves that wrapper empty; an empty wrapper
|
|
50
|
+
* beside a Think must not keep the row unfolded.
|
|
51
|
+
*/
|
|
52
|
+
export declare function foldsWholeRow(row: ContentNode, thinks: readonly unknown[]): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* True for an assistant-step row that renders nothing at all — no Think, no
|
|
55
|
+
* prose, no image, no notice. DSH 0.2.1-rc.5 produces one for a step whose
|
|
56
|
+
* only reasoning is a `<signature>…</signature>` (ReasoningRow hides itself,
|
|
57
|
+
* the wrappers stay). It is not process to count in the chip, and it is not
|
|
58
|
+
* content either: left alone it is a blank band in the flow (the gap again),
|
|
59
|
+
* so it is hidden on its own marker ({@link BLANK}) that the chip's open
|
|
60
|
+
* override never releases. On 0.2.1-rc.2, which renders such a block as an
|
|
61
|
+
* ordinary Think, the row is not blank and folds exactly as before.
|
|
62
|
+
*/
|
|
63
|
+
export declare function isBlankStep(row: ContentNode): boolean;
|
|
64
|
+
/** Marker of a {@link isBlankStep} row; the stylesheet hides it while the
|
|
65
|
+
* fold is live. Never counted, never opened. */
|
|
66
|
+
export declare const BLANK = "data-mnav-blank";
|
|
25
67
|
/**
|
|
26
68
|
* S8 — collapse a turn's process into one summary row (< 768px by default;
|
|
27
69
|
* every width when the plugin row sets `config.turnFoldDesktop` or a browser
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"turn-fold.d.ts","sourceRoot":"","sources":["../../../../src/client/effects/turn-fold.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAuDvD;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CACvC,oBAAoB,EAAE,OAAO,EAC7B,UAAU,EAAE,OAAO,GAClB,OAAO,GAAG,MAAM,GAAG,MAAM,CAG3B;AAMD;;;;;;;sDAOsD;AACtD,eAAO,MAAM,aAAa,8CAA+C,CAAA;AAMzE;sEACsE;AACtE,eAAO,MAAM,KAAK,6BAA2B,CAAA;AAS7C,4DAA4D;AAC5D,eAAO,MAAM,IAAI,wBAAwB,CAAA;
|
|
1
|
+
{"version":3,"file":"turn-fold.d.ts","sourceRoot":"","sources":["../../../../src/client/effects/turn-fold.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAuDvD;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CACvC,oBAAoB,EAAE,OAAO,EAC7B,UAAU,EAAE,OAAO,GAClB,OAAO,GAAG,MAAM,GAAG,MAAM,CAG3B;AAMD;;;;;;;sDAOsD;AACtD,eAAO,MAAM,aAAa,8CAA+C,CAAA;AAMzE;sEACsE;AACtE,eAAO,MAAM,KAAK,6BAA2B,CAAA;AAS7C,4DAA4D;AAC5D,eAAO,MAAM,IAAI,wBAAwB,CAAA;AAmBzC;uEACuE;AACvE,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IACnC,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC,WAAW,CAAC,CAAA;IAC3C,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB,OAAO,CAAC,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAA;CACpC;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAWjF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,OAAO,EAAE,GAAG,OAAO,CAGnF;AAED;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAErD;AAED;gDACgD;AAChD,eAAO,MAAM,KAAK,oBAAoB,CAAA;AA0EtC;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CA6PxD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"turn-fold.css.d.ts","sourceRoot":"","sources":["../../../../src/client/styles/turn-fold.css.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"turn-fold.css.d.ts","sourceRoot":"","sources":["../../../../src/client/styles/turn-fold.css.ts"],"names":[],"mappings":"AAyKA,eAAO,MAAM,aAAa,QAyBzB,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-zen-remote",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.5",
|
|
4
4
|
"description": "一个插件、两种角色:主服务端把 DeepSeek Harness 变成手机能安全访问的 PWA(移动端界面、配对码网关、Web Push),并把选定会话共享给已配对的桌面端;子客户端在另一台 DSH 桌面端的侧边栏里远程使用这些会话。One DSH plugin, two roles: the host turns DeepSeek Harness into a phone-reachable PWA (mobile UI, pairing-code gateway behind your own reverse proxy, Web Push) and shares chosen sessions to paired desktops; the client uses those sessions remotely from another DSH desktop app's sidebar.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -116,43 +116,43 @@
|
|
|
116
116
|
},
|
|
117
117
|
"devDependencies": {
|
|
118
118
|
"@deepseek-ai/cordis": "4.0.5",
|
|
119
|
-
"@deepseek-ai/cosmokit": "1.8.
|
|
120
|
-
"@deepseek-ai/dsh-agent": "0.2.1-rc.
|
|
121
|
-
"@deepseek-ai/dsh-api-remotes": "0.2.1-rc.
|
|
122
|
-
"@deepseek-ai/dsh-api-session-controller": "0.2.1-rc.
|
|
123
|
-
"@deepseek-ai/dsh-api-workspace-controller": "0.2.1-rc.
|
|
124
|
-
"@deepseek-ai/dsh-brand": "0.2.1-rc.
|
|
125
|
-
"@deepseek-ai/dsh-client-connection": "0.2.1-rc.
|
|
126
|
-
"@deepseek-ai/dsh-client-locale": "0.2.1-rc.
|
|
127
|
-
"@deepseek-ai/dsh-client-store": "0.2.1-rc.
|
|
128
|
-
"@deepseek-ai/dsh-client-ui-conversation": "0.2.1-rc.
|
|
129
|
-
"@deepseek-ai/dsh-client-ui-layout": "0.2.1-rc.
|
|
130
|
-
"@deepseek-ai/dsh-client-ui-primitives": "0.2.1-rc.
|
|
131
|
-
"@deepseek-ai/dsh-client-ui-renderer": "0.2.1-rc.
|
|
132
|
-
"@deepseek-ai/dsh-client-ui-session": "0.2.1-rc.
|
|
133
|
-
"@deepseek-ai/dsh-client-ui-sidebar": "0.2.1-rc.
|
|
134
|
-
"@deepseek-ai/dsh-client-ui-slots": "0.2.1-rc.
|
|
135
|
-
"@deepseek-ai/dsh-host-webserver": "0.2.1-rc.
|
|
136
|
-
"@deepseek-ai/dsh-llm": "0.2.1-rc.
|
|
137
|
-
"@deepseek-ai/dsh-ptc-runtime": "0.2.1-rc.
|
|
138
|
-
"@deepseek-ai/dsh-sandbox": "0.2.1-rc.
|
|
139
|
-
"@deepseek-ai/dsh-sandbox-policy": "0.2.1-rc.
|
|
140
|
-
"@deepseek-ai/dsh-scope": "0.2.1-rc.
|
|
141
|
-
"@deepseek-ai/dsh-session": "0.2.1-rc.
|
|
142
|
-
"@deepseek-ai/dsh-session-log-export": "0.2.1-rc.
|
|
143
|
-
"@deepseek-ai/dsh-session-projection": "0.2.1-rc.
|
|
144
|
-
"@deepseek-ai/dsh-session-query": "0.2.1-rc.
|
|
145
|
-
"@deepseek-ai/dsh-session-stats": "0.2.1-rc.
|
|
146
|
-
"@deepseek-ai/dsh-subagent": "0.2.1-rc.
|
|
147
|
-
"@deepseek-ai/dsh-timeout": "0.2.1-rc.
|
|
148
|
-
"@deepseek-ai/dsh-token-meter": "0.2.1-rc.
|
|
149
|
-
"@deepseek-ai/dsh-tools": "0.2.1-rc.
|
|
150
|
-
"@deepseek-ai/dsh-typert-protocol": "0.2.1-rc.
|
|
151
|
-
"@deepseek-ai/dsh-util-crypto": "0.2.1-rc.
|
|
152
|
-
"@deepseek-ai/dsh-util-values": "0.2.1-rc.
|
|
153
|
-
"@deepseek-ai/dsh-workspace": "0.2.1-rc.
|
|
119
|
+
"@deepseek-ai/cosmokit": "1.8.6",
|
|
120
|
+
"@deepseek-ai/dsh-agent": "0.2.1-rc.5",
|
|
121
|
+
"@deepseek-ai/dsh-api-remotes": "0.2.1-rc.5",
|
|
122
|
+
"@deepseek-ai/dsh-api-session-controller": "0.2.1-rc.5",
|
|
123
|
+
"@deepseek-ai/dsh-api-workspace-controller": "0.2.1-rc.5",
|
|
124
|
+
"@deepseek-ai/dsh-brand": "0.2.1-rc.5",
|
|
125
|
+
"@deepseek-ai/dsh-client-connection": "0.2.1-rc.5",
|
|
126
|
+
"@deepseek-ai/dsh-client-locale": "0.2.1-rc.5",
|
|
127
|
+
"@deepseek-ai/dsh-client-store": "0.2.1-rc.5",
|
|
128
|
+
"@deepseek-ai/dsh-client-ui-conversation": "0.2.1-rc.5",
|
|
129
|
+
"@deepseek-ai/dsh-client-ui-layout": "0.2.1-rc.5",
|
|
130
|
+
"@deepseek-ai/dsh-client-ui-primitives": "0.2.1-rc.5",
|
|
131
|
+
"@deepseek-ai/dsh-client-ui-renderer": "0.2.1-rc.5",
|
|
132
|
+
"@deepseek-ai/dsh-client-ui-session": "0.2.1-rc.5",
|
|
133
|
+
"@deepseek-ai/dsh-client-ui-sidebar": "0.2.1-rc.5",
|
|
134
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.2.1-rc.5",
|
|
135
|
+
"@deepseek-ai/dsh-host-webserver": "0.2.1-rc.5",
|
|
136
|
+
"@deepseek-ai/dsh-llm": "0.2.1-rc.5",
|
|
137
|
+
"@deepseek-ai/dsh-ptc-runtime": "0.2.1-rc.5",
|
|
138
|
+
"@deepseek-ai/dsh-sandbox": "0.2.1-rc.5",
|
|
139
|
+
"@deepseek-ai/dsh-sandbox-policy": "0.2.1-rc.5",
|
|
140
|
+
"@deepseek-ai/dsh-scope": "0.2.1-rc.5",
|
|
141
|
+
"@deepseek-ai/dsh-session": "0.2.1-rc.5",
|
|
142
|
+
"@deepseek-ai/dsh-session-log-export": "0.2.1-rc.5",
|
|
143
|
+
"@deepseek-ai/dsh-session-projection": "0.2.1-rc.5",
|
|
144
|
+
"@deepseek-ai/dsh-session-query": "0.2.1-rc.5",
|
|
145
|
+
"@deepseek-ai/dsh-session-stats": "0.2.1-rc.5",
|
|
146
|
+
"@deepseek-ai/dsh-subagent": "0.2.1-rc.5",
|
|
147
|
+
"@deepseek-ai/dsh-timeout": "0.2.1-rc.5",
|
|
148
|
+
"@deepseek-ai/dsh-token-meter": "0.2.1-rc.5",
|
|
149
|
+
"@deepseek-ai/dsh-tools": "0.2.1-rc.5",
|
|
150
|
+
"@deepseek-ai/dsh-typert-protocol": "0.2.1-rc.5",
|
|
151
|
+
"@deepseek-ai/dsh-util-crypto": "0.2.1-rc.5",
|
|
152
|
+
"@deepseek-ai/dsh-util-values": "0.2.1-rc.5",
|
|
153
|
+
"@deepseek-ai/dsh-workspace": "0.2.1-rc.5",
|
|
154
154
|
"@deepseek-ai/schemastery": "3.18.5",
|
|
155
|
-
"@types/node": "^26.
|
|
155
|
+
"@types/node": "^26.6.5",
|
|
156
156
|
"@types/react": "~18.3.1",
|
|
157
157
|
"@types/react-dom": "~18.3.0",
|
|
158
158
|
"typescript": "^6.0.3"
|
|
@@ -111,39 +111,97 @@ const ACTIVE_ATTR = 'data-mnav-fold-on'
|
|
|
111
111
|
/** Selector of the one row kind that can fold whole — see {@link foldsWholeRow}. */
|
|
112
112
|
const STEP_SELECTOR = '[data-chat-flow-kind="assistant-step"]'
|
|
113
113
|
|
|
114
|
+
/** Elements that show something without a text node of their own. Every other
|
|
115
|
+
* element is a wrapper: it is content only if something inside it is. */
|
|
116
|
+
const SELF_VISIBLE = new Set([
|
|
117
|
+
'AUDIO', 'CANVAS', 'EMBED', 'HR', 'IFRAME', 'IMG', 'INPUT', 'OBJECT',
|
|
118
|
+
'PICTURE', 'SELECT', 'SVG', 'TEXTAREA', 'VIDEO',
|
|
119
|
+
])
|
|
120
|
+
|
|
121
|
+
/** The DOM surface {@link hasContentOutside} reads — kept this narrow so the
|
|
122
|
+
* verdict is testable off-DOM (test/turn-fold-blank-step.test.cjs). */
|
|
123
|
+
export interface ContentNode {
|
|
124
|
+
readonly nodeType: number
|
|
125
|
+
readonly textContent: string | null
|
|
126
|
+
readonly childNodes: ArrayLike<ContentNode>
|
|
127
|
+
readonly tagName?: string
|
|
128
|
+
matches?(selector: string): boolean
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* True when `node` renders anything a reader could see outside the subtrees
|
|
133
|
+
* that match `skip`: a non-blank text node, or a {@link SELF_VISIBLE} element.
|
|
134
|
+
* Structural, no class names — same discipline as the rest of this effect.
|
|
135
|
+
*/
|
|
136
|
+
export function hasContentOutside(node: ContentNode, skip: string | null): boolean {
|
|
137
|
+
if (node.nodeType === 3) return (node.textContent ?? '').trim() !== ''
|
|
138
|
+
if (node.nodeType !== 1) return false
|
|
139
|
+
if (skip !== null && node.matches?.(skip) === true) return false
|
|
140
|
+
if (SELF_VISIBLE.has((node.tagName ?? '').toUpperCase())) return true
|
|
141
|
+
const children = node.childNodes
|
|
142
|
+
for (let i = 0; i < children.length; i++) {
|
|
143
|
+
const child = children[i]
|
|
144
|
+
if (child !== undefined && hasContentOutside(child, skip)) return true
|
|
145
|
+
}
|
|
146
|
+
return false
|
|
147
|
+
}
|
|
148
|
+
|
|
114
149
|
/**
|
|
115
150
|
* True when the reasoning rows are the ONLY thing this row renders, so the
|
|
116
151
|
* whole flow item has to go: the flow column is `display: flex` with
|
|
117
152
|
* `gap: 16px`, and an emptied-out flex item still claims its gap — the
|
|
118
153
|
* reader would see the folded steps as a column of blank bands.
|
|
119
|
-
* Structural test, no class names:
|
|
120
|
-
*
|
|
154
|
+
* Structural test, no class names: outside the reasoning rows the row holds
|
|
155
|
+
* nothing visible — only wrappers. Each reasoning block sits in its own
|
|
156
|
+
* ProcessReasoning wrapper, and since DSH 0.2.1-rc.5 a block whose display
|
|
157
|
+
* text is empty once a leading `<signature>…</signature>` is stripped renders
|
|
158
|
+
* its ReasoningRow as null and leaves that wrapper empty; an empty wrapper
|
|
159
|
+
* beside a Think must not keep the row unfolded.
|
|
121
160
|
*/
|
|
122
|
-
function foldsWholeRow(row:
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const body = first.parentElement
|
|
126
|
-
if (body === null || body.childElementCount !== thinks.length) return false
|
|
127
|
-
let node: Element = body
|
|
128
|
-
while (node !== row) {
|
|
129
|
-
const parent = node.parentElement
|
|
130
|
-
if (parent === null) return false
|
|
131
|
-
if (parent.childElementCount !== 1) return false
|
|
132
|
-
node = parent
|
|
133
|
-
}
|
|
134
|
-
return true
|
|
161
|
+
export function foldsWholeRow(row: ContentNode, thinks: readonly unknown[]): boolean {
|
|
162
|
+
if (thinks.length === 0) return false
|
|
163
|
+
return !hasContentOutside(row, THINK)
|
|
135
164
|
}
|
|
136
165
|
|
|
137
|
-
/**
|
|
138
|
-
*
|
|
139
|
-
|
|
166
|
+
/**
|
|
167
|
+
* True for an assistant-step row that renders nothing at all — no Think, no
|
|
168
|
+
* prose, no image, no notice. DSH 0.2.1-rc.5 produces one for a step whose
|
|
169
|
+
* only reasoning is a `<signature>…</signature>` (ReasoningRow hides itself,
|
|
170
|
+
* the wrappers stay). It is not process to count in the chip, and it is not
|
|
171
|
+
* content either: left alone it is a blank band in the flow (the gap again),
|
|
172
|
+
* so it is hidden on its own marker ({@link BLANK}) that the chip's open
|
|
173
|
+
* override never releases. On 0.2.1-rc.2, which renders such a block as an
|
|
174
|
+
* ordinary Think, the row is not blank and folds exactly as before.
|
|
175
|
+
*/
|
|
176
|
+
export function isBlankStep(row: ContentNode): boolean {
|
|
177
|
+
return !hasContentOutside(row, null)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Marker of a {@link isBlankStep} row; the stylesheet hides it while the
|
|
181
|
+
* fold is live. Never counted, never opened. */
|
|
182
|
+
export const BLANK = 'data-mnav-blank'
|
|
183
|
+
|
|
184
|
+
/** Hold {@link BLANK} on an assistant-step row exactly while it is
|
|
185
|
+
* {@link isBlankStep} — set the moment it empties, dropped the moment
|
|
186
|
+
* anything (a Think, prose, an image) arrives in it. */
|
|
187
|
+
function markBlank(row: Element, thinks: readonly Element[]): void {
|
|
188
|
+
if (thinks.length === 0 && isBlankStep(row)) row.setAttribute(BLANK, '')
|
|
189
|
+
else row.removeAttribute(BLANK)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** The process items one flow row contributes — the row itself, its reasoning
|
|
193
|
+
* rows, or nothing at all — and how many steps they are on the chip: a row
|
|
194
|
+
* folded whole still counts each reasoning block it holds, so the count does
|
|
195
|
+
* not depend on how the row had to be hidden. */
|
|
196
|
+
function processItems(row: Element): { readonly items: readonly Element[], readonly steps: number } {
|
|
140
197
|
const kind = row.getAttribute('data-chat-flow-kind')
|
|
141
|
-
if (kind === null) return []
|
|
142
|
-
if (PROCESS_KIND_SET.has(kind)) return [row]
|
|
143
|
-
if (kind !== 'assistant-step') return []
|
|
198
|
+
if (kind === null) return { items: [], steps: 0 }
|
|
199
|
+
if (PROCESS_KIND_SET.has(kind)) return { items: [row], steps: 1 }
|
|
200
|
+
if (kind !== 'assistant-step') return { items: [], steps: 0 }
|
|
144
201
|
const thinks = [...row.querySelectorAll(THINK)]
|
|
145
|
-
|
|
146
|
-
|
|
202
|
+
markBlank(row, thinks)
|
|
203
|
+
if (thinks.length === 0) return { items: [], steps: 0 }
|
|
204
|
+
return { items: foldsWholeRow(row, thinks) ? [row] : thinks, steps: thinks.length }
|
|
147
205
|
}
|
|
148
206
|
|
|
149
207
|
/**
|
|
@@ -170,6 +228,8 @@ interface Group {
|
|
|
170
228
|
readonly key: string
|
|
171
229
|
/** Every process element of this turn, in flow order. */
|
|
172
230
|
readonly items: Element[]
|
|
231
|
+
/** Steps the chip announces (see {@link processItems}). */
|
|
232
|
+
steps: number
|
|
173
233
|
}
|
|
174
234
|
|
|
175
235
|
/** Split the flow column into turns: a group runs from one user/steering row
|
|
@@ -183,10 +243,12 @@ function groupsOf(flow: Element): readonly Group[] {
|
|
|
183
243
|
const kind = row.getAttribute('data-chat-flow-kind')
|
|
184
244
|
if (kind === null) continue
|
|
185
245
|
if (current === undefined || TURN_HEAD_KINDS.has(kind)) {
|
|
186
|
-
current = { key: row.getAttribute('data-chat-flow-key') ?? `pos${groups.length}`, items: [] }
|
|
246
|
+
current = { key: row.getAttribute('data-chat-flow-key') ?? `pos${groups.length}`, items: [], steps: 0 }
|
|
187
247
|
groups.push(current)
|
|
188
248
|
}
|
|
189
|
-
|
|
249
|
+
const { items, steps } = processItems(row)
|
|
250
|
+
current.items.push(...items)
|
|
251
|
+
current.steps += steps
|
|
190
252
|
}
|
|
191
253
|
return groups
|
|
192
254
|
}
|
|
@@ -279,7 +341,7 @@ export function installTurnFold(ctx: ClientContext): void {
|
|
|
279
341
|
const groupRunning = running && index === groups.length - 1
|
|
280
342
|
if (groupRunning) chip.dataset['running'] = ''
|
|
281
343
|
else delete chip.dataset['running']
|
|
282
|
-
label(chip, group.
|
|
344
|
+
label(chip, group.steps, groupRunning)
|
|
283
345
|
live.add(group.key)
|
|
284
346
|
})
|
|
285
347
|
|
|
@@ -302,6 +364,11 @@ export function installTurnFold(ctx: ClientContext): void {
|
|
|
302
364
|
stale.removeAttribute(FOLD)
|
|
303
365
|
stale.removeAttribute(OPEN)
|
|
304
366
|
}
|
|
367
|
+
// {@link BLANK} is held by groupsOf on every assistant-step row of this
|
|
368
|
+
// column; anything else carrying it left the column (or changed kind).
|
|
369
|
+
for (const stale of flow.querySelectorAll(`[${BLANK}]`)) {
|
|
370
|
+
if (stale.parentElement !== flow || !stale.matches(STEP_SELECTOR)) stale.removeAttribute(BLANK)
|
|
371
|
+
}
|
|
305
372
|
}
|
|
306
373
|
|
|
307
374
|
/**
|
|
@@ -359,6 +426,9 @@ export function installTurnFold(ctx: ClientContext): void {
|
|
|
359
426
|
const thinks = [...row.querySelectorAll(THINK)]
|
|
360
427
|
if (thinks.length > 0 && foldsWholeRow(row, thinks)) row.setAttribute(FOLD, '')
|
|
361
428
|
else row.removeAttribute(FOLD)
|
|
429
|
+
// Same frame for the blank row (see {@link isBlankStep}): hidden
|
|
430
|
+
// before it paints its band, shown before its first content paints.
|
|
431
|
+
markBlank(row, thinks)
|
|
362
432
|
}
|
|
363
433
|
}
|
|
364
434
|
|
|
@@ -396,6 +466,7 @@ export function installTurnFold(ctx: ClientContext): void {
|
|
|
396
466
|
item.removeAttribute(FOLD)
|
|
397
467
|
item.removeAttribute(OPEN)
|
|
398
468
|
}
|
|
469
|
+
for (const item of document.querySelectorAll(`[${BLANK}]`)) item.removeAttribute(BLANK)
|
|
399
470
|
}
|
|
400
471
|
|
|
401
472
|
const attach = (): void => {
|
|
@@ -83,6 +83,15 @@ const rules = (scope: string): string => `
|
|
|
83
83
|
display: none !important;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
/* An assistant-step row that renders nothing at all (effects/turn-fold.ts
|
|
87
|
+
isBlankStep — DSH 0.2.1-rc.5 leaves one behind for a step whose only
|
|
88
|
+
reasoning is a <signature>…</signature>). Not a step: never counted on
|
|
89
|
+
the chip and never released by the open override, it would only ever
|
|
90
|
+
paint the flow column's 16px gap as a blank band. Literal mirror of BLANK in effects/turn-fold.ts. */
|
|
91
|
+
${scope} [data-chat-flow] > [data-mnav-blank] {
|
|
92
|
+
display: none !important;
|
|
93
|
+
}
|
|
94
|
+
|
|
86
95
|
/* Compact chip, sized like the composer's own pills rather than a button:
|
|
87
96
|
it is a reading affordance in the middle of the message flow, so it has
|
|
88
97
|
to stay quieter than the content around it. The flow column is a flex
|