@lijian-ui/dsh-file-manager 0.1.20
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/LICENSE +38 -0
- package/README.i18n.yaml +6 -0
- package/README.md +66 -0
- package/README.zh.md +66 -0
- package/cordis.patch.yml +13 -0
- package/lib/assets/mermaid.min.js +3587 -0
- package/lib/client.js +7621 -0
- package/lib/client.js.map +1 -0
- package/lib/index.js +3207 -0
- package/lib/tsconfig.client.tsbuildinfo +1 -0
- package/lib/tsconfig.host.tsbuildinfo +1 -0
- package/lib/types/client/FileManagerSettingsCard.d.ts +56 -0
- package/lib/types/client/FileManagerSettingsCard.d.ts.map +1 -0
- package/lib/types/client/PluginSettingsCard.d.ts +104 -0
- package/lib/types/client/PluginSettingsCard.d.ts.map +1 -0
- package/lib/types/client/api.d.ts +75 -0
- package/lib/types/client/api.d.ts.map +1 -0
- package/lib/types/client/chat/AttachedFilesDock.d.ts +51 -0
- package/lib/types/client/chat/AttachedFilesDock.d.ts.map +1 -0
- package/lib/types/client/chat/file-ref.d.ts +35 -0
- package/lib/types/client/chat/file-ref.d.ts.map +1 -0
- package/lib/types/client/chat/mermaid-chat.d.ts +30 -0
- package/lib/types/client/chat/mermaid-chat.d.ts.map +1 -0
- package/lib/types/client/chat/placeholder-hint.d.ts +16 -0
- package/lib/types/client/chat/placeholder-hint.d.ts.map +1 -0
- package/lib/types/client/components/ExplorerPanel.d.ts +26 -0
- package/lib/types/client/components/ExplorerPanel.d.ts.map +1 -0
- package/lib/types/client/components/FileIcon.d.ts +15 -0
- package/lib/types/client/components/FileIcon.d.ts.map +1 -0
- package/lib/types/client/components/ScmPanel.d.ts +23 -0
- package/lib/types/client/components/ScmPanel.d.ts.map +1 -0
- package/lib/types/client/components/a11y.d.ts +12 -0
- package/lib/types/client/components/a11y.d.ts.map +1 -0
- package/lib/types/client/components/icons.d.ts +41 -0
- package/lib/types/client/components/icons.d.ts.map +1 -0
- package/lib/types/client/components/overlay.d.ts +45 -0
- package/lib/types/client/components/overlay.d.ts.map +1 -0
- package/lib/types/client/drag/DragFileInlay.d.ts +31 -0
- package/lib/types/client/drag/DragFileInlay.d.ts.map +1 -0
- package/lib/types/client/drag/file-drag.d.ts +47 -0
- package/lib/types/client/drag/file-drag.d.ts.map +1 -0
- package/lib/types/client/drag.d.ts +31 -0
- package/lib/types/client/drag.d.ts.map +1 -0
- package/lib/types/client/file-source.d.ts +29 -0
- package/lib/types/client/file-source.d.ts.map +1 -0
- package/lib/types/client/fileType.d.ts +31 -0
- package/lib/types/client/fileType.d.ts.map +1 -0
- package/lib/types/client/floating.d.ts +27 -0
- package/lib/types/client/floating.d.ts.map +1 -0
- package/lib/types/client/hooks/useResizableSplit.d.ts +37 -0
- package/lib/types/client/hooks/useResizableSplit.d.ts.map +1 -0
- package/lib/types/client/hooks/useStore.d.ts +10 -0
- package/lib/types/client/hooks/useStore.d.ts.map +1 -0
- package/lib/types/client/index.d.ts +57 -0
- package/lib/types/client/index.d.ts.map +1 -0
- package/lib/types/client/layout.d.ts +104 -0
- package/lib/types/client/layout.d.ts.map +1 -0
- package/lib/types/client/locales.d.ts +125 -0
- package/lib/types/client/locales.d.ts.map +1 -0
- package/lib/types/client/maximize.d.ts +24 -0
- package/lib/types/client/maximize.d.ts.map +1 -0
- package/lib/types/client/mention.d.ts +44 -0
- package/lib/types/client/mention.d.ts.map +1 -0
- package/lib/types/client/mount.d.ts +22 -0
- package/lib/types/client/mount.d.ts.map +1 -0
- package/lib/types/client/persist.d.ts +55 -0
- package/lib/types/client/persist.d.ts.map +1 -0
- package/lib/types/client/picker/FilePickerModal.d.ts +47 -0
- package/lib/types/client/picker/FilePickerModal.d.ts.map +1 -0
- package/lib/types/client/picker/file-picker.d.ts +62 -0
- package/lib/types/client/picker/file-picker.d.ts.map +1 -0
- package/lib/types/client/preview/PreviewPanel.d.ts +17 -0
- package/lib/types/client/preview/PreviewPanel.d.ts.map +1 -0
- package/lib/types/client/preview/PreviewTabs.d.ts +30 -0
- package/lib/types/client/preview/PreviewTabs.d.ts.map +1 -0
- package/lib/types/client/preview/PreviewToolbar.d.ts +37 -0
- package/lib/types/client/preview/PreviewToolbar.d.ts.map +1 -0
- package/lib/types/client/preview/content.d.ts +43 -0
- package/lib/types/client/preview/content.d.ts.map +1 -0
- package/lib/types/client/preview/markdown.d.ts +60 -0
- package/lib/types/client/preview/markdown.d.ts.map +1 -0
- package/lib/types/client/preview/mermaid.d.ts +78 -0
- package/lib/types/client/preview/mermaid.d.ts.map +1 -0
- package/lib/types/client/preview/selection.d.ts +26 -0
- package/lib/types/client/preview/selection.d.ts.map +1 -0
- package/lib/types/client/reference.d.ts +75 -0
- package/lib/types/client/reference.d.ts.map +1 -0
- package/lib/types/client/settings-form.d.ts +206 -0
- package/lib/types/client/settings-form.d.ts.map +1 -0
- package/lib/types/client/store.d.ts +279 -0
- package/lib/types/client/store.d.ts.map +1 -0
- package/lib/types/core/types.d.ts +132 -0
- package/lib/types/core/types.d.ts.map +1 -0
- package/lib/types/host/fs-service.d.ts +96 -0
- package/lib/types/host/fs-service.d.ts.map +1 -0
- package/lib/types/host/gate.d.ts +45 -0
- package/lib/types/host/gate.d.ts.map +1 -0
- package/lib/types/host/git-runner.d.ts +80 -0
- package/lib/types/host/git-runner.d.ts.map +1 -0
- package/lib/types/host/git-service.d.ts +130 -0
- package/lib/types/host/git-service.d.ts.map +1 -0
- package/lib/types/host/loopback.d.ts +25 -0
- package/lib/types/host/loopback.d.ts.map +1 -0
- package/lib/types/host/poll-guard.d.ts +55 -0
- package/lib/types/host/poll-guard.d.ts.map +1 -0
- package/lib/types/host/routes.d.ts +54 -0
- package/lib/types/host/routes.d.ts.map +1 -0
- package/lib/types/index.d.ts +35 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/mount-once.d.ts +25 -0
- package/lib/types/mount-once.d.ts.map +1 -0
- package/package.json +85 -0
- package/src/client/FileManagerSettingsCard.tsx +117 -0
- package/src/client/PluginSettingsCard.tsx +337 -0
- package/src/client/api.ts +141 -0
- package/src/client/chat/file-ref.ts +144 -0
- package/src/client/chat/mermaid-chat.tsx +102 -0
- package/src/client/chat/placeholder-hint.tsx +46 -0
- package/src/client/components/ExplorerPanel.tsx +534 -0
- package/src/client/components/FileIcon.tsx +46 -0
- package/src/client/components/ScmPanel.tsx +480 -0
- package/src/client/components/a11y.ts +20 -0
- package/src/client/components/icons.tsx +274 -0
- package/src/client/components/overlay.tsx +225 -0
- package/src/client/css-modules.d.ts +5 -0
- package/src/client/drag/DragFileInlay.tsx +89 -0
- package/src/client/drag/file-drag.ts +67 -0
- package/src/client/drag.ts +154 -0
- package/src/client/file-source.ts +58 -0
- package/src/client/fileType.ts +129 -0
- package/src/client/floating.ts +63 -0
- package/src/client/hooks/useResizableSplit.ts +91 -0
- package/src/client/hooks/useStore.ts +15 -0
- package/src/client/index.ts +389 -0
- package/src/client/layout.ts +546 -0
- package/src/client/locales.ts +247 -0
- package/src/client/maximize.ts +34 -0
- package/src/client/mount.tsx +74 -0
- package/src/client/persist.ts +194 -0
- package/src/client/picker/FilePickerModal.tsx +506 -0
- package/src/client/picker/file-picker.ts +86 -0
- package/src/client/preview/PreviewPanel.tsx +233 -0
- package/src/client/preview/PreviewTabs.tsx +159 -0
- package/src/client/preview/PreviewToolbar.tsx +160 -0
- package/src/client/preview/content.tsx +617 -0
- package/src/client/preview/markdown.ts +351 -0
- package/src/client/preview/mermaid.ts +274 -0
- package/src/client/preview/selection.ts +82 -0
- package/src/client/reference.ts +161 -0
- package/src/client/settings-card.module.css +316 -0
- package/src/client/settings-form.ts +451 -0
- package/src/client/store.ts +1290 -0
- package/src/client/styles/chip.module.css +38 -0
- package/src/client/styles/drag.module.css +30 -0
- package/src/client/styles/explorer.module.css +371 -0
- package/src/client/styles/picker.module.css +344 -0
- package/src/client/styles/preview.module.css +904 -0
- package/src/client/styles/scm.module.css +380 -0
- package/src/client/styles/tokens.module.css +480 -0
- package/src/core/types.ts +162 -0
- package/src/host/fs-service.ts +513 -0
- package/src/host/gate.ts +74 -0
- package/src/host/git-runner.ts +116 -0
- package/src/host/git-service.ts +395 -0
- package/src/host/loopback.ts +63 -0
- package/src/host/poll-guard.ts +109 -0
- package/src/host/routes.ts +695 -0
- package/src/index.ts +97 -0
- package/src/mount-once.ts +48 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host filesystem service for the panel: directory listing, file read with a
|
|
3
|
+
* preview ceiling, text write with an mtime conflict check, filename search
|
|
4
|
+
* with directory pruning, and delete (untracked discard). Every operation
|
|
5
|
+
* resolves against a gated project root and refuses to escape it (path
|
|
6
|
+
* traversal guard). Text is decoded utf-8; images come back as data URLs
|
|
7
|
+
* (capped) so the browser renders them without extra round trips.
|
|
8
|
+
*
|
|
9
|
+
* Change delivery is intentionally NOT watch-based: the panel refreshes on
|
|
10
|
+
* user actions (list/read/write) and the SCM tab polls git status on its own
|
|
11
|
+
* schedule. A recursive `fs.watch` over the workspace root turned out to be
|
|
12
|
+
* a measurable CPU/IO cost on large trees (a 70k-file workspace pushed the
|
|
13
|
+
* host process to ~70% CPU during write bursts) for near-zero visible
|
|
14
|
+
* benefit — content edits never auto-reloaded the preview anyway (only a
|
|
15
|
+
* "updated" badge), so the always-on watcher was removed.
|
|
16
|
+
* @module dsh-filemgr/host/fs-service
|
|
17
|
+
*/
|
|
18
|
+
import type { DirListing, FileRead, PanelError, SearchView } from '../core/types.ts';
|
|
19
|
+
import { type GateVerdict, type WorkspaceGate } from './gate.ts';
|
|
20
|
+
/** Preview text ceiling — mirrors FileManager's single-tab 80k-char cap. */
|
|
21
|
+
export declare const TEXT_CAP_CHARS = 80000;
|
|
22
|
+
/** The image probe: parse PNG/JPEG/GIF/WebP header dimensions (undefined on failure). */
|
|
23
|
+
export declare function probeImageSize(data: Buffer): {
|
|
24
|
+
width: number;
|
|
25
|
+
height: number;
|
|
26
|
+
} | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* Filesystem service: gated listing/read/write/search/delete. All relative
|
|
29
|
+
* paths are resolved against the gated root.
|
|
30
|
+
* @param gate - the workspace gate (host: registered workspace membership).
|
|
31
|
+
*/
|
|
32
|
+
export declare class FsService {
|
|
33
|
+
private readonly gate;
|
|
34
|
+
constructor(gate: WorkspaceGate);
|
|
35
|
+
/** Verify a project root against the workspace gate (used by the SSE layer). */
|
|
36
|
+
verify(root: string): Promise<GateVerdict>;
|
|
37
|
+
/**
|
|
38
|
+
* List one directory (relative path; '' = root). Sorted dirs-first alpha.
|
|
39
|
+
* Entry metadata comes straight from `Dirent` (name + is-directory only) —
|
|
40
|
+
* no per-file stat — so listing a giant dir stays cheap. The result is
|
|
41
|
+
* capped at MAX_ENTRIES (truncated flag) to keep the tree and the '@'
|
|
42
|
+
* menu responsive on node_modules-style flat dirs.
|
|
43
|
+
*/
|
|
44
|
+
list(root: string, rel: string): Promise<DirListing | PanelError>;
|
|
45
|
+
/** Read one file for preview: text decoded utf-8 (capped), images as data URLs. */
|
|
46
|
+
read(root: string, rel: string, asImage: boolean): Promise<FileRead | PanelError>;
|
|
47
|
+
/**
|
|
48
|
+
* Resolve one file for raw streaming (the markdown image / pdf preview
|
|
49
|
+
* route): gated, traversal-guarded, and .git-refusing. Returns the absolute
|
|
50
|
+
* path with the derived mime, size and mtime — the HTTP layer streams the
|
|
51
|
+
* bytes itself (createReadStream + Range), so even large files never sit in
|
|
52
|
+
* host memory. Mime magic detection reads only the first few bytes. The
|
|
53
|
+
* mtime feeds the route's ETag/Last-Modified validators.
|
|
54
|
+
*/
|
|
55
|
+
readRaw(root: string, rel: string): Promise<{
|
|
56
|
+
abs: string;
|
|
57
|
+
mime: string;
|
|
58
|
+
size: number;
|
|
59
|
+
mtime: number;
|
|
60
|
+
} | PanelError>;
|
|
61
|
+
/** Write text content back, refusing when the file moved on disk (mtime conflict). */
|
|
62
|
+
write(root: string, rel: string, content: string, baseMtime?: number): Promise<{
|
|
63
|
+
mtime: number;
|
|
64
|
+
} | PanelError>;
|
|
65
|
+
/**
|
|
66
|
+
* Rename a path within the root. newName is a bare name (no separators,
|
|
67
|
+
* no '.'/'..') so the target always stays in the source's own directory;
|
|
68
|
+
* the joined target is re-checked against the canonical root anyway.
|
|
69
|
+
*/
|
|
70
|
+
rename(root: string, rel: string, newName: string): Promise<{
|
|
71
|
+
ok: true;
|
|
72
|
+
} | PanelError>;
|
|
73
|
+
/** Create a directory at a relative path (its parent must already exist). */
|
|
74
|
+
mkdir(root: string, rel: string): Promise<{
|
|
75
|
+
ok: true;
|
|
76
|
+
} | PanelError>;
|
|
77
|
+
/** Create an empty file at a relative path (wx: refuses to overwrite). */
|
|
78
|
+
newFile(root: string, rel: string): Promise<{
|
|
79
|
+
ok: true;
|
|
80
|
+
} | PanelError>;
|
|
81
|
+
/**
|
|
82
|
+
* Resolve a relative path to its gated absolute path without touching it —
|
|
83
|
+
* the route layer uses it for reveal-in-file-manager / open-with-default.
|
|
84
|
+
*/
|
|
85
|
+
resolveAbsolute(root: string, rel: string): Promise<{
|
|
86
|
+
ok: true;
|
|
87
|
+
abs: string;
|
|
88
|
+
} | PanelError>;
|
|
89
|
+
/** Recursive filename search (case-insensitive substring), pruned at noise dirs. */
|
|
90
|
+
search(root: string, query: string): Promise<SearchView | PanelError>;
|
|
91
|
+
/** Delete a path (discard of untracked files). Recursive for directories. */
|
|
92
|
+
delete(root: string, rel: string): Promise<{
|
|
93
|
+
ok: true;
|
|
94
|
+
} | PanelError>;
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=fs-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs-service.d.ts","sourceRoot":"","sources":["../../../src/host/fs-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAKH,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAW,UAAU,EAAa,UAAU,EAAE,MAAM,kBAAkB,CAAA;AACxG,OAAO,EAAgB,KAAK,WAAW,EAAE,KAAK,aAAa,EAAE,MAAM,WAAW,CAAA;AAE9E,4EAA4E;AAC5E,eAAO,MAAM,cAAc,QAAS,CAAA;AAqEpC,yFAAyF;AACzF,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAsD1F;AAuCD;;;;GAIG;AACH,qBAAa,SAAS;IAElB,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,aAAa;IAGtC,gFAAgF;IAChF,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAI1C;;;;;;OAMG;IACG,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,UAAU,CAAC;IA8BvE,mFAAmF;IAC7E,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,GAAG,UAAU,CAAC;IAgEvF;;;;;;;OAOG;IACG,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,UAAU,CAAC;IAkB1H,sFAAsF;IAChF,KAAK,CACT,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,UAAU,CAAC;IAyB1C;;;;OAIG;IACG,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,IAAI,CAAA;KAAE,GAAG,UAAU,CAAC;IA0B5F,6EAA6E;IACvE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,IAAI,CAAA;KAAE,GAAG,UAAU,CAAC;IAe1E,0EAA0E;IACpE,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,IAAI,CAAA;KAAE,GAAG,UAAU,CAAC;IAe5E;;;OAGG;IACG,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,IAAI,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,UAAU,CAAC;IASjG,oFAAoF;IAC9E,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,UAAU,CAAC;IAuD3E,6EAA6E;IACvE,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,IAAI,CAAA;KAAE,GAAG,UAAU,CAAC;CAiB5E"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace gate for the /filemgr routes: canonicalize the requested
|
|
3
|
+
* project root and require it to be a registered workspace (or a directory
|
|
4
|
+
* inside one). This is the security boundary of the panel's fs/git routes -
|
|
5
|
+
* the browser may only read and mutate files under registered workspace
|
|
6
|
+
* roots, never arbitrary host directories.
|
|
7
|
+
* @module dsh-filemgr/host/gate
|
|
8
|
+
*/
|
|
9
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
10
|
+
import type { PanelError } from '../core/types.ts';
|
|
11
|
+
/** The gate verdict for one project root. */
|
|
12
|
+
export type GateVerdict = {
|
|
13
|
+
ok: true;
|
|
14
|
+
canonical: string;
|
|
15
|
+
} | {
|
|
16
|
+
ok: false;
|
|
17
|
+
error: PanelError;
|
|
18
|
+
};
|
|
19
|
+
/** The workspace-membership check the services run on every request. */
|
|
20
|
+
export type WorkspaceGate = (root: string) => Promise<GateVerdict>;
|
|
21
|
+
/**
|
|
22
|
+
* Normalize a path for prefix comparison: collapse Windows separators to `/`
|
|
23
|
+
* and drop any trailing slash. On win32 the whole path is also lower-cased so
|
|
24
|
+
* a case-insensitive FS cannot trip the membership check (the drive letter and
|
|
25
|
+
* every segment are compared case-insensitively). On any other platform the
|
|
26
|
+
* path separator and case are left untouched.
|
|
27
|
+
*/
|
|
28
|
+
export declare function normalizeForPrefix(value: string): string;
|
|
29
|
+
/**
|
|
30
|
+
* The canonical prefix check: child must live inside (or equal) the root.
|
|
31
|
+
* Separator- and case-robust on Windows: `path.join` yields backslashes while
|
|
32
|
+
* git (`rev-parse --show-toplevel`) and the browser (`./x`) yield forward
|
|
33
|
+
* slashes, so both sides are normalized to forward slashes before comparing,
|
|
34
|
+
* and the comparison is case-insensitive on win32 (the FS is case-insensitive).
|
|
35
|
+
*/
|
|
36
|
+
export declare function isPathInside(root: string, child: string): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Production gate: canonicalize the requested root and require it to be a
|
|
39
|
+
* registered workspace path (or a subdirectory of one). The host's workspace
|
|
40
|
+
* registry owns canonicalization, so an unowned path is rejected outright.
|
|
41
|
+
* @param ctx - context carrying the workspace service.
|
|
42
|
+
* @returns the gate.
|
|
43
|
+
*/
|
|
44
|
+
export declare function createWorkspaceGate(ctx: Context): WorkspaceGate;
|
|
45
|
+
//# sourceMappingURL=gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../../../src/host/gate.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAElD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAElD,6CAA6C;AAC7C,MAAM,MAAM,WAAW,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,UAAU,CAAA;CAAE,CAAA;AAE5F,wEAAwE;AACxE,MAAM,MAAM,aAAa,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,WAAW,CAAC,CAAA;AAElE;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGxD;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAMjE;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,aAAa,CAmB/D"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared host git subprocess plumbing: the run result shape, the runner seam,
|
|
3
|
+
* the collected-output cap, and the production runner over the subprocess
|
|
4
|
+
* service. Packages receive this file as a generated copy via
|
|
5
|
+
* scripts/sync-shared.mjs; edit the shared source and re-run the sync instead
|
|
6
|
+
* of editing a copy.
|
|
7
|
+
*
|
|
8
|
+
* The context shape is declared structurally so this module stays
|
|
9
|
+
* self-contained (shared/ has no cordis dependency): any context whose
|
|
10
|
+
* `subprocess` satisfies SubprocessServiceLike works, which the plugin
|
|
11
|
+
* contexts do.
|
|
12
|
+
* @module dsh-web-ui-shared/host/git-runner
|
|
13
|
+
*/
|
|
14
|
+
/** One finished git invocation. */
|
|
15
|
+
export interface GitRunResult {
|
|
16
|
+
exitCode: number | null;
|
|
17
|
+
stdout: string;
|
|
18
|
+
stderr: string;
|
|
19
|
+
}
|
|
20
|
+
/** The spawn seam the service runs git through (subprocess service in production). */
|
|
21
|
+
export interface GitRunner {
|
|
22
|
+
run(argv: readonly string[], cwd: string): Promise<GitRunResult>;
|
|
23
|
+
}
|
|
24
|
+
/** Collected-output cap for one git command. */
|
|
25
|
+
export declare const OUTPUT_CAP_BYTES: number;
|
|
26
|
+
/** The subprocess service surface this runner consumes (structural). */
|
|
27
|
+
export interface SubprocessServiceLike {
|
|
28
|
+
spawn(spec: {
|
|
29
|
+
argv: readonly string[];
|
|
30
|
+
cwd: string;
|
|
31
|
+
stdio: {
|
|
32
|
+
stdin: 'ignore';
|
|
33
|
+
stdout: {
|
|
34
|
+
maxBytes: number;
|
|
35
|
+
};
|
|
36
|
+
stderr: {
|
|
37
|
+
maxBytes: number;
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
graceMs: number;
|
|
41
|
+
}): {
|
|
42
|
+
done: Promise<{
|
|
43
|
+
exitCode: number | null;
|
|
44
|
+
}>;
|
|
45
|
+
collected: {
|
|
46
|
+
stdout?: {
|
|
47
|
+
readFrom(offset: number): {
|
|
48
|
+
text: string;
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
stderr?: {
|
|
52
|
+
readFrom(offset: number): {
|
|
53
|
+
text: string;
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/** Per-package knobs for the shared production runner. */
|
|
60
|
+
export interface GitRunnerOptions {
|
|
61
|
+
/** Build the full spawn argv from the git args (default ['git', ...argv]). */
|
|
62
|
+
spawnArgv?: (argv: readonly string[]) => readonly string[];
|
|
63
|
+
/** degrade turns spawn/run failures into exitCode 127 results instead of throwing. */
|
|
64
|
+
failureMode?: 'throw' | 'degrade';
|
|
65
|
+
/** console.error tag for degrade mode. */
|
|
66
|
+
errorTag?: string;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Production runner over the subprocess service: one managed child per
|
|
70
|
+
* command, bounded collect on both streams. Degrade mode keeps the SCM tab
|
|
71
|
+
* showing the friendly "not a git repository" state instead of a bare 400
|
|
72
|
+
* when git is missing or the subprocess service fails.
|
|
73
|
+
* @param ctx - context carrying the subprocess service.
|
|
74
|
+
* @param options - per-package behavior knobs.
|
|
75
|
+
* @returns the runner.
|
|
76
|
+
*/
|
|
77
|
+
export declare function subprocessRunner(ctx: {
|
|
78
|
+
subprocess: SubprocessServiceLike;
|
|
79
|
+
}, options?: GitRunnerOptions): GitRunner;
|
|
80
|
+
//# sourceMappingURL=git-runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-runner.d.ts","sourceRoot":"","sources":["../../../src/host/git-runner.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;GAYG;AAEH,mCAAmC;AACnC,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;CACf;AAED,sFAAsF;AACtF,MAAM,WAAW,SAAS;IACxB,GAAG,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAAA;CACjE;AAED,gDAAgD;AAChD,eAAO,MAAM,gBAAgB,QAAU,CAAA;AAEvC,wEAAwE;AACxE,MAAM,WAAW,qBAAqB;IACpC,KAAK,CAAC,IAAI,EAAE;QACV,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;QACvB,GAAG,EAAE,MAAM,CAAA;QACX,KAAK,EAAE;YACL,KAAK,EAAE,QAAQ,CAAA;YACf,MAAM,EAAE;gBAAE,QAAQ,EAAE,MAAM,CAAA;aAAE,CAAA;YAC5B,MAAM,EAAE;gBAAE,QAAQ,EAAE,MAAM,CAAA;aAAE,CAAA;SAC7B,CAAA;QACD,OAAO,EAAE,MAAM,CAAA;KAChB,GAAG;QACF,IAAI,EAAE,OAAO,CAAC;YAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;SAAE,CAAC,CAAA;QAC1C,SAAS,EAAE;YACT,MAAM,CAAC,EAAE;gBAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG;oBAAE,IAAI,EAAE,MAAM,CAAA;iBAAE,CAAA;aAAE,CAAA;YACvD,MAAM,CAAC,EAAE;gBAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG;oBAAE,IAAI,EAAE,MAAM,CAAA;iBAAE,CAAA;aAAE,CAAA;SACxD,CAAA;KACF,CAAA;CACF;AAED,0DAA0D;AAC1D,MAAM,WAAW,gBAAgB;IAC/B,8EAA8E;IAC9E,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,KAAK,SAAS,MAAM,EAAE,CAAA;IAC1D,sFAAsF;IACtF,WAAW,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IACjC,0CAA0C;IAC1C,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE;IAAE,UAAU,EAAE,qBAAqB,CAAA;CAAE,EAAE,OAAO,GAAE,gBAAqB,GAAG,SAAS,CA8CtH"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host git service for the SCM tab: working-tree status (porcelain v1, -z),
|
|
3
|
+
* stage/unstage/discard batches, all scoped to the gated project root and
|
|
4
|
+
* executed through the managed subprocess seam. Parsing is pure and exported
|
|
5
|
+
* for tests; the service only wraps the runner. Discard never touches the
|
|
6
|
+
* staged side (the index is only ever rewritten by stage/unstage), matching
|
|
7
|
+
* the "discard = worktree side" contract.
|
|
8
|
+
* @module dsh-filemgr/host/git-service
|
|
9
|
+
*/
|
|
10
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
11
|
+
import { type GitRunner } from './git-runner.ts';
|
|
12
|
+
import type { GitBatchResult, GitChangeRow, GitFileState, GitStatusView, PanelError } from '../core/types.ts';
|
|
13
|
+
import { type WorkspaceGate } from './gate.ts';
|
|
14
|
+
/** One finished git invocation (shared runner plumbing). */
|
|
15
|
+
export type { GitRunResult, GitRunner } from './git-runner.ts';
|
|
16
|
+
/** Production runner over `ctx.subprocess`: shared plumbing, degrade mode for the SCM tab. */
|
|
17
|
+
export declare function subprocessRunner(ctx: Context): GitRunner;
|
|
18
|
+
/** Map one porcelain letter to the row state (unknown letters stay unknown). */
|
|
19
|
+
export declare function porcelainState(letter: string): GitFileState;
|
|
20
|
+
/**
|
|
21
|
+
* Parse `git status --porcelain=v1 -z` output into staged/unstaged/untracked
|
|
22
|
+
* rows. With -z every entry is NUL-terminated; rename entries carry two paths
|
|
23
|
+
* (old and new). Pure — exported for tests.
|
|
24
|
+
* @param output - raw porcelain v1 -z output.
|
|
25
|
+
* @returns the three change groups.
|
|
26
|
+
*/
|
|
27
|
+
export declare function parsePorcelain(output: string): {
|
|
28
|
+
staged: GitChangeRow[];
|
|
29
|
+
unstaged: GitChangeRow[];
|
|
30
|
+
untracked: GitChangeRow[];
|
|
31
|
+
};
|
|
32
|
+
/** Parse the porcelain row set into the status view shape. */
|
|
33
|
+
export declare function parseStatusView(root: string, branch: string, output: string): GitStatusView;
|
|
34
|
+
/**
|
|
35
|
+
* Workspace-scoped git operations. Gated methods pass the gate, resolve the
|
|
36
|
+
* repository root, and reject non-repositories with a stable error; the
|
|
37
|
+
* `Canonical` variants trust an already-gated canonical root (the SSE poll)
|
|
38
|
+
* and skip the gate.
|
|
39
|
+
* @param runner - the spawn seam.
|
|
40
|
+
* @param gate - workspace-membership gate.
|
|
41
|
+
* @param fsDelete - delete seam for untracked discard (host: FsService.delete).
|
|
42
|
+
*/
|
|
43
|
+
export declare class GitService {
|
|
44
|
+
private readonly runner;
|
|
45
|
+
private readonly gate;
|
|
46
|
+
private readonly fsDelete;
|
|
47
|
+
constructor(runner: GitRunner, gate: WorkspaceGate, fsDelete: (root: string, rel: string) => Promise<{
|
|
48
|
+
ok: true;
|
|
49
|
+
} | PanelError>);
|
|
50
|
+
/** Cached one-shot git binary probe; never re-probes after the first call. */
|
|
51
|
+
private availablePromise;
|
|
52
|
+
/**
|
|
53
|
+
* Cached repo-top-level resolution per canonical workspace, with a TTL so
|
|
54
|
+
* running `git init` (positive self-heal) or deleting `.git` (negative
|
|
55
|
+
* self-heal) is discovered by a later probe. Positive verdicts live 60s,
|
|
56
|
+
* negative (null) verdicts 30s; exitCode 127 is never cached because it
|
|
57
|
+
* means spawn/run failed rather than "not a repository".
|
|
58
|
+
*/
|
|
59
|
+
private readonly repoCache;
|
|
60
|
+
/**
|
|
61
|
+
* Probe the git binary once (git --version) and cache the verdict for the
|
|
62
|
+
* service lifetime. A machine without git then degrades every operation to
|
|
63
|
+
* the stable "not a git repository" state after a single failed spawn,
|
|
64
|
+
* instead of re-spawning ENOENT on every poll tick. The cache stays false
|
|
65
|
+
* even if git is installed later; the host restart picks it up.
|
|
66
|
+
*/
|
|
67
|
+
gitAvailable(): Promise<boolean>;
|
|
68
|
+
/**
|
|
69
|
+
* Resolve the repo top-level for one canonical root. Verdicts are cached
|
|
70
|
+
* with a TTL: a positive repo path for 60s, a negative null for 30s. After
|
|
71
|
+
* expiry the next call re-runs `rev-parse --show-toplevel`, so a repo
|
|
72
|
+
* created or removed while the host is running is picked up later. An
|
|
73
|
+
* exitCode 127 means the spawn/run itself failed; it returns null but is
|
|
74
|
+
* deliberately not cached so the next call retries. Any other failure is
|
|
75
|
+
* cached as a negative verdict for its TTL.
|
|
76
|
+
*/
|
|
77
|
+
private repoOf;
|
|
78
|
+
/**
|
|
79
|
+
* Whether an already-gated canonical root is a git repository. Skips the
|
|
80
|
+
* workspace gate so the SSE poll does not double-gate every 2s tick; the
|
|
81
|
+
* underlying repoOf cache keeps rev-parse probes at TTL cadence.
|
|
82
|
+
*/
|
|
83
|
+
isRepositoryCanonical(canonicalRoot: string): Promise<boolean>;
|
|
84
|
+
/**
|
|
85
|
+
* Whether a workspace root is a git repository. Gates the root first (POST
|
|
86
|
+
* route entry point); the SSE poll should use `isRepositoryCanonical`.
|
|
87
|
+
*/
|
|
88
|
+
isRepository(root: string): Promise<boolean>;
|
|
89
|
+
/** Resolve the gated canonical root and the repository top-level. */
|
|
90
|
+
private repo;
|
|
91
|
+
/** Run one git invocation and classify failures. */
|
|
92
|
+
private run;
|
|
93
|
+
/** The repo status view; null when the root is not a repository. */
|
|
94
|
+
status(root: string): Promise<GitStatusView | null | PanelError>;
|
|
95
|
+
/**
|
|
96
|
+
* The repo status view for an already-gated canonical root; null when it is
|
|
97
|
+
* not a repository. Skips the workspace gate (SSE subscribers were gated at
|
|
98
|
+
* connect) and reuses the same repoOf cache + status parsing as `status`.
|
|
99
|
+
*/
|
|
100
|
+
statusCanonical(canonicalRoot: string): Promise<GitStatusView | null>;
|
|
101
|
+
/** Run branch + porcelain status for one resolved repo and parse the view. */
|
|
102
|
+
private statusAt;
|
|
103
|
+
/** The repo root for the watch layer (null when not a repository). */
|
|
104
|
+
repoRoot(root: string): Promise<string | null>;
|
|
105
|
+
/**
|
|
106
|
+
* The unified diff of one path ('' when there is no diff to show). Staged
|
|
107
|
+
* paths diff the index against HEAD (`--cached`); unstaged paths diff the
|
|
108
|
+
* worktree against the index. Untracked paths have no index/HEAD entry, so
|
|
109
|
+
* they diff against /dev/null (the canonical new-file shape); its exit code
|
|
110
|
+
* is 1 — differences exist — which is a success here, not a failure.
|
|
111
|
+
*/
|
|
112
|
+
diff(root: string, path: string, staged: boolean): Promise<{
|
|
113
|
+
content: string;
|
|
114
|
+
} | PanelError>;
|
|
115
|
+
/** Verify paths stay inside the repo root (defense in depth). */
|
|
116
|
+
private pathsInside;
|
|
117
|
+
/** Stage paths (git add). Batch result reflects the post-op status. */
|
|
118
|
+
stage(root: string, paths: string[]): Promise<GitBatchResult | PanelError>;
|
|
119
|
+
/** Unstage paths (git restore --staged). */
|
|
120
|
+
unstage(root: string, paths: string[]): Promise<GitBatchResult | PanelError>;
|
|
121
|
+
/**
|
|
122
|
+
* Discard paths (worktree side only). Tracked paths are restored from the
|
|
123
|
+
* index; untracked paths are deleted through the fs seam. The batch reports
|
|
124
|
+
* applied/failed per path.
|
|
125
|
+
*/
|
|
126
|
+
discard(root: string, paths: string[]): Promise<GitBatchResult | PanelError>;
|
|
127
|
+
/** Shared batch plumbing: gate, repo resolve, path filter, run the op. */
|
|
128
|
+
private batch;
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=git-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-service.d.ts","sourceRoot":"","sources":["../../../src/host/git-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loopback trust fence shared by the host route families: socket address,
|
|
3
|
+
* Host header, and browser same-origin markers. Packages receive this file as
|
|
4
|
+
* a generated copy via scripts/sync-shared.mjs; edit the shared source and
|
|
5
|
+
* re-run the sync instead of editing a copy.
|
|
6
|
+
*
|
|
7
|
+
* Semantics: RFC 5735 IPv4 127/8, ::1, IPv4-mapped ::ffff:127/8 (matching the
|
|
8
|
+
* remote-web-ui gate), localhost hostnames, plus the browser same-origin
|
|
9
|
+
* markers (sec-fetch-site and Origin) for the request-level fence.
|
|
10
|
+
* @module dsh-web-ui-shared/host/loopback
|
|
11
|
+
*/
|
|
12
|
+
import type { IncomingMessage } from 'node:http';
|
|
13
|
+
/** IPv4 127/8 predicate (four decimal octets, first == 127). */
|
|
14
|
+
export declare function isIPv4Loopback(v4: string): boolean;
|
|
15
|
+
/** Whether a socket remote address names the loopback range (127/8, ::1, IPv4-mapped). */
|
|
16
|
+
export declare function isLoopbackAddress(address: string | undefined): boolean;
|
|
17
|
+
/** Whether a normalized URL hostname names the loopback authority (localhost, [::1], 127/8). */
|
|
18
|
+
export declare function isLoopbackHostname(hostname: string): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Request-level trust fence: a loopback socket address AND a loopback Host
|
|
21
|
+
* header, plus browser same-origin markers. The socket address is
|
|
22
|
+
* authoritative; X-Forwarded-For is never trusted.
|
|
23
|
+
*/
|
|
24
|
+
export declare function isLoopbackRequest(request: IncomingMessage): boolean;
|
|
25
|
+
//# sourceMappingURL=loopback.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loopback.d.ts","sourceRoot":"","sources":["../../../src/host/loopback.ts"],"names":[],"mappings":"AACA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAEhD,gEAAgE;AAChE,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAKlD;AAED,0FAA0F;AAC1F,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAMtE;AAED,gGAAgG;AAChG,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAG5D;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAmBnE"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Poll-loop guard shared by the plugin family's Host halves (git-graph,
|
|
3
|
+
* filemgr): one bounded refresh loop that never overlaps itself,
|
|
4
|
+
* backs off on consecutive failures, and stops at a global deadline. Pure
|
|
5
|
+
* logic with injectable timers, so consumers test it without wall clocks.
|
|
6
|
+
*/
|
|
7
|
+
/** Timer injection seam; defaults to the global setTimeout/clearTimeout pair. */
|
|
8
|
+
export interface PollTimers {
|
|
9
|
+
/** Schedule fn(ms). */
|
|
10
|
+
set: (fn: () => void, ms: number) => unknown;
|
|
11
|
+
/** Cancel a scheduled handle. */
|
|
12
|
+
clear: (handle: unknown) => void;
|
|
13
|
+
}
|
|
14
|
+
export interface PollGuardOptions {
|
|
15
|
+
/** Base interval between runs. */
|
|
16
|
+
intervalMs: number;
|
|
17
|
+
/** Runs stop entirely once this much wall time passed since start(). */
|
|
18
|
+
deadlineMs: number;
|
|
19
|
+
/** Cap for the failure backoff; each consecutive failure doubles the delay up to this. */
|
|
20
|
+
maxBackoffMs: number;
|
|
21
|
+
/** Optional timer seam for tests. */
|
|
22
|
+
timers?: PollTimers;
|
|
23
|
+
/** Invoked once when the deadline fires; no run starts afterwards. */
|
|
24
|
+
onDeadline?: () => void;
|
|
25
|
+
/** Invoked after a run settles with the consecutive-failure count. */
|
|
26
|
+
onSettled?: (consecutiveFailures: number) => void;
|
|
27
|
+
/** The task each tick runs; a rejection counts as a failure. */
|
|
28
|
+
onRun: () => Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Owns one bounded poll loop.
|
|
32
|
+
*
|
|
33
|
+
* Guarantees: at most one task runs at a time (a scheduled tick whose turn
|
|
34
|
+
* arrives while a run is in flight is dropped); consecutive failures double
|
|
35
|
+
* the delay up to maxBackoffMs and reset on the first success; the loop
|
|
36
|
+
* stops forever at deadlineMs and cancels its timer.
|
|
37
|
+
*/
|
|
38
|
+
export declare class PollGuard {
|
|
39
|
+
private readonly options;
|
|
40
|
+
private handle;
|
|
41
|
+
private running;
|
|
42
|
+
private startedAt;
|
|
43
|
+
private stopped;
|
|
44
|
+
private failures;
|
|
45
|
+
/** @param options - loop bounds; interval/deadline/backoff/onRun are required, the rest optional. */
|
|
46
|
+
constructor(options: PollGuardOptions);
|
|
47
|
+
/** Start the loop. Safe to call once; later calls are ignored. */
|
|
48
|
+
start(): void;
|
|
49
|
+
/** Stop the loop permanently and drop any pending tick. */
|
|
50
|
+
stop(): void;
|
|
51
|
+
private schedule;
|
|
52
|
+
private delay;
|
|
53
|
+
private tick;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=poll-guard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"poll-guard.d.ts","sourceRoot":"","sources":["../../../src/host/poll-guard.ts"],"names":[],"mappings":"AACA;;;;;GAKG;AAEH,iFAAiF;AACjF,MAAM,WAAW,UAAU;IACzB,uBAAuB;IACvB,GAAG,EAAE,CAAC,EAAE,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,KAAK,OAAO,CAAA;IAC5C,iCAAiC;IACjC,KAAK,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAA;CACjC;AAOD,MAAM,WAAW,gBAAgB;IAC/B,kCAAkC;IAClC,UAAU,EAAE,MAAM,CAAA;IAClB,wEAAwE;IACxE,UAAU,EAAE,MAAM,CAAA;IAClB,0FAA0F;IAC1F,YAAY,EAAE,MAAM,CAAA;IACpB,qCAAqC;IACrC,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB,sEAAsE;IACtE,UAAU,CAAC,EAAE,MAAM,IAAI,CAAA;IACvB,sEAAsE;IACtE,SAAS,CAAC,EAAE,CAAC,mBAAmB,EAAE,MAAM,KAAK,IAAI,CAAA;IACjD,gEAAgE;IAChE,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3B;AAED;;;;;;;GAOG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA4B;IACpD,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,SAAS,CAAI;IACrB,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,QAAQ,CAAI;IAEpB,qGAAqG;gBACzF,OAAO,EAAE,gBAAgB;IASrC,kEAAkE;IAClE,KAAK,IAAI,IAAI;IAMb,2DAA2D;IAC3D,IAAI,IAAI,IAAI;IAMZ,OAAO,CAAC,QAAQ;IAKhB,OAAO,CAAC,KAAK;YAKC,IAAI;CAoBnB"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /filemgr/* route layer: JSON envelope (ok/error with stable codes) for
|
|
3
|
+
* the fs/git operations and one SSE stream (git status changes) per project
|
|
4
|
+
* root. The services own gating and parsing; this layer owns HTTP shape and
|
|
5
|
+
* subscriber bookkeeping.
|
|
6
|
+
*
|
|
7
|
+
* There is deliberately no fs-change push: the explorer/preview refresh on
|
|
8
|
+
* user actions and the SCM tab polls git status here, so a recursive
|
|
9
|
+
* fs.watch over the workspace (a measurable cost on large trees) is not
|
|
10
|
+
* needed. Subscribers only exist while an SCM panel is visible, so the
|
|
11
|
+
* git poll below runs only during that window.
|
|
12
|
+
* @module dsh-filemgr/host/routes
|
|
13
|
+
*/
|
|
14
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
15
|
+
import type { FsService } from './fs-service.ts';
|
|
16
|
+
import type { GitService } from './git-service.ts';
|
|
17
|
+
/**
|
|
18
|
+
* Platform argv for "reveal in file manager" (select the entry). Windows
|
|
19
|
+
* Explorer selects via /select,; macOS Finder via open -R; Linux desktops
|
|
20
|
+
* have no select mode, so xdg-open opens the parent directory.
|
|
21
|
+
*/
|
|
22
|
+
export declare function revealArgv(platform: NodeJS.Platform, abs: string): string[];
|
|
23
|
+
/** Platform argv for "open with the default app". */
|
|
24
|
+
export declare function openArgv(platform: NodeJS.Platform, abs: string): string[];
|
|
25
|
+
/**
|
|
26
|
+
* Parse a `Range: bytes=start-end` header against the file size. RFC 7233
|
|
27
|
+
* lets a server ignore any Range it does not support, so unknown units,
|
|
28
|
+
* malformed headers and multi-range requests all return null (the caller
|
|
29
|
+
* answers 200 with the full body); only a syntactically valid single range
|
|
30
|
+
* that cannot be satisfied returns 'invalid' (the caller answers 416).
|
|
31
|
+
* Suffix ranges (`bytes=-N`) select the last N bytes. Range support added
|
|
32
|
+
* after human review on #242 (pdf seeking); ignore-instead-of-416 for
|
|
33
|
+
* unsupported shapes per maintainer feedback.
|
|
34
|
+
*/
|
|
35
|
+
export declare function parseRangeHeader(header: string | undefined, size: number): {
|
|
36
|
+
start: number;
|
|
37
|
+
end: number;
|
|
38
|
+
} | 'invalid' | null;
|
|
39
|
+
/**
|
|
40
|
+
* Whether an If-None-Match header matches the current etag. Handles `*` and
|
|
41
|
+
* comma-separated entity-tag lists; GET revalidation uses weak comparison
|
|
42
|
+
* (RFC 9110), so the weak prefix is ignored on both sides.
|
|
43
|
+
*/
|
|
44
|
+
export declare function ifNoneMatchSaidFresh(header: string | undefined, etag: string): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Register the /filemgr routes (prefix for JSON, exact for the SSE
|
|
47
|
+
* stream — longest-prefix-wins keeps them disjoint).
|
|
48
|
+
* @param ctx - context carrying the webServer service.
|
|
49
|
+
* @param fs - the gated filesystem service.
|
|
50
|
+
* @param git - the gated git service.
|
|
51
|
+
* @returns the route disposers.
|
|
52
|
+
*/
|
|
53
|
+
export declare function registerPanelRoutes(ctx: Context, fs: FsService, git: GitService): () => void;
|
|
54
|
+
//# sourceMappingURL=routes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../src/host/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAQH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAIlD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAChD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAUlD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAI3E;AAED,qDAAqD;AACrD,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAIzE;AA8CD;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,IAAI,EAAE,MAAM,GACX;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,GAAG,IAAI,CAanD;AAOD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAOtF;AA8ED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,UAAU,GAAG,MAAM,IAAI,CAkd5F"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @lijian-ui/dsh-file-manager — host half: the workspace-gated
|
|
3
|
+
* filesystem + git services and the /filemgr/* HTTP routes (JSON
|
|
4
|
+
* operations + SSE change stream) on the shared webserver. The browser half
|
|
5
|
+
* (exports "./client") is served by client-modules from the same package's
|
|
6
|
+
* dsh.client declaration.
|
|
7
|
+
*
|
|
8
|
+
* The host half also announces the plugin to every agent through the
|
|
9
|
+
* system-prompt section mechanism (the same band task-board uses), so agents
|
|
10
|
+
* know the right-panel system exists and how to cooperate with it.
|
|
11
|
+
*
|
|
12
|
+
* FileManager right-panel design (Apache-2.0, iOfficeAI/FileManager) — re-implemented
|
|
13
|
+
* from measured behavior and architecture, not copied code.
|
|
14
|
+
* @module @lijian-ui/dsh-file-manager
|
|
15
|
+
*/
|
|
16
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
17
|
+
/** Required services: the route registry, the managed subprocess seam, the workspace registry, and the prompt band. */
|
|
18
|
+
export declare const inject: string[];
|
|
19
|
+
/** Settings namespace the browser settings card edits (the Host registers it). */
|
|
20
|
+
export declare const FILEMANAGER_PANEL_SETTINGS_NAMESPACE = "filemgr";
|
|
21
|
+
/** Model-facing announcement: plugin presence, capabilities, and limits. */
|
|
22
|
+
export declare const FILEMANAGER_PANEL_GUIDANCE = "\u672C\u673A\u5DF2\u5B89\u88C5 dsh-filemgr \u63D2\u4EF6\uFF08DSH Web GUI \u7684\u53F3\u4FA7\u9762\u677F\u7CFB\u7EDF\uFF09\uFF1A\u9879\u76EE\u4F1A\u8BDD\u6253\u5F00\u65F6\uFF0C\u804A\u5929\u533A\u53F3\u4FA7\u51FA\u73B0\u300C\u9884\u89C8\u300D\u4E0E\u300C\u6587\u4EF6/\u53D8\u66F4\u300D\u4E24\u5757\u9762\u677F\u3002\u80FD\u529B\uFF1AExplorer \u6587\u4EF6\u6811\uFF08\u70B9\u51FB\u6587\u4EF6\u5728\u9884\u89C8\u9762\u677F\u6253\u5F00\u3001\u6574\u884C\u70B9\u51FB\u5C55\u5F00\u6587\u4EF6\u5939\u3001\u6309\u6587\u4EF6\u540D\u641C\u7D22\u5B9A\u4F4D\uFF09\uFF1BPreview \u591A tab \u9884\u89C8\uFF08markdown/html/code/diff/csv/pdf/office/\u56FE\u7247/\u6587\u672C\u7B49\u683C\u5F0F\uFF0C\u652F\u6301\u6E90\u7801/\u9884\u89C8\u5207\u6362\u3001\u5206\u5C4F\u7F16\u8F91\u3001\u4FDD\u5B58\uFF1Bmarkdown \u4E0E\u804A\u5929\u6D88\u606F\u4E2D\u7684 mermaid \u4EE3\u7801\u5757\u4F1A\u6E32\u67D3\u6210\u56FE\u8868\uFF0C\u56FE\u6E90\u8BED\u6CD5\u9519\u8BEF\u65F6\u56DE\u9000\u4E3A\u4EE3\u7801\u5757\uFF09\uFF1BSCM \u53D8\u66F4\u9762\u677F\uFF08\u771F\u5B9E git stage/unstage/discard\uFF09\uFF1B\u9762\u677F\u5BBD\u5EA6\u53EF\u62D6\u62FD\u8C03\u6574\uFF08Explorer 220~500px\u3001Preview 340~1200px\uFF09\uFF0C\u53CC\u51FB\u628A\u624B\u590D\u4F4D\u9ED8\u8BA4\u5BBD\u5EA6\uFF0C\u6298\u53E0\u72B6\u6001\u4E0E\u5BBD\u5EA6\u6309\u9879\u76EE\u6301\u4E45\u5316\uFF08localStorage\uFF09\u3002\u6570\u636E\u6E90\u4E3A\u5F53\u524D\u4F1A\u8BDD\u5DE5\u4F5C\u76EE\u5F55\u7684\u771F\u5B9E\u6587\u4EF6\u7CFB\u7EDF\u4E0E\u771F\u5B9E git \u4ED3\u5E93\uFF0C\u5BBF\u4E3B\u8FDB\u7A0B\u7ECF /filemgr/* \u8DEF\u7531\u63D0\u4F9B\u3002\u7528\u6237\u63D0\u5230\u300C\u53F3\u4FA7\u9762\u677F / \u9884\u89C8\u9762\u677F / \u6587\u4EF6\u6811 / \u53D8\u66F4\u9762\u677F\u300D\u65F6\u5373\u6307\u672C\u63D2\u4EF6\uFF0C\u8BF7\u636E\u6B64\u534F\u4F5C\u3002";
|
|
23
|
+
/** Settings section the browser card edits: a total on/off switch (issue #307). */
|
|
24
|
+
export interface FileManagerPanelSettings {
|
|
25
|
+
/** Whether the right-panel system is mounted at all; default on. */
|
|
26
|
+
enabled?: boolean;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Mount the panel data services and their routes.
|
|
30
|
+
* @param ctx - context carrying webServer, subprocess, workspaceRegistry, systemPrompt.
|
|
31
|
+
*/
|
|
32
|
+
export declare const apply: typeof applyImpl;
|
|
33
|
+
declare function applyImpl(ctx: Context, config?: FileManagerPanelSettings): void;
|
|
34
|
+
export {};
|
|
35
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAalD,uHAAuH;AACvH,eAAO,MAAM,MAAM,UAAmE,CAAA;AAEtF,kFAAkF;AAClF,eAAO,MAAM,oCAAoC,YAAY,CAAA;AAK7D,4EAA4E;AAC5E,eAAO,MAAM,0BAA0B,oxDAAke,CAAA;AAEzgB,mFAAmF;AACnF,MAAM,WAAW,wBAAwB;IACvC,oEAAoE;IACpE,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED;;;GAGG;AACH,eAAO,MAAM,KAAK,kBAAsD,CAAA;AAExE,iBAAS,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,GAAE,wBAA6B,GAAG,IAAI,CA2C5E"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host single-instance guard shared by the plugin family. The family bundle
|
|
3
|
+
* (dsh-web-ui-all / dsh-skins) namespaces every child row id (web-ui-*), so
|
|
4
|
+
* the loader accepts a standalone install of the same package side by side;
|
|
5
|
+
* without this guard the second instance would still re-register the same
|
|
6
|
+
* webserver routes, tools, settings namespaces, and system-prompt sections
|
|
7
|
+
* and fail the boot. mountOnce makes the second host apply a no-op for the
|
|
8
|
+
* lifetime of the first instance (the browser half is already deduped by
|
|
9
|
+
* package name in the client module host).
|
|
10
|
+
*
|
|
11
|
+
* The registry rides a global symbol so two module instances of the same
|
|
12
|
+
* package (npm copy vs repository link) still share one verdict. cordis
|
|
13
|
+
* `ctx.effect` runs its callback immediately and treats the callback's
|
|
14
|
+
* return value as the fiber disposer, so the unmarker is returned, not run.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Wrap a cordis plugin apply so the package runs at most once per process.
|
|
18
|
+
* The first mount registers normally and unmarks when its fiber disposes;
|
|
19
|
+
* any later mount of the same package name is a no-op.
|
|
20
|
+
* @param packageName - npm package identity shared by every install source.
|
|
21
|
+
* @param fn - the original plugin apply.
|
|
22
|
+
* @returns an apply of the same shape.
|
|
23
|
+
*/
|
|
24
|
+
export declare function mountOnce<T extends (...args: any[]) => unknown>(packageName: string, fn: T): T;
|
|
25
|
+
//# sourceMappingURL=mount-once.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mount-once.d.ts","sourceRoot":"","sources":["../../src/mount-once.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;GAcG;AAaH;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,GAAG,CAAC,CAW9F"}
|