@huanlin/dsh-plugin-ya-workspace-sidebar 0.6.1 → 0.8.0

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.
@@ -1,4 +1,4 @@
1
- /** One scoped stylesheet injected for the lifetime of the client activation. */
2
- export declare const CSS = "\n[data-ya-workspace-sidebar] { flex:1; min-height:0; display:flex; flex-direction:column; box-sizing:border-box; padding-right:var(--dsh-sidebar-inline-padding); color:var(--dsw-alias-label-primary); }\n[data-ya-workspace-sidebar].ya-rail { padding-right:0; }\n.ya-section-header { flex:none; height:36px; display:flex; align-items:center; justify-content:flex-end; gap:4px; padding-left:12px; margin-bottom:4px; box-sizing:border-box; color:var(--dsw-alias-label-tertiary); }\n.ya-section-title { flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:14px; }\n.ya-icon-button { flex:none; width:28px; height:28px; border:0; border-radius:50%; padding:0; display:inline-flex; align-items:center; justify-content:center; color:var(--dsw-alias-label-secondary); background:transparent; cursor:pointer; }\n.ya-icon-button:hover { background:var(--dsw-alias-interactive-bg-hover); }\n.ya-search { flex:none; height:38px; margin:0 2px 10px; padding:0 14px; display:flex; align-items:center; gap:8px; box-sizing:border-box; border:1px solid var(--dsw-alias-border-l2); border-radius:24px; background:var(--dsw-static-neutral-bluish-75); color:var(--dsw-alias-label-caption); }\nbody[data-ds-dark-theme] .ya-search { background:var(--dsw-static-neutral-bluish-900); }\n.ya-search-input { flex:1; min-width:0; border:0; outline:0; background:transparent; color:var(--dsw-alias-label-primary); font:inherit; font-size:14px; }\n.ya-search-input::placeholder { color:var(--dsw-alias-label-tertiary); }\n.ya-search-icon { flex:none; display:inline-flex; border:0; padding:0; color:inherit; background:transparent; }\n.ya-body { flex:1; min-height:0; display:flex; flex-direction:column; overflow:hidden; margin-right:calc(-1 * var(--dsh-sidebar-inline-padding)); padding-right:var(--dsh-sidebar-inline-padding); }\n.ya-recent { flex:none; }\n.ya-recent-list-wrap { display:grid; grid-template-rows:1fr; transition:grid-template-rows 220ms ease-out; }\n.ya-recent-collapsed .ya-recent-list-wrap { grid-template-rows:0fr; }\n.ya-recent-list { position:relative; overflow-y:auto; overscroll-behavior:contain; min-height:0; max-height:min(280px, 40vh); }\n.ya-virtual-canvas { position:relative; width:100%; }\n.ya-virtual-slot { position:absolute; left:0; right:0; height:35px; box-sizing:border-box; padding:1px 0; display:flex; flex-direction:column; justify-content:center; }\n.ya-virtual-slot .ya-row { margin:0; min-height:33px; }\n.ya-recent-resizer { position:relative; flex:none; height:9px; margin:1px 0 3px; display:flex; align-items:center; justify-content:center; border-radius:5px; cursor:row-resize; touch-action:none; }\n.ya-recent-resizer::before { content:''; position:absolute; left:8px; right:8px; top:50%; height:1px; transform:translateY(-0.5px); background:var(--dsw-alias-border-l2); }\n.ya-recent-resizer::after { content:''; width:26px; height:3px; border-radius:2px; background:var(--dsw-alias-border-l2); transition:background 120ms ease-out; }\n.ya-recent-resizer:hover::after, .ya-recent-resizer:focus-visible::after, .ya-recent-resizer.ya-resizing::after { background:var(--dsw-alias-label-tertiary); }\n.ya-recent-resizer.ya-resizing::before { background:var(--dsw-alias-interactive-bg-selected); }\n.ya-block-label { height:26px; display:flex; align-items:center; gap:2px; padding:0 8px; color:var(--dsw-alias-label-tertiary); font-size:12px; font-weight:600; letter-spacing:.02em; text-transform:uppercase; }\n.ya-block-label-toggle { flex:none; width:20px; height:20px; margin-left:auto; border:0; border-radius:6px; padding:0; display:inline-flex; align-items:center; justify-content:center; background:transparent; color:var(--dsw-alias-label-tertiary); cursor:pointer; transition:transform 180ms ease-out; }\n.ya-block-label-toggle:hover { background:var(--dsw-alias-interactive-bg-hover); color:var(--dsw-alias-label-secondary); }\n.ya-block-label-toggle.ya-collapsed { transform:rotate(-90deg); }\n.ya-date-group-label { height:26px; display:flex; align-items:center; padding:0 8px; color:var(--dsw-alias-label-tertiary); font-size:12px; font-weight:600; letter-spacing:.02em; }\n.ya-breadcrumb { flex:none; height:34px; display:flex; align-items:center; gap:2px; padding:0 6px; color:var(--dsw-alias-label-tertiary); font-size:13px; }\n.ya-crumb { border:0; padding:4px 3px; border-radius:6px; background:transparent; color:inherit; font:inherit; cursor:default; min-width:0; overflow:hidden; white-space:nowrap; text-overflow:ellipsis; }\nbutton.ya-crumb:hover { background:var(--dsw-alias-interactive-bg-hover); color:var(--dsw-alias-label-primary); cursor:pointer; }\n.ya-scroll { flex:1; min-height:0; overflow-y:auto; padding-bottom:12px; }\n.ya-row { position:relative; min-height:34px; display:flex; align-items:center; gap:6px; margin:1px 0; padding:0 7px; border-radius:9px; box-sizing:border-box; color:var(--dsw-alias-label-primary); cursor:pointer; user-select:none; }\n.ya-row:hover, .ya-row.ya-menu-open { background:var(--dsw-alias-interactive-bg-hover); }\n.ya-row.ya-selected { background:var(--dsw-alias-interactive-bg-selected); }\n.ya-workspace-row { min-height:40px; }\n.ya-row-main { flex:1; min-width:0; display:flex; flex-direction:column; justify-content:center; }\n.ya-row-line { display:flex; align-items:center; min-width:0; gap:6px; }\n.ya-row-title { flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:13px; line-height:18px; }\n.ya-row-meta { flex:none; color:var(--dsw-alias-label-tertiary); font-size:11px; white-space:nowrap; }\n.ya-workspace-path { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; color:var(--dsw-alias-label-tertiary); font-size:11px; line-height:15px; }\n.ya-row-actions { flex:none; display:flex; align-items:center; gap:2px; opacity:0; pointer-events:none; transition:opacity 120ms ease-out; }\n.ya-row:hover .ya-row-actions, .ya-menu-open .ya-row-actions { opacity:1; pointer-events:auto; }\n.ya-status-slot { flex:none; width:16px; height:16px; display:inline-flex; align-items:center; justify-content:center; color:var(--dsw-alias-label-tertiary); }\n.ya-recent .ya-row { min-height:31px; }\n.ya-search-workspace { color:var(--dsw-alias-label-tertiary); font-size:11px; line-height:15px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }\n.ya-empty, .ya-status { padding:18px 10px; color:var(--dsw-alias-label-tertiary); text-align:center; font-size:13px; }\n.ya-warning { color:var(--dsw-alias-status-warning); }\n.ya-rename-input { width:100%; height:38px; box-sizing:border-box; border:1px solid var(--dsw-alias-border-l2); border-radius:9px; padding:0 11px; background:transparent; color:var(--dsw-alias-label-primary); outline:none; }\n.ya-error { margin-top:8px; color:var(--dsw-alias-status-error); font-size:12px; }\n.ya-rail .ya-section-header { padding-left:0; margin-bottom:12px; }\n.ya-rail .ya-icon-button, .ya-rail .ya-search { width:36px; height:36px; padding:0; margin:0 0 12px; border-color:transparent; background:transparent; }\n.ya-rail .ya-search { justify-content:center; }\n.ya-rail .ya-search-icon { cursor:pointer; color:var(--dsw-alias-label-primary); }\n.ya-picker-error { color:var(--dsw-alias-status-error); white-space:pre-wrap; }\n.ya-action-mode-toggle.ya-action-mode-delete { color:var(--dsw-alias-state-error-primary); }\n.ya-action-mode-toggle.ya-action-mode-delete:hover { background:var(--dsw-alias-interactive-bg-hover-danger); }\n@keyframes ya-slide-in-forward { from { opacity:0; transform:translateX(10px); } to { opacity:1; transform:translateX(0); } }\n@keyframes ya-slide-in-backward { from { opacity:0; transform:translateX(-10px); } to { opacity:1; transform:translateX(0); } }\n.ya-level-enter-forward { animation:ya-slide-in-forward 180ms ease-out; }\n.ya-level-enter-backward { animation:ya-slide-in-backward 180ms ease-out; }\n";
3
- /** Install the stylesheet and return its disposer. */
4
- export declare function installStyles(): () => void;
1
+ /** One scoped stylesheet injected for the lifetime of the client activation. */
2
+ export declare const CSS = "\n[data-ya-workspace-sidebar] { flex:1; min-height:0; display:flex; flex-direction:column; box-sizing:border-box; padding-right:var(--dsh-sidebar-inline-padding); color:var(--dsw-alias-label-primary); }\n[data-ya-workspace-sidebar].ya-rail { padding-right:0; }\n.ya-section-header { flex:none; height:36px; display:flex; align-items:center; justify-content:flex-end; gap:4px; padding-left:12px; margin-bottom:4px; box-sizing:border-box; color:var(--dsw-alias-label-tertiary); }\n.ya-section-title { flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:14px; }\n.ya-icon-button { flex:none; width:28px; height:28px; border:0; border-radius:50%; padding:0; display:inline-flex; align-items:center; justify-content:center; color:var(--dsw-alias-label-secondary); background:transparent; cursor:pointer; }\n.ya-icon-button:hover { background:var(--dsw-alias-interactive-bg-hover); }\n.ya-search { flex:none; height:38px; margin:0 2px 10px; padding:0 14px; display:flex; align-items:center; gap:8px; box-sizing:border-box; border:1px solid var(--dsw-alias-border-l2); border-radius:24px; background:var(--dsw-static-neutral-bluish-75); color:var(--dsw-alias-label-caption); }\nbody[data-ds-dark-theme] .ya-search { background:var(--dsw-static-neutral-bluish-900); }\n.ya-search-input { flex:1; min-width:0; border:0; outline:0; background:transparent; color:var(--dsw-alias-label-primary); font:inherit; font-size:14px; }\n.ya-search-input::placeholder { color:var(--dsw-alias-label-tertiary); }\n.ya-search-icon { flex:none; display:inline-flex; border:0; padding:0; color:inherit; background:transparent; }\n.ya-body { flex:1; min-height:0; display:flex; flex-direction:column; overflow:hidden; margin-right:calc(-1 * var(--dsh-sidebar-inline-padding)); padding-right:var(--dsh-sidebar-inline-padding); }\n.ya-recent { flex:none; }\n.ya-recent-list-wrap { display:grid; grid-template-rows:1fr; transition:grid-template-rows 220ms ease-out; }\n.ya-recent-collapsed .ya-recent-list-wrap { grid-template-rows:0fr; }\n.ya-recent-list { position:relative; overflow-y:auto; overscroll-behavior:contain; min-height:0; max-height:min(280px, 40vh); }\n.ya-virtual-canvas { position:relative; width:100%; }\n.ya-virtual-slot { position:absolute; left:0; right:0; height:35px; box-sizing:border-box; padding:1px 0; display:flex; flex-direction:column; justify-content:center; }\n.ya-virtual-slot .ya-row { margin:0; min-height:33px; }\n.ya-recent-resizer { position:relative; flex:none; height:9px; margin:1px 0 3px; display:flex; align-items:center; justify-content:center; border-radius:5px; cursor:row-resize; touch-action:none; }\n.ya-recent-resizer::before { content:''; position:absolute; left:8px; right:8px; top:50%; height:1px; transform:translateY(-0.5px); background:var(--dsw-alias-border-l2); }\n.ya-recent-resizer::after { content:''; width:26px; height:3px; border-radius:2px; background:var(--dsw-alias-border-l2); transition:background 120ms ease-out; }\n.ya-recent-resizer:hover::after, .ya-recent-resizer:focus-visible::after, .ya-recent-resizer.ya-resizing::after { background:var(--dsw-alias-label-tertiary); }\n.ya-recent-resizer.ya-resizing::before { background:var(--dsw-alias-interactive-bg-selected); }\n.ya-block-label { height:26px; display:flex; align-items:center; gap:2px; padding:0 8px; color:var(--dsw-alias-label-tertiary); font-size:12px; font-weight:600; letter-spacing:.02em; text-transform:uppercase; }\n.ya-block-label-toggle { flex:none; width:20px; height:20px; margin-left:auto; border:0; border-radius:6px; padding:0; display:inline-flex; align-items:center; justify-content:center; background:transparent; color:var(--dsw-alias-label-tertiary); cursor:pointer; transition:transform 180ms ease-out; }\n.ya-block-label-toggle:hover { background:var(--dsw-alias-interactive-bg-hover); color:var(--dsw-alias-label-secondary); }\n.ya-block-label-toggle.ya-collapsed { transform:rotate(-90deg); }\n.ya-date-group-label { height:26px; display:flex; align-items:center; padding:0 8px; color:var(--dsw-alias-label-tertiary); font-size:12px; font-weight:600; letter-spacing:.02em; }\n.ya-breadcrumb { flex:none; height:34px; display:flex; align-items:center; gap:2px; padding:0 6px; color:var(--dsw-alias-label-tertiary); font-size:13px; }\n.ya-crumb { border:0; padding:4px 3px; border-radius:6px; background:transparent; color:inherit; font:inherit; cursor:default; min-width:0; overflow:hidden; white-space:nowrap; text-overflow:ellipsis; }\nbutton.ya-crumb:hover { background:var(--dsw-alias-interactive-bg-hover); color:var(--dsw-alias-label-primary); cursor:pointer; }\n.ya-scroll { flex:1; min-height:0; overflow-y:auto; padding-bottom:12px; }\n.ya-row { position:relative; min-height:34px; display:flex; align-items:center; gap:6px; margin:1px 0; padding:0 7px; border-radius:9px; box-sizing:border-box; color:var(--dsw-alias-label-primary); cursor:pointer; user-select:none; }\n.ya-row:hover, .ya-row.ya-menu-open { background:var(--dsw-alias-interactive-bg-hover); }\n.ya-row.ya-selected { background:var(--dsw-alias-interactive-bg-selected); }\n.ya-workspace-row { min-height:40px; }\n.ya-row-main { flex:1; min-width:0; display:flex; flex-direction:column; justify-content:center; }\n.ya-row-line { display:flex; align-items:center; min-width:0; gap:6px; }\n.ya-row-title { flex:1; min-width:0; overflow:hidden; white-space:nowrap; font-size:13px; line-height:18px; -webkit-mask-image:linear-gradient(to right,#000 calc(100% - 24px),transparent 100%); mask-image:linear-gradient(to right,#000 calc(100% - 24px),transparent 100%); }\n.ya-row-meta { flex:none; color:var(--dsw-alias-label-tertiary); font-size:11px; white-space:nowrap; }\n.ya-workspace-path { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; color:var(--dsw-alias-label-tertiary); font-size:11px; line-height:15px; }\n.ya-row-actions { flex:none; display:flex; align-items:center; gap:2px; opacity:0; pointer-events:none; transition:opacity 120ms ease-out; }\n.ya-row:hover .ya-row-actions, .ya-menu-open .ya-row-actions { opacity:1; pointer-events:auto; }\n.ya-status-slot { flex:none; width:16px; height:16px; display:inline-flex; align-items:center; justify-content:center; color:var(--dsw-alias-label-tertiary); }\n.ya-recent .ya-row { min-height:31px; }\n.ya-search-workspace { color:var(--dsw-alias-label-tertiary); font-size:11px; line-height:15px; overflow:hidden; white-space:nowrap; -webkit-mask-image:linear-gradient(to right,#000 calc(100% - 24px),transparent 100%); mask-image:linear-gradient(to right,#000 calc(100% - 24px),transparent 100%); }\n.ya-empty, .ya-status { padding:18px 10px; color:var(--dsw-alias-label-tertiary); text-align:center; font-size:13px; }\n.ya-warning { color:var(--dsw-alias-status-warning); }\n.ya-rename-input { width:100%; height:38px; box-sizing:border-box; border:1px solid var(--dsw-alias-border-l2); border-radius:9px; padding:0 11px; background:transparent; color:var(--dsw-alias-label-primary); outline:none; }\n.ya-error { margin-top:8px; color:var(--dsw-alias-status-error); font-size:12px; }\n.ya-rail .ya-section-header { padding-left:0; margin-bottom:12px; }\n.ya-rail .ya-icon-button, .ya-rail .ya-search { width:36px; height:36px; padding:0; margin:0 0 12px; border-color:transparent; background:transparent; }\n.ya-rail .ya-search { justify-content:center; }\n.ya-rail .ya-search-icon { cursor:pointer; color:var(--dsw-alias-label-primary); }\n.ya-picker-error { color:var(--dsw-alias-status-error); white-space:pre-wrap; }\n.ya-action-mode-toggle.ya-action-mode-delete { color:var(--dsw-alias-state-error-primary); }\n.ya-action-mode-toggle.ya-action-mode-delete:hover { background:var(--dsw-alias-interactive-bg-hover-danger); }\n@keyframes ya-slide-in-forward { from { opacity:0; transform:translateX(10px); } to { opacity:1; transform:translateX(0); } }\n@keyframes ya-slide-in-backward { from { opacity:0; transform:translateX(-10px); } to { opacity:1; transform:translateX(0); } }\n.ya-level-enter-forward { animation:ya-slide-in-forward 180ms ease-out; }\n.ya-level-enter-backward { animation:ya-slide-in-backward 180ms ease-out; }\n.ya-unread-dot { position:relative; flex:none; width:10px; height:10px; }\n.ya-unread-dot::before { content:''; position:absolute; inset:0; border-radius:50%; background:var(--dsw-static-deepseek-450); opacity:.1; }\n.ya-unread-dot::after { content:''; position:absolute; inset:20%; border-radius:50%; background:var(--dsw-static-deepseek-450); }\n.ya-row.ya-unread .ya-row-title { font-weight:600; }\n.ya-pin-mark { flex:none; display:inline-flex; color:var(--dsw-alias-label-tertiary); }\n.ya-row.ya-pinned .ya-pin-mark { color:var(--dsw-alias-label-secondary); }\n";
3
+ /** Install the stylesheet and return its disposer. */
4
+ export declare function installStyles(): () => void;
@@ -1,3 +1,3 @@
1
- import type { Context } from '@deepseek-ai/cordis';
2
- export declare const name = "ya-workspace-sidebar";
3
- export declare function apply(ctx: Context): void;
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ export declare const name = "ya-workspace-sidebar";
3
+ export declare function apply(ctx: Context): void;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Build the opener invocation that reveals/selects a path (pure, platform injectable).
3
+ * @param path - absolute path to reveal.
4
+ * @param platform - Node platform selector; win32 selects the entry, darwin
5
+ * reveals with `open -R`, everything else opens the parent directory.
6
+ * @returns the argv invocation (never a shell string).
7
+ */
8
+ export declare function revealCommand(path: string, platform: NodeJS.Platform): {
9
+ command: string;
10
+ args: string[];
11
+ };
12
+ /**
13
+ * Launch the reveal opener detached and return immediately.
14
+ * @param path - absolute path to reveal in the OS file manager.
15
+ * @returns started acknowledgement; opener failures after the spawn surface
16
+ * through the OS (the child's error event is swallowed on purpose).
17
+ */
18
+ export declare function launchReveal(path: string): {
19
+ started: true;
20
+ };
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Locate a session's jsonl storage directory and newest log file.
3
+ *
4
+ * The official jsonl backend lays sessions out as
5
+ * `<root>/<projectKey(cwd)>/<encodeSegment(id)>/vN.jsonl(.zstd)`. This module
6
+ * mirrors only {@link encodeSegment} (id → one safe path segment) and SCANS
7
+ * the project directories instead of reimplementing `projectKey` (its lossy
8
+ * slug rules are the fragile half); a session with several historical project
9
+ * copies resolves to the newest directory mtime. sqlite-backed deployments
10
+ * (e.g. @morlay/better-session) produce no directories at all → double null,
11
+ * which the client renders as disabled menu rows.
12
+ */
13
+ import type { Dirent } from 'node:fs';
14
+ /** Minimal fs surface (readdir withFileTypes + stat), injectable for tests. */
15
+ export interface ArtifactsIo {
16
+ readdir(path: string, options: {
17
+ withFileTypes: true;
18
+ }): Promise<Dirent[]>;
19
+ stat(path: string): Promise<{
20
+ mtimeMs: number;
21
+ }>;
22
+ }
23
+ /**
24
+ * Encode an arbitrary string as one safe path segment, injectively over all
25
+ * JS strings (parity with the official jsonl backend's segment encoder: safe
26
+ * units literal, every other unit `~XXXX`, dot segments traversal-proofed).
27
+ * @param raw - the string to encode; must be non-empty.
28
+ * @returns the escaped single path segment.
29
+ */
30
+ export declare function encodeSegment(raw: string): string;
31
+ /**
32
+ * Find the session's storage dir and newest log file under a sessions root.
33
+ * @param root - sessions root directory (`$DSH_HOME/sessions`).
34
+ * @param sessionId - raw session id (encoded here before any filesystem use).
35
+ * @param io - fs surface; a stat miss under one project directory keeps the scan going.
36
+ * @returns the newest owning directory and its highest-generation log file,
37
+ * either null when absent (sqlite deployments, unflushed sessions).
38
+ */
39
+ export declare function findSessionArtifacts(root: string, sessionId: string, io: ArtifactsIo): Promise<{
40
+ dir: string | null;
41
+ log: string | null;
42
+ }>;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Browser-trust fence for this plugin's host routes: Host-header loopback
3
+ * (or a caller-supplied trusted authority) plus same-origin browser markers.
4
+ * Behaviorally the DNS-rebinding / cross-site defense of the /api gateway
5
+ * (mirrored in condensed form from DSH-better-sidebar's trust-fence); this
6
+ * is not authentication.
7
+ */
8
+ import type { IncomingHttpHeaders } from 'node:http';
9
+ /** The request facts the fence reads (structural subset of IncomingMessage). */
10
+ interface RouteTrustRequest {
11
+ headers: IncomingHttpHeaders;
12
+ }
13
+ /**
14
+ * Decide whether one plugin route request may proceed.
15
+ * @param request - node HTTP request facts (headers).
16
+ * @param trustedHosts - non-loopback authorities this deployment serves.
17
+ * @returns true when the Host is ours (loopback or trusted) and browser
18
+ * markers are same-origin; absent Origin passes on the bound Host alone,
19
+ * the opaque "null" origin is refused.
20
+ */
21
+ export declare function isTrustedRouteRequest(request: RouteTrustRequest, trustedHosts?: readonly string[]): boolean;
22
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@huanlin/dsh-plugin-ya-workspace-sidebar",
3
- "version": "0.6.1",
3
+ "version": "0.8.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -119,6 +119,7 @@
119
119
  },
120
120
  "devDependencies": {
121
121
  "@deepseek-ai/cordis": "link:D:/Projects/deepseek-harness/dsh/vendor/cordis",
122
+ "@deepseek-ai/dsh-home-paths": "link:D:/Projects/deepseek-harness/dsh/packages/util/home-paths",
122
123
  "@deepseek-ai/dsh-api-remotes": "link:D:/Projects/deepseek-harness/dsh/packages/api/remotes",
123
124
  "@deepseek-ai/dsh-api-session-controller": "link:D:/Projects/deepseek-harness/dsh/packages/api/session-controller",
124
125
  "@deepseek-ai/dsh-api-workspace-controller": "link:D:/Projects/deepseek-harness/dsh/packages/api/workspace-controller",