@deepseek-ai/dsh-api-workspace-files 0.1.5-alpha.1 → 0.1.5-alpha.2
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/README.i18n.yaml +2 -2
- package/README.md +21 -19
- package/README.zh.md +21 -19
- package/lib/client.js +34 -82
- package/lib/index.js +138 -62
- package/lib/typert.host.js +160 -525
- package/lib/typert.remote-client.d.ts +14 -17
- package/lib/typert.remote-client.js +125 -40
- package/lib/types/changes.d.ts +2 -2
- package/lib/types/changes.js +2 -2
- package/lib/types/client/change-feed.d.ts +6 -18
- package/lib/types/client/change-feed.js +6 -38
- package/lib/types/client/index.d.ts +3 -4
- package/lib/types/client/index.js +4 -8
- package/lib/types/client/provider.d.ts +9 -20
- package/lib/types/client/provider.js +13 -33
- package/lib/types/client/types.d.ts +7 -26
- package/lib/types/index.d.ts +65 -50
- package/lib/types/index.js +108 -61
- package/lib/types/types.d.ts +4 -4
- package/package.json +12 -14
|
@@ -1,30 +1,29 @@
|
|
|
1
1
|
import { RemoteError } from '@deepseek-ai/dsh-typert-protocol';
|
|
2
2
|
import { parseFileAddress } from '@deepseek-ai/dsh-util-workspace-path';
|
|
3
3
|
/**
|
|
4
|
-
* Build the `file` provider over one Remote face
|
|
4
|
+
* Build the `file` provider over one Remote face and one change feed.
|
|
5
5
|
* @param remote - the Remote face carrying `workspaceFiles.stat`.
|
|
6
6
|
* @param changes - the per-session change fan-out.
|
|
7
|
-
* @param sessions - the current Session, read for absolute addresses on every open and reload.
|
|
8
7
|
* @returns the provider to register into `ctx.resources`.
|
|
9
8
|
*/
|
|
10
|
-
export function createFileResourceProvider(remote, changes
|
|
9
|
+
export function createFileResourceProvider(remote, changes) {
|
|
11
10
|
return {
|
|
12
11
|
protocol: 'file',
|
|
13
12
|
async *open(address, { signal }) {
|
|
14
|
-
const resolved = resolve(address
|
|
13
|
+
const resolved = resolve(address);
|
|
15
14
|
if (!resolved.ok) {
|
|
16
15
|
yield resolved;
|
|
17
16
|
return;
|
|
18
17
|
}
|
|
19
18
|
const { sessionId, path } = resolved.value;
|
|
20
19
|
// Queue changes delivered to this Client while stat is pending.
|
|
21
|
-
const notices = changes.follow(sessionId,
|
|
20
|
+
const notices = changes.follow(sessionId, signal);
|
|
22
21
|
const stat = () => remote.workspaceFiles.stat(sessionId, path, signal);
|
|
23
22
|
// Read through a call: a plain `signal.aborted` is narrowed to `false` by
|
|
24
23
|
// the first check and would read as always-false after the later awaits.
|
|
25
24
|
const aborted = () => signal.aborted;
|
|
26
25
|
// Undefined while the last stat failed: the follow is on the address, not
|
|
27
|
-
// on the file, so a write
|
|
26
|
+
// on the file, so a write can still bring the file live.
|
|
28
27
|
let current;
|
|
29
28
|
try {
|
|
30
29
|
if (!await notices.ready || aborted())
|
|
@@ -34,7 +33,7 @@ export function createFileResourceProvider(remote, changes, sessions) {
|
|
|
34
33
|
return;
|
|
35
34
|
if (first.ok) {
|
|
36
35
|
notices.bind(first.value.absolutePath);
|
|
37
|
-
current =
|
|
36
|
+
current = first.value;
|
|
38
37
|
yield { ok: true, value: current };
|
|
39
38
|
}
|
|
40
39
|
else {
|
|
@@ -51,11 +50,11 @@ export function createFileResourceProvider(remote, changes, sessions) {
|
|
|
51
50
|
// consumer learns nothing new.
|
|
52
51
|
if (notice.version === current.version)
|
|
53
52
|
continue;
|
|
54
|
-
current = { ...current, version: notice.version
|
|
53
|
+
current = { ...current, version: notice.version };
|
|
55
54
|
yield { ok: true, value: current };
|
|
56
55
|
continue;
|
|
57
56
|
}
|
|
58
|
-
// A Host notice may mean stale content
|
|
57
|
+
// A Host notice may mean stale content.
|
|
59
58
|
const again = await stat();
|
|
60
59
|
if (aborted())
|
|
61
60
|
return;
|
|
@@ -65,7 +64,7 @@ export function createFileResourceProvider(remote, changes, sessions) {
|
|
|
65
64
|
continue;
|
|
66
65
|
}
|
|
67
66
|
notices.bind(again.value.absolutePath);
|
|
68
|
-
current =
|
|
67
|
+
current = again.value;
|
|
69
68
|
yield { ok: true, value: current };
|
|
70
69
|
}
|
|
71
70
|
}
|
|
@@ -73,20 +72,14 @@ export function createFileResourceProvider(remote, changes, sessions) {
|
|
|
73
72
|
notices.dispose();
|
|
74
73
|
}
|
|
75
74
|
},
|
|
76
|
-
reload(address) {
|
|
77
|
-
const resolved = resolve(address, sessions);
|
|
78
|
-
if (resolved.ok)
|
|
79
|
-
changes.requestRestat(resolved.value.sessionId, address);
|
|
80
|
-
},
|
|
81
75
|
};
|
|
82
76
|
}
|
|
83
77
|
/**
|
|
84
78
|
* Resolve one address to the Host call it stands for, or to the failure frame it earns.
|
|
85
79
|
* @param address - the full address, scheme included.
|
|
86
|
-
* @param sessions - the Client's Session list.
|
|
87
80
|
* @returns the Host file, or the `unsupported-address` / `unknown-workspace` failure.
|
|
88
81
|
*/
|
|
89
|
-
function resolve(address
|
|
82
|
+
function resolve(address) {
|
|
90
83
|
const parsed = parseFileAddress(address);
|
|
91
84
|
if (parsed === undefined)
|
|
92
85
|
return { ok: false, error: unsupportedAddress(address) };
|
|
@@ -95,10 +88,7 @@ function resolve(address, sessions) {
|
|
|
95
88
|
const sessionId = parsed.sessionId;
|
|
96
89
|
return { ok: true, value: { sessionId, path: parsed.path } };
|
|
97
90
|
}
|
|
98
|
-
|
|
99
|
-
if (sessionId === undefined)
|
|
100
|
-
return { ok: false, error: unknownWorkspace(address) };
|
|
101
|
-
return { ok: true, value: { sessionId, path: parsed.path } };
|
|
91
|
+
return { ok: false, error: unknownWorkspace(address) };
|
|
102
92
|
}
|
|
103
93
|
/**
|
|
104
94
|
* The failure frame's error for an address this provider does not serve.
|
|
@@ -109,21 +99,11 @@ function unsupportedAddress(address) {
|
|
|
109
99
|
return new RemoteError('workspace-file/unsupported-address', `${address} is not a dsh-resource://file/session/<sessionId>/<path> or dsh-resource://file/absolute/<path> address`, { address });
|
|
110
100
|
}
|
|
111
101
|
/**
|
|
112
|
-
* The failure frame's error for an absolute address with no
|
|
102
|
+
* The failure frame's error for an absolute address with no Session.
|
|
113
103
|
* @param address - the offending address.
|
|
114
104
|
* @returns the typed error.
|
|
115
105
|
*/
|
|
116
106
|
function unknownWorkspace(address) {
|
|
117
|
-
return new RemoteError('workspace-file/unknown-workspace', `${address} requires a
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* The resource value one `stat` result amounts to.
|
|
121
|
-
* @param stat - what the Host reported.
|
|
122
|
-
* @param changed - whether the consumer's content may be stale: `true` after a
|
|
123
|
-
* Host notice prompted the stat, `false` for the opening stat and a reload's.
|
|
124
|
-
* @returns the metadata frame value.
|
|
125
|
-
*/
|
|
126
|
-
function metadataOf(stat, changed) {
|
|
127
|
-
return { absolutePath: stat.absolutePath, version: stat.version, changed, ...(stat.bytes === undefined ? {} : { bytes: stat.bytes }) };
|
|
107
|
+
return new RemoteError('workspace-file/unknown-workspace', `${address} requires a dsh-resource://file/session/<sessionId>/<path> address`, { address });
|
|
128
108
|
}
|
|
129
109
|
//# sourceMappingURL=provider.js.map
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
+
import type { WorkspaceFileStat } from '../types.ts';
|
|
1
2
|
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
2
3
|
interface ResourceProtocolMap {
|
|
3
4
|
/**
|
|
4
5
|
* One workspace file's metadata, addressed as
|
|
5
|
-
* `dsh-resource://file/session/<sessionId>/<
|
|
6
|
-
* `dsh-resource://file/absolute/<absolute path>`.
|
|
6
|
+
* `dsh-resource://file/session/<sessionId>/<path>` (absolute or workspace-relative).
|
|
7
7
|
*/
|
|
8
|
-
file:
|
|
8
|
+
file: WorkspaceFileStat;
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
11
|
/** What a `file` tab is asked to reveal on open or navigation; JSON-shaped. */
|
|
@@ -17,7 +17,7 @@ declare module '@deepseek-ai/dsh-typert-protocol' {
|
|
|
17
17
|
interface RemoteErrorDetailsMap {
|
|
18
18
|
/**
|
|
19
19
|
* The address is not a `dsh-resource://file/` address in a scope the
|
|
20
|
-
* provider
|
|
20
|
+
* provider recognizes: `session/<sessionId>/<path>` or
|
|
21
21
|
* `absolute/<absolute path>`. Raised by the Client provider; the Host never
|
|
22
22
|
* emits it.
|
|
23
23
|
*/
|
|
@@ -25,7 +25,7 @@ declare module '@deepseek-ai/dsh-typert-protocol' {
|
|
|
25
25
|
readonly address: string;
|
|
26
26
|
};
|
|
27
27
|
/**
|
|
28
|
-
* An `absolute` address has no
|
|
28
|
+
* An `absolute` address has no Session to authorize its Host call.
|
|
29
29
|
* Raised by the Client provider; the Host never emits it. Session addresses
|
|
30
30
|
* are resolved by the Host without a Client Session summary.
|
|
31
31
|
*/
|
|
@@ -34,23 +34,6 @@ declare module '@deepseek-ai/dsh-typert-protocol' {
|
|
|
34
34
|
};
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
|
-
/**
|
|
38
|
-
* One workspace file as the resource model carries it: metadata only.
|
|
39
|
-
*
|
|
40
|
-
* The stream reports that the file moved on; it never carries content. A
|
|
41
|
-
* consumer reads the text itself, by page, and uses `version` and `changed` to
|
|
42
|
-
* know when its pages are stale.
|
|
43
|
-
*/
|
|
44
|
-
export interface WorkspaceFileResource {
|
|
45
|
-
/** Absolute path in the Host filesystem, as returned by the last successful stat. */
|
|
46
|
-
readonly absolutePath: string;
|
|
47
|
-
/** The Host's latest report of the file's version: from `stat` first, then from each reported write. */
|
|
48
|
-
readonly version: string;
|
|
49
|
-
/** Byte size as of the last `stat`, when the backend reports it. */
|
|
50
|
-
readonly bytes?: number;
|
|
51
|
-
/** The Host reported a write after the last `stat`; a reload (`stat` again) clears it. */
|
|
52
|
-
readonly changed: boolean;
|
|
53
|
-
}
|
|
54
37
|
/** One Host-reported write inside the session's workspace. */
|
|
55
38
|
export type WorkspaceFileEdit = {
|
|
56
39
|
readonly kind: 'changed';
|
|
@@ -58,8 +41,6 @@ export type WorkspaceFileEdit = {
|
|
|
58
41
|
} | {
|
|
59
42
|
readonly kind: 'absent';
|
|
60
43
|
};
|
|
61
|
-
/** What one follower of a path receives: a Host write
|
|
62
|
-
export type WorkspaceFileNotice = WorkspaceFileEdit
|
|
63
|
-
readonly kind: 'restat';
|
|
64
|
-
};
|
|
44
|
+
/** What one follower of a path receives: a Host write. */
|
|
45
|
+
export type WorkspaceFileNotice = WorkspaceFileEdit;
|
|
65
46
|
//# sourceMappingURL=types.d.ts.map
|
package/lib/types/index.d.ts
CHANGED
|
@@ -1,20 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Workspace file service:
|
|
3
|
-
* listings, and the
|
|
4
|
-
*
|
|
2
|
+
* Workspace file service: read-only file previews, workspace directory
|
|
3
|
+
* listings, and the filesystem-observation change feed, exposed as
|
|
4
|
+
* `workspaceFiles`.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* path strings: `resolve` realpaths, so a prefix test cannot see a symlink
|
|
13
|
-
* that leaves the root. `lstat` rejects a link before that follow happens.
|
|
14
|
-
* 3. Every cap is validated Config, changeable per deployment. A page is cut by
|
|
15
|
-
* lines and refused, not shortened, when its bytes exceed the byte cap; a
|
|
16
|
-
* listing is cut by entries and says so.
|
|
17
|
-
* 4. Failures are one `RemoteError` per reason, declared in `./types`.
|
|
6
|
+
* File reads follow the composed filesystem's read access, including paths
|
|
7
|
+
* outside the workspace. The selected Session header supplies the base for
|
|
8
|
+
* relative paths, with the sandbox policy root as its no-cwd fallback, not a
|
|
9
|
+
* read-containment restriction. Directory listings and change observations
|
|
10
|
+
* remain workspace-scoped. File-kind checks and configured read caps apply to
|
|
11
|
+
* every preview; this service exposes no mutations.
|
|
18
12
|
*
|
|
19
13
|
* A page is cut from `streamText`, which decodes and rejects non-UTF-8 as it
|
|
20
14
|
* goes, so the file is read only up to the first character past the page and
|
|
@@ -26,8 +20,8 @@
|
|
|
26
20
|
*/
|
|
27
21
|
import type { Context } from '@deepseek-ai/cordis';
|
|
28
22
|
import z from '@deepseek-ai/schemastery';
|
|
29
|
-
import type {
|
|
30
|
-
import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
23
|
+
import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
24
|
+
import { TypertRemoteService, type TypertLookup } from '@deepseek-ai/dsh-typert-protocol';
|
|
31
25
|
import type { WorkspaceByteRange, WorkspaceDirectoryListing, WorkspaceFileBytes, WorkspaceFileRange, WorkspaceFileStat, WorkspaceFileText, WorkspaceFileWatchFrame } from './types.ts';
|
|
32
26
|
export type * from './types.ts';
|
|
33
27
|
declare module '@deepseek-ai/cordis' {
|
|
@@ -36,6 +30,19 @@ declare module '@deepseek-ai/cordis' {
|
|
|
36
30
|
workspaceFiles: WorkspaceFiles;
|
|
37
31
|
}
|
|
38
32
|
}
|
|
33
|
+
/** Header-derived file resolution context for one Session identity. */
|
|
34
|
+
export interface WorkspaceFileScope {
|
|
35
|
+
/** Session identity received on the wire. */
|
|
36
|
+
readonly sessionId: SessionId;
|
|
37
|
+
/** Session workspace root, or the deployment fallback when its header has no cwd. */
|
|
38
|
+
readonly workspaceRoot: string;
|
|
39
|
+
}
|
|
40
|
+
declare module '@deepseek-ai/dsh-typert-protocol' {
|
|
41
|
+
interface TypertLookupMap {
|
|
42
|
+
/** Resolve a Session id to its workspace root without loading its event body or activating an Agent. */
|
|
43
|
+
workspaceFileScope: TypertLookup<WorkspaceFileScope, SessionId>;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
39
46
|
/** Deployment caps on one page or one listing. */
|
|
40
47
|
export interface Config {
|
|
41
48
|
/**
|
|
@@ -46,12 +53,14 @@ export interface Config {
|
|
|
46
53
|
* way. The file itself has no size cap: a caller pages through it.
|
|
47
54
|
*/
|
|
48
55
|
readonly maxBytes: number;
|
|
56
|
+
/** Inclusive byte cap on a complete-file read; larger files are refused, never truncated. */
|
|
57
|
+
readonly maxFileBytes: number;
|
|
49
58
|
/** Default and largest page size in lines; a request asking for more is refused. */
|
|
50
59
|
readonly maxLines: number;
|
|
51
60
|
/** Cap on returned directory entries; the rest is dropped and reported cut. */
|
|
52
61
|
readonly maxEntries: number;
|
|
53
62
|
}
|
|
54
|
-
/** Host Remote
|
|
63
|
+
/** Host Remote file reads and workspace directory observations over the composed filesystem. */
|
|
55
64
|
export declare class WorkspaceFiles extends TypertRemoteService {
|
|
56
65
|
private readonly config;
|
|
57
66
|
static inject: string[];
|
|
@@ -63,68 +72,74 @@ export declare class WorkspaceFiles extends TypertRemoteService {
|
|
|
63
72
|
*/
|
|
64
73
|
constructor(ctx: Context, config: Config);
|
|
65
74
|
/**
|
|
66
|
-
* Read one page of lines from a UTF-8
|
|
67
|
-
* @param
|
|
68
|
-
* @param path -
|
|
75
|
+
* Read one page of lines from a UTF-8 file readable by the filesystem backend.
|
|
76
|
+
* @param workspaceFileScope - header-derived workspace root for the Session identity on the wire.
|
|
77
|
+
* @param path - absolute path or path relative to the workspace root; files outside it are allowed.
|
|
69
78
|
* @param range - the line window; omitted fields take the page defaults.
|
|
70
79
|
* @param signal - caller cancellation.
|
|
71
80
|
* @returns the page, the file's version at the stat before it, and whether it reaches the last line.
|
|
72
81
|
*/
|
|
73
|
-
read(
|
|
82
|
+
read(workspaceFileScope: WorkspaceFileScope, path: string, range: WorkspaceFileRange, signal: AbortSignal): Promise<WorkspaceFileText>;
|
|
74
83
|
/**
|
|
75
|
-
* Read one byte window of a regular file
|
|
84
|
+
* Read one byte window of a regular file readable by the filesystem backend: raw
|
|
76
85
|
* bytes, no text decoding and no binary rejection.
|
|
77
|
-
* @param
|
|
78
|
-
* @param path -
|
|
86
|
+
* @param workspaceFileScope - header-derived workspace root for the Session identity on the wire.
|
|
87
|
+
* @param path - absolute path or path relative to the workspace root; files outside it are allowed.
|
|
79
88
|
* @param range - the byte window; omitted fields take the window defaults.
|
|
80
89
|
* @param signal - caller cancellation.
|
|
81
90
|
* @returns the window in base64, the file's version and size at the stat before it, and whether it reaches the last byte.
|
|
82
91
|
*/
|
|
83
|
-
readBytes(
|
|
92
|
+
readBytes(workspaceFileScope: WorkspaceFileScope, path: string, range: WorkspaceByteRange, signal: AbortSignal): Promise<WorkspaceFileBytes>;
|
|
93
|
+
/**
|
|
94
|
+
* Read a complete regular file as bytes, subject to the configured full-file cap.
|
|
95
|
+
* @param workspaceFileScope - header-derived workspace root for the Session identity on the wire.
|
|
96
|
+
* @param path - absolute or workspace-relative file path.
|
|
97
|
+
* @param signal - caller cancellation.
|
|
98
|
+
* @returns one complete base64 window with offset zero and eof true; oversized files fail with too-large.
|
|
99
|
+
*/
|
|
100
|
+
readAll(workspaceFileScope: WorkspaceFileScope, path: string, signal: AbortSignal): Promise<WorkspaceFileBytes>;
|
|
101
|
+
/**
|
|
102
|
+
* Read a complete file relative to another file's directory, including outside the workspace.
|
|
103
|
+
* @param workspaceFileScope - header-derived workspace root for the Session identity on the wire.
|
|
104
|
+
* @param path - base file, absolute or workspace-relative.
|
|
105
|
+
* @param relativePath - relative filesystem path, not a URL or absolute path.
|
|
106
|
+
* @param signal - caller cancellation.
|
|
107
|
+
* @returns the complete related file using the ordinary file-size and access checks.
|
|
108
|
+
*/
|
|
109
|
+
readRelated(workspaceFileScope: WorkspaceFileScope, path: string, relativePath: string, signal: AbortSignal): Promise<WorkspaceFileBytes>;
|
|
84
110
|
/**
|
|
85
111
|
* Report one regular file's identity, version, and size without its content.
|
|
86
|
-
* @param
|
|
87
|
-
* @param path -
|
|
112
|
+
* @param workspaceFileScope - header-derived workspace root for the Session identity on the wire.
|
|
113
|
+
* @param path - absolute path or path relative to the workspace root; files outside it are allowed.
|
|
88
114
|
* @param signal - caller cancellation.
|
|
89
115
|
* @returns the file's absolute path, current version, and byte size.
|
|
90
116
|
*/
|
|
91
|
-
stat(
|
|
117
|
+
stat(workspaceFileScope: WorkspaceFileScope, path: string, signal: AbortSignal): Promise<WorkspaceFileStat>;
|
|
92
118
|
/**
|
|
93
|
-
* List the direct children of one directory inside the
|
|
94
|
-
* @param
|
|
119
|
+
* List the direct children of one directory inside the Session's workspace.
|
|
120
|
+
* @param workspaceFileScope - header-derived workspace root for the Session identity on the wire.
|
|
95
121
|
* @param path - workspace path, absolute or relative to the workspace root.
|
|
96
122
|
* @param signal - caller cancellation.
|
|
97
123
|
* @returns the directory's children in the backend's stable name order, bounded by the entry cap.
|
|
98
124
|
*/
|
|
99
|
-
list(
|
|
125
|
+
list(workspaceFileScope: WorkspaceFileScope, path: string, signal: AbortSignal): Promise<WorkspaceDirectoryListing>;
|
|
100
126
|
/**
|
|
101
|
-
* Stream every `fs/observed` observation of a file inside the
|
|
102
|
-
* workspace. Only
|
|
103
|
-
* watched.
|
|
104
|
-
* @param
|
|
127
|
+
* Stream every `fs/observed` observation of a file inside the Session's
|
|
128
|
+
* workspace. Only instrumented filesystem operations report here; the OS is
|
|
129
|
+
* not watched.
|
|
130
|
+
* @param workspaceFileScope - header-derived workspace root for the Session identity on the wire.
|
|
105
131
|
* @param signal - generation cancellation.
|
|
106
132
|
* @returns `ready` once the Host observation queue is active and the workspace
|
|
107
133
|
* root is resolved, then queued and live observations in emission order.
|
|
108
134
|
*/
|
|
109
|
-
changes(
|
|
135
|
+
changes(workspaceFileScope: WorkspaceFileScope, signal: AbortSignal): AsyncIterable<WorkspaceFileWatchFrame>;
|
|
110
136
|
/** Apply the page defaults and caps here, so the request never carries them implicitly. */
|
|
111
137
|
private resolvePage;
|
|
112
138
|
/** Apply the byte-window defaults and cap; a window above the cap is refused, not shortened. */
|
|
113
139
|
private resolveWindow;
|
|
114
140
|
/**
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
* `fs-local` whose cwd differs, and resolving explicitly makes the answer
|
|
118
|
-
* the same whichever instance answers.
|
|
119
|
-
*/
|
|
120
|
-
private workspaceRootOf;
|
|
121
|
-
/**
|
|
122
|
-
* Gates 1 and 2 up to the point where the path's own type is known. The
|
|
123
|
-
* path is inspected before containment is decided, so a caller learns whether
|
|
124
|
-
* an outside path exists and what kind it is before `outside-workspace`
|
|
125
|
-
* refuses it; the caller is the Session's own owner, who can read the Host
|
|
126
|
-
* through the Agent anyway, and the accepted cost buys one `lstat` gate for
|
|
127
|
-
* every method instead of two resolution orders.
|
|
141
|
+
* Inspect the requested path itself before resolution follows its final
|
|
142
|
+
* component. Directory containment is checked separately by `list`.
|
|
128
143
|
*/
|
|
129
144
|
private inspect;
|
|
130
145
|
/** Resolve an inspected path and refuse it unless the workspace contains it. */
|