@hydraharness/harness-host-directory-picker 0.0.0-stage → 0.1.1-rc.5
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 +21 -0
- package/README.md +16 -2
- package/lib/index.js +44 -0
- package/lib/invariant.js +20 -0
- package/lib/types/index.d.ts +128 -0
- package/lib/types/invariant.d.ts +13 -0
- package/package.json +39 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DeepSeek
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @hydraharness/harness-host-directory-picker
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The web GUI host's workspace-directory picker is a capability seam. The abstract `DirectoryPicker` service (`ctx.directoryPicker`) is its Service Definition. Its only method, `capability()`, returns a discriminated union describing how an operator selects a directory. Backends differ in user interaction, not just implementation: `{ kind: 'native', pick(signal) }` opens one native OS chooser on the host display ([`-native`](../directory-picker-native/README.md)); `{ kind: 'browse', list(path?), createDirectory(path, name) }` provides listing and creation operations for an in-app browser, which works for remote clients that cannot reach an OS chooser ([`-browse`](../directory-picker-browse/README.md)). Consumers switch on `capability().kind`; the union derives from the merge-extensible `DirectoryPickerCapabilities` map, and a new backend adds its variant there through declaration merging. For an unknown kind, consumers hide directory picking rather than fail. The capability object must be stable for the service lifetime. Each backend package also has a browser entrypoint that registers the matching interaction in ui-workspace's directory-flow slots, so one composition row selects both the host capability and the client flow. A composition that should choose at runtime mounts [`-auto`](../directory-picker-auto/README.md), which inspects the host once at boot and mounts the matching backend row.
|
|
4
|
+
|
|
5
|
+
Browse primitives fail with the typed `DirectoryPickerError` (`directory-unreadable` / `directory-exists` / `directory-create-failed`, each carrying the subject `path`), which the consuming gateway maps 1:1 onto wire error codes. `DirectoryEntry` rows carry a host-owned `hidden` flag (POSIX dot convention) so display policy stays client-side; `DirectoryListing.crumbs` is the ancestor chain from the filesystem root, every crumb a jump target. Design rationale, the `ctx.fs` separation, and the policy decisions live in [the directory-picker capability seam Agent Note](../../../.agents/notes/implemented/architecture/2026-07-28-directory-picker-capability-seam.md).
|
|
6
|
+
|
|
7
|
+
## Model Experience
|
|
8
|
+
|
|
9
|
+
None, as the seam serves the GUI host's directory selection; nothing here reaches a model request.
|
|
10
|
+
|
|
11
|
+
#### KV Cache effect
|
|
12
|
+
|
|
13
|
+
None; this package neither assembles nor sends a provider request.
|
|
14
|
+
|
|
15
|
+
## Known Limitations and Deferred Work
|
|
16
|
+
|
|
17
|
+
- **No multi-root support** — the browse contract exposes one ancestry chain per listing; per-deployment root scoping (and Windows drive-root enumeration above a drive) waits for a consumer that needs it, per the DirectoryPicker Agent Note.
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Service } from "@hydraharness/cordis";
|
|
2
|
+
//#region lib/types/index.js
|
|
3
|
+
/**
|
|
4
|
+
* Service Definition for the `ctx.directoryPicker` capability seam: how the web-GUI host lets an operator
|
|
5
|
+
* select a workspace directory. Backends differ in interaction shape, not
|
|
6
|
+
* just mechanism, so the service exposes a discriminated capability instead
|
|
7
|
+
* of one method set: a `native` backend opens one OS chooser on the
|
|
8
|
+
* host's display, while a `browse` backend serves listing/creation primitives
|
|
9
|
+
* for an in-app browser (and thereby works for remote clients no OS dialog
|
|
10
|
+
* can reach). Consumers switch on `capability().kind`; the union is
|
|
11
|
+
* merge-extensible, and the documented default for an unknown kind is to
|
|
12
|
+
* hide the picking affordance rather than fail.
|
|
13
|
+
* @module @hydraharness/harness-host-directory-picker
|
|
14
|
+
*/
|
|
15
|
+
/** Typed failure thrown by browse primitives so consumers can map business codes without string matching. */
|
|
16
|
+
var DirectoryPickerError = class extends Error {
|
|
17
|
+
code;
|
|
18
|
+
path;
|
|
19
|
+
/**
|
|
20
|
+
* @param code - closed business code of the failure.
|
|
21
|
+
* @param path - the absolute path the failure is about.
|
|
22
|
+
* @param message - operator-facing description.
|
|
23
|
+
*/
|
|
24
|
+
constructor(code, path, message) {
|
|
25
|
+
super(message);
|
|
26
|
+
this.code = code;
|
|
27
|
+
this.path = path;
|
|
28
|
+
this.name = "DirectoryPickerError";
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Abstract directory-picking service. Subclass, implement `capability()`, and
|
|
33
|
+
* load the subclass as a plugin — it registers as `ctx.directoryPicker` (one
|
|
34
|
+
* implementation per context; loading a second throws, cordis' standard
|
|
35
|
+
* duplicate-service behavior). The capability object must be stable for the
|
|
36
|
+
* service lifetime: consumers may capture it across calls.
|
|
37
|
+
*/
|
|
38
|
+
var DirectoryPicker = class extends Service {
|
|
39
|
+
constructor(ctx) {
|
|
40
|
+
super(ctx, "directoryPicker");
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
//#endregion
|
|
44
|
+
export { DirectoryPicker, DirectoryPicker as default, DirectoryPickerError };
|
package/lib/invariant.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//#region lib/types/invariant.js
|
|
2
|
+
/** Package-owned invariant companion for the directory-picker seam. @module @hydraharness/harness-host-directory-picker/invariant */
|
|
3
|
+
const PACKAGE_NAME = "@hydraharness/harness-host-directory-picker";
|
|
4
|
+
/** Cordis companion plugin name. */
|
|
5
|
+
const name = "host-directory-picker-invariant";
|
|
6
|
+
/** Service required before the companion can reserve package ownership. */
|
|
7
|
+
const inject = ["invariants"];
|
|
8
|
+
/**
|
|
9
|
+
* No runtime invariant: this stateless Service Definition owns the capability
|
|
10
|
+
* vocabulary, while backends and the RPC consumer own observations.
|
|
11
|
+
*/
|
|
12
|
+
const install = () => {};
|
|
13
|
+
/**
|
|
14
|
+
* Register the directory-picker invariant companion.
|
|
15
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
16
|
+
* @returns the installed registration's disposer after setup succeeds.
|
|
17
|
+
*/
|
|
18
|
+
const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
|
|
19
|
+
//#endregion
|
|
20
|
+
export { apply, inject, name };
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service Definition for the `ctx.directoryPicker` capability seam: how the web-GUI host lets an operator
|
|
3
|
+
* select a workspace directory. Backends differ in interaction shape, not
|
|
4
|
+
* just mechanism, so the service exposes a discriminated capability instead
|
|
5
|
+
* of one method set: a `native` backend opens one OS chooser on the
|
|
6
|
+
* host's display, while a `browse` backend serves listing/creation primitives
|
|
7
|
+
* for an in-app browser (and thereby works for remote clients no OS dialog
|
|
8
|
+
* can reach). Consumers switch on `capability().kind`; the union is
|
|
9
|
+
* merge-extensible, and the documented default for an unknown kind is to
|
|
10
|
+
* hide the picking affordance rather than fail.
|
|
11
|
+
* @module @hydraharness/harness-host-directory-picker
|
|
12
|
+
*/
|
|
13
|
+
import { Context, Service } from '@hydraharness/cordis';
|
|
14
|
+
/** The native interaction: one OS directory chooser on the host display. */
|
|
15
|
+
export interface DirectoryPickerNativeCapability {
|
|
16
|
+
kind: 'native';
|
|
17
|
+
/**
|
|
18
|
+
* Open the chooser and wait for the operator.
|
|
19
|
+
* @param signal - caller/connection lifetime; abort terminates the chooser.
|
|
20
|
+
* @returns the chosen absolute path, or null when the operator cancels.
|
|
21
|
+
*/
|
|
22
|
+
pick(signal: AbortSignal): Promise<string | null>;
|
|
23
|
+
}
|
|
24
|
+
/** One directory row: a listing child or a breadcrumb ancestor. */
|
|
25
|
+
export interface DirectoryEntry {
|
|
26
|
+
/** Base name shown in a browser row (a root crumb carries its full path). */
|
|
27
|
+
name: string;
|
|
28
|
+
/** Absolute host path — clients never join path segments themselves. */
|
|
29
|
+
path: string;
|
|
30
|
+
/** Hidden by the host platform's convention (dot-prefixed on POSIX); the client owns whether to show it. */
|
|
31
|
+
hidden: boolean;
|
|
32
|
+
}
|
|
33
|
+
/** One directory level plus its ancestry, as a browse backend reports it. */
|
|
34
|
+
export interface DirectoryListing {
|
|
35
|
+
/** Absolute path of the listed directory. */
|
|
36
|
+
path: string;
|
|
37
|
+
/** The host account's home directory (breadcrumb "Home" rooting). */
|
|
38
|
+
home: string;
|
|
39
|
+
/**
|
|
40
|
+
* Ancestor chain from the filesystem root to the listed directory
|
|
41
|
+
* inclusive; every crumb is a jump target (crumb `hidden` is always false).
|
|
42
|
+
*/
|
|
43
|
+
crumbs: DirectoryEntry[];
|
|
44
|
+
/** Direct child directories, name-sorted; symlinks to directories included. */
|
|
45
|
+
entries: DirectoryEntry[];
|
|
46
|
+
/**
|
|
47
|
+
* True when the backend cut `entries` at its complete-result bound: the
|
|
48
|
+
* level has more child directories than reported, and the missing rows are
|
|
49
|
+
* the name-sorted tail (hidden rows count toward the bound).
|
|
50
|
+
*/
|
|
51
|
+
truncated: boolean;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The browse interaction: listing/creation primitives an in-app browser
|
|
55
|
+
* drives one level at a time. Works for remote clients — nothing renders on
|
|
56
|
+
* the host display.
|
|
57
|
+
*/
|
|
58
|
+
export interface DirectoryPickerBrowseCapability {
|
|
59
|
+
kind: 'browse';
|
|
60
|
+
/**
|
|
61
|
+
* List one directory level.
|
|
62
|
+
* @param path - absolute directory to list; absent lists the home directory.
|
|
63
|
+
* @param signal - caller lifetime; abort stops the scan (a stalled network
|
|
64
|
+
* directory must not outlive a disconnected caller) and rejects with the
|
|
65
|
+
* abort reason.
|
|
66
|
+
* @returns the level's listing with ancestry; backends bound the complete
|
|
67
|
+
* result, and a cut level reports `truncated`.
|
|
68
|
+
* @throws {DirectoryPickerError} `directory-unreadable` when the target is not fully
|
|
69
|
+
* qualified (a wire value must never resolve against the host cwd or, on
|
|
70
|
+
* Windows, its current drive) or cannot be listed.
|
|
71
|
+
*/
|
|
72
|
+
list(path?: string, signal?: AbortSignal): Promise<DirectoryListing>;
|
|
73
|
+
/**
|
|
74
|
+
* Create one child directory under an existing parent.
|
|
75
|
+
* @param path - absolute existing parent directory.
|
|
76
|
+
* @param name - single non-blank path segment (no separators, not `.`/`..`).
|
|
77
|
+
* @returns the created directory's absolute path.
|
|
78
|
+
* @throws {DirectoryPickerError} `directory-exists` for an existing child,
|
|
79
|
+
* `directory-create-failed` for a parent that is not fully qualified or any other failure.
|
|
80
|
+
*/
|
|
81
|
+
createDirectory(path: string, name: string): Promise<string>;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Merge-extensible registry of interaction shapes keyed by capability kind: a
|
|
85
|
+
* new backend declaration-merges its shape here (the entry's `kind` literal
|
|
86
|
+
* must equal its key) instead of editing this package.
|
|
87
|
+
*/
|
|
88
|
+
export interface DirectoryPickerCapabilities {
|
|
89
|
+
native: DirectoryPickerNativeCapability;
|
|
90
|
+
browse: DirectoryPickerBrowseCapability;
|
|
91
|
+
}
|
|
92
|
+
/** Union of interaction shapes a backend can provide, derived from the merge-extensible {@link DirectoryPickerCapabilities} map. */
|
|
93
|
+
export type DirectoryPickerCapability = DirectoryPickerCapabilities[keyof DirectoryPickerCapabilities];
|
|
94
|
+
/** Closed failure vocabulary of the browse primitives (mirrored onto the wire by consumers). */
|
|
95
|
+
export type DirectoryPickerErrorCode = 'directory-unreadable' | 'directory-exists' | 'directory-create-failed';
|
|
96
|
+
/** Typed failure thrown by browse primitives so consumers can map business codes without string matching. */
|
|
97
|
+
export declare class DirectoryPickerError extends Error {
|
|
98
|
+
readonly code: DirectoryPickerErrorCode;
|
|
99
|
+
readonly path: string;
|
|
100
|
+
/**
|
|
101
|
+
* @param code - closed business code of the failure.
|
|
102
|
+
* @param path - the absolute path the failure is about.
|
|
103
|
+
* @param message - operator-facing description.
|
|
104
|
+
*/
|
|
105
|
+
constructor(code: DirectoryPickerErrorCode, path: string, message: string);
|
|
106
|
+
}
|
|
107
|
+
declare module '@hydraharness/cordis' {
|
|
108
|
+
interface Context {
|
|
109
|
+
directoryPicker: DirectoryPicker;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Abstract directory-picking service. Subclass, implement `capability()`, and
|
|
114
|
+
* load the subclass as a plugin — it registers as `ctx.directoryPicker` (one
|
|
115
|
+
* implementation per context; loading a second throws, cordis' standard
|
|
116
|
+
* duplicate-service behavior). The capability object must be stable for the
|
|
117
|
+
* service lifetime: consumers may capture it across calls.
|
|
118
|
+
*/
|
|
119
|
+
export declare abstract class DirectoryPicker extends Service {
|
|
120
|
+
constructor(ctx: Context);
|
|
121
|
+
/**
|
|
122
|
+
* The backend's interaction capability.
|
|
123
|
+
* @returns the discriminated capability consumers switch on.
|
|
124
|
+
*/
|
|
125
|
+
abstract capability(): DirectoryPickerCapability;
|
|
126
|
+
}
|
|
127
|
+
export default DirectoryPicker;
|
|
128
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Package-owned invariant companion for the directory-picker seam. @module @hydraharness/harness-host-directory-picker/invariant */
|
|
2
|
+
import type { Context } from '@hydraharness/cordis';
|
|
3
|
+
/** Cordis companion plugin name. */
|
|
4
|
+
export declare const name = "host-directory-picker-invariant";
|
|
5
|
+
/** Service required before the companion can reserve package ownership. */
|
|
6
|
+
export declare const inject: string[];
|
|
7
|
+
/**
|
|
8
|
+
* Register the directory-picker invariant companion.
|
|
9
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
10
|
+
* @returns the installed registration's disposer after setup succeeds.
|
|
11
|
+
*/
|
|
12
|
+
export declare const apply: (ctx: Context) => Promise<() => void>;
|
|
13
|
+
//# sourceMappingURL=invariant.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,42 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hydraharness/harness-host-directory-picker",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"description": "Abstract workspace-directory picking seam (ctx.directoryPicker) for the Hydra harness web GUI host",
|
|
4
|
+
"version": "0.1.1-rc.5",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/MaiHongPhong1902/Hydra-Harness.git",
|
|
11
|
+
"directory": "packages/host/directory-picker"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "lib/index.js",
|
|
15
|
+
"types": "lib/types/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./lib/types/index.d.ts",
|
|
19
|
+
"default": "./lib/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./invariant": {
|
|
22
|
+
"types": "./lib/types/invariant.d.ts",
|
|
23
|
+
"default": "./lib/invariant.js"
|
|
24
|
+
},
|
|
25
|
+
"./src/*": "./src/*",
|
|
26
|
+
"./package.json": "./package.json"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"lib/index.js",
|
|
30
|
+
"lib/invariant.js",
|
|
31
|
+
"lib/types/**/*.d.ts"
|
|
32
|
+
],
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"@hydraharness/harness-invariants": "^0.1.1-rc.5",
|
|
36
|
+
"@hydraharness/cordis": "^4.0.2"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@hydraharness/harness-invariants": "^0.1.1-rc.5",
|
|
40
|
+
"@hydraharness/cordis": "^4.0.2"
|
|
41
|
+
}
|
|
6
42
|
}
|