@dsh-electron/dsh-plugin-multi-root-workspace 0.1.10 → 0.1.12
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/CHANGELOG.en.md +25 -1
- package/CHANGELOG.md +25 -1
- package/README.en.md +11 -9
- package/README.md +11 -9
- package/cordis.patch.yml +3 -2
- package/lib/auto-review.js +1 -1
- package/lib/client.js +252 -129
- package/lib/client.js.map +1 -1
- package/lib/{command-BGdz7VBH.js → command-CmLekG1M.js} +3 -2
- package/lib/command.js +1 -1
- package/lib/{compat-D-MljsPV.js → compat-Bg2HFVMX.js} +6 -1
- package/lib/compat.js +1 -1
- package/lib/{sandbox-CXk5h-ct.js → dialects-Dr4rDR-H.js} +6 -264
- package/lib/{fs-BTXySwSG.js → fs-CfzJq2wd.js} +23 -8
- package/lib/fs.js +1 -1
- package/lib/index.js +7 -6
- package/lib/{instructions-MePoRigy.js → instructions-KsUwUFE0.js} +53 -37
- package/lib/instructions.js +1 -1
- package/lib/lsp.js +19 -16
- package/lib/native-win32-2waCRkPI.js +312 -0
- package/lib/process-fence.js +62 -12
- package/lib/protocol-4zdwV7u4.js +641 -0
- package/lib/{registry-KSsmU393.js → registry-YTUOjmVu.js} +368 -25
- package/lib/registry-operation-C2ZV3Jhw.js +116 -0
- package/lib/registry.js +3 -2
- package/lib/{review-context-AH-SXHmj.js → review-context-NDpKeMCc.js} +1 -1
- package/lib/review-context.js +1 -1
- package/lib/sandbox-C-vCJCp3.js +278 -0
- package/lib/sandbox.js +1 -1
- package/lib/scope.js +70 -10
- package/lib/skills.js +48 -29
- package/lib/types/authority/native-win32.d.ts +7 -0
- package/lib/types/authority/native.d.ts +23 -0
- package/lib/types/authority/protocol.d.ts +70 -0
- package/lib/types/client/open-in-app/OpenInAppAction.d.ts +5 -3
- package/lib/types/client/open-in-app/OpenTargetButton.d.ts +16 -10
- package/lib/types/client/open-in-app/adapter.d.ts +5 -1
- package/lib/types/client/open-in-app/controller.d.ts +5 -2
- package/lib/types/client/open-in-app/device.d.ts +11 -0
- package/lib/types/client/open-in-app/locales.d.ts +1 -1
- package/lib/types/client/open-in-app/open-path.d.ts +6 -5
- package/lib/types/client/open-in-app/open-toast.d.ts +39 -0
- package/lib/types/client/open-in-app/outcome.d.ts +23 -0
- package/lib/types/client/styles.d.ts +1 -1
- package/lib/types/compat/authority-storage.d.ts +6 -0
- package/lib/types/compat/dsh-version.d.ts +4 -4
- package/lib/types/compat/open-in-app-device.d.ts +23 -0
- package/lib/types/compat/process-entrypoints.d.ts +2 -0
- package/lib/types/compat/process-profile.d.ts +3 -0
- package/lib/types/compat/sandbox-confine.d.ts +2 -0
- package/lib/types/fs.d.ts +4 -1
- package/lib/types/instructions.d.ts +0 -47
- package/lib/types/registry-operation.d.ts +31 -0
- package/lib/types/registry.d.ts +47 -6
- package/lib/types/scope.d.ts +12 -0
- package/lib/types/skills/catalog.d.ts +3 -0
- package/lib/types/skills/pool.d.ts +1 -1
- package/lib/workspace-files.js +63 -7
- package/package.json +87 -80
- package/lib/types/client/open-in-app/open-failure-toast.d.ts +0 -11
|
@@ -11,4 +11,4 @@
|
|
|
11
11
|
* @module @dsh-electron/dsh-plugin-multi-root-workspace/client/styles
|
|
12
12
|
*/
|
|
13
13
|
/** The stylesheet text; every class is prefixed `mrfw-` to stay collision-free. */
|
|
14
|
-
export declare const STYLES = "\n/* Sidebar footer row, mirroring the host ui-settings-general trigger: the row\n * wrapper overhangs each side by 2px (width calc(100% + 4px) against the\n * shell's 12px inline padding) so the icon's ink line lands at 18px from the\n * column edge \u2014 exactly where the Settings gear below sits. */\n.mrfw-triggerRow {\n flex: none;\n display: flex;\n align-items: center;\n gap: 8px;\n width: calc(100% + 4px);\n margin: 4px -2px;\n}\n\n.mrfw-triggerRow.mrfw-railRow {\n width: 36px;\n margin: 8px 0 10px;\n}\n\n.mrfw-trigger {\n box-sizing: border-box;\n flex: 1;\n min-width: 0;\n display: flex;\n align-items: center;\n gap: 8px;\n width: auto;\n height: 42px;\n margin: 0;\n padding: 0 10px 0 8px;\n border: none;\n border-radius: 12px;\n background: transparent;\n color: var(--dsw-alias-label-primary);\n font-family: inherit;\n font-size: 14px;\n line-height: 22px;\n text-align: left;\n cursor: pointer;\n overflow: hidden;\n transition: background-color var(--ds-transition-duration-fast) var(--ds-ease-in-out);\n}\n\n.mrfw-trigger:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.mrfw-triggerIcon {\n flex: none;\n display: inline-flex;\n}\n\n.mrfw-triggerLabel {\n overflow: hidden;\n white-space: nowrap;\n}\n\n.mrfw-triggerRail {\n flex: none;\n width: 36px;\n height: 36px;\n margin: 0;\n padding: 0;\n gap: 0;\n border-radius: 50%;\n justify-content: center;\n}\n\n/* Icon-only launcher inside the upstream Files tab header. Upstream's\n FilesBody renders .header as one flat flex row \u2014 PathLabel, then the\n reload button, then the files.actions slot occupants \u2014 and each of those\n children carries a stable data-files-* attribute on the flex item itself\n (data-files-root on the pane, data-files-path, data-files-reload).\n A registration order sorts only INSIDE the actions slot and can never\n overtake the reload button, so the launcher is moved with the flex order\n property instead: this wrapper is the slot's root element and therefore a\n direct flex item of .header, and order: -1 puts it ahead of every\n order-0 sibling while the :has() rule below keeps the path first. */\n.mrfw-fileLauncher {\n order: -1;\n flex: none;\n display: flex;\n align-items: center;\n}\n\n/* Pin the upstream path label ahead of the launcher \u2014 but only while this\n plugin is in the row: the :has() guard keeps a rule that names an\n upstream element inert whenever the launcher is absent. The\n :not([data-files-entry]) qualifier is load-bearing, not decorative:\n upstream puts data-files-path on EVERY tree row too (the row's own\n absolute path), so an unqualified selector would match the whole tree,\n not just the header's PathLabel. Both selectors depend on upstream's DOM\n shape, which is not part of the slot contract; if it changes, the rules\n simply stop matching and the launcher falls back to its in-slot position \u2014\n a silent degradation, never a broken header. */\n[data-files-root]:has(.mrfw-fileLauncher) [data-files-path]:not([data-files-entry]) {\n order: -2;\n}\n\n/* The root switcher is the launcher's grown-up form on runtimes whose\n files.actions owner carries absolutePath. It takes the path label's flex\n position and stretch: order: -1 ahead of every order-0 sibling, and a\n growing min-width:0 item so both the trigger label and the PathLabel\n inside it can ellipsize instead of pushing the header controls out. */\n.mrfw-rootSwitcher {\n order: -1;\n flex: 1 1 auto;\n min-width: 0;\n display: flex;\n align-items: center;\n /* Same 4px the upstream .header puts between its own flex items, so the\n takeover controls (reload, open-in-app fork) sit at the same offsets the\n hidden native ones did \u2014 no drift between primary and additional state. */\n gap: 4px;\n}\n\n/* While the switcher exists it hides the upstream path label \u2014 the\n :has() guard again keeps the upstream-naming rule inert unless this\n plugin is actually in the row. The switcher draws its own PathLabel of\n the selected root next to the trigger (className mrfw-switcherPath, no\n data-files-* attributes), so the header keeps a path in the same spot.\n :not([data-files-entry]) is what confines this rule to the header:\n every tree row carries data-files-path with its own path as the value,\n and only the PathLabel carries it bare. Without the qualifier this\n display:none would blank the entire native tree. */\n[data-files-root]:has(.mrfw-rootSwitcher) [data-files-path]:not([data-files-entry]) {\n display: none;\n}\n\n/* While the switcher names an additional root, the native tree body and its\n reload hide: the reload refreshes the very tree that is no longer\n visible, and the plugin's forked tree is portaled in as the body's sibling.\n The header still offers a reload \u2014 the switcher's own .mrfw-switcherReload\n calls the forked face's refresh. The native tree keeps watching in the\n background, so switching back to the primary root restores it instantly. */\n[data-files-root]:has(.mrfw-rootSwitcher[data-root-kind=\"additional\"]) [data-files-body],\n[data-files-root]:has(.mrfw-rootSwitcher[data-root-kind=\"additional\"]) [data-files-reload] {\n display: none;\n}\n\n/* While the switcher names an additional root, the native open-in-app split\n button hides too: its slot props carry the pane's primary root, so it\n would always open the primary root no matter what the switcher selected.\n The header gets the plugin's own fork of that same control instead (the\n open-in-app directory), wired to the selected root.\n\n The slot's render site is wrapped in the renderer's own anchor \u2014\n <div data-slot=\"sidebar.right.tab.files.actions\" style=\"display:contents\">\n \u2014 so the header's flex items inside it are the occupants themselves, and\n the native item is the ui-primitives Menu anchor span wrapping the marked\n .split. The first selector hides that occupant item through the data-slot\n anchor: hiding only the inner control would leave the empty anchor span\n as a zero-width flex item whose header gap pushes the fork 4px left of\n the native position. The wrapper itself can never be the target \u2014 its\n inline display:contents beats any stylesheet rule, and hiding it would\n take the switcher with it. The second selector is the fallback for an\n upstream that moves the attribute off the item. The fork survives both:\n the only data-open-target inside .mrfw-rootSwitcher is its own\n .mrfw-openSplit, which :not() excludes. */\n[data-files-root]:has(.mrfw-rootSwitcher[data-root-kind=\"additional\"]) [data-slot=\"sidebar.right.tab.files.actions\"] > *:has([data-open-target=\"directory\"]:not(.mrfw-openSplit)),\n[data-files-root]:has(.mrfw-rootSwitcher[data-root-kind=\"additional\"]) [data-open-target=\"directory\"]:not(.mrfw-openSplit) {\n display: none;\n}\n\n/* The trigger borrows the path label's look: quiet text with a folder\n glyph, the hover of the neighboring header buttons, and the overflow\n behavior PathLabel itself uses in a narrow header. It may shrink but\n never grows \u2014 the PathLabel after it owns the free header space. */\n.mrfw-rootSwitchBtn {\n flex: 0 1 auto;\n min-width: 0;\n display: inline-flex;\n align-items: center;\n gap: 4px;\n height: 28px;\n margin: 0;\n padding: 0 4px;\n border: none;\n border-radius: 8px;\n background: transparent;\n color: var(--dsw-alias-label-primary);\n font-family: inherit;\n font-size: inherit;\n line-height: 20px;\n text-align: left;\n cursor: pointer;\n transition: background-color var(--ds-transition-duration-fast) var(--ds-ease-in-out);\n}\n\n.mrfw-rootSwitchBtn:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.mrfw-rootSwitchBtn .mrfw-triggerIcon {\n color: var(--dsw-alias-label-secondary);\n}\n\n.mrfw-rootSwitchLabel {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.mrfw-rootSwitchChevron {\n margin-left: auto;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* The selected root's full path sits between the trigger and the reload \u2014\n the very PathLabel upstream draws there, so it brings its own grow,\n left-clip and hover-title behavior; only the gutters are ours. The right\n margin plus the container gap reproduces upstream's .path margin-right\n 12px + header gap 4px; the left gutter comes from the same container gap. */\n.mrfw-switcherPath {\n margin: 0 12px 0 0;\n}\n\n/* The takeover's own reload, standing exactly where the hidden native one\n did: the upstream .tool recipe verbatim (28px hit area, 15px glyph,\n secondary voice, hover fills). It calls the forked face's refresh \u2014 an\n in-place re-list, not a remount. */\n.mrfw-switcherReload {\n flex: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n padding: 6px;\n border: none;\n border-radius: var(--dsw-radius-sm);\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n line-height: 1;\n cursor: pointer;\n}\n\n.mrfw-switcherReload:hover {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n/* \u2500\u2500 files-tree fork (verbatim rules, renamed classes) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Copied from upstream's FilesBody.module.css (ui-sidebar-files @\n 0.2.1-alpha.2) with local names mapped to mrfw-ft* so the rules cannot\n collide with host DOM \u2014 and so the fork's own body (which must NOT carry\n data-files-body) stays visible while the rules above hide the native one.\n The header-only rules (root/header/path/tool/status/statusLine/titleIcon)\n belong to the upstream shell this fork drops and are not mapped. Fork sync\n re-diffs the upstream file and re-applies the rename \u2014 see\n src/client/files-tree/FORK.md. */\n\n/* The pane's own .root already supplies the flex column, color, and font;\n the fork's body takes the same stretch item the hidden native body held,\n including the workspace list's 2px scrollbar offset and stable gutter. */\n.mrfw-ftBody {\n flex: 1 1 auto;\n min-height: 0;\n margin-right: 2px;\n padding: 8px 0 8px 8px;\n overflow: auto;\n scrollbar-gutter: stable;\n}\n\n.mrfw-ftBody::-webkit-scrollbar-track {\n margin: 2px;\n}\n\n.mrfw-ftLevel {\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.mrfw-ftLevel .mrfw-ftLevel {\n padding-left: 18px;\n}\n\n.mrfw-ftItem {\n margin: 0;\n padding: 0;\n}\n\n/* Rows abut, so the hover fill is the whole row; the tree's air is this\n inset, not a gap between rows. */\n.mrfw-ftRow {\n display: flex;\n gap: 6px;\n align-items: center;\n width: 100%;\n min-width: 0;\n padding: 5px 10px;\n color: inherit;\n font: inherit;\n text-align: left;\n background: transparent;\n border: 0;\n border-radius: var(--dsw-radius-md);\n cursor: pointer;\n}\n\n.mrfw-ftRow:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n/* The folder glyph rides the tertiary ink, as the workspace tree's does. */\n.mrfw-ftIcon {\n flex: 0 0 auto;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* The type's coloured sheet at the folder glyph's 16px; its colour is its own. */\n.mrfw-ftFileIcon {\n flex: 0 0 auto;\n}\n\n.mrfw-ftName {\n min-width: 0;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n/* Neither a file nor a directory: shown so the directory is reported whole,\n greyed so nobody tries to open it. */\n.mrfw-ftOther {\n color: var(--dsw-alias-label-tertiary);\n cursor: default;\n}\n\n.mrfw-ftOther:hover {\n background: transparent;\n}\n\n.mrfw-ftNote {\n margin: 0;\n padding: 3px 10px;\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n}\n\n/* \u2500\u2500 open-in-app fork (verbatim rules, renamed classes) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Copied from upstream's OpenTargetButton.module.css\n (dsh-client-ui-open-in-app @ 0.2.1-alpha.2) with local names mapped to\n mrfw-open* so the rules cannot collide with host DOM. Fork sync re-diffs\n the upstream file and re-applies the rename \u2014 see\n src/client/open-in-app/FORK.md. */\n\n/* Sized to sit in the document header's 24px control row beside its round\n icon buttons: a hairline pill with a 12px label and a narrow chevron half. */\n.mrfw-openMenuAnchor {\n display: inline-flex;\n flex: none;\n align-self: center;\n}\n\n.mrfw-openSplit {\n display: inline-flex;\n align-items: stretch;\n box-sizing: border-box;\n height: 24px;\n overflow: hidden;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: var(--dsw-radius-sm);\n font-family: var(--dsw-font-family);\n}\n\n.mrfw-openMain,\n.mrfw-openChevron {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: 0;\n background: none;\n color: var(--dsw-alias-label-primary);\n font-size: 11px;\n line-height: 16px;\n white-space: nowrap;\n cursor: pointer;\n}\n\n.mrfw-openMain {\n gap: 4px;\n padding: 3px 5px;\n}\n\n.mrfw-openChevron {\n padding: 3px 4px 3px 3px;\n border-left: 0.5px solid var(--dsw-alias-border-l4);\n color: var(--dsw-alias-label-secondary);\n}\n\n.mrfw-openMain:hover:not(:disabled),\n.mrfw-openMain:focus-visible,\n.mrfw-openChevron:hover:not(:disabled),\n.mrfw-openChevron:focus-visible {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n/* Pending disables clicks without a restyle; only hover feedback pauses. */\n.mrfw-openMain:disabled,\n.mrfw-openChevron:disabled {\n cursor: default;\n}\n\n.mrfw-openAppIcon {\n object-fit: contain;\n}\n\n/* The unavailable preview emphasizes the same actions with a labeled button. */\n.mrfw-openSplit[data-size='large'] {\n height: 36px;\n border-radius: var(--dsw-radius-md);\n border-color: var(--dsw-alias-border-l2);\n}\n\n.mrfw-openSplit[data-size='large'] .mrfw-openMain {\n gap: 6px;\n padding: 6px 14px;\n font-size: 14px;\n}\n\n.mrfw-openSplit[data-size='large'] .mrfw-openChevron {\n padding: 6px 10px;\n}\n\n.mrfw-openSkeleton { display: inline-block; flex: none; border-radius: var(--dsw-radius-xs); background: var(--dsw-alias-interactive-bg-hover); }\n\n.mrfw-overlay {\n position: fixed;\n inset: 0;\n z-index: 1000;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 24px;\n}\n\n.mrfw-mask {\n position: absolute;\n inset: 0;\n background: var(--dsw-alias-bg-mask-1);\n backdrop-filter: var(--dsw-mask-blur);\n}\n\n.mrfw-dialog {\n position: relative;\n z-index: 1;\n display: flex;\n flex-direction: column;\n width: min(640px, 100%);\n max-height: 100%;\n overflow: hidden;\n border: none;\n border-radius: 24px;\n background: var(--dsw-alias-bg-layer-2);\n box-shadow: var(--dsw-elevation-prominent);\n color: var(--dsw-alias-label-primary);\n font-family: inherit;\n outline: none;\n}\n\n.mrfw-header {\n flex: none;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 22px 14px 12px 24px;\n}\n\n.mrfw-title {\n margin: 0;\n font-size: 16px;\n line-height: 24px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n.mrfw-close {\n flex: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n border: none;\n border-radius: 8px;\n background: transparent;\n cursor: pointer;\n color: var(--dsw-alias-label-secondary);\n transition: background-color var(--ds-transition-duration-fast) var(--ds-ease-in-out);\n}\n\n.mrfw-close:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.mrfw-body {\n display: flex;\n flex-direction: column;\n min-height: 0;\n overflow-y: auto;\n padding: 0 24px 24px;\n}\n\n.mrfw-alert {\n margin: 0 0 8px;\n font-size: 14px;\n line-height: 22px;\n color: var(--dsw-alias-state-error-primary);\n}\n\n.mrfw-section {\n display: flex;\n flex-direction: column;\n}\n\n.mrfw-section + .mrfw-section {\n margin-top: 8px;\n}\n\n.mrfw-sectionTitleRow {\n display: flex;\n align-items: baseline;\n gap: 8px;\n padding: 8px 0 4px;\n}\n\n.mrfw-sectionTitle {\n font-size: 14px;\n line-height: 22px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n.mrfw-sectionNote {\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.mrfw-row {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n padding: 12px 0;\n border-bottom: 0.5px solid var(--dsw-alias-border-l2);\n}\n\n.mrfw-rowBare {\n border-bottom: none;\n}\n\n/* A root entry's two lines: the display name on top, the absolute path under\n it in the tertiary voice (the workspace rows recipe of the host sidebar). */\n/* Match upstream General Settings' DeveloperToolsRow layout. */\n.mrfw-skillsSettings {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 24px;\n padding: 16px 0;\n border-bottom: 0.5px solid var(--dsw-alias-border-l2);\n}\n.mrfw-skillsSettingsText { min-width: 0; }\n.mrfw-skillsSettingsTitle { font-size: 14px; line-height: 20px; }\n.mrfw-skillsSettingsDescription {\n margin-top: 4px;\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n line-height: 18px;\n}\n.mrfw-rootText {\n display: flex;\n flex: 1 1 240px;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n}\n\n/* Keep status tags and permission glyphs visible beside the alias, wrapping\n onto another line when the dialog is narrow. */\n.mrfw-rootName {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 6px;\n min-width: 0;\n font-size: 14px;\n line-height: 20px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n.mrfw-rootNameText {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.mrfw-rootPath {\n font-family: var(--ds-font-family-code);\n font-size: 12px;\n line-height: 18px;\n word-break: break-all;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* The path doubles as the copy control: an unstyled button that keeps the\n text recipe and makes the whole string clickable \u2014 the whole region, not\n just the icon, copies on click. */\n.mrfw-pathCopy {\n display: block;\n width: 100%;\n padding: 0;\n border: 0;\n background: transparent;\n text-align: left;\n cursor: pointer;\n}\n\n/* The trailing icon stays in flow but invisible until the path is hovered\n or focused: appearing never re-wraps the text, and the icon inherits the\n path's own tertiary ink. */\n.mrfw-pathCopyIcon {\n display: inline-flex;\n margin-left: 4px;\n vertical-align: -2px;\n opacity: 0;\n transition: opacity var(--ds-transition-duration-fast) var(--ds-ease-in-out);\n}\n\n.mrfw-pathCopy:hover .mrfw-pathCopyIcon,\n.mrfw-pathCopy:focus-visible .mrfw-pathCopyIcon {\n opacity: 1;\n}\n\n.mrfw-note {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.mrfw-stateWarn {\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n.mrfw-btn {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n gap: 4px;\n height: 28px;\n padding: 0 10px;\n border: none;\n border-radius: 14px;\n background: transparent;\n color: var(--dsw-alias-label-primary);\n font-family: inherit;\n font-size: 12px;\n line-height: 18px;\n cursor: pointer;\n transition: background-color var(--ds-transition-duration-fast) var(--ds-ease-in-out);\n}\n\n.mrfw-btn[aria-busy='true'] {\n cursor: wait;\n}\n\n.mrfw-btn:disabled {\n cursor: not-allowed;\n opacity: 0.4;\n}\n\n.mrfw-btnGhost:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.mrfw-btnGhost:active:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-active);\n}\n\n/* The solid destructive action of the capsule family: the upgrade confirm's\n red button. state-error-primary is the error ink upstream also uses as a\n fill (StateDot); the hover mixes in the host's primary label token to\n follow its theme and contrast choices. Neither color is a guessed token:\n an undefined var() would fall back\n to transparent and white-out the whole button. */\n.mrfw-btnDanger {\n background: var(--dsw-alias-state-error-primary);\n color: var(--dsw-alias-label-primary-foreground);\n}\n\n.mrfw-btnDanger:hover:not(:disabled) {\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 85%, var(--dsw-alias-label-primary));\n}\n\n.mrfw-btnOutline {\n border: 0.5px solid var(--dsw-alias-border-l3);\n background: transparent;\n}\n\n.mrfw-btnOutline:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.mrfw-btnPrimary {\n background: var(--dsw-alias-button-primary-fill);\n color: var(--dsw-alias-label-primary-foreground);\n}\n\n.mrfw-btnPrimary:hover:not(:disabled) {\n background: var(--dsw-alias-button-primary-hover);\n}\n\n/* One icon-only action: a square of the capsule family, meaning in title.\n Declared after the .mrfw-btn family so its padding wins the cascade tie:\n the capsule's 0 10px padding would otherwise squeeze the icon glyph. */\n.mrfw-iconBtn {\n width: 28px;\n padding: 0;\n border-radius: 8px;\n}\n\n.mrfw-input {\n flex: 1 1 220px;\n min-width: 0;\n height: 32px;\n padding: 0 8px;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n font-family: inherit;\n font-size: 14px;\n line-height: 22px;\n outline: none;\n transition: border-color var(--ds-transition-duration-fast) var(--ds-ease-in-out);\n}\n\n.mrfw-input:focus {\n border-color: var(--dsw-alias-brand-primary);\n}\n\n.mrfw-input::placeholder {\n color: var(--dsw-alias-label-dimmed);\n}\n\n.mrfw-inputInline {\n flex: 1 1 auto;\n}\n\n.mrfw-accessSelect {\n flex: 0 0 auto;\n}\n\n/* The access dropdown's capsule trigger: the Menu anchor wrapper is the flex\n item, the button inside keeps the control row's 32px capsule recipe. */\n.mrfw-accessTrigger {\n padding: 0 12px;\n}\n\n.mrfw-cwdTag {\n flex: none;\n padding: 0 6px;\n border-radius: 4px;\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-secondary);\n font-size: 11px;\n line-height: 18px;\n}\n\n.mrfw-accessBadge {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n}\n\n.mrfw-accessBadge {\n flex: none;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.mrfw-aliasEditor {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n flex-basis: 100%;\n}\n\n.mrfw-actions {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n margin-top: 16px;\n padding-top: 16px;\n border-top: 0.5px solid var(--dsw-alias-border-l2);\n}\n\n/* One control row, one height: the capsule buttons match the 32px input. */\n.mrfw-actions .mrfw-btn {\n height: 32px;\n border-radius: 16px;\n}\n\n.mrfw-actions .mrfw-iconBtn {\n width: 32px;\n}\n";
|
|
14
|
+
export declare const STYLES = "\n/* Sidebar footer row, mirroring the host ui-settings-general trigger: the row\n * wrapper overhangs each side by 2px (width calc(100% + 4px) against the\n * shell's 12px inline padding) so the icon's ink line lands at 18px from the\n * column edge \u2014 exactly where the Settings gear below sits. */\n.mrfw-triggerRow {\n flex: none;\n display: flex;\n align-items: center;\n gap: 8px;\n width: calc(100% + 4px);\n margin: 4px -2px;\n}\n\n.mrfw-triggerRow.mrfw-railRow {\n width: 36px;\n margin: 8px 0 10px;\n}\n\n.mrfw-trigger {\n box-sizing: border-box;\n flex: 1;\n min-width: 0;\n display: flex;\n align-items: center;\n gap: 8px;\n width: auto;\n height: 42px;\n margin: 0;\n padding: 0 10px 0 8px;\n border: none;\n border-radius: 12px;\n background: transparent;\n color: var(--dsw-alias-label-primary);\n font-family: inherit;\n font-size: 14px;\n line-height: 22px;\n text-align: left;\n cursor: pointer;\n overflow: hidden;\n transition: background-color var(--ds-transition-duration-fast) var(--ds-ease-in-out);\n}\n\n.mrfw-trigger:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.mrfw-triggerIcon {\n flex: none;\n display: inline-flex;\n}\n\n.mrfw-triggerLabel {\n overflow: hidden;\n white-space: nowrap;\n}\n\n.mrfw-triggerRail {\n flex: none;\n width: 36px;\n height: 36px;\n margin: 0;\n padding: 0;\n gap: 0;\n border-radius: 50%;\n justify-content: center;\n}\n\n/* Icon-only launcher inside the upstream Files tab header. Upstream's\n FilesBody renders .header as one flat flex row \u2014 PathLabel, then the\n reload button, then the files.actions slot occupants \u2014 and each of those\n children carries a stable data-files-* attribute on the flex item itself\n (data-files-root on the pane, data-files-path, data-files-reload).\n A registration order sorts only INSIDE the actions slot and can never\n overtake the reload button, so the launcher is moved with the flex order\n property instead: this wrapper is the slot's root element and therefore a\n direct flex item of .header, and order: -1 puts it ahead of every\n order-0 sibling while the :has() rule below keeps the path first. */\n.mrfw-fileLauncher {\n order: -1;\n flex: none;\n display: flex;\n align-items: center;\n}\n\n/* Pin the upstream path label ahead of the launcher \u2014 but only while this\n plugin is in the row: the :has() guard keeps a rule that names an\n upstream element inert whenever the launcher is absent. The\n :not([data-files-entry]) qualifier is load-bearing, not decorative:\n upstream puts data-files-path on EVERY tree row too (the row's own\n absolute path), so an unqualified selector would match the whole tree,\n not just the header's PathLabel. Both selectors depend on upstream's DOM\n shape, which is not part of the slot contract; if it changes, the rules\n simply stop matching and the launcher falls back to its in-slot position \u2014\n a silent degradation, never a broken header. */\n[data-files-root]:has(.mrfw-fileLauncher) [data-files-path]:not([data-files-entry]) {\n order: -2;\n}\n\n/* The root switcher is the launcher's grown-up form on runtimes whose\n files.actions owner carries absolutePath. It takes the path label's flex\n position and stretch: order: -1 ahead of every order-0 sibling, and a\n growing min-width:0 item so both the trigger label and the PathLabel\n inside it can ellipsize instead of pushing the header controls out. */\n.mrfw-rootSwitcher {\n order: -1;\n flex: 1 1 auto;\n min-width: 0;\n display: flex;\n align-items: center;\n /* Same 4px the upstream .header puts between its own flex items, so the\n takeover controls (reload, open-in-app fork) sit at the same offsets the\n hidden native ones did \u2014 no drift between primary and additional state. */\n gap: 4px;\n}\n\n/* While the switcher exists it hides the upstream path label \u2014 the\n :has() guard again keeps the upstream-naming rule inert unless this\n plugin is actually in the row. The switcher draws its own PathLabel of\n the selected root next to the trigger (className mrfw-switcherPath, no\n data-files-* attributes), so the header keeps a path in the same spot.\n :not([data-files-entry]) is what confines this rule to the header:\n every tree row carries data-files-path with its own path as the value,\n and only the PathLabel carries it bare. Without the qualifier this\n display:none would blank the entire native tree. */\n[data-files-root]:has(.mrfw-rootSwitcher) [data-files-path]:not([data-files-entry]) {\n display: none;\n}\n\n/* While the switcher names an additional root, the native tree body and its\n reload hide: the reload refreshes the very tree that is no longer\n visible, and the plugin's forked tree is portaled in as the body's sibling.\n The header still offers a reload \u2014 the switcher's own .mrfw-switcherReload\n calls the forked face's refresh. The native tree keeps watching in the\n background, so switching back to the primary root restores it instantly. */\n[data-files-root]:has(.mrfw-rootSwitcher[data-root-kind=\"additional\"]) [data-files-body],\n[data-files-root]:has(.mrfw-rootSwitcher[data-root-kind=\"additional\"]) [data-files-reload] {\n display: none;\n}\n\n/* While the switcher names an additional root, the native open-in-app split\n button hides too: its slot props carry the pane's primary root, so it\n would always open the primary root no matter what the switcher selected.\n The header gets the plugin's own fork of that same control instead (the\n open-in-app directory), wired to the selected root.\n\n The slot's render site is wrapped in the renderer's own anchor \u2014\n <div data-slot=\"sidebar.right.tab.files.actions\" style=\"display:contents\">\n \u2014 so the header's flex items inside it are the occupants themselves, and\n the native item is the ui-primitives Menu anchor span wrapping the marked\n .split. The first selector hides that occupant item through the data-slot\n anchor: hiding only the inner control would leave the empty anchor span\n as a zero-width flex item whose header gap pushes the fork 4px left of\n the native position. The wrapper itself can never be the target \u2014 its\n inline display:contents beats any stylesheet rule, and hiding it would\n take the switcher with it. The second selector is the fallback for an\n upstream that moves the attribute off the item. The fork survives both:\n the only data-open-target inside .mrfw-rootSwitcher is its own\n .mrfw-openSplit, which :not() excludes. */\n[data-files-root]:has(.mrfw-rootSwitcher[data-root-kind=\"additional\"]) [data-slot=\"sidebar.right.tab.files.actions\"] > *:has([data-open-target=\"directory\"]:not(.mrfw-openSplit)),\n[data-files-root]:has(.mrfw-rootSwitcher[data-root-kind=\"additional\"]) [data-open-target=\"directory\"]:not(.mrfw-openSplit) {\n display: none;\n}\n\n/* The trigger borrows the path label's look: quiet text with a folder\n glyph, the hover of the neighboring header buttons, and the overflow\n behavior PathLabel itself uses in a narrow header. It may shrink but\n never grows \u2014 the PathLabel after it owns the free header space. */\n.mrfw-rootSwitchBtn {\n flex: 0 1 auto;\n min-width: 0;\n display: inline-flex;\n align-items: center;\n gap: 4px;\n height: 28px;\n margin: 0;\n padding: 0 4px;\n border: none;\n border-radius: 8px;\n background: transparent;\n color: var(--dsw-alias-label-primary);\n font-family: inherit;\n font-size: inherit;\n line-height: 20px;\n text-align: left;\n cursor: pointer;\n transition: background-color var(--ds-transition-duration-fast) var(--ds-ease-in-out);\n}\n\n.mrfw-rootSwitchBtn:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.mrfw-rootSwitchBtn .mrfw-triggerIcon {\n color: var(--dsw-alias-label-secondary);\n}\n\n.mrfw-rootSwitchLabel {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.mrfw-rootSwitchChevron {\n margin-left: auto;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* The selected root's full path sits between the trigger and the reload \u2014\n the very PathLabel upstream draws there, so it brings its own grow,\n left-clip and hover-title behavior; only the gutters are ours. The right\n margin plus the container gap reproduces upstream's .path margin-right\n 12px + header gap 4px; the left gutter comes from the same container gap. */\n.mrfw-switcherPath {\n margin: 0 12px 0 0;\n}\n\n/* The takeover's own reload, standing exactly where the hidden native one\n did: the upstream .tool recipe verbatim (28px hit area, 15px glyph,\n secondary voice, hover fills). It calls the forked face's refresh \u2014 an\n in-place re-list, not a remount. */\n.mrfw-switcherReload {\n flex: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n padding: 6px;\n border: none;\n border-radius: var(--dsw-radius-sm);\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n line-height: 1;\n cursor: pointer;\n}\n\n.mrfw-switcherReload:hover {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n/* \u2500\u2500 files-tree fork (verbatim rules, renamed classes) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Copied from upstream's FilesBody.module.css (ui-sidebar-files @\n 0.2.2-alpha.2) with local names mapped to mrfw-ft* so the rules cannot\n collide with host DOM \u2014 and so the fork's own body (which must NOT carry\n data-files-body) stays visible while the rules above hide the native one.\n The header-only rules (root/header/path/tool/status/statusLine/titleIcon)\n belong to the upstream shell this fork drops and are not mapped. Fork sync\n re-diffs the upstream file and re-applies the rename \u2014 see\n src/client/files-tree/FORK.md. */\n\n/* The pane's own .root already supplies the flex column, color, and font;\n the fork's body takes the same stretch item the hidden native body held,\n including the workspace list's 2px scrollbar offset and stable gutter. */\n.mrfw-ftBody {\n flex: 1 1 auto;\n min-height: 0;\n margin-right: 2px;\n padding: 8px 0 8px 8px;\n overflow: auto;\n scrollbar-gutter: stable;\n}\n\n.mrfw-ftBody::-webkit-scrollbar-track {\n margin: 2px;\n}\n\n.mrfw-ftLevel {\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.mrfw-ftLevel .mrfw-ftLevel {\n padding-left: 18px;\n}\n\n.mrfw-ftItem {\n margin: 0;\n padding: 0;\n}\n\n/* Rows abut, so the hover fill is the whole row; the tree's air is this\n inset, not a gap between rows. */\n.mrfw-ftRow {\n display: flex;\n gap: 6px;\n align-items: center;\n width: 100%;\n min-width: 0;\n padding: 5px 10px;\n color: inherit;\n font: inherit;\n text-align: left;\n background: transparent;\n border: 0;\n border-radius: var(--dsw-radius-md);\n cursor: pointer;\n}\n\n@media (hover: hover) {\n .mrfw-ftRow:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n }\n}\n\n/* The folder glyph rides the tertiary ink, as the workspace tree's does. */\n.mrfw-ftIcon {\n flex: 0 0 auto;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* The type's coloured sheet at the folder glyph's 16px; its colour is its own. */\n.mrfw-ftFileIcon {\n flex: 0 0 auto;\n}\n\n.mrfw-ftName {\n min-width: 0;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n/* Neither a file nor a directory: shown so the directory is reported whole,\n greyed so nobody tries to open it. */\n.mrfw-ftOther {\n color: var(--dsw-alias-label-tertiary);\n cursor: default;\n}\n\n@media (hover: hover) {\n .mrfw-ftOther:hover {\n background: transparent;\n }\n}\n\n.mrfw-ftNote {\n margin: 0;\n padding: 3px 10px;\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n}\n\n/* \u2500\u2500 open-in-app fork (verbatim rules, renamed classes) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Copied from upstream's OpenTargetButton.module.css\n (dsh-client-ui-open-in-app @ 0.2.1-alpha.2) with local names mapped to\n mrfw-open* so the rules cannot collide with host DOM. Fork sync re-diffs\n the upstream file and re-applies the rename \u2014 see\n src/client/open-in-app/FORK.md. */\n\n/* Sized to sit in the document header's 24px control row beside its round\n icon buttons: a hairline pill with a 12px label and a narrow chevron half. */\n.mrfw-openMenuAnchor {\n display: inline-flex;\n flex: none;\n align-self: center;\n}\n\n.mrfw-openSplit {\n display: inline-flex;\n align-items: stretch;\n box-sizing: border-box;\n height: 24px;\n overflow: hidden;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: var(--dsw-radius-sm);\n font-family: var(--dsw-font-family);\n}\n\n.mrfw-openMain,\n.mrfw-openChevron {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: 0;\n background: none;\n color: var(--dsw-alias-label-primary);\n font-size: 11px;\n line-height: 16px;\n white-space: nowrap;\n cursor: pointer;\n}\n\n.mrfw-openMain {\n gap: 4px;\n padding: 3px 5px;\n}\n\n.mrfw-openChevron {\n padding: 3px 4px 3px 3px;\n border-left: 0.5px solid var(--dsw-alias-border-l4);\n color: var(--dsw-alias-label-secondary);\n}\n\n.mrfw-openMain:hover:not(:disabled),\n.mrfw-openMain:focus-visible,\n.mrfw-openChevron:hover:not(:disabled),\n.mrfw-openChevron:focus-visible {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n/* Pending disables clicks without a restyle; only hover feedback pauses. */\n.mrfw-openMain:disabled,\n.mrfw-openChevron:disabled {\n cursor: default;\n}\n\n.mrfw-openAppIcon {\n object-fit: contain;\n}\n\n/* The unavailable preview emphasizes the same actions with a labeled button. */\n.mrfw-openSplit[data-size='large'] {\n height: 36px;\n border-radius: var(--dsw-radius-md);\n border-color: var(--dsw-alias-border-l2);\n}\n\n.mrfw-openSplit[data-size='large'] .mrfw-openMain {\n gap: 6px;\n padding: 6px 14px;\n font-size: 14px;\n}\n\n.mrfw-openSplit[data-size='large'] .mrfw-openChevron {\n padding: 6px 10px;\n}\n\n.mrfw-openSkeleton { display: inline-block; flex: none; border-radius: var(--dsw-radius-xs); background: var(--dsw-alias-interactive-bg-hover); }\n\n.mrfw-overlay {\n position: fixed;\n inset: 0;\n z-index: 1000;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 24px;\n}\n\n.mrfw-mask {\n position: absolute;\n inset: 0;\n background: var(--dsw-alias-bg-mask-1);\n backdrop-filter: var(--dsw-mask-blur);\n}\n\n.mrfw-dialog {\n position: relative;\n z-index: 1;\n display: flex;\n flex-direction: column;\n width: min(640px, 100%);\n max-height: 100%;\n overflow: hidden;\n border: none;\n border-radius: 24px;\n background: var(--dsw-alias-bg-layer-2);\n box-shadow: var(--dsw-elevation-prominent);\n color: var(--dsw-alias-label-primary);\n font-family: inherit;\n outline: none;\n}\n\n.mrfw-header {\n flex: none;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 22px 14px 12px 24px;\n}\n\n.mrfw-title {\n margin: 0;\n font-size: 16px;\n line-height: 24px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n.mrfw-close {\n flex: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n border: none;\n border-radius: 8px;\n background: transparent;\n cursor: pointer;\n color: var(--dsw-alias-label-secondary);\n transition: background-color var(--ds-transition-duration-fast) var(--ds-ease-in-out);\n}\n\n.mrfw-close:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.mrfw-body {\n display: flex;\n flex-direction: column;\n min-height: 0;\n overflow-y: auto;\n padding: 0 24px 24px;\n}\n\n.mrfw-alert {\n margin: 0 0 8px;\n font-size: 14px;\n line-height: 22px;\n color: var(--dsw-alias-state-error-primary);\n}\n\n.mrfw-section {\n display: flex;\n flex-direction: column;\n}\n\n.mrfw-section + .mrfw-section {\n margin-top: 8px;\n}\n\n.mrfw-sectionTitleRow {\n display: flex;\n align-items: baseline;\n gap: 8px;\n padding: 8px 0 4px;\n}\n\n.mrfw-sectionTitle {\n font-size: 14px;\n line-height: 22px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n.mrfw-sectionNote {\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.mrfw-row {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n padding: 12px 0;\n border-bottom: 0.5px solid var(--dsw-alias-border-l2);\n}\n\n.mrfw-rowBare {\n border-bottom: none;\n}\n\n/* A root entry's two lines: the display name on top, the absolute path under\n it in the tertiary voice (the workspace rows recipe of the host sidebar). */\n/* Match upstream General Settings' DeveloperToolsRow layout. */\n.mrfw-skillsSettings {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 24px;\n padding: 16px 0;\n border-bottom: 0.5px solid var(--dsw-alias-border-l2);\n}\n.mrfw-skillsSettingsText { min-width: 0; }\n.mrfw-skillsSettingsTitle { font-size: 14px; line-height: 20px; }\n.mrfw-skillsSettingsDescription {\n margin-top: 4px;\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n line-height: 18px;\n}\n.mrfw-rootText {\n display: flex;\n flex: 1 1 240px;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n}\n\n/* Keep status tags and permission glyphs visible beside the alias, wrapping\n onto another line when the dialog is narrow. */\n.mrfw-rootName {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 6px;\n min-width: 0;\n font-size: 14px;\n line-height: 20px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n.mrfw-rootNameText {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.mrfw-rootPath {\n font-family: var(--ds-font-family-code);\n font-size: 12px;\n line-height: 18px;\n word-break: break-all;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* The path doubles as the copy control: an unstyled button that keeps the\n text recipe and makes the whole string clickable \u2014 the whole region, not\n just the icon, copies on click. */\n.mrfw-pathCopy {\n display: block;\n width: 100%;\n padding: 0;\n border: 0;\n background: transparent;\n text-align: left;\n cursor: pointer;\n}\n\n/* The trailing icon stays in flow but invisible until the path is hovered\n or focused: appearing never re-wraps the text, and the icon inherits the\n path's own tertiary ink. */\n.mrfw-pathCopyIcon {\n display: inline-flex;\n margin-left: 4px;\n vertical-align: -2px;\n opacity: 0;\n transition: opacity var(--ds-transition-duration-fast) var(--ds-ease-in-out);\n}\n\n.mrfw-pathCopy:hover .mrfw-pathCopyIcon,\n.mrfw-pathCopy:focus-visible .mrfw-pathCopyIcon {\n opacity: 1;\n}\n\n.mrfw-note {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.mrfw-stateWarn {\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n.mrfw-btn {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n gap: 4px;\n height: 28px;\n padding: 0 10px;\n border: none;\n border-radius: 14px;\n background: transparent;\n color: var(--dsw-alias-label-primary);\n font-family: inherit;\n font-size: 12px;\n line-height: 18px;\n cursor: pointer;\n transition: background-color var(--ds-transition-duration-fast) var(--ds-ease-in-out);\n}\n\n.mrfw-btn[aria-busy='true'] {\n cursor: wait;\n}\n\n.mrfw-btn:disabled {\n cursor: not-allowed;\n opacity: 0.4;\n}\n\n.mrfw-btnGhost:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.mrfw-btnGhost:active:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-active);\n}\n\n/* The solid destructive action of the capsule family: the upgrade confirm's\n red button. state-error-primary is the error ink upstream also uses as a\n fill (StateDot); the hover mixes in the host's primary label token to\n follow its theme and contrast choices. Neither color is a guessed token:\n an undefined var() would fall back\n to transparent and white-out the whole button. */\n.mrfw-btnDanger {\n background: var(--dsw-alias-state-error-primary);\n color: var(--dsw-alias-label-primary-foreground);\n}\n\n.mrfw-btnDanger:hover:not(:disabled) {\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 85%, var(--dsw-alias-label-primary));\n}\n\n.mrfw-btnOutline {\n border: 0.5px solid var(--dsw-alias-border-l3);\n background: transparent;\n}\n\n.mrfw-btnOutline:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.mrfw-btnPrimary {\n background: var(--dsw-alias-button-primary-fill);\n color: var(--dsw-alias-label-primary-foreground);\n}\n\n.mrfw-btnPrimary:hover:not(:disabled) {\n background: var(--dsw-alias-button-primary-hover);\n}\n\n/* One icon-only action: a square of the capsule family, meaning in title.\n Declared after the .mrfw-btn family so its padding wins the cascade tie:\n the capsule's 0 10px padding would otherwise squeeze the icon glyph. */\n.mrfw-iconBtn {\n width: 28px;\n padding: 0;\n border-radius: 8px;\n}\n\n.mrfw-input {\n flex: 1 1 220px;\n min-width: 0;\n height: 32px;\n padding: 0 8px;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n font-family: inherit;\n font-size: 14px;\n line-height: 22px;\n outline: none;\n transition: border-color var(--ds-transition-duration-fast) var(--ds-ease-in-out);\n}\n\n.mrfw-input:focus {\n border-color: var(--dsw-alias-brand-primary);\n}\n\n.mrfw-input::placeholder {\n color: var(--dsw-alias-label-dimmed);\n}\n\n.mrfw-inputInline {\n flex: 1 1 auto;\n}\n\n.mrfw-accessSelect {\n flex: 0 0 auto;\n}\n\n/* The access dropdown's capsule trigger: the Menu anchor wrapper is the flex\n item, the button inside keeps the control row's 32px capsule recipe. */\n.mrfw-accessTrigger {\n padding: 0 12px;\n}\n\n.mrfw-cwdTag {\n flex: none;\n padding: 0 6px;\n border-radius: 4px;\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-secondary);\n font-size: 11px;\n line-height: 18px;\n}\n\n.mrfw-accessBadge {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n}\n\n.mrfw-accessBadge {\n flex: none;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.mrfw-aliasEditor {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n flex-basis: 100%;\n}\n\n.mrfw-actions {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n margin-top: 16px;\n padding-top: 16px;\n border-top: 0.5px solid var(--dsw-alias-border-l2);\n}\n\n/* One control row, one height: the capsule buttons match the 32px input. */\n.mrfw-actions .mrfw-btn {\n height: 32px;\n border-radius: 16px;\n}\n\n.mrfw-actions .mrfw-iconBtn {\n width: 32px;\n}\n";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
import type { DomainSpec } from '@deepseek-ai/dsh-storage-domain';
|
|
3
|
+
export declare function openAuthorityStorage<S extends DomainSpec>(ctx: Context, root: string, spec: S): Promise<{
|
|
4
|
+
table: <N extends keyof S["tables"] & string>(name: N) => import("@deepseek-ai/dsh-storage-domain").KvTable<import("@deepseek-ai/dsh-storage-domain").TableKeyOf<S, N>, import("@deepseek-ai/dsh-storage-domain").TableValueOf<S, N>>;
|
|
5
|
+
close(): Promise<void>;
|
|
6
|
+
}>;
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
* the upgrade smoke passed on that exact version — a green CI run on an
|
|
29
29
|
* unlisted release is evidence, not authorization (ADR-0009).
|
|
30
30
|
*/
|
|
31
|
-
export declare const SUPPORTED_DSH_RELEASES: readonly ["0.1.5-rc.2", "0.1.6-alpha.1", "0.1.6-alpha.2", "0.1.7-alpha.1", "0.1.7-alpha.2", "0.1.7-rc.1", "0.1.7-rc.2", "0.2.0-rc.1", "0.2.0-rc.2", "0.2.1-alpha.1", "0.2.1-alpha.2", "0.2.1-alpha.3", "0.2.1-alpha.4", "0.2.1-alpha.5", "0.2.1-rc.1", "0.2.1-rc.2", "0.2.1-rc.3"];
|
|
31
|
+
export declare const SUPPORTED_DSH_RELEASES: readonly ["0.1.5-rc.2", "0.1.6-alpha.1", "0.1.6-alpha.2", "0.1.7-alpha.1", "0.1.7-alpha.2", "0.1.7-rc.1", "0.1.7-rc.2", "0.2.0-rc.1", "0.2.0-rc.2", "0.2.1-alpha.1", "0.2.1-alpha.2", "0.2.1-alpha.3", "0.2.1-alpha.4", "0.2.1-alpha.5", "0.2.1-rc.1", "0.2.1-rc.2", "0.2.1-rc.3", "0.2.1-rc.4", "0.2.1-rc.5", "0.2.2-alpha.2"];
|
|
32
32
|
/** One release this plugin claims to support. */
|
|
33
33
|
export type SupportedDshRelease = typeof SUPPORTED_DSH_RELEASES[number];
|
|
34
34
|
/**
|
|
@@ -42,7 +42,7 @@ export declare const REQUIRED_CORE_PACKAGES: readonly ["@deepseek-ai/dsh-fs", "@
|
|
|
42
42
|
* Absence is fine; a version that DISAGREES with the required packages is not —
|
|
43
43
|
* that is the mixed-installation failure this contract exists to catch.
|
|
44
44
|
*/
|
|
45
|
-
export declare const OPTIONAL_CORE_PACKAGES: readonly ["@deepseek-ai/dsh-skill", "@deepseek-ai/dsh-skill-filesystem", "@deepseek-ai/dsh-settings", "@deepseek-ai/dsh-api-session-controller", "@deepseek-ai/dsh-session-query", "@deepseek-ai/dsh-tools", "@deepseek-ai/dsh-working-directory", "@deepseek-ai/dsh-lsp", "@deepseek-ai/dsh-lsp-stdio", "@deepseek-ai/dsh-api-workspace-files", "@deepseek-ai/dsh-typert-protocol", "@deepseek-ai/dsh-sandbox-windows-acl", "@deepseek-ai/dsh-agent", "@deepseek-ai/dsh-agent-instructions", "@deepseek-ai/dsh-client-connection", "@deepseek-ai/dsh-commands", "@deepseek-ai/dsh-llm", "@deepseek-ai/dsh-session", "@deepseek-ai/dsh-subprocess", "@deepseek-ai/dsh-system-prompt"];
|
|
45
|
+
export declare const OPTIONAL_CORE_PACKAGES: readonly ["@deepseek-ai/dsh-storage-json", "@deepseek-ai/dsh-skill", "@deepseek-ai/dsh-skill-filesystem", "@deepseek-ai/dsh-settings", "@deepseek-ai/dsh-api-session-controller", "@deepseek-ai/dsh-session-query", "@deepseek-ai/dsh-tools", "@deepseek-ai/dsh-working-directory", "@deepseek-ai/dsh-lsp", "@deepseek-ai/dsh-lsp-stdio", "@deepseek-ai/dsh-api-workspace-files", "@deepseek-ai/dsh-typert-protocol", "@deepseek-ai/dsh-sandbox-windows-acl", "@deepseek-ai/dsh-agent", "@deepseek-ai/dsh-agent-instructions", "@deepseek-ai/dsh-client-connection", "@deepseek-ai/dsh-commands", "@deepseek-ai/dsh-llm", "@deepseek-ai/dsh-session", "@deepseek-ai/dsh-subprocess", "@deepseek-ai/dsh-system-prompt"];
|
|
46
46
|
/**
|
|
47
47
|
* The optional packages this plugin loads itself at run time, through dynamic
|
|
48
48
|
* `import()`s inside this package's own modules. The copy this module resolves
|
|
@@ -50,7 +50,7 @@ export declare const OPTIONAL_CORE_PACKAGES: readonly ["@deepseek-ai/dsh-skill",
|
|
|
50
50
|
* private copy inside this package's own tree genuinely loads, and a version
|
|
51
51
|
* that disagrees with the host is a real mixed installation, not a phantom.
|
|
52
52
|
*/
|
|
53
|
-
export declare const RUNTIME_OPTIONAL_PACKAGES: readonly ["@deepseek-ai/dsh-skill", "@deepseek-ai/dsh-skill-filesystem", "@deepseek-ai/dsh-agent-instructions", "@deepseek-ai/dsh-llm", "@deepseek-ai/dsh-session", "@deepseek-ai/dsh-typert-protocol"];
|
|
53
|
+
export declare const RUNTIME_OPTIONAL_PACKAGES: readonly ["@deepseek-ai/dsh-storage-json", "@deepseek-ai/dsh-skill", "@deepseek-ai/dsh-skill-filesystem", "@deepseek-ai/dsh-agent-instructions", "@deepseek-ai/dsh-llm", "@deepseek-ai/dsh-session", "@deepseek-ai/dsh-typert-protocol"];
|
|
54
54
|
/**
|
|
55
55
|
* The optional packages this plugin consumes only as mounted host services or
|
|
56
56
|
* ambient types — it never imports them, so a copy inside this package's own
|
|
@@ -79,7 +79,7 @@ export declare const HOST_SERVICE_PACKAGES: readonly ["@deepseek-ai/dsh-settings
|
|
|
79
79
|
*/
|
|
80
80
|
export declare const RUNNER_ANCHORED_PACKAGES: readonly ["@deepseek-ai/dsh-sandbox-windows-acl"];
|
|
81
81
|
/** Every package the contract inspects, required first. */
|
|
82
|
-
export declare const CORE_PACKAGES: readonly ["@deepseek-ai/dsh-fs", "@deepseek-ai/dsh-fs-local", "@deepseek-ai/dsh-sandbox", "@deepseek-ai/dsh-sandbox-local", "@deepseek-ai/dsh-sandbox-policy", "@deepseek-ai/dsh-storage-domain", "@deepseek-ai/dsh-skill", "@deepseek-ai/dsh-skill-filesystem", "@deepseek-ai/dsh-settings", "@deepseek-ai/dsh-api-session-controller", "@deepseek-ai/dsh-session-query", "@deepseek-ai/dsh-tools", "@deepseek-ai/dsh-working-directory", "@deepseek-ai/dsh-lsp", "@deepseek-ai/dsh-lsp-stdio", "@deepseek-ai/dsh-api-workspace-files", "@deepseek-ai/dsh-typert-protocol", "@deepseek-ai/dsh-sandbox-windows-acl", "@deepseek-ai/dsh-agent", "@deepseek-ai/dsh-agent-instructions", "@deepseek-ai/dsh-client-connection", "@deepseek-ai/dsh-commands", "@deepseek-ai/dsh-llm", "@deepseek-ai/dsh-session", "@deepseek-ai/dsh-subprocess", "@deepseek-ai/dsh-system-prompt"];
|
|
82
|
+
export declare const CORE_PACKAGES: readonly ["@deepseek-ai/dsh-fs", "@deepseek-ai/dsh-fs-local", "@deepseek-ai/dsh-sandbox", "@deepseek-ai/dsh-sandbox-local", "@deepseek-ai/dsh-sandbox-policy", "@deepseek-ai/dsh-storage-domain", "@deepseek-ai/dsh-storage-json", "@deepseek-ai/dsh-skill", "@deepseek-ai/dsh-skill-filesystem", "@deepseek-ai/dsh-settings", "@deepseek-ai/dsh-api-session-controller", "@deepseek-ai/dsh-session-query", "@deepseek-ai/dsh-tools", "@deepseek-ai/dsh-working-directory", "@deepseek-ai/dsh-lsp", "@deepseek-ai/dsh-lsp-stdio", "@deepseek-ai/dsh-api-workspace-files", "@deepseek-ai/dsh-typert-protocol", "@deepseek-ai/dsh-sandbox-windows-acl", "@deepseek-ai/dsh-agent", "@deepseek-ai/dsh-agent-instructions", "@deepseek-ai/dsh-client-connection", "@deepseek-ai/dsh-commands", "@deepseek-ai/dsh-llm", "@deepseek-ai/dsh-session", "@deepseek-ai/dsh-subprocess", "@deepseek-ai/dsh-system-prompt"];
|
|
83
83
|
/** How this plugin reacts to an installation it does not support. */
|
|
84
84
|
export type CompatEnforcement = 'enforce' | 'warn';
|
|
85
85
|
/** The environment variable that relaxes the gate. */
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compatibility adapter for `isMobileDevice` from
|
|
3
|
+
* `@deepseek-ai/dsh-client-ui-primitives`.
|
|
4
|
+
*
|
|
5
|
+
* ```text
|
|
6
|
+
* 0.1.5-rc.2 through 0.2.1-rc.5 no `isMobileDevice` export
|
|
7
|
+
* 0.2.2-alpha.2 + `isMobileDevice` exported
|
|
8
|
+
* ```
|
|
9
|
+
*
|
|
10
|
+
* The open-in-app fork's verbatim `open-toast.tsx` spells the detector as a
|
|
11
|
+
* primitives import — a name absent before `0.2.2-alpha.2` — so the lookup
|
|
12
|
+
* is by export name, not by release string (the `client-icons.ts` pattern).
|
|
13
|
+
* An older runtime falls back to upstream's identical detection: mobile
|
|
14
|
+
* userAgent, or a touch-capable MacIntel platform, memoized like upstream's.
|
|
15
|
+
*
|
|
16
|
+
* @module @dsh-electron/dsh-plugin-multi-root-workspace/compat/open-in-app-device
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Resolve the installed release's `isMobileDevice`, or the identical local
|
|
20
|
+
* fallback on releases that predate the export.
|
|
21
|
+
* @returns the mobile-device verdict.
|
|
22
|
+
*/
|
|
23
|
+
export declare function isMobileDevice(): boolean;
|
|
@@ -29,3 +29,5 @@ export declare function shellEntrypoints(shell: object): readonly ProcessEntrypo
|
|
|
29
29
|
* @returns the present entries — a structural probe only.
|
|
30
30
|
*/
|
|
31
31
|
export declare function ptcEntrypoints(runtime: object): readonly ProcessEntrypoint[];
|
|
32
|
+
/** Async public entries can acquire admission before calling their implementation. */
|
|
33
|
+
export declare function isAsyncProcessEntry(entry: unknown): boolean;
|
|
@@ -58,3 +58,5 @@ export declare function mapConfined<T>(value: UpstreamConfined, map: (confined:
|
|
|
58
58
|
* @returns the widened result, in the shape the installed release uses.
|
|
59
59
|
*/
|
|
60
60
|
export declare function widenConfined(base: ConfineCall, widen: (confined: ConfinedArgv) => ConfinedArgv, argv: readonly string[], policy: SandboxPolicy, signal?: AbortSignal): UpstreamConfined;
|
|
61
|
+
/** Public consumer call with the installed provider's arity and return shape. */
|
|
62
|
+
export declare function confineInstalled(provider: Pick<LocalSandboxProvider, 'confine'>, argv: readonly string[], policy: SandboxPolicy, signal?: AbortSignal): ConfinedArgv | Promise<ConfinedArgv>;
|
package/lib/types/fs.d.ts
CHANGED
|
@@ -64,6 +64,8 @@ export declare class MultiRootFileSystem extends LocalFileSystem {
|
|
|
64
64
|
editText(target: FsTarget, edit: FsEditRequest, expected?: {
|
|
65
65
|
version: FsVersion;
|
|
66
66
|
}, signal?: AbortSignal, sandboxPolicy?: SandboxExecutionPolicy): Promise<FsEditOutcome>;
|
|
67
|
+
/** The provider must also work without the gated registry (ADR-0009). */
|
|
68
|
+
private withOperation;
|
|
67
69
|
/**
|
|
68
70
|
* The writable roots of one confined call: the upstream derivation (primary
|
|
69
71
|
* root plus the platform temp areas, canonical and deduplicated — empty
|
|
@@ -104,7 +106,8 @@ export declare class MultiRootFileSystem extends LocalFileSystem {
|
|
|
104
106
|
* to primary-root-only writes (no shared temp or additional-root grants)
|
|
105
107
|
* until the authority returns.
|
|
106
108
|
* @param target - the resolved target about to be mutated.
|
|
107
|
-
* @param
|
|
109
|
+
* @param policy - the per-call policy captured before admission.
|
|
110
|
+
* @param scope - the admission snapshot shared by every fence check.
|
|
108
111
|
* @returns the fresh canonical target the mutation must use.
|
|
109
112
|
*/
|
|
110
113
|
private checkedTarget;
|
|
@@ -1,50 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `multi-root-instructions`: the additional roots' own instruction files, made
|
|
3
|
-
* visible to the model.
|
|
4
|
-
*
|
|
5
|
-
* Upstream `agent-instructions` discovers instruction files by walking UP from
|
|
6
|
-
* the session cwd (plus the one user-global file). In a multi-root workspace
|
|
7
|
-
* that walk never reaches an additional root, so `repo-b/AGENTS.md` is
|
|
8
|
-
* invisible to a model that is nevertheless allowed to write `repo-b`. This row
|
|
9
|
-
* closes exactly that gap, and nothing else: the primary root's chain — nested
|
|
10
|
-
* files included — and the user-global file stay upstream's business.
|
|
11
|
-
*
|
|
12
|
-
* Four deliberate choices:
|
|
13
|
-
*
|
|
14
|
-
* 1. **Not a system-prompt contribution.** Instruction text is
|
|
15
|
-
* producer-supplied context, not system authority, so it enters as a
|
|
16
|
-
* user-role message tagged `form: 'instructions'`. Through session format 3
|
|
17
|
-
* the source kind is `plugin`; format 4 retired that shared kind, so the
|
|
18
|
-
* adapter persists this plugin's own (`multi-root-workspace`). It is also
|
|
19
|
-
* the only channel that is logged, and model-visible content must be logged.
|
|
20
|
-
* 2. **Injected from `agent/pre-step`, not a session lifecycle event.**
|
|
21
|
-
* `pre-step` is an awaited waterfall, so discovery, reading and rendering all
|
|
22
|
-
* complete BEFORE the step it feeds — deterministically, on the first step.
|
|
23
|
-
* A synchronous prompt callback could not await them, and an emit-mode
|
|
24
|
-
* lifecycle listener would race the first step. It is also the one hook whose
|
|
25
|
-
* shape is identical across every supported release, which keeps the
|
|
26
|
-
* instruction feature out of the compatibility matrix (ADR-0009).
|
|
27
|
-
* 3. **Discovery is pinned to the root.** `projectRoot` is the additional root,
|
|
28
|
-
* so the walk can never leave it, and every candidate is then required to be
|
|
29
|
-
* canonically INSIDE that root. That is what keeps `$DSH_HOME/AGENTS.md`, the
|
|
30
|
-
* primary root's `AGENTS.md`, and any ancestor's file from being injected a
|
|
31
|
-
* second time by this plugin.
|
|
32
|
-
* 4. **Nested files are driven by the session's own tool activity.** A
|
|
33
|
-
* subdirectory's `AGENTS.md` only matters once the session has worked in that
|
|
34
|
-
* subdirectory, so the directories worth examining are derived from the
|
|
35
|
-
* session's persisted events: `tool/call` and `tool/result` are paired, and a
|
|
36
|
-
* successful `read`/`write`/`edit` makes the path from the root down to the
|
|
37
|
-
* touched file's directory relevant (ADR-0010). A file that disappears is
|
|
38
|
-
* retracted explicitly, because the text it contributed earlier is still in
|
|
39
|
-
* the conversation and silence does not withdraw it.
|
|
40
|
-
*
|
|
41
|
-
* The byte budget is shared: `maxBytes` bounds the WHOLE additional-root
|
|
42
|
-
* snapshot, not each root, because ten roots must not quietly cost ten budgets
|
|
43
|
-
* of context. Roots consume it in scope order. Retractions are exempt, because a
|
|
44
|
-
* truncated withdrawal is worse than a slightly over-budget message.
|
|
45
|
-
*
|
|
46
|
-
* @module @dsh-electron/dsh-plugin-multi-root-workspace/instructions
|
|
47
|
-
*/
|
|
48
1
|
import { Context } from '@deepseek-ai/cordis';
|
|
49
2
|
import type { Agent } from '@deepseek-ai/dsh-agent';
|
|
50
3
|
declare module '@deepseek-ai/cordis' {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { SandboxMode } from '@deepseek-ai/dsh-sandbox';
|
|
2
|
+
import type { FilesystemScope } from './scope.ts';
|
|
3
|
+
export type RegistryOperationKind = 'fs-write' | 'fs-edit' | 'process' | 'pty' | 'lsp' | 'files' | 'instructions' | 'skills';
|
|
4
|
+
export declare function currentRegistryOperation(proof: object): RegistryOperation | undefined;
|
|
5
|
+
export interface RegistryOperationIdentity {
|
|
6
|
+
readonly operationId: string;
|
|
7
|
+
readonly kind: RegistryOperationKind;
|
|
8
|
+
readonly mode: SandboxMode | undefined;
|
|
9
|
+
readonly primaryRoot: string;
|
|
10
|
+
/** Absent when the registry cannot grant additional authority. */
|
|
11
|
+
readonly authorityEpoch: string | undefined;
|
|
12
|
+
/** Effective publication revision, meaningful only within this epoch. */
|
|
13
|
+
readonly revision: number;
|
|
14
|
+
}
|
|
15
|
+
export interface RegistryOperation {
|
|
16
|
+
readonly identity: RegistryOperationIdentity;
|
|
17
|
+
/** Only readable during this operation's callback. */
|
|
18
|
+
readonly scope: FilesystemScope;
|
|
19
|
+
}
|
|
20
|
+
/** Capture by value: neither later publication nor a caller can mutate admission. */
|
|
21
|
+
export declare function freezeOperationScope(scope: FilesystemScope): FilesystemScope;
|
|
22
|
+
/** Execute outside the authority queue; completion also expires async descendants. */
|
|
23
|
+
export declare function runRegistryOperation<T>(scope: FilesystemScope, kind: RegistryOperationKind, authorityEpoch: string | undefined, revision: number, body: (operation: RegistryOperation) => T | Promise<T>, proof?: object, mode?: SandboxMode): Promise<T>;
|
|
24
|
+
/** Optional Registry seam for public consumers; the legacy composition stays exact. */
|
|
25
|
+
export declare function withRegistryRead<T>(ctx: import('@deepseek-ai/cordis').Context, primary: string, kind: RegistryOperationKind, body: () => T | Promise<T>): Promise<T>;
|
|
26
|
+
export declare function withRegistryAdmission<T>(ctx: import('@deepseek-ai/cordis').Context, policy: import('@deepseek-ai/dsh-sandbox').SandboxExecutionPolicy, kind: RegistryOperationKind, body: () => T | Promise<T>): Promise<T>;
|
|
27
|
+
/** Preserve a legacy synchronous entry while granting only the safe primary-root fallback. */
|
|
28
|
+
export declare function runPrimaryOperation<T>(scope: FilesystemScope, mode: SandboxMode, proof: object, body: () => T): T;
|
|
29
|
+
/** Only profiles produced within this exact admission can reach a sync spawn. */
|
|
30
|
+
export declare function rememberOperationProfile(proof: object, argv: readonly string[], capped: boolean): void;
|
|
31
|
+
export declare function operationPermitsProfile(proof: object, argv: readonly string[], capped: boolean): boolean;
|
package/lib/types/registry.d.ts
CHANGED
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
* only when the operator removes it (or when the store is repaired). Storage
|
|
23
23
|
* that cannot be parsed at all is backed up and skipped by the domain layer,
|
|
24
24
|
* with a warning.
|
|
25
|
-
* - **Mutations are serialized
|
|
26
|
-
* whole read → validate → persist sequence inside
|
|
25
|
+
* - **Mutations and fs admissions are serialized store-wide.** Every mutation
|
|
26
|
+
* runs its whole read → validate → persist sequence inside the store queue. The
|
|
27
27
|
* storage domain serializes individual `put()`/`delete()` calls, which is not
|
|
28
28
|
* enough: two concurrent `add()` calls would both read the same snapshot and
|
|
29
29
|
* the later write would drop the earlier one, and a removal racing an add
|
|
@@ -45,7 +45,10 @@
|
|
|
45
45
|
*/
|
|
46
46
|
import { Context, Service } from '@deepseek-ai/cordis';
|
|
47
47
|
import type { SandboxExecutionPolicy } from '@deepseek-ai/dsh-sandbox';
|
|
48
|
+
import type { RegistryOperation, RegistryOperationKind } from './registry-operation.ts';
|
|
48
49
|
import { type AdditionalRootId, type RegisteredRoot, type RootAccess, type RootRef, type RootStatus } from './roots.ts';
|
|
50
|
+
/** Operator/test capability diagnostics; never bypasses peer authentication. */
|
|
51
|
+
export { nativeHostAvailability } from './authority/native.ts';
|
|
49
52
|
/** The domain name; also the store file stem (`$DSH_HOME/storages/<name>.json`). */
|
|
50
53
|
export declare const DOMAIN_NAME = "multi_root_workspace";
|
|
51
54
|
/** The single table name inside the domain. */
|
|
@@ -76,6 +79,9 @@ export type RegistryAuthorityState = {
|
|
|
76
79
|
readonly kind: 'active';
|
|
77
80
|
} | {
|
|
78
81
|
readonly kind: 'contended';
|
|
82
|
+
} | {
|
|
83
|
+
readonly kind: 'follower';
|
|
84
|
+
readonly epoch: string;
|
|
79
85
|
} | {
|
|
80
86
|
readonly kind: 'storage-failed';
|
|
81
87
|
readonly reason: string;
|
|
@@ -92,6 +98,8 @@ export type RegistryAuthorityState = {
|
|
|
92
98
|
export interface Config {
|
|
93
99
|
/** Absolute path of the store-wide kernel lock file. */
|
|
94
100
|
readonly leasePath?: string;
|
|
101
|
+
/** Explicit medium binding for the multi-Host authority service. */
|
|
102
|
+
readonly sharedStorageRoot?: string;
|
|
95
103
|
/**
|
|
96
104
|
* Cap on STORED additional-root records per primary root (every state
|
|
97
105
|
* counts, including withheld and unusable ones, so the count is stable and
|
|
@@ -191,6 +199,16 @@ export declare class MultiRootRegistry extends Service {
|
|
|
191
199
|
static inject: string[];
|
|
192
200
|
/** Absolute lock path; absent means the row was composed without `leasePath`. */
|
|
193
201
|
private readonly leasePath;
|
|
202
|
+
private readonly sharedStorageRoot;
|
|
203
|
+
private readonly authorityStorageContext;
|
|
204
|
+
private medium;
|
|
205
|
+
private server;
|
|
206
|
+
private client;
|
|
207
|
+
private connectionFailure;
|
|
208
|
+
private connectionGeneration;
|
|
209
|
+
private remoteProjectionReady;
|
|
210
|
+
private readonly remoteRevisions;
|
|
211
|
+
private readonly operationProof;
|
|
194
212
|
/** Whether this process currently owns the store. */
|
|
195
213
|
private authorityState;
|
|
196
214
|
/** Held kernel lease; present once acquire succeeded, including after a failed open. */
|
|
@@ -203,8 +221,12 @@ export declare class MultiRootRegistry extends Service {
|
|
|
203
221
|
private readonly cache;
|
|
204
222
|
/** Signature of the last published grant per key, so a no-op refresh stays a no-op. */
|
|
205
223
|
private readonly published;
|
|
206
|
-
/**
|
|
224
|
+
/** Pending store-ordered work, grouped by primary for teardown accounting. */
|
|
207
225
|
private readonly chains;
|
|
226
|
+
/** Admissions and mutations share one order, including across primary roots. */
|
|
227
|
+
private operationChain;
|
|
228
|
+
private operationEpoch;
|
|
229
|
+
private operationRevision;
|
|
208
230
|
/** Store-wide queue for lease acquire / open / release. */
|
|
209
231
|
private authorityChain;
|
|
210
232
|
/** Change listeners, keyed by their own disposer identity. */
|
|
@@ -245,6 +267,9 @@ export declare class MultiRootRegistry extends Service {
|
|
|
245
267
|
* Whether this process is the Registry Authority for the store-wide medium.
|
|
246
268
|
* Tests and diagnostics read this; surfaces use {@link unavailable}.
|
|
247
269
|
*/
|
|
270
|
+
get epoch(): string | undefined;
|
|
271
|
+
get shared(): boolean;
|
|
272
|
+
get available(): boolean;
|
|
248
273
|
get authority(): RegistryAuthorityState;
|
|
249
274
|
/**
|
|
250
275
|
* The configured cap on stored additional-root records per primary root.
|
|
@@ -289,6 +314,13 @@ export declare class MultiRootRegistry extends Service {
|
|
|
289
314
|
* @returns the refreshed status list.
|
|
290
315
|
*/
|
|
291
316
|
refresh(primaryRoot: string): Promise<readonly RootStatus[]>;
|
|
317
|
+
/**
|
|
318
|
+
* Admit one fs invocation in the same order as mutations, then run its I/O
|
|
319
|
+
* OUTSIDE the queue. Revocation does not wait for already-admitted work.
|
|
320
|
+
* Shared followers obtain the same ordered snapshot through attested IPC.
|
|
321
|
+
*/
|
|
322
|
+
withOperation<T>(policy: SandboxExecutionPolicy, kind: RegistryOperationKind, body: (operation: RegistryOperation) => T | Promise<T>): Promise<T>;
|
|
323
|
+
withPrimaryOperation<T>(policy: SandboxExecutionPolicy, body: () => T): T;
|
|
292
324
|
/**
|
|
293
325
|
* Registers one additional root.
|
|
294
326
|
* @param primaryRoot - the session workspace root the root belongs to.
|
|
@@ -419,8 +451,8 @@ export declare class MultiRootRegistry extends Service {
|
|
|
419
451
|
*/
|
|
420
452
|
private persist;
|
|
421
453
|
/**
|
|
422
|
-
* Run one job
|
|
423
|
-
*
|
|
454
|
+
* Run one job after every store job already queued. The whole
|
|
455
|
+
* read → validate → write sequence belongs inside the job:
|
|
424
456
|
* the storage domain serializes individual writes, so without this the loser
|
|
425
457
|
* of a race reads a snapshot that is already stale and writes it back.
|
|
426
458
|
*/
|
|
@@ -446,6 +478,15 @@ export declare class MultiRootRegistry extends Service {
|
|
|
446
478
|
*/
|
|
447
479
|
private readonly metadataPublished;
|
|
448
480
|
private publish;
|
|
481
|
+
private projectRoots;
|
|
482
|
+
private snapshotOf;
|
|
483
|
+
private captureSnapshot;
|
|
484
|
+
private notifyShared;
|
|
485
|
+
private applySnapshot;
|
|
486
|
+
private remoteUnavailable;
|
|
487
|
+
private remoteSnapshot;
|
|
488
|
+
private remote;
|
|
489
|
+
private dispatchShared;
|
|
449
490
|
/**
|
|
450
491
|
* The open table.
|
|
451
492
|
* @returns the live table.
|
|
@@ -494,7 +535,7 @@ export declare class MultiRootRegistry extends Service {
|
|
|
494
535
|
*/
|
|
495
536
|
private releaseAuthority;
|
|
496
537
|
/**
|
|
497
|
-
* Wait until no
|
|
538
|
+
* Wait until no store work is queued or running (operation I/O is outside).
|
|
498
539
|
*
|
|
499
540
|
* One pass is enough, and only because the authority state has already been
|
|
500
541
|
* flipped: no further mutation can be admitted (it would fail in
|
package/lib/types/scope.d.ts
CHANGED
|
@@ -208,6 +208,7 @@ export declare class MultiRootScopeService extends Service {
|
|
|
208
208
|
/** Additional roots keyed by canonical primary root; empty until roots are registered. */
|
|
209
209
|
private readonly rootsByPrimary;
|
|
210
210
|
private readonly listeners;
|
|
211
|
+
private admissionProof;
|
|
211
212
|
/**
|
|
212
213
|
* The registry-authority fact the registry row publishes. Defaults to
|
|
213
214
|
* `available`: a composition without a registry row can never have
|
|
@@ -244,6 +245,15 @@ export declare class MultiRootScopeService extends Service {
|
|
|
244
245
|
* @param policy - the upstream policy whose workspace root is the primary root.
|
|
245
246
|
* @returns the canonical scope; `additionalRoots` is empty when none are registered.
|
|
246
247
|
*/
|
|
248
|
+
requireAdmission(proof: object): void;
|
|
249
|
+
rememberProfile(argv: readonly string[]): void;
|
|
250
|
+
permitsProfile(argv: readonly string[]): boolean;
|
|
251
|
+
get admissionRequired(): boolean;
|
|
252
|
+
admissionPathsValid(): boolean;
|
|
253
|
+
hasAdmission(): boolean;
|
|
254
|
+
/** Trusted display/review projection, never an execution permit. */
|
|
255
|
+
snapshot(policy: SandboxExecutionPolicy): FilesystemScope;
|
|
256
|
+
resolveSnapshot(policy: SandboxExecutionPolicy, roots: readonly AdditionalWorkspaceRoot[], available: boolean): FilesystemScope;
|
|
247
257
|
resolve(policy: SandboxExecutionPolicy): FilesystemScope;
|
|
248
258
|
/** Read-only pending-dispatch projection. Does not mint an ALS execution grant. */
|
|
249
259
|
previewForTool(policy: SandboxExecutionPolicy, toolName: string): FilesystemScope;
|
|
@@ -307,6 +317,8 @@ export declare class MultiRootScopeService extends Service {
|
|
|
307
317
|
* (before acquisition, on contention or storage failure, at teardown) and
|
|
308
318
|
* `available` only once its stored records have been published. Nothing
|
|
309
319
|
* else may call it, and it never touches the root table.
|
|
320
|
+
* Only transitions notify: a failed refresh may republish `unavailable`,
|
|
321
|
+
* and notifying on that no-op would re-arm Skills clients' refresh loop.
|
|
310
322
|
* @param available - whether this process holds the registry authority.
|
|
311
323
|
*/
|
|
312
324
|
setRegistryAuthority(available: boolean): void;
|
|
@@ -7,6 +7,7 @@ export declare const SKILLS_PROVIDER = "multi-root-workspace";
|
|
|
7
7
|
export declare function internalSkillName(primary: string, rootId: string, skillName: string): string;
|
|
8
8
|
export interface LocatedSkill {
|
|
9
9
|
readonly primary: string;
|
|
10
|
+
readonly authorityEpoch?: string | undefined;
|
|
10
11
|
readonly rootId: string;
|
|
11
12
|
readonly aliasId: string;
|
|
12
13
|
readonly candidate: SkillCandidate;
|
|
@@ -27,8 +28,10 @@ export declare class AdditionalSkillCatalog {
|
|
|
27
28
|
current(primary: string, id: string): RootStatus | undefined;
|
|
28
29
|
private withReader;
|
|
29
30
|
list(primary: string, options: SkillLookupOptions): Promise<SkillCandidate[]>;
|
|
31
|
+
private listAdmitted;
|
|
30
32
|
/** Locators are created here and are not accepted from a client path or object. */
|
|
31
33
|
load(primary: string, entry: SkillCandidate, options: SkillLookupOptions, user: boolean): Promise<SkillDefinition | undefined>;
|
|
34
|
+
private loadAdmitted;
|
|
32
35
|
admissionValid(definition: SkillDefinition): Promise<boolean>;
|
|
33
36
|
dispose(): Promise<void>;
|
|
34
37
|
}
|
|
@@ -10,7 +10,7 @@ export declare class SkillReaderPool {
|
|
|
10
10
|
constructor(invalidate: () => void, capacity?: number, intervalMs?: number);
|
|
11
11
|
get size(): number;
|
|
12
12
|
get roots(): readonly string[];
|
|
13
|
-
use<T>(
|
|
13
|
+
use<T>(root: string, create: (signal: AbortSignal) => Promise<Reader | undefined>, run: (reader: Reader | undefined) => Promise<T>, signal?: AbortSignal, identity?: string): Promise<T>;
|
|
14
14
|
poll(): Promise<void>;
|
|
15
15
|
dispose(): Promise<void>;
|
|
16
16
|
private closeEntry;
|
package/lib/workspace-files.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { t as wrapMethod } from "./method-wrapper-CFY3p3j2.js";
|
|
2
|
+
import { c as withRegistryRead } from "./registry-operation-C2ZV3Jhw.js";
|
|
2
3
|
import { l as isCanonicallyUnder } from "./roots-Ca_W1S9n.js";
|
|
3
4
|
import { t as routeWorkspacePath } from "./workspace-routing-YPxaQ4V3.js";
|
|
4
5
|
import { posix, resolve, win32 } from "node:path";
|
|
@@ -78,19 +79,35 @@ function apply(ctx) {
|
|
|
78
79
|
return original.call(this, routed.request, routed.path, relativePath, signal);
|
|
79
80
|
})) : void 0;
|
|
80
81
|
const restoreWatch = adaptPathWatch(service, (originalChanges) => async function* (request, path, signal) {
|
|
81
|
-
|
|
82
|
-
|
|
82
|
+
const check = async () => {
|
|
83
|
+
if (await withRegistryRead(scope, request.workspaceRoot, "files", () => routeRead(scope, request, path, signal)) === "deny") throw await outside(path);
|
|
84
|
+
};
|
|
85
|
+
if (scope.get("multiRootRegistry")?.shared) await check();
|
|
86
|
+
if (scope.multiRootScope.snapshot({
|
|
87
|
+
workspaceRoot: request.workspaceRoot,
|
|
88
|
+
mode: "read-only"
|
|
89
|
+
}).additionalRoots.length === 0 && registeredRoots(scope, request.workspaceRoot).length === 0) {
|
|
90
|
+
for await (const frame of originalChanges.call(this, request, path, signal)) {
|
|
91
|
+
if (scope.get("multiRootRegistry")?.shared) await check();
|
|
92
|
+
yield frame;
|
|
93
|
+
}
|
|
83
94
|
return;
|
|
84
95
|
}
|
|
85
|
-
const route = await routeWorkspacePath(scope, request.workspaceRoot, path, signal);
|
|
96
|
+
const route = await withRegistryRead(scope, request.workspaceRoot, "files", () => routeWorkspacePath(scope, request.workspaceRoot, path, signal));
|
|
86
97
|
if (route === void 0 || route.workspaceRoot === route.primaryRoot) {
|
|
87
98
|
if (underWithheldRoot(scope, request.workspaceRoot, path)) throw await outside(path);
|
|
88
|
-
|
|
99
|
+
for await (const frame of originalChanges.call(this, request, path, signal)) {
|
|
100
|
+
if (scope.get("multiRootRegistry")?.shared) await check();
|
|
101
|
+
yield frame;
|
|
102
|
+
}
|
|
89
103
|
return;
|
|
90
104
|
}
|
|
91
105
|
const lifetime = new AbortController();
|
|
92
106
|
const unsubscribe = scope.multiRootScope.subscribe(() => {
|
|
93
|
-
if (!scope.multiRootScope.
|
|
107
|
+
if (!scope.multiRootScope.snapshot({
|
|
108
|
+
workspaceRoot: route.primaryRoot,
|
|
109
|
+
mode: "read-only"
|
|
110
|
+
}).additionalRoots.includes(route.workspaceRoot)) lifetime.abort(/* @__PURE__ */ new Error("the additional root was revoked"));
|
|
94
111
|
});
|
|
95
112
|
try {
|
|
96
113
|
for await (const frame of originalChanges.call(this, {
|
|
@@ -101,14 +118,35 @@ function apply(ctx) {
|
|
|
101
118
|
lifetime.signal,
|
|
102
119
|
rowLifetime.signal
|
|
103
120
|
]))) {
|
|
104
|
-
|
|
121
|
+
await withRegistryRead(scope, route.primaryRoot, "files", async () => {
|
|
122
|
+
if (!scope.multiRootScope.scopeOf(route.primaryRoot).includes(route.workspaceRoot)) throw await outside(path);
|
|
123
|
+
});
|
|
105
124
|
yield frame;
|
|
106
125
|
}
|
|
107
126
|
} finally {
|
|
108
127
|
unsubscribe();
|
|
109
128
|
}
|
|
110
129
|
});
|
|
130
|
+
const admissionRestores = [];
|
|
131
|
+
{
|
|
132
|
+
const methods = service;
|
|
133
|
+
for (const key of [
|
|
134
|
+
"list",
|
|
135
|
+
"stat",
|
|
136
|
+
"read",
|
|
137
|
+
"readBytes",
|
|
138
|
+
"readAll",
|
|
139
|
+
"readRelated"
|
|
140
|
+
]) {
|
|
141
|
+
if (typeof methods[key] !== "function") continue;
|
|
142
|
+
admissionRestores.push(wrapMethod(methods, key, (original) => async function(...args) {
|
|
143
|
+
const request = args[0];
|
|
144
|
+
return withRegistryRead(scope, request.workspaceRoot, "files", () => original.apply(this, args));
|
|
145
|
+
}));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
111
148
|
return () => {
|
|
149
|
+
for (const restore of admissionRestores.reverse()) restore();
|
|
112
150
|
rowLifetime.abort(/* @__PURE__ */ new Error("multi-root workspace-files routing unloaded"));
|
|
113
151
|
restoreList();
|
|
114
152
|
restoreStat();
|
|
@@ -134,6 +172,16 @@ function apply(ctx) {
|
|
|
134
172
|
* root, genuinely unrelated paths) keeps upstream's default read access.
|
|
135
173
|
*/
|
|
136
174
|
async function routeRead(scope, request, path, signal) {
|
|
175
|
+
if (scope.get("multiRootRegistry")?.shared && scope.multiRootScope.resolve({
|
|
176
|
+
workspaceRoot: request.workspaceRoot,
|
|
177
|
+
mode: "read-only"
|
|
178
|
+
}).authority === "unavailable") {
|
|
179
|
+
const route = await routeWorkspacePath(scope, request.workspaceRoot, path, signal);
|
|
180
|
+
return route !== void 0 && route.workspaceRoot === route.primaryRoot ? {
|
|
181
|
+
request,
|
|
182
|
+
path: route.absolutePath
|
|
183
|
+
} : "deny";
|
|
184
|
+
}
|
|
137
185
|
if (scope.multiRootScope.scopeOf(request.workspaceRoot).length === 0 && registeredRoots(scope, request.workspaceRoot).length === 0) return {
|
|
138
186
|
request,
|
|
139
187
|
path
|
|
@@ -187,12 +235,20 @@ function underWithheldRoot(scope, primaryRoot, path) {
|
|
|
187
235
|
* file, so the granted base must not reach back into a dead grant.
|
|
188
236
|
*/
|
|
189
237
|
async function relatedTailWithheld(scope, primaryRoot, base, tail, signal) {
|
|
190
|
-
|
|
238
|
+
const unknown = scope.get("multiRootRegistry")?.shared && scope.multiRootScope.resolve({
|
|
239
|
+
workspaceRoot: primaryRoot,
|
|
240
|
+
mode: "read-only"
|
|
241
|
+
}).authority === "unavailable";
|
|
242
|
+
if (registeredRoots(scope, primaryRoot).length === 0 && !unknown) return void 0;
|
|
191
243
|
const targetBase = await scope.fs.resolve(base.path, {
|
|
192
244
|
cwd: base.request.workspaceRoot,
|
|
193
245
|
signal
|
|
194
246
|
});
|
|
195
247
|
const target = resolveRelatedWorkspacePath(scope.fs.processPath(targetBase), tail);
|
|
248
|
+
if (unknown && await routeRead(scope, {
|
|
249
|
+
...base.request,
|
|
250
|
+
workspaceRoot: primaryRoot
|
|
251
|
+
}, target, signal) === "deny") return target;
|
|
196
252
|
return underWithheldRoot(scope, primaryRoot, target) ? target : void 0;
|
|
197
253
|
}
|
|
198
254
|
async function outside(path) {
|