@dsh-electron/dsh-plugin-multi-root-workspace 0.1.5 → 0.1.7

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.
Files changed (101) hide show
  1. package/CHANGELOG.en.md +56 -1
  2. package/CHANGELOG.md +56 -1
  3. package/README.en.md +157 -92
  4. package/README.md +158 -93
  5. package/cordis.patch.yml +61 -0
  6. package/lib/client.js +2819 -115
  7. package/lib/client.js.map +1 -1
  8. package/lib/command-CESuwlVt.js +1306 -0
  9. package/lib/command.js +2 -2
  10. package/lib/{compat-82Y27Iom.js → compat-DF_yL1bB.js} +180 -4
  11. package/lib/compat.js +1 -1
  12. package/lib/contract-DZ4zczDT.js +238 -0
  13. package/lib/{fs-D_Sw3o5z.js → fs-9JIzGaf3.js} +55 -19
  14. package/lib/fs.js +2 -1
  15. package/lib/index.js +10 -8
  16. package/lib/{instructions-Cn1Jj5Xi.js → instructions-D19jRxN0.js} +109 -28
  17. package/lib/instructions.js +1 -1
  18. package/lib/lsp.js +43 -0
  19. package/lib/method-wrapper-CFY3p3j2.js +26 -0
  20. package/lib/process-fence.js +280 -0
  21. package/lib/providers.js +87 -0
  22. package/lib/{registry-DySMbjgW.js → registry-DCxC5Lay.js} +256 -35
  23. package/lib/registry.js +2 -2
  24. package/lib/{roots-DasWEBJk.js → roots-B-0l8mNG.js} +117 -20
  25. package/lib/sandbox-Kup4_ZIb.js +747 -0
  26. package/lib/sandbox.js +2 -1
  27. package/lib/scope.js +240 -25
  28. package/lib/session-directory-BLOstut1.js +9 -0
  29. package/lib/tool-context-CpgIFcCB.js +6 -0
  30. package/lib/types/client/RootSwitcher.d.ts +63 -0
  31. package/lib/types/client/WorkspaceFoldersAction.d.ts +41 -0
  32. package/lib/types/client/files-tree/FilesBody.module.css.d.ts +24 -0
  33. package/lib/types/client/files-tree/FilesTree.d.ts +47 -0
  34. package/lib/types/client/files-tree/adapter.d.ts +94 -0
  35. package/lib/types/client/files-tree/directory-node.d.ts +62 -0
  36. package/lib/types/client/files-tree/face.d.ts +107 -0
  37. package/lib/types/client/files-tree/locales.d.ts +10 -0
  38. package/lib/types/client/files-tree/primitives.d.ts +33 -0
  39. package/lib/types/client/files-tree/store.d.ts +81 -0
  40. package/lib/types/client/index.d.ts +13 -2
  41. package/lib/types/client/locales.d.ts +60 -0
  42. package/lib/types/client/open-in-app/OpenInAppAction.d.ts +27 -0
  43. package/lib/types/client/open-in-app/OpenTargetButton.d.ts +52 -0
  44. package/lib/types/client/open-in-app/OpenTargetButton.module.css.d.ts +29 -0
  45. package/lib/types/client/open-in-app/adapter.d.ts +46 -0
  46. package/lib/types/client/open-in-app/applications.d.ts +4 -0
  47. package/lib/types/client/open-in-app/controller.d.ts +53 -0
  48. package/lib/types/client/open-in-app/locales.d.ts +10 -0
  49. package/lib/types/client/open-in-app/open-failure-toast.d.ts +11 -0
  50. package/lib/types/client/open-in-app/open-path.d.ts +13 -0
  51. package/lib/types/client/panel-client.d.ts +2 -1
  52. package/lib/types/client/styles.d.ts +1 -1
  53. package/lib/types/command.d.ts +16 -3
  54. package/lib/types/compat/client-icons.d.ts +18 -0
  55. package/lib/types/compat/client-session.d.ts +2 -0
  56. package/lib/types/compat/dsh-version.d.ts +5 -3
  57. package/lib/types/compat/instruction-overlap.d.ts +7 -0
  58. package/lib/types/compat/open-in-app-routes.d.ts +38 -0
  59. package/lib/types/compat/process-entrypoints.d.ts +31 -0
  60. package/lib/types/compat/process-result.d.ts +14 -0
  61. package/lib/types/compat/pty-marker.d.ts +83 -0
  62. package/lib/types/compat/sandbox-confine.d.ts +2 -0
  63. package/lib/types/compat/session-directory.d.ts +4 -0
  64. package/lib/types/compat/storage-zod.d.ts +65 -0
  65. package/lib/types/compat/tool-context.d.ts +9 -0
  66. package/lib/types/compat/windows-deny-preflight.d.ts +21 -0
  67. package/lib/types/compat/windows-native.d.ts +68 -0
  68. package/lib/types/compat/windows-path-guard.d.ts +56 -0
  69. package/lib/types/compat/workspace-files.d.ts +7 -0
  70. package/lib/types/contract.d.ts +35 -4
  71. package/lib/types/dialects.d.ts +99 -3
  72. package/lib/types/fs.d.ts +32 -10
  73. package/lib/types/index.d.ts +5 -0
  74. package/lib/types/lsp.d.ts +4 -0
  75. package/lib/types/method-wrapper.d.ts +2 -0
  76. package/lib/types/process-fence.d.ts +54 -0
  77. package/lib/types/profile-isolation.d.ts +178 -0
  78. package/lib/types/providers.d.ts +70 -0
  79. package/lib/types/registry.d.ts +132 -31
  80. package/lib/types/roots.d.ts +100 -13
  81. package/lib/types/sandbox.d.ts +59 -12
  82. package/lib/types/scope.d.ts +203 -14
  83. package/lib/types/windows-broker.d.ts +17 -0
  84. package/lib/types/windows-deny.d.ts +1 -0
  85. package/lib/types/windows-profile.d.ts +10 -0
  86. package/lib/types/windows-runner-contract.d.ts +97 -0
  87. package/lib/types/windows-runner.d.ts +1 -0
  88. package/lib/types/workspace-files.d.ts +4 -0
  89. package/lib/types/workspace-routing.d.ts +11 -0
  90. package/lib/windows-runner-contract-CEfmZzu0.js +141 -0
  91. package/lib/windows-runner.js +1311 -0
  92. package/lib/workspace-files.js +203 -0
  93. package/lib/workspace-routing-YPxaQ4V3.js +26 -0
  94. package/package.json +116 -51
  95. package/scripts/windows-broker/install.ps1 +89 -0
  96. package/scripts/windows-broker/rights.cs +58 -0
  97. package/scripts/windows-broker/service.cs +323 -0
  98. package/scripts/windows-broker/verify-install.ps1 +80 -0
  99. package/lib/command-DyOWujPo.js +0 -690
  100. package/lib/sandbox-D8Fw95FM.js +0 -290
  101. package/lib/{registry-lease-win32-dy8TQMD9.js → registry-lease-win32-DTO6QpT9.js} +2 -2
@@ -0,0 +1,53 @@
1
+ /** Browser availability/choice state and the launch carrier for the split button. */
2
+ import { type SnapshotStore } from '@deepseek-ai/dsh-client-store';
3
+ /** Shared launch status for controls targeting the captured workspace path. */
4
+ export interface OpenInAppLaunchState {
5
+ readonly phase: 'idle' | 'busy' | 'error';
6
+ readonly path: string | null;
7
+ }
8
+ type Fetch = (input: string | URL, init?: RequestInit) => Promise<Response>;
9
+ /**
10
+ * Owns the once-per-page availability read, the persisted last choice, and
11
+ * the launch POST. Availability and choice publish through uSES-safe sources
12
+ * so every Session header shares one truth.
13
+ */
14
+ export declare class OpenInAppController {
15
+ private readonly fetcher;
16
+ /** Installed app ids in host menu order; null until the host answered. */
17
+ readonly apps: SnapshotStore<readonly string[] | null>;
18
+ /** Last chosen app id, or empty before the first choice, shared across sessions and browser restarts. */
19
+ readonly choice: SnapshotStore<string>;
20
+ /** Current launch, shared by pointer and keyboard gestures. */
21
+ readonly operation: SnapshotStore<OpenInAppLaunchState>;
22
+ /**
23
+ * Resolve the remembered nameable installed application, with the button's first-app fallback.
24
+ * @returns the installed app id, or undefined while unavailable.
25
+ */
26
+ currentApp(): string | undefined;
27
+ private loading;
28
+ /**
29
+ * @param fetcher - HTTP carrier for the apps read and the launch POST.
30
+ */
31
+ constructor(fetcher?: Fetch);
32
+ /**
33
+ * Read availability once per controller life; concurrent calls share the read.
34
+ * A failed read publishes an empty list, which renders no button at all.
35
+ * @returns after availability is published.
36
+ */
37
+ load(): Promise<void>;
38
+ /**
39
+ * Remember one picked app id.
40
+ * @param appId - catalog id from the availability list.
41
+ */
42
+ choose(appId: string): void;
43
+ /**
44
+ * Launch one installed app on a workspace directory.
45
+ * @param appId - catalog id from the availability list.
46
+ * @param path - the session's absolute workspace directory.
47
+ * Concurrent gestures are ignored until the current Host request settles.
48
+ * @returns after the host acknowledged the launch; rejects on any failure.
49
+ */
50
+ launch(appId: string, path: string): Promise<void>;
51
+ private run;
52
+ }
53
+ export {};
@@ -0,0 +1,10 @@
1
+ /** Dictionary namespace owned by upstream's plugin (verbatim). */
2
+ export declare const NS = "open-in-app";
3
+ /** Key domain of the `open-in-app` namespace (verbatim union of upstream's zh keys). */
4
+ export type OpenInAppKey = 'open.title' | 'path.appDefault' | 'path.appsError' | 'shortcut.busy' | 'shortcut.unavailable' | 'open.tooltip' | 'path.open' | 'path.more' | 'path.reveal' | 'path.openError' | 'path.revealError' | 'app.cursor' | 'app.vscode' | 'app.vscodeinsiders' | 'app.windsurf' | 'app.zed' | 'app.sublimetext' | 'app.xcode' | 'app.androidstudio' | 'app.intellij' | 'app.pycharm' | 'app.webstorm' | 'app.phpstorm' | 'app.goland' | 'app.rider' | 'app.rustrover' | 'app.fork' | 'app.sourcetree' | 'app.github' | 'app.tower' | 'app.gitkraken' | 'app.smartgit' | 'app.sublimemerge' | 'app.ghostty' | 'app.warp' | 'app.iterm' | 'app.kitty' | 'app.terminal' | 'app.windowsterminal' | 'app.gitbash' | 'app.gnometerminal' | 'app.konsole' | 'app.finder' | 'app.explorer' | 'app.filemanager';
5
+ declare module '@deepseek-ai/dsh-client-ui-slots' {
6
+ interface LocaleNamespaceMap {
7
+ /** Session-header "open workspace in application" copy (upstream declaration). */
8
+ 'open-in-app': OpenInAppKey;
9
+ }
10
+ }
@@ -0,0 +1,11 @@
1
+ import type { ReactNode } from 'react';
2
+ /**
3
+ * Transient failure banner owned by the control that initiated the gesture,
4
+ * so one failed request announces once, from the control the user pressed.
5
+ * @returns `toast` owned by the control and `show` to announce one
6
+ * failure with resolved copy; announcing again replays the banner.
7
+ */
8
+ export declare function useOpenFailureToast(): {
9
+ toast: ReactNode;
10
+ show: (text: string) => void;
11
+ };
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Fork-facing shim for upstream `ui-open-in-app/src/client/open-path.ts`.
3
+ *
4
+ * The fork only needs the failure union `OpenInAppPathButton`'s `execute`
5
+ * returns; upstream's file also carries `OpenInAppPathController`, the
6
+ * document-preview path controller that speaks to a Session Remote this
7
+ * plugin does not hold. Keeping the verbatim file would pull
8
+ * `@deepseek-ai/dsh-api-remotes` and `dsh-api-session-controller` into the
9
+ * bundle for a class nothing here calls — so this shim owns just the type,
10
+ * spelled verbatim. See FORK.md.
11
+ */
12
+ /** Failure kind of one settled path gesture, for the initiating control to announce. */
13
+ export type OpenInAppPathFailure = 'openError' | 'revealError';
@@ -43,11 +43,12 @@ export interface PanelClient {
43
43
  * @param endpoint - the endpoint name; it also selects the response type.
44
44
  * @param payload - the request fields for that endpoint (the endpoint name
45
45
  * travels as the RPC method, so it is not repeated in the payload).
46
+ * @param signal - optional cancellation; aborting rejects the call.
46
47
  * @returns the host's value, in the shape the endpoint promises.
47
48
  * @throws {PanelError} when the host answered with a failure, or with a value
48
49
  * that is not what this endpoint promises.
49
50
  */
50
- call<E extends PanelEndpoint>(endpoint: E, payload: PanelRequest): Promise<PanelResponseMap[E]>;
51
+ call<E extends PanelEndpoint>(endpoint: E, payload: PanelRequest, signal?: AbortSignal): Promise<PanelResponseMap[E]>;
51
52
  }
52
53
  /**
53
54
  * Build the panel client over a Connection handle.
@@ -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.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-description {\n margin: 0 0 8px;\n font-size: 14px;\n line-height: 22px;\n color: var(--dsw-alias-label-tertiary);\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.mrfw-rootText {\n display: flex;\n flex: 1 1 240px;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n}\n\n.mrfw-rootName {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 14px;\n line-height: 20px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\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.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: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.mrfw-btnDanger:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover-danger);\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-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.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-description {\n margin: 0 0 8px;\n font-size: 14px;\n line-height: 22px;\n color: var(--dsw-alias-label-tertiary);\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.mrfw-rootText {\n display: flex;\n flex: 1 1 240px;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n}\n\n/* The name line is a flex row: the truncating name text first, the access\n badge beside it never shrinking \u2014 baseline-aligned so the badge's smaller\n voice sits on the name's baseline rather than its middle. */\n.mrfw-rootName {\n display: flex;\n align-items: baseline;\n gap: 6px;\n min-width: 0;\n overflow: hidden;\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: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-accessBadge,\n.mrfw-effectiveAccess {\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";
@@ -28,6 +28,7 @@
28
28
  * @module @dsh-electron/dsh-plugin-multi-root-workspace/command
29
29
  */
30
30
  import { Context } from '@deepseek-ai/cordis';
31
+ import type { SandboxMode } from '@deepseek-ai/dsh-sandbox';
31
32
  import { type RootStatus } from './roots.ts';
32
33
  /** Services this row needs before it may activate. */
33
34
  export declare const inject: string[];
@@ -36,7 +37,7 @@ export declare const COMMAND_NAME = "workspace-folders";
36
37
  /** One parsed command line. */
37
38
  export interface FoldersCommand {
38
39
  /** The subcommand; `list` when the line named none. */
39
- readonly verb: 'list' | 'add' | 'remove' | 'alias' | 'reveal' | 'help';
40
+ readonly verb: 'list' | 'add' | 'remove' | 'alias' | 'access' | 'reveal' | 'isolate-profiles' | 'help';
40
41
  /** Everything after the verb, verbatim (paths may contain spaces). */
41
42
  readonly rest: string;
42
43
  }
@@ -56,12 +57,18 @@ export declare function parseFoldersCommand(rawInput: string): FoldersCommand;
56
57
  * @param unavailable - the store failure, when the registry could not read it.
57
58
  * @returns the report text.
58
59
  */
59
- export declare function renderRootsReport(primaryRoot: string, statuses: readonly RootStatus[], unavailable?: string): string;
60
+ export declare function renderRootsReport(primaryRoot: string, statuses: readonly RootStatus[], unavailable?: string, limit?: number, mode?: SandboxMode): string;
61
+ /** The command-row config: the canonical DSH home `isolate-profiles` inspects. */
62
+ export interface CommandConfig {
63
+ /** `$DSH_HOME` as the host resolved it — an absolute path, or the verb fails. */
64
+ readonly dshHome?: string;
65
+ }
60
66
  /**
61
67
  * Register the command and, where a host Connection exists, the panel channel.
62
68
  * @param ctx - the host context carrying `commands`, `sandboxPolicy`, and the registry.
69
+ * @param config - the row's config; `dshHome` feeds `isolate-profiles` only.
63
70
  */
64
- export declare function apply(ctx: Context): void;
71
+ export declare function apply(ctx: Context, config?: CommandConfig): void;
65
72
  /**
66
73
  * Derive the primary root a panel request may act on.
67
74
  *
@@ -81,3 +88,9 @@ export declare function apply(ctx: Context): void;
81
88
  export declare function resolvePanelPrimaryRoot(ctx: Context, sessionId: string): string;
82
89
  /** The per-platform argv that reveals one directory in the OS file manager. */
83
90
  export declare function revealArgv(platform: NodeJS.Platform, path: string): readonly string[];
91
+ /**
92
+ * Whether the file manager's exit code means the reveal was accepted.
93
+ * `explorer.exe` answers 1 even when it successfully selected the item, so on
94
+ * Windows both 0 and 1 are success; the other managers honor the usual 0.
95
+ */
96
+ export declare function revealAccepted(platform: NodeJS.Platform, exitCode: number | null): boolean;
@@ -28,6 +28,19 @@ type IconModule = Readonly<Record<string, PanelIcon | undefined>>;
28
28
  * break to adapt here, not a missing optional peer.
29
29
  */
30
30
  export declare function selectIcon(module: IconModule, names: readonly string[]): PanelIcon;
31
+ /**
32
+ * The same by-name probe for a decorative glyph a release may simply not
33
+ * carry: absence degrades to no icon, not to a boot error. Required icons go
34
+ * through `selectIcon` and fail loud; optional ones go through here.
35
+ */
36
+ export declare function maybeIcon(names: readonly string[]): PanelIcon | undefined;
37
+ /**
38
+ * The same by-name probe for a whole component a release may not carry:
39
+ * `PathLabel` arrived in `0.1.7-alpha.1`, so pixel-era runtimes degrade to a
40
+ * plain span where the header invites one. Absence degrades to no component,
41
+ * not to a boot error.
42
+ */
43
+ export declare function maybeComponent<T>(names: readonly string[]): T | undefined;
31
44
  export declare const IconChevronDown: PanelIcon;
32
45
  export declare const IconChevronUp: PanelIcon;
33
46
  export declare const IconCopy: PanelIcon;
@@ -36,5 +49,10 @@ export declare const IconEllipsis: PanelIcon;
36
49
  export declare const IconFolderClose: PanelIcon;
37
50
  export declare const IconFolderOpen: PanelIcon;
38
51
  export declare const IconPlus: PanelIcon;
52
+ export declare const IconRefresh: PanelIcon;
53
+ export declare const IconSettings: PanelIcon;
39
54
  export declare const IconTrash: PanelIcon;
55
+ export declare const IconAccessReadWrite: PanelIcon | undefined;
56
+ export declare const IconAccessReadOnly: PanelIcon | undefined;
57
+ export declare const IconAccessInherit: PanelIcon | undefined;
40
58
  export {};
@@ -30,3 +30,5 @@
30
30
  * @returns the session id, or `undefined` when no main view holds one.
31
31
  */
32
32
  export declare function currentSessionIdOf(sessions: unknown): string | undefined;
33
+ /** Observe native catalog changes when this surface provides a subscribable store. */
34
+ export declare function subscribeSessions(sessions: unknown, listener: () => void): () => void;
@@ -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"];
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"];
32
32
  /** One release this plugin claims to support. */
33
33
  export type SupportedDshRelease = typeof SUPPORTED_DSH_RELEASES[number];
34
34
  /**
@@ -42,9 +42,11 @@ 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-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-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
  /** Every package the contract inspects, required first. */
47
- 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-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"];
47
+ 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-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"];
48
+ /** Services looked up structurally; older runtimes never install these modules. */
49
+ export declare const HOST_RESOLVED_PACKAGES: readonly ["@deepseek-ai/dsh-working-directory"];
48
50
  /** How this plugin reacts to an installation it does not support. */
49
51
  export type CompatEnforcement = 'enforce' | 'warn';
50
52
  /** The environment variable that relaxes the gate. */
@@ -0,0 +1,7 @@
1
+ import type { UserMessage } from '@deepseek-ai/dsh-llm';
2
+ import type { LoadedInstructionFile } from './agent-instructions.ts';
3
+ export interface NativeInstructionCopies {
4
+ pathOf: (file: LoadedInstructionFile) => string | undefined;
5
+ withdraws: (displayPath: string) => boolean;
6
+ }
7
+ export declare function nativeInstructionCopies(messages: readonly UserMessage[], cwd: string | undefined): NativeInstructionCopies;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Compatibility adapter for the route constants of
3
+ * `@deepseek-ai/dsh-host-open-in-app/shared`.
4
+ *
5
+ * ```text
6
+ * 0.1.5-rc.2 / 0.1.6 OPEN_IN_APP_ICON_PREFIX = '/open-in-app/icon' (absolute pathname)
7
+ * 0.1.7-alpha.1 + OPEN_IN_APP_ICON_PREFIX_ROUTE = 'open-in-app/icon' (document-relative)
8
+ * ```
9
+ *
10
+ * `0.1.7-alpha.1` split every shared route constant into a `*_PATH` spelling
11
+ * the Host registers and a `*_ROUTE` spelling the browser addresses, retiring
12
+ * the old names. The fork's `iconUrl` needs the browser-facing form — which a
13
+ * pixel-era release spells without the `_ROUTE` suffix, so a named import of
14
+ * the new name does not even BUILD against those manifests (MISSING_EXPORT at
15
+ * bundle time, not a runtime undefined). The lookup is by export name, not by
16
+ * release string.
17
+ *
18
+ * @module @dsh-electron/dsh-plugin-multi-root-workspace/compat/open-in-app-routes
19
+ */
20
+ /** Exports addressed by name. A release spells each route exactly one way. */
21
+ type SharedModule = Readonly<Record<string, string | undefined>>;
22
+ /**
23
+ * Pick the first export name this release actually provides.
24
+ * @param module - the shared module to read.
25
+ * @param names - candidate export names, preferred first.
26
+ * @returns the route string.
27
+ * @throws {Error} when none of the names is exported. That is a compatibility
28
+ * break to adapt here, not a missing optional peer.
29
+ */
30
+ export declare function selectSharedRoute(module: SharedModule, names: readonly string[]): string;
31
+ /**
32
+ * The browser-facing icon-prefix route, whichever spelling this release
33
+ * exports. Resolved once at module load: the fork's adapter only reaches
34
+ * `iconUrl` on runtimes whose glyph gate already passed, and on the pixel era
35
+ * the absolute form is exactly what upstream's own client fetched.
36
+ */
37
+ export declare const OPEN_IN_APP_ICON_URL_PREFIX: string;
38
+ export {};
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Compatibility adapter: which public executor entries the mounted process
3
+ * services expose.
4
+ *
5
+ * The process fence (`src/process-fence.ts`) wraps the entries through which
6
+ * a `danger-full-access` call can spawn an ambient subprocess. Which of them
7
+ * exist is a per-release shape — `execute` is the shell executor's only entry
8
+ * on current releases while `start` existed through 0.1.6, and the PTC
9
+ * runtime's public seam is `run` (its `execute` helper is private to the
10
+ * concrete runtime and is deliberately never a cross-package seam). The fence
11
+ * therefore asks this module, structurally, which entries the MOUNTED service
12
+ * actually carries — never a version string.
13
+ *
14
+ * @module @dsh-electron/dsh-plugin-multi-root-workspace/compat/process-entrypoints
15
+ */
16
+ /** A public shell/executor entry the process fence can wrap. */
17
+ export type ProcessEntrypoint = 'execute' | 'run' | 'start';
18
+ /**
19
+ * The public shell executor entries present on the mounted service, in this
20
+ * order: `execute`, `run`, `start` (`start` existed through 0.1.6).
21
+ * @param shell - the mounted `ctx.shell` service.
22
+ * @returns the present entries — a structural probe only.
23
+ */
24
+ export declare function shellEntrypoints(shell: object): readonly ProcessEntrypoint[];
25
+ /**
26
+ * The PTC runtime's public entry: `['run']` when it is a function; never the
27
+ * concrete runtime's private `execute` helper.
28
+ * @param runtime - the mounted `ctx.ptcRuntime` service.
29
+ * @returns the present entries — a structural probe only.
30
+ */
31
+ export declare function ptcEntrypoints(runtime: object): readonly ProcessEntrypoint[];
@@ -0,0 +1,14 @@
1
+ /** Structural adaptation of settled shell executions and PTC results. Keeps
2
+ * the execution identity, cached result, and installed sync/async shape.
3
+ */
4
+ import type { ConfinedArgv, SandboxExecutionPolicy } from '@deepseek-ai/dsh-sandbox';
5
+ /** Diagnostics a caller can supply for outcomes that carry no stderr of their
6
+ * own — a non-consuming `collected.stderr.readFrom(0)` tail on the subprocess
7
+ * the confined call spawned, read at settle the way the executor's own
8
+ * `onProcessDone` seam does.
9
+ */
10
+ export interface SettledOutcomeDiagnostics {
11
+ readonly stderr?: (() => string | undefined) | undefined;
12
+ }
13
+ export declare function stampProcessResult(value: unknown, facts: ConfinedArgv, policy: SandboxExecutionPolicy): unknown;
14
+ export declare function decorateProcessOutcome(value: unknown, facts: ConfinedArgv, policy: SandboxExecutionPolicy, diagnostics?: SettledOutcomeDiagnostics): unknown;
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Boot-time probe for the agent-PTY marker contract.
3
+ *
4
+ * `multi-root-process-fence` tells an agent terminal from the user's own by
5
+ * one env fact: every supported `dsh-terminal-bash` backend stamps
6
+ * `DSH_PTY_SESSION_ID` (alongside `DSH_SESSION_ID`) into the PTY spawn spec,
7
+ * while the panel's terminal controller stamps only `DSH_SESSION_ID`. That
8
+ * marker is the ONLY discriminator — a backend that ever stops stamping it
9
+ * would reclassify agent PTYs as user terminals and pass them through
10
+ * unconfined under a restricted `danger-full-access`, which is a silent
11
+ * fail-open on a security boundary.
12
+ *
13
+ * The marker is an env key inside upstream's emitted code, not an exported
14
+ * symbol, so the honest structural probe is content: the installed backend
15
+ * package must still write the marker. This module is called from the
16
+ * `multi-root-compat` gate (`src/compat.ts`) BEFORE the service registers —
17
+ * like the version verdict it is a precondition, not a diagnostic, and a
18
+ * breach refuses the composition the same way `mixed`/`incomplete` do: it is
19
+ * not relaxable by `DSH_MULTI_ROOT_COMPAT=warn`.
20
+ *
21
+ * Resolution follows the optional-peer precedent (`agent-instructions.ts`):
22
+ * the backend is not a peer of this plugin, so the plugin anchor is tried
23
+ * first (flat layouts) and the `@deepseek-ai/dsh` host anchor second — the
24
+ * same `HOST_RESOLVED_PACKAGES` pattern as `dsh-version.ts` — because the
25
+ * backend rides inside the harness meta package's own dependency tree.
26
+ *
27
+ * @module @dsh-electron/dsh-plugin-multi-root-workspace/compat/pty-marker
28
+ */
29
+ /** The env key that identifies an agent-launched PTY spawn spec. */
30
+ export declare const AGENT_PTY_MARKER = "DSH_PTY_SESSION_ID";
31
+ /**
32
+ * What the probe established about the installed backend.
33
+ *
34
+ * - `stamped` — the marker assignment is present in the installed code.
35
+ * - `uninstalled` — no `dsh-terminal-bash` resolves; an installation without
36
+ * the backend can never launch an agent PTY, so the contract holds vacuously.
37
+ * - `unverifiable` — the package resolves but the marker cannot be confirmed:
38
+ * absent from the emitted code, or the code cannot be read. Either way the
39
+ * fence's agent/user split is unproven, and the gate must refuse rather
40
+ * than guess — "cannot verify" is not "nothing is stamped".
41
+ */
42
+ export type AgentPtyMarkerVerdict = 'stamped' | 'uninstalled' | 'unverifiable';
43
+ /**
44
+ * Raised when the installed terminal backend does not uphold the marker
45
+ * contract. Thrown from `apply` like `DshCompatUnsupportedError`, so it
46
+ * surfaces as a plugin load failure with the diagnosis attached.
47
+ */
48
+ export declare class PtyMarkerContractError extends Error {
49
+ /** The resolved backend path when one was found. */
50
+ readonly backend: string | undefined;
51
+ constructor(message: string, backend?: string);
52
+ }
53
+ /** The filesystem seams the probe needs — injectable so tests stay pure. */
54
+ export interface AgentPtyMarkerIo {
55
+ /**
56
+ * Resolve the backend package's install root, or `undefined` when it is not
57
+ * installed anywhere the contract can see. A resolution failure that is not
58
+ * "module not found" must THROW — the package may exist and be malformed,
59
+ * which is `unverifiable`, not `uninstalled`.
60
+ */
61
+ resolveBackendRoot(): string | undefined;
62
+ /** Every emitted `.js` file under the package root, bounded by the caps. */
63
+ listSources(root: string): string[];
64
+ /** One file's contents; throws when the file cannot be read. */
65
+ read(path: string): string;
66
+ }
67
+ /**
68
+ * Judge the installed terminal backend against the marker contract.
69
+ *
70
+ * Never throws: every outcome — absent package, unreadable source, missing
71
+ * marker — is folded into the verdict so the caller's reaction is a policy
72
+ * decision in one place.
73
+ * @param io - filesystem seams; tests inject them, production uses real fs.
74
+ * @returns the verdict.
75
+ */
76
+ export declare function agentPtyMarkerVerdict(io?: AgentPtyMarkerIo): AgentPtyMarkerVerdict;
77
+ /**
78
+ * Enforce the contract: refuse when the marker cannot be confirmed.
79
+ *
80
+ * @param io - filesystem seams; tests inject them.
81
+ * @throws {PtyMarkerContractError} on `unverifiable`.
82
+ */
83
+ export declare function assertAgentPtyMarker(io?: AgentPtyMarkerIo): void;
@@ -43,6 +43,8 @@ export type UpstreamConfined = ReturnType<LocalSandboxProvider['confine']>;
43
43
  * cancellation support simply never observes it.
44
44
  */
45
45
  export type ConfineCall = (argv: readonly string[], policy: SandboxPolicy, signal?: AbortSignal) => ConfinedArgv | Promise<ConfinedArgv>;
46
+ /** Map an installed confine result without changing its synchronous shape. */
47
+ export declare function mapConfined<T>(value: UpstreamConfined, map: (confined: ConfinedArgv) => T): T | Promise<T>;
46
48
  /**
47
49
  * Call the upstream `confine` and widen its result, preserving sync/async shape.
48
50
  *
@@ -0,0 +1,4 @@
1
+ /** Optional upstream workingDirectory lookup; identity always stays in the Session header. */
2
+ import type { Context } from '@deepseek-ai/cordis';
3
+ import type { Session } from '@deepseek-ai/dsh-session';
4
+ export declare function effectiveDirectory(ctx: Context, session: Session): string | undefined;
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Build the domain record schemas with the zod module the installed
3
+ * `@deepseek-ai/dsh-storage-domain` resolves.
4
+ *
5
+ * ```text
6
+ * through 0.2.1-alpha.2 storage-domain declares zod ^4.4.3
7
+ * from 0.2.1-alpha.3 storage-domain declares zod 4.4.3 exactly
8
+ * ```
9
+ *
10
+ * `domainTable` types its `schema` parameter as `ZodType`, resolved from
11
+ * whichever zod copy storage-domain itself depends on — never from the copy
12
+ * this package installs for its own validation work (`src/contract.ts`
13
+ * parses the panel wire shapes with it). While upstream declared `^4.4.3`,
14
+ * both sides resolved to one install; the exact `4.4.3` pin since
15
+ * `0.2.1-alpha.3` splits the tree into two resolutions of the same v4 line,
16
+ * and zod v4 brands every type with the minor it was compiled by, so a
17
+ * schema produced by `4.6.x` is not assignable to a `ZodType` declared by
18
+ * `4.4.3`. Pinning this package to `4.4.3` would only move the split to the
19
+ * other side: on the supported releases before the pin changed, upstream's
20
+ * `^4.4.3` resolves to `4.6.x` and the pinned copy is the foreign one.
21
+ *
22
+ * The fix resolves the BUILDER the same way the boundary resolves the TYPE:
23
+ * `require('zod')` from inside the storage-domain package. A schema built by
24
+ * that namespace is by construction an instance of the `ZodType` the
25
+ * installed `domainTable` accepts, on every supported release — whichever
26
+ * zod version a release pins, both sides keep coming from one module.
27
+ *
28
+ * @module @dsh-electron/dsh-plugin-multi-root-workspace/compat/storage-zod
29
+ */
30
+ import type { DomainTableSpec } from '@deepseek-ai/dsh-storage-domain';
31
+ /**
32
+ * The `ZodType` the installed `domainTable` accepts for records of `V`.
33
+ * Indexed off `DomainTableSpec` rather than named `zod`: the specifier would
34
+ * resolve THIS package's zod copy, which is exactly the split this module
35
+ * exists to avoid.
36
+ */
37
+ export type DomainValueSchema<V> = DomainTableSpec<string, V>['valueSchema'];
38
+ /**
39
+ * The slice of the zod namespace the domain schemas are built from.
40
+ * Structurally typed — `typeof import('zod')` would brand every product with
41
+ * this package's copy — and products stay `unknown`-shaped; the
42
+ * {@link DomainValueSchema} annotation on the schema declaration is what
43
+ * binds them to the boundary type.
44
+ */
45
+ interface DomainZodModule {
46
+ object(shape: Record<string, unknown>): any;
47
+ string(): {
48
+ min(length: number): any;
49
+ optional(): any;
50
+ };
51
+ array(item: unknown): any;
52
+ }
53
+ /**
54
+ * The `z` namespace as the installed `@deepseek-ai/dsh-storage-domain`
55
+ * resolves it.
56
+ *
57
+ * A resolution failure propagates rather than falling back to this package's
58
+ * zod: a storage-domain that no longer depends on zod changes what
59
+ * `domainTable` accepts, and that is an adapter decision, not a silent
60
+ * degradation. The failure surfaces where a missing peer already would — at
61
+ * module load, before any row runs.
62
+ * @returns the zod module, typed as the used surface only.
63
+ */
64
+ export declare function domainZod(): DomainZodModule;
65
+ export {};
@@ -0,0 +1,9 @@
1
+ /** The around-dispatch seam is optional on older hosts; do not import their tool runtime. */
2
+ import type { Context } from '@deepseek-ai/cordis';
3
+ import type { Agent } from '@deepseek-ai/dsh-agent';
4
+ export interface ToolCaller {
5
+ readonly agent?: Agent;
6
+ readonly callId: string;
7
+ readonly name: string;
8
+ }
9
+ export declare function aroundTool(ctx: Context, listener: (exec: ToolCaller, next: () => Promise<unknown>) => Promise<unknown>): void;
@@ -0,0 +1,21 @@
1
+ import type { WindowsPathIdentity } from './windows-path-guard.ts';
2
+ import type { NativeApi, NativePointer, WindowsNative } from './windows-native.ts';
3
+ export interface DenyPreflightEntry {
4
+ /** The deny root (canonical spelling) this entry was scanned under. */
5
+ readonly root: string;
6
+ /** The entry's full path spelling as walked — the key lowercased. */
7
+ readonly path: string;
8
+ readonly identity: WindowsPathIdentity;
9
+ }
10
+ export interface WindowsDenyPreflight {
11
+ /** Every scanned node in depth-first insertion order, keyed `path.toLowerCase()`. */
12
+ readonly entries: ReadonlyMap<string, DenyPreflightEntry>;
13
+ /** Refuse if the live handle's identity differs from the snapshot. */
14
+ verifyHandle(api: NativeApi, path: string, handle: NativePointer): void;
15
+ }
16
+ /**
17
+ * Scan every root recursively. The returned snapshot keys every node by its
18
+ * lowercased walked spelling; a later same-key path (shared subtree) keeps
19
+ * the first entry and is not re-scanned.
20
+ */
21
+ export declare function preflightWindowsDenyRoots(native: WindowsNative, roots: readonly string[], protectedHome?: string): WindowsDenyPreflight;