dsh-zen-remote 1.1.16 → 2.0.1
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/AGENTS.md +54 -9
- package/README.md +249 -190
- package/README.zh-CN.md +247 -201
- package/assets/home.png +0 -0
- package/assets/info.png +0 -0
- package/assets/pairing.png +0 -0
- package/assets/session.png +0 -0
- package/assets/sheet.png +0 -0
- package/cordis.patch.yml +12 -11
- package/cordis.patch.yml.example +24 -9
- package/docs/interface.md +57 -2
- package/docs/remote-access.en.md +99 -58
- package/docs/remote-access.md +99 -42
- package/dsh-push.mjs +67 -54
- package/lan-gate.mjs +88 -20
- package/lib/activity.js +168 -0
- package/lib/activity.js.map +1 -0
- package/lib/admin-routes.js +526 -0
- package/lib/admin-routes.js.map +1 -0
- package/lib/client-pairing.js +224 -0
- package/lib/client-pairing.js.map +1 -0
- package/lib/client-routes.js +674 -0
- package/lib/client-routes.js.map +1 -0
- package/lib/client.js +11498 -5990
- package/lib/config.js +346 -0
- package/lib/config.js.map +1 -0
- package/lib/fetch-route-intercept.js +418 -0
- package/lib/fetch-route-intercept.js.map +1 -0
- package/lib/fingerprint.js +365 -0
- package/lib/fingerprint.js.map +1 -0
- package/lib/http.js +69 -0
- package/lib/http.js.map +1 -0
- package/lib/index.js +675 -54
- package/lib/index.js.map +1 -1
- package/lib/intercept-shape.js +81 -0
- package/lib/intercept-shape.js.map +1 -0
- package/lib/intercept.js +3181 -0
- package/lib/intercept.js.map +1 -0
- package/lib/lan-gate-server.cjs +532 -121
- package/lib/merge-streams.js +1172 -0
- package/lib/merge-streams.js.map +1 -0
- package/lib/relay-access.js +496 -0
- package/lib/relay-access.js.map +1 -0
- package/lib/relay-client.js +1303 -0
- package/lib/relay-client.js.map +1 -0
- package/lib/relay-filter.js +282 -0
- package/lib/relay-filter.js.map +1 -0
- package/lib/relay-server.js +1813 -0
- package/lib/relay-server.js.map +1 -0
- package/lib/restart-fields.js +34 -0
- package/lib/restart-fields.js.map +1 -0
- package/lib/restart-watcher.js +106 -0
- package/lib/restart-watcher.js.map +1 -0
- package/lib/session-reference.js +157 -0
- package/lib/session-reference.js.map +1 -0
- package/lib/share-export.js +16 -1
- package/lib/share-export.js.map +1 -1
- package/lib/share-ops.js +86 -0
- package/lib/share-ops.js.map +1 -0
- package/lib/share-store.js +294 -0
- package/lib/share-store.js.map +1 -0
- package/lib/types/activity.d.ts +99 -0
- package/lib/types/activity.d.ts.map +1 -0
- package/lib/types/admin-routes.d.ts +129 -0
- package/lib/types/admin-routes.d.ts.map +1 -0
- package/lib/types/client/MobileDrawerFooter.d.ts.map +1 -1
- package/lib/types/client/MobileHome.d.ts +1 -1
- package/lib/types/client/MobileHome.d.ts.map +1 -1
- package/lib/types/client/MobileHomeChips.d.ts +1 -1
- package/lib/types/client/MobileHomeChips.d.ts.map +1 -1
- package/lib/types/client/MobileSessionHeader.d.ts +13 -2
- package/lib/types/client/MobileSessionHeader.d.ts.map +1 -1
- package/lib/types/client/MobileSessionInfo.d.ts +20 -3
- package/lib/types/client/MobileSessionInfo.d.ts.map +1 -1
- package/lib/types/client/RemoteComposerBanner.d.ts +42 -0
- package/lib/types/client/RemoteComposerBanner.d.ts.map +1 -0
- package/lib/types/client/RemoteHeaderIcon.d.ts +37 -0
- package/lib/types/client/RemoteHeaderIcon.d.ts.map +1 -0
- package/lib/types/client/RemoteShareMenu.d.ts +28 -0
- package/lib/types/client/RemoteShareMenu.d.ts.map +1 -0
- package/lib/types/client/RemoteStatusIcon.d.ts +36 -0
- package/lib/types/client/RemoteStatusIcon.d.ts.map +1 -0
- package/lib/types/client/compat/desktop.d.ts +21 -0
- package/lib/types/client/compat/desktop.d.ts.map +1 -0
- package/lib/types/client/compat/types.d.ts +57 -0
- package/lib/types/client/compat/types.d.ts.map +1 -1
- package/lib/types/client/current-session-report.d.ts +37 -0
- package/lib/types/client/current-session-report.d.ts.map +1 -0
- package/lib/types/client/effects/header-status.d.ts.map +1 -1
- package/lib/types/client/effects/keyboard-guard.d.ts.map +1 -1
- package/lib/types/client/effects/model-group-side.d.ts +13 -0
- package/lib/types/client/effects/model-group-side.d.ts.map +1 -0
- package/lib/types/client/effects/permission-sheet.d.ts +39 -0
- package/lib/types/client/effects/permission-sheet.d.ts.map +1 -0
- package/lib/types/client/effects/remote-session.d.ts +25 -0
- package/lib/types/client/effects/remote-session.d.ts.map +1 -0
- package/lib/types/client/effects/turn-fold.d.ts +9 -0
- package/lib/types/client/effects/turn-fold.d.ts.map +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/locales.d.ts +187 -2
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/remote-fetch.d.ts +51 -0
- package/lib/types/client/remote-fetch.d.ts.map +1 -0
- package/lib/types/client/remote-group-expansion.d.ts +41 -0
- package/lib/types/client/remote-group-expansion.d.ts.map +1 -0
- package/lib/types/client/remote-share-css.d.ts +19 -0
- package/lib/types/client/remote-share-css.d.ts.map +1 -0
- package/lib/types/client/remote-share-register.d.ts +113 -0
- package/lib/types/client/remote-share-register.d.ts.map +1 -0
- package/lib/types/client/remote-status-css.d.ts +15 -0
- package/lib/types/client/remote-status-css.d.ts.map +1 -0
- package/lib/types/client/remote-status-register.d.ts +46 -0
- package/lib/types/client/remote-status-register.d.ts.map +1 -0
- package/lib/types/client/settings/SettingsSection.d.ts +46 -0
- package/lib/types/client/settings/SettingsSection.d.ts.map +1 -0
- package/lib/types/client/settings/register-settings.d.ts +58 -0
- package/lib/types/client/settings/register-settings.d.ts.map +1 -0
- package/lib/types/client/settings/settings-css.d.ts +10 -0
- package/lib/types/client/settings/settings-css.d.ts.map +1 -0
- package/lib/types/client/share/share-preview.d.ts.map +1 -1
- package/lib/types/client/styles/composer.css.d.ts +1 -1
- package/lib/types/client/styles/composer.css.d.ts.map +1 -1
- package/lib/types/client/styles/header.css.d.ts +1 -1
- package/lib/types/client/styles/header.css.d.ts.map +1 -1
- package/lib/types/client/styles/info.css.d.ts +1 -1
- package/lib/types/client/styles/info.css.d.ts.map +1 -1
- package/lib/types/client/styles/remote-session.css.d.ts +2 -0
- package/lib/types/client/styles/remote-session.css.d.ts.map +1 -0
- package/lib/types/client-data/current-session.d.ts +41 -0
- package/lib/types/client-data/current-session.d.ts.map +1 -0
- package/lib/types/client-data/model-group-side.d.ts +44 -0
- package/lib/types/client-data/model-group-side.d.ts.map +1 -0
- package/lib/types/client-data/remote-group-expansion.d.ts +76 -0
- package/lib/types/client-data/remote-group-expansion.d.ts.map +1 -0
- package/lib/types/client-data/remote-status.d.ts +148 -0
- package/lib/types/client-data/remote-status.d.ts.map +1 -0
- package/lib/types/client-data/settings-form.d.ts +774 -0
- package/lib/types/client-data/settings-form.d.ts.map +1 -0
- package/lib/types/client-data/shares.d.ts +211 -0
- package/lib/types/client-data/shares.d.ts.map +1 -0
- package/lib/types/client-pairing.d.ts +114 -0
- package/lib/types/client-pairing.d.ts.map +1 -0
- package/lib/types/client-routes.d.ts +112 -0
- package/lib/types/client-routes.d.ts.map +1 -0
- package/lib/types/config.d.ts +200 -0
- package/lib/types/config.d.ts.map +1 -0
- package/lib/types/fetch-route-intercept.d.ts +156 -0
- package/lib/types/fetch-route-intercept.d.ts.map +1 -0
- package/lib/types/fingerprint.d.ts +166 -0
- package/lib/types/fingerprint.d.ts.map +1 -0
- package/lib/types/http.d.ts +41 -0
- package/lib/types/http.d.ts.map +1 -0
- package/lib/types/index.d.ts +56 -24
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/intercept-shape.d.ts +32 -0
- package/lib/types/intercept-shape.d.ts.map +1 -0
- package/lib/types/intercept.d.ts +442 -0
- package/lib/types/intercept.d.ts.map +1 -0
- package/lib/types/merge-streams.d.ts +398 -0
- package/lib/types/merge-streams.d.ts.map +1 -0
- package/lib/types/relay-access.d.ts +310 -0
- package/lib/types/relay-access.d.ts.map +1 -0
- package/lib/types/relay-client.d.ts +274 -0
- package/lib/types/relay-client.d.ts.map +1 -0
- package/lib/types/relay-filter.d.ts +103 -0
- package/lib/types/relay-filter.d.ts.map +1 -0
- package/lib/types/relay-server.d.ts +208 -0
- package/lib/types/relay-server.d.ts.map +1 -0
- package/lib/types/restart-fields.d.ts +22 -0
- package/lib/types/restart-fields.d.ts.map +1 -0
- package/lib/types/restart-watcher.d.ts +83 -0
- package/lib/types/restart-watcher.d.ts.map +1 -0
- package/lib/types/session-reference.d.ts +66 -0
- package/lib/types/session-reference.d.ts.map +1 -0
- package/lib/types/share-export.d.ts +14 -1
- package/lib/types/share-export.d.ts.map +1 -1
- package/lib/types/share-ops.d.ts +79 -0
- package/lib/types/share-ops.d.ts.map +1 -0
- package/lib/types/share-store.d.ts +87 -0
- package/lib/types/share-store.d.ts.map +1 -0
- package/lib/types/virtual-id.d.ts +52 -0
- package/lib/types/virtual-id.d.ts.map +1 -0
- package/lib/virtual-id.js +66 -0
- package/lib/virtual-id.js.map +1 -0
- package/llms.txt +17 -14
- package/package.json +60 -50
- package/scripts/build-client.mjs +5 -3
- package/scripts/check-host-shadow.mjs +19 -2
- package/scripts/check-settings-form.mjs +933 -0
- package/scripts/check-shares.mjs +397 -0
- package/scripts/check-upload-endpoint.mjs +59 -3
- package/scripts/sync-doc-version.mjs +8 -15
- package/scripts/verify-fingerprint-live.mjs +111 -0
- package/src/activity.ts +218 -0
- package/src/admin-routes.ts +627 -0
- package/src/client/MobileAttachButton.tsx +2 -2
- package/src/client/MobileAttachChips.tsx +3 -3
- package/src/client/MobileDrawerFooter.tsx +7 -4
- package/src/client/MobileHome.tsx +20 -16
- package/src/client/MobileHomeChips.tsx +16 -16
- package/src/client/MobileNavOverlay.tsx +2 -2
- package/src/client/MobileNavToggle.tsx +3 -3
- package/src/client/MobileSessionHeader.tsx +63 -42
- package/src/client/MobileSessionInfo.tsx +118 -17
- package/src/client/RemoteComposerBanner.tsx +76 -0
- package/src/client/RemoteHeaderIcon.tsx +103 -0
- package/src/client/RemoteShareMenu.tsx +57 -0
- package/src/client/RemoteStatusIcon.tsx +91 -0
- package/src/client/compat/desktop.ts +22 -0
- package/src/client/compat/types.ts +62 -0
- package/src/client/current-session-report.ts +164 -0
- package/src/client/effects/header-status.ts +21 -17
- package/src/client/effects/keyboard-guard.ts +65 -1
- package/src/client/effects/model-group-side.ts +132 -0
- package/src/client/effects/permission-sheet.ts +149 -0
- package/src/client/effects/remote-session.ts +68 -0
- package/src/client/effects/turn-fold.ts +52 -0
- package/src/client/index.tsx +158 -36
- package/src/client/locales.ts +396 -2
- package/src/client/remote-fetch.ts +125 -0
- package/src/client/remote-group-expansion.ts +190 -0
- package/src/client/remote-share-css.ts +54 -0
- package/src/client/remote-share-register.ts +244 -0
- package/src/client/remote-status-css.ts +64 -0
- package/src/client/remote-status-register.ts +107 -0
- package/src/client/settings/SettingsSection.tsx +1229 -0
- package/src/client/settings/register-settings.ts +116 -0
- package/src/client/settings/settings-css.ts +236 -0
- package/src/client/share/share-preview.tsx +6 -3
- package/src/client/share/share-sheet.tsx +3 -3
- package/src/client/styles/composer.css.ts +119 -26
- package/src/client/styles/header.css.ts +7 -7
- package/src/client/styles/info.css.ts +74 -0
- package/src/client/styles/remote-session.css.ts +274 -0
- package/src/client-data/current-session.ts +57 -0
- package/src/client-data/model-group-side.ts +65 -0
- package/src/client-data/remote-group-expansion.ts +145 -0
- package/src/client-data/remote-status.ts +374 -0
- package/src/client-data/settings-form.ts +1371 -0
- package/src/client-data/shares.ts +467 -0
- package/src/client-pairing.ts +244 -0
- package/src/client-routes.ts +732 -0
- package/src/config.ts +447 -0
- package/src/fetch-route-intercept.ts +510 -0
- package/src/fingerprint.ts +410 -0
- package/src/http.ts +68 -0
- package/src/index.ts +719 -62
- package/src/intercept-shape.ts +87 -0
- package/src/intercept.ts +3510 -0
- package/src/merge-streams.ts +1279 -0
- package/src/relay-access.ts +672 -0
- package/src/relay-client.ts +1514 -0
- package/src/relay-filter.ts +291 -0
- package/src/relay-server.ts +2063 -0
- package/src/restart-fields.ts +36 -0
- package/src/restart-watcher.ts +140 -0
- package/src/session-reference.ts +156 -0
- package/src/share-export.ts +26 -1
- package/src/share-ops.ts +114 -0
- package/src/share-store.ts +373 -0
- package/src/virtual-id.ts +74 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { ClientContext, MobileSessionRow } from '../compat/types.ts'
|
|
2
|
+
import { mainSessionIdOf } from '../compat/types.ts'
|
|
3
|
+
import { isVirtual } from '../../virtual-id.ts'
|
|
4
|
+
import { REMOTE_SESSION_CSS } from '../styles/remote-session.css.ts'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The remote-session marker (T41b): the CURRENT main-view session id, read
|
|
8
|
+
* the way the official uiSession `publishMain` reads it (RT
|
|
9
|
+
* dsh-client-ui-session lib/client.js:279-291), stamped onto <html> as
|
|
10
|
+
* `data-zr-remote-session="1"` while it is a virtual (relay) id and removed
|
|
11
|
+
* otherwise. remote-session.css.ts turns the attribute into the hiding of
|
|
12
|
+
* every open-on-the-server-machine entry, so the decision lives at the one
|
|
13
|
+
* place that knows the session and never inside component DOM.
|
|
14
|
+
*
|
|
15
|
+
* The official tie-break is kept verbatim: when the previous answer is still
|
|
16
|
+
* mainView-retained it STAYS the answer, and only otherwise does the first
|
|
17
|
+
* mainView-retained row in catalog order win — so two simultaneously
|
|
18
|
+
* retained rows are decided by "current", never by iteration order. The
|
|
19
|
+
* current value comes from the uiSession service's own main binding source
|
|
20
|
+
* (`current.value.key`); where the service has not registered yet the scan
|
|
21
|
+
* (compat/types.ts mainSessionIdOf) stands in, and a later list or main
|
|
22
|
+
* change re-decides — which is why BOTH sources are subscribed below.
|
|
23
|
+
*
|
|
24
|
+
* Kept outside React for the same reason the header status dot is: the
|
|
25
|
+
* attribute must track the current session regardless of which panel is
|
|
26
|
+
* mounted, and an attribute + stylesheet survives every re-render untouched.
|
|
27
|
+
*/
|
|
28
|
+
export function installRemoteSessionGuard(ctx: ClientContext): void {
|
|
29
|
+
ctx.effect(() => {
|
|
30
|
+
const tag = document.createElement('style')
|
|
31
|
+
tag.dataset.plugin = 'dsh-zen-remote'
|
|
32
|
+
tag.dataset.pluginCss = 'dsh-zen-remote/remote-session.css'
|
|
33
|
+
tag.textContent = REMOTE_SESSION_CSS
|
|
34
|
+
document.head.appendChild(tag)
|
|
35
|
+
return () => {
|
|
36
|
+
tag.remove()
|
|
37
|
+
}
|
|
38
|
+
}, 'dsh-zen-remote: remote-session styles')
|
|
39
|
+
|
|
40
|
+
ctx.effect(() => {
|
|
41
|
+
const apply = (): void => {
|
|
42
|
+
const { byId } = ctx.sessions.list.getSnapshot()
|
|
43
|
+
// publishMain's tie-break (see the module comment): prefer the still-
|
|
44
|
+
// retained current main binding, else the first mainView row.
|
|
45
|
+
const uiSession = ctx.get('uiSession') as { current?: { value?: { key?: unknown }, subscribe?: (listener: () => void) => () => void } } | undefined
|
|
46
|
+
const current = uiSession?.current?.value?.key
|
|
47
|
+
const rows = byId as Readonly<Record<string, MobileSessionRow>>
|
|
48
|
+
const sessionId =
|
|
49
|
+
typeof current === 'string' && (rows[current]?.retainedBy?.mainView ?? 0) > 0
|
|
50
|
+
? current
|
|
51
|
+
: mainSessionIdOf(byId)
|
|
52
|
+
const root = document.documentElement
|
|
53
|
+
if (sessionId !== undefined && isVirtual(sessionId)) root.setAttribute('data-zr-remote-session', '1')
|
|
54
|
+
else root.removeAttribute('data-zr-remote-session')
|
|
55
|
+
}
|
|
56
|
+
apply()
|
|
57
|
+
const offs: Array<() => void> = [ctx.sessions.list.subscribe(apply)]
|
|
58
|
+
// The main binding can move without a list event (a retention change on
|
|
59
|
+
// the current main re-publishes through uiSession's own retain watcher,
|
|
60
|
+
// RT lib/client.js:293-301); subscribe to that source too when it exists.
|
|
61
|
+
const current = (ctx.get('uiSession') as { current?: { subscribe?: (listener: () => void) => () => void } } | undefined)?.current
|
|
62
|
+
if (typeof current?.subscribe === 'function') offs.push(current.subscribe(apply))
|
|
63
|
+
return () => {
|
|
64
|
+
for (const off of offs) off()
|
|
65
|
+
document.documentElement.removeAttribute('data-zr-remote-session')
|
|
66
|
+
}
|
|
67
|
+
}, 'dsh-zen-remote: remote-session attribute')
|
|
68
|
+
}
|
|
@@ -44,6 +44,31 @@ async function desktopConfigured(): Promise<boolean> {
|
|
|
44
44
|
* marker, so scoping here is what keeps this effect Chat-only. */
|
|
45
45
|
const FLOW = '[data-chat-flow]'
|
|
46
46
|
|
|
47
|
+
/** DSH 0.1.7's official process-group root (ChatGroupSeat). A group nests
|
|
48
|
+
* its OWN `data-chat-flow` layer whose rows BORN_FOLDED would hide by kind,
|
|
49
|
+
* while {@link groupsOf} only walks the outer column — so a folded tool call
|
|
50
|
+
* inside a group would vanish with no chip to recover it. While the page has
|
|
51
|
+
* any official group the whole fold steps aside (rules too: the stylesheet
|
|
52
|
+
* keys its born-folded block on {@link ACTIVE_ATTR}, which the suppress path
|
|
53
|
+
* removes), and returns when the last group leaves the DOM. */
|
|
54
|
+
const OFFICIAL_GROUP = '[data-chat-group-key]'
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* One suppression state transition, given whether an official group is on
|
|
58
|
+
* the page and whether the fold is currently suppressed. `'enter'`: groups
|
|
59
|
+
* appeared — drop every mark and the ACTIVE_ATTR the stylesheet keys on.
|
|
60
|
+
* `'exit'`: the last group left — re-arm and fold again. `'hold'`: nothing
|
|
61
|
+
* to do. Exported pure so the state machine itself is testable off-DOM
|
|
62
|
+
* (test/fold-suppression.test.cjs); {@link scan} applies it.
|
|
63
|
+
*/
|
|
64
|
+
export function foldSuppressionTransition(
|
|
65
|
+
officialGroupPresent: boolean,
|
|
66
|
+
suppressed: boolean,
|
|
67
|
+
): 'enter' | 'exit' | 'hold' {
|
|
68
|
+
if (officialGroupPresent) return suppressed ? 'hold' : 'enter'
|
|
69
|
+
return suppressed ? 'exit' : 'hold'
|
|
70
|
+
}
|
|
71
|
+
|
|
47
72
|
/** Marker + selector of the injected summary row. */
|
|
48
73
|
const CHIP = 'turn-fold'
|
|
49
74
|
const CHIP_SELECTOR = '[data-mobile-nav="turn-fold"]'
|
|
@@ -199,6 +224,10 @@ export function installTurnFold(ctx: ClientContext): void {
|
|
|
199
224
|
const expanded = new Set<string>()
|
|
200
225
|
let observer: MutationObserver | null = null
|
|
201
226
|
let frame = 0
|
|
227
|
+
/** True while an official process group is on the page (see OFFICIAL_GROUP):
|
|
228
|
+
* ACTIVE_ATTR is off and every mark is cleared; the observer stays open so
|
|
229
|
+
* the fold resumes by itself once the last group leaves. */
|
|
230
|
+
let suppressed = false
|
|
202
231
|
|
|
203
232
|
const label = (chip: HTMLElement, count: number, running: boolean): void => {
|
|
204
233
|
const next = running ? t('turnFoldRunning', { count }) : t('turnFold', { count })
|
|
@@ -208,6 +237,20 @@ export function installTurnFold(ctx: ClientContext): void {
|
|
|
208
237
|
const scan = (): void => {
|
|
209
238
|
const flow = document.querySelector(FLOW)
|
|
210
239
|
if (flow === null) return
|
|
240
|
+
const transition = foldSuppressionTransition(
|
|
241
|
+
document.querySelector(OFFICIAL_GROUP) !== null,
|
|
242
|
+
suppressed,
|
|
243
|
+
)
|
|
244
|
+
if (transition === 'enter') {
|
|
245
|
+
suppressed = true
|
|
246
|
+
document.documentElement.removeAttribute(ACTIVE_ATTR)
|
|
247
|
+
clear()
|
|
248
|
+
return
|
|
249
|
+
}
|
|
250
|
+
if (transition === 'exit') {
|
|
251
|
+
suppressed = false
|
|
252
|
+
document.documentElement.setAttribute(ACTIVE_ATTR, '')
|
|
253
|
+
}
|
|
211
254
|
// TurnStatus, ChatView's own running banner — present only while the
|
|
212
255
|
// last turn is still working (lib/client.js:5548).
|
|
213
256
|
const running = flow.querySelector(':scope > [role="status"]') !== null
|
|
@@ -284,6 +327,11 @@ export function installTurnFold(ctx: ClientContext): void {
|
|
|
284
327
|
* characterData — the fold verdict only changes when elements do.
|
|
285
328
|
*/
|
|
286
329
|
const markWholeRows = (records: readonly MutationRecord[]): void => {
|
|
330
|
+
// Suppressed (an official group is on the page): reasoning-only rows in
|
|
331
|
+
// the OUTER column would still get marked here — the synchronous path
|
|
332
|
+
// runs before the next scan flips anything — and a marked row stays
|
|
333
|
+
// hidden with no chip to recover it.
|
|
334
|
+
if (suppressed) return
|
|
287
335
|
const rows = new Set<Element>()
|
|
288
336
|
for (const record of records) {
|
|
289
337
|
const target = record.target
|
|
@@ -305,6 +353,9 @@ export function installTurnFold(ctx: ClientContext): void {
|
|
|
305
353
|
// to bring it back. It also drops rows detached since the batch.
|
|
306
354
|
const parent = row.parentElement
|
|
307
355
|
if (parent === null || !parent.matches(FLOW)) continue
|
|
356
|
+
// A group's inner flow layer also matches FLOW; rows inside an
|
|
357
|
+
// official group are never ours to fold (see OFFICIAL_GROUP).
|
|
358
|
+
if (row.closest(OFFICIAL_GROUP) !== null) continue
|
|
308
359
|
const thinks = [...row.querySelectorAll(THINK)]
|
|
309
360
|
if (thinks.length > 0 && foldsWholeRow(row, thinks)) row.setAttribute(FOLD, '')
|
|
310
361
|
else row.removeAttribute(FOLD)
|
|
@@ -366,6 +417,7 @@ export function installTurnFold(ctx: ClientContext): void {
|
|
|
366
417
|
if (frame !== 0) cancelAnimationFrame(frame)
|
|
367
418
|
frame = 0
|
|
368
419
|
expanded.clear()
|
|
420
|
+
suppressed = false
|
|
369
421
|
clear()
|
|
370
422
|
}
|
|
371
423
|
|
package/src/client/index.tsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { ClientContext, SessionId, WorkspaceId } from './compat/types.ts'
|
|
1
|
+
import type { ClientContext, JobsLike, SessionId, WorkspaceId } from './compat/types.ts'
|
|
2
|
+
import { isDesktopShell } from './compat/desktop.ts'
|
|
2
3
|
import { MobileNavToggle } from './MobileNavToggle.tsx'
|
|
3
4
|
import { MobileNavOverlay } from './MobileNavOverlay.tsx'
|
|
4
5
|
import { MobileDrawerFooter } from './MobileDrawerFooter.tsx'
|
|
@@ -18,13 +19,28 @@ import { installGestures } from './effects/gestures.ts'
|
|
|
18
19
|
import { installTurnFold } from './effects/turn-fold.ts'
|
|
19
20
|
import { installModalBack } from './effects/modal-back.ts'
|
|
20
21
|
import { installModelSheetExtras } from './effects/model-sheet-extras.ts'
|
|
22
|
+
import { installPermissionSheet } from './effects/permission-sheet.ts'
|
|
21
23
|
import { installNativeTriggerOverlay } from './effects/native-trigger-overlay.ts'
|
|
22
24
|
import { installWelcomeNoticeOptOut } from './effects/welcome-notice.ts'
|
|
23
25
|
import { installKeyboardGuard } from './effects/keyboard-guard.ts'
|
|
24
26
|
import { installKeyboardAvoid } from './effects/keyboard-avoid.ts'
|
|
25
27
|
import { SharePreview } from './share/share-preview.tsx'
|
|
26
|
-
import { NS
|
|
28
|
+
import { NS } from './locales.ts'
|
|
27
29
|
import type { MobileNavKey } from './locales.ts'
|
|
30
|
+
import { SettingsSection } from './settings/SettingsSection.tsx'
|
|
31
|
+
import { registerSettingsPage } from './settings/register-settings.ts'
|
|
32
|
+
import { RemoteHeaderIcon } from './RemoteHeaderIcon.tsx'
|
|
33
|
+
import { RemoteShareMenuItem } from './RemoteShareMenu.tsx'
|
|
34
|
+
import { getSharesStore } from '../client-data/shares.ts'
|
|
35
|
+
import { registerRemoteShareUi } from './remote-share-register.ts'
|
|
36
|
+
import { RemoteStatusIcon } from './RemoteStatusIcon.tsx'
|
|
37
|
+
import { RemoteComposerBanner } from './RemoteComposerBanner.tsx'
|
|
38
|
+
import { registerRemoteStatusUi } from './remote-status-register.ts'
|
|
39
|
+
import { mountCurrentSessionReporter } from './current-session-report.ts'
|
|
40
|
+
import { mountRemoteGroupExpansionKeeper } from './remote-group-expansion.ts'
|
|
41
|
+
import { installRemoteApiFetch } from './remote-fetch.ts'
|
|
42
|
+
import { installRemoteSessionGuard } from './effects/remote-session.ts'
|
|
43
|
+
import { installModelGroupSide } from './effects/model-group-side.ts'
|
|
28
44
|
|
|
29
45
|
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
30
46
|
interface LocaleNamespaceMap {
|
|
@@ -37,20 +53,50 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
|
37
53
|
export const inject = ['slots', 'layout', 'locale', 'sessionLogDownload', 'sessions', 'workspaces']
|
|
38
54
|
|
|
39
55
|
/**
|
|
40
|
-
* 0.1.2
|
|
41
|
-
* 0.1.
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* ——0.1.1 装不到。
|
|
56
|
+
* 0.1.2 把新建会话、归档会话与打开会话的界面级操作挪到了 uiWorkspace
|
|
57
|
+
* 服务(0.1.7 删掉了 ctx.sessions.open,这是它唯一的替代入口)。类型用
|
|
58
|
+
* 本地最小接口,不 import 那个 dsh-client-ui-workspace 包——本插件的
|
|
59
|
+
* peerDependencies 不含它,运行时实例由宿主提供,类型自己声明就够。
|
|
45
60
|
*/
|
|
46
61
|
interface UiWorkspaceLike {
|
|
62
|
+
openSession(target: SessionId): void
|
|
47
63
|
startSession(workspaceId?: WorkspaceId): void
|
|
48
64
|
archiveSession(sessionId: SessionId): Promise<void>
|
|
49
65
|
}
|
|
50
66
|
|
|
51
|
-
/**
|
|
52
|
-
|
|
53
|
-
|
|
67
|
+
/**
|
|
68
|
+
* Activity data both session-header entries read (0.1.7 sources — 0.1.5's
|
|
69
|
+
* per-parent subagent snapshot and per-session job snapshot are gone):
|
|
70
|
+
* - the subagent catalog is read straight off the sessions snapshot's
|
|
71
|
+
* explicit-read store (`projectionsBySession[id].values.subagentCatalog`),
|
|
72
|
+
* exactly like the official SubagentHeaderLineage — no refresh is issued
|
|
73
|
+
* from here: every projection refresh call registers another session in
|
|
74
|
+
* the controller's load table, and all of them are re-read on every
|
|
75
|
+
* reconnect (manager handleConnected), so per-open refreshes would grow
|
|
76
|
+
* into a batch of re-reads forever;
|
|
77
|
+
* - job rows come from the `jobs` client service; the inject face mirrors
|
|
78
|
+
* the official JobListAction registration (`hooks.jobs` observable → the
|
|
79
|
+
* renderer hands the component a `useJobs` hook prop). When the service is
|
|
80
|
+
* absent (a build without the job controller) a fixed empty source keeps
|
|
81
|
+
* the prop present, so the component's hook count never varies — the
|
|
82
|
+
* counts simply read zero instead of the pill silently disappearing.
|
|
83
|
+
*/
|
|
84
|
+
const NO_JOBS_SNAPSHOT = Object.freeze({ rows: Object.freeze({}) }) as {
|
|
85
|
+
readonly rows: Readonly<Record<string, readonly never[]>>
|
|
86
|
+
}
|
|
87
|
+
/** Module-level singleton: one stable observable identity across renders. */
|
|
88
|
+
const NO_JOBS_SOURCE = {
|
|
89
|
+
getSnapshot: () => NO_JOBS_SNAPSHOT,
|
|
90
|
+
subscribe: (): (() => void) => () => {},
|
|
91
|
+
}
|
|
92
|
+
const NO_WATCH_ROWS = (): (() => void) => () => {}
|
|
93
|
+
|
|
94
|
+
function activityInject(ctx: ClientContext) {
|
|
95
|
+
const jobs = ctx.get('jobs') as JobsLike | undefined
|
|
96
|
+
return {
|
|
97
|
+
hooks: { jobs: jobs === undefined ? NO_JOBS_SOURCE : jobs.state },
|
|
98
|
+
watchRows: jobs === undefined ? NO_WATCH_ROWS : (id: SessionId) => jobs.watchRows(id),
|
|
99
|
+
}
|
|
54
100
|
}
|
|
55
101
|
|
|
56
102
|
/**
|
|
@@ -60,7 +106,64 @@ interface LegacyWorkspacesLike {
|
|
|
60
106
|
* @param ctx - client root context.
|
|
61
107
|
*/
|
|
62
108
|
export function apply(ctx: ClientContext): void {
|
|
63
|
-
|
|
109
|
+
// 2.0.0 设置页:必须在桌面门之前注册——桌面端 App 里手机外壳整体不生效,
|
|
110
|
+
// 但设置页必须生效(主服务端就是桌面端)。字典、设置页样式与设置表单服务
|
|
111
|
+
// 的按需注入都在 registerSettingsPage 里(那个服务名绝不进顶层的 inject
|
|
112
|
+
// 数组,否则没有该服务的环境整个界面半边都加载不了)。
|
|
113
|
+
registerSettingsPage(ctx, SettingsSection)
|
|
114
|
+
|
|
115
|
+
// T33b 会话共享的三个入口里前两个(标题行远程图标、会话右键菜单项):
|
|
116
|
+
// 同样必须在桌面门之前注册——桌面端 App 就是主服务端,图标和菜单恰好
|
|
117
|
+
// 住在它的标题行与会话菜单里。部件自身按数据降级:shares 路由 404
|
|
118
|
+
// (子客户端部署没有这条路由)时渲染为空;手机外壳下样式表把
|
|
119
|
+
// header.actions 全量隐藏(styles/header.css.ts),图标也就不订阅
|
|
120
|
+
// (subscribeIfNotPhoneShell)——订阅是轮询的燃料,不订阅就不发
|
|
121
|
+
// admin/shares 请求。设置页的共享列表在 SettingsSection 内部,随设置
|
|
122
|
+
// 页已在门之前。
|
|
123
|
+
registerRemoteShareUi(ctx, RemoteHeaderIcon, RemoteShareMenuItem)
|
|
124
|
+
|
|
125
|
+
// T34 子客户端的远程状态两件套(标题行连接图标、composer 横幅):同样在
|
|
126
|
+
// 桌面门之前注册——桌面端 App 也能以 client 角色接入服务端。部件自身按
|
|
127
|
+
// 数据降级:只在虚拟 id 会话渲染、首次 GET 应答前不渲染,所以主服务端
|
|
128
|
+
// 的页面上两者都是空,桌面保持逐像素 no-op;订阅本身也按虚拟 id 门控
|
|
129
|
+
// (remote-status.ts 的 subscribeIfVirtual)——本地会话不建立订阅,
|
|
130
|
+
// store 只在有订阅者时才轮询,主服务端的页面不白发 remote-status 请求。
|
|
131
|
+
registerRemoteStatusUi(ctx, RemoteStatusIcon, RemoteComposerBanner)
|
|
132
|
+
// T62 当前会话信号的上报:拦截层跑在后台 Node 进程里,读不到浏览器的
|
|
133
|
+
// localStorage——由这里(唯一读得到宿主选择存储的地方)把「当前打开的
|
|
134
|
+
// 会话」在值变化时 POST 给后台路由。同样在桌面门之前挂载:client 角色
|
|
135
|
+
// 的桌面端 App 正是远程会话所在;只在探测出的子客户端角色下运行,主
|
|
136
|
+
// 服务端的页面探测一次后就静默。
|
|
137
|
+
mountCurrentSessionReporter(ctx)
|
|
138
|
+
// T64 服务端分组展开状态的记忆与恢复:宿主在工作区列表 ready 时会把不在
|
|
139
|
+
// 列表里的分组展开记录清掉(retainAccountKeys),而本地工作区先 ready、
|
|
140
|
+
// 服务端分组后并入,zr~ 键每次加载都被清掉、分组回到折叠。与上报器共用
|
|
141
|
+
// 挂载点与角色判断(同一个 client-config 探测)、同样的 1 秒节拍(自己的
|
|
142
|
+
// 定时器),逻辑在 remote-group-expansion.ts 自己的文件里。
|
|
143
|
+
mountRemoteGroupExpansionKeeper(ctx)
|
|
144
|
+
// T41b 的两个远程会话部件:fetch 改写(改动 / diff 面板的两条 /api GET)与
|
|
145
|
+
// 远程会话标记(data-zr-remote-session + 隐藏「在服务端机器上打开」类入口
|
|
146
|
+
// 的样式)。同样必须在桌面门之前注册——桌面窗口也可以是配对好的子客户端,
|
|
147
|
+
// 改写与隐藏在它里面一样必要。改写只碰「同源 + 精确两条路径 + zr~ 虚拟
|
|
148
|
+
// 会话 id」的 GET(remote-fetch.ts),宿主角色的桌面端(会话 id 永非虚拟)
|
|
149
|
+
// 逐字节原样放行;标记属性只在虚拟会话打开时出现,卸载时还原。
|
|
150
|
+
ctx.effect(() => installRemoteApiFetch(window), 'dsh-zen-remote: remote api fetch')
|
|
151
|
+
installRemoteSessionGuard(ctx)
|
|
152
|
+
// T74 模型选单分组按会话归属显示:0.2.0 的 MenuGroup 的 aria-labelledby 只剩
|
|
153
|
+
// useId(分组 id 不再出现在标记里),旧的 zr~ 前缀选择器失效——改为 effect 给
|
|
154
|
+
// 分组打 data-zr-group 标(分类依据是虚拟分组名开头的不可见标记,见
|
|
155
|
+
// client-data/model-group-side.ts),CSS 按标隐藏。同样必须在桌面门之前挂载:
|
|
156
|
+
// 桌面端子客户端的模型选单一样要分侧,桌面无手机外壳不影响它。
|
|
157
|
+
installModelGroupSide(ctx)
|
|
158
|
+
|
|
159
|
+
// Desktop gate (DSH 0.1.7): the official Electron shell can be dragged
|
|
160
|
+
// down to ~520px wide, where every width-based gate would flip the phone
|
|
161
|
+
// shell on inside the desktop app. Inside that shell this plugin is a
|
|
162
|
+
// complete no-op — no stylesheet, no effects, no slot entries, which is
|
|
163
|
+
// bit-for-bit "not installed" and also retires the desktop-fold knobs
|
|
164
|
+
// there (they exist for narrow desktop *browsers*, which never carry the
|
|
165
|
+
// dshDesktop bridge). See compat/desktop.ts for the marker's provenance.
|
|
166
|
+
if (isDesktopShell()) return
|
|
64
167
|
|
|
65
168
|
ctx.effect(() => {
|
|
66
169
|
const tag = document.createElement('style')
|
|
@@ -111,6 +214,9 @@ export function apply(ctx: ClientContext): void {
|
|
|
111
214
|
// Third-party composer entries (speed chip, vision toggle) move into the
|
|
112
215
|
// model sheet — the row has no width to spare and both are model settings.
|
|
113
216
|
installModelSheetExtras(ctx)
|
|
217
|
+
// T70: the permission menu portals to body on DSH 0.2.0 — mark it so
|
|
218
|
+
// composer.css section 4 can still make it the bottom sheet.
|
|
219
|
+
installPermissionSheet(ctx)
|
|
114
220
|
|
|
115
221
|
// "内测声明" first-run notice: keep it visible (CSS-hiding it leaked the
|
|
116
222
|
// dialog's #root inert lock — see effects/welcome-notice.ts) and offer a
|
|
@@ -156,6 +262,8 @@ export function apply(ctx: ClientContext): void {
|
|
|
156
262
|
id: 'mobile-header-actions',
|
|
157
263
|
order: 0,
|
|
158
264
|
locale: NS,
|
|
265
|
+
// Activity-pill data (subagent catalog + job roster), see activityInject.
|
|
266
|
+
inject: () => activityInject(ctx),
|
|
159
267
|
}, MobileHeaderActions))
|
|
160
268
|
|
|
161
269
|
// Session-info entry (placeholder — S4 owns the sheet) + workbench entry
|
|
@@ -180,14 +288,29 @@ export function apply(ctx: ClientContext): void {
|
|
|
180
288
|
locale: NS,
|
|
181
289
|
// The factory's own sessionId param is unused: every function below
|
|
182
290
|
// takes its own session id explicitly (they're generic action bindings
|
|
183
|
-
// reused verbatim, not closures over one particular session).
|
|
291
|
+
// reused verbatim, not closures over one particular session). The
|
|
292
|
+
// spread adds the activity-pill data (subagent catalog + job roster for
|
|
293
|
+
// the info-sheet badges), see activityInject.
|
|
184
294
|
inject: (_sessionId: SessionId) => ({
|
|
295
|
+
...activityInject(ctx),
|
|
296
|
+
// T67: the SAME page-wide shares store the header icon and the menu
|
|
297
|
+
// item read — the row subscribes only while the sheet is open, so no
|
|
298
|
+
// second poll loop and no phone-shell polling either.
|
|
299
|
+
shares: getSharesStore(),
|
|
185
300
|
forkSession: (id: SessionId) => ctx.sessions.fork({ sessionId: id }),
|
|
186
|
-
|
|
301
|
+
// 0.1.7: ctx.sessions.open is gone; uiWorkspace.openSession is the one
|
|
302
|
+
// navigation entry (also what the official subagent catalog uses).
|
|
303
|
+
// Lazy lookup like every other uiWorkspace binding here: the callback
|
|
304
|
+
// runs on a user tap, by which time the service is registered.
|
|
305
|
+
openSession: (id: SessionId) => {
|
|
306
|
+
const ui = ctx.get('uiWorkspace') as UiWorkspaceLike | undefined
|
|
307
|
+
if (ui === undefined) console.warn('[dsh-zen-remote] uiWorkspace service unavailable; cannot open session', id)
|
|
308
|
+
else ui.openSession(id)
|
|
309
|
+
},
|
|
187
310
|
renameSession: (id: SessionId, title: string) => ctx.sessions.binding(id)?.session.rename(title),
|
|
188
311
|
// 0.1.2 的界面级归档在 uiWorkspace(顺带清当前选中);懒查——回调是用户
|
|
189
|
-
// 点击才跑,那时服务必已注册。本插件的 inject 不包含 uiWorkspace
|
|
190
|
-
//
|
|
312
|
+
// 点击才跑,那时服务必已注册。本插件的 inject 不包含 uiWorkspace,
|
|
313
|
+
// 所以启动时不能查一次就用:可能早于它注册。
|
|
191
314
|
archiveSession: (id: SessionId) => {
|
|
192
315
|
const ui = ctx.get('uiWorkspace') as UiWorkspaceLike | undefined
|
|
193
316
|
return ui === undefined ? ctx.workspaces.archiveSession(id) : ui.archiveSession(id)
|
|
@@ -242,16 +365,18 @@ export function apply(ctx: ClientContext): void {
|
|
|
242
365
|
locale: NS,
|
|
243
366
|
store: nav,
|
|
244
367
|
inject: () => ({
|
|
245
|
-
openSession
|
|
368
|
+
// Same uiWorkspace.openSession binding as the session-info sheet above.
|
|
369
|
+
openSession: (id: SessionId) => {
|
|
370
|
+
const ui = ctx.get('uiWorkspace') as UiWorkspaceLike | undefined
|
|
371
|
+
if (ui === undefined) console.warn('[dsh-zen-remote] uiWorkspace service unavailable; cannot open session', id)
|
|
372
|
+
else ui.openSession(id)
|
|
373
|
+
},
|
|
246
374
|
// uiWorkspace 懒查:回调是用户点击才跑,服务那时必已注册(inject 不含
|
|
247
|
-
// uiWorkspace,启动时可能先于它注册,不能启动查一次就用)。
|
|
248
|
-
// undefined,退回 ctx.workspaces.startSession——0.1.1 专用分支,0.1.2 的
|
|
249
|
-
// 类型里该方法已不存在,故对类型做 LegacyWorkspacesLike cast,运行时走不到。
|
|
375
|
+
// uiWorkspace,启动时可能先于它注册,不能启动查一次就用)。
|
|
250
376
|
startSession: (workspaceId?: WorkspaceId) => {
|
|
251
377
|
const ui = ctx.get('uiWorkspace') as UiWorkspaceLike | undefined
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
: ui.startSession(workspaceId)
|
|
378
|
+
if (ui === undefined) console.warn('[dsh-zen-remote] uiWorkspace service unavailable; cannot start a session')
|
|
379
|
+
else ui.startSession(workspaceId)
|
|
255
380
|
},
|
|
256
381
|
// S5 session-log chip — the same service call MobileDrawerFooter uses.
|
|
257
382
|
downloadSessionLog: (id: SessionId) => ctx.sessionLogDownload.download(id),
|
|
@@ -315,22 +440,19 @@ import type {} from '@deepseek-ai/dsh-client-ui-conversation/client'
|
|
|
315
440
|
import type {} from '@deepseek-ai/dsh-client-ui-sidebar/client'
|
|
316
441
|
import type {} from '@deepseek-ai/dsh-client-locale/client'
|
|
317
442
|
import type {} from '@deepseek-ai/dsh-session-log-export/client'
|
|
318
|
-
//
|
|
319
|
-
//
|
|
320
|
-
// sessionId/useSession/useProjection, and declares the `ctx.uiSession`
|
|
321
|
-
// service.
|
|
322
|
-
// build output carries no trace of it and the runtime is unaffected.
|
|
443
|
+
// ui-session: augments GlobalStandardProps with useSessions /
|
|
444
|
+
// useSessionPendingInteraction / useSessionStatus and SessionStandardProps
|
|
445
|
+
// with sessionId/useSession/useProjection, and declares the `ctx.uiSession`
|
|
446
|
+
// service.
|
|
323
447
|
import type {} from '@deepseek-ai/dsh-client-ui-session/client'
|
|
324
|
-
//
|
|
325
|
-
// `ctx.
|
|
326
|
-
//
|
|
327
|
-
// runtime, so a 0.1.1 installation is unaffected.
|
|
448
|
+
// Controller packages: their `/client` type entries merge `ctx.sessions` /
|
|
449
|
+
// `ctx.workspaces` onto the cordis Context. Type-only — nothing to load at
|
|
450
|
+
// runtime.
|
|
328
451
|
import type {} from '@deepseek-ai/dsh-api-session-controller/client'
|
|
329
452
|
import type {} from '@deepseek-ai/dsh-api-workspace-controller/client'
|
|
330
|
-
//
|
|
331
|
-
//
|
|
332
|
-
//
|
|
333
|
-
//
|
|
334
|
-
// dsh-client-runtime bundle that no longer exists.
|
|
453
|
+
// Type-only: the renderer declares `ctx.slots` on the cordis Context (the
|
|
454
|
+
// service this plugin registers every slot through) and `dsh-subagent/client`
|
|
455
|
+
// types the subagent catalog projection entries the session header reads
|
|
456
|
+
// (projectionsBySession[].values.subagentCatalog on 0.1.7).
|
|
335
457
|
import type {} from '@deepseek-ai/dsh-client-ui-renderer/client'
|
|
336
458
|
import type {} from '@deepseek-ai/dsh-subagent/client'
|