@dsh-electron/dsh-plugin-multi-root-workspace 0.1.14 → 0.1.16
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/CHANGELOG.en.md +32 -1
- package/CHANGELOG.md +32 -1
- package/README.en.md +25 -8
- package/README.md +25 -8
- package/cordis.patch.yml +5 -1
- package/lib/{llm-message-fYORacvh.js → agent-instructions-BXuKhOzr.js} +1 -84
- package/lib/auto-review.js +2 -2
- package/lib/client.js +969 -100
- package/lib/client.js.map +1 -1
- package/lib/{command-Dcz6nULo.js → command-DaVJ-mK3.js} +76 -11
- package/lib/command.js +1 -1
- package/lib/compat-CTJbpCxu.js +267 -0
- package/lib/compat.js +1 -1
- package/lib/{dialects-Dr4rDR-H.js → dialects-CdlG3RF9.js} +1 -1
- package/lib/{compat-veDqCOoa.js → dsh-version-DCwF7REk.js} +2 -264
- package/lib/index.js +10 -8
- package/lib/{instructions-KsUwUFE0.js → instructions-Ca80SkZq.js} +696 -27
- package/lib/instructions.js +1 -1
- package/lib/llm-message-CepyzEBQ.js +84 -0
- package/lib/lsp.js +4 -4
- package/lib/{metadata-DgqSdVX_.js → metadata-99jgViHQ.js} +1 -1
- package/lib/{method-wrapper-CFY3p3j2.js → method-wrapper-D4oUCsEF.js} +22 -2
- package/lib/{native-win32-CRNXr11U.js → native-win32-C5oMtt7f.js} +1 -1
- package/lib/ops-compatibility.js +58 -0
- package/lib/ops-settings-UN2oOKEp.js +40 -0
- package/lib/ops-tools-k4JYaOrM.js +152 -0
- package/lib/process-fence.js +193 -34
- package/lib/{protocol-CnR1KTXy.js → protocol-CwDnFEyK.js} +8 -4
- package/lib/{registry-DR_sMuvS.js → registry-BibiukWk.js} +236 -80
- package/lib/{registry-lease-win32-DDO-c7ig.js → registry-lease-win32-r4puac9m.js} +1 -1
- package/lib/{registry-operation-C2ZV3Jhw.js → registry-operation-CIlmt6_c.js} +5 -1
- package/lib/registry.js +2 -2
- package/lib/{review-context-NDpKeMCc.js → review-context-OxO-fMWR.js} +24 -7
- package/lib/review-context.js +1 -1
- package/lib/{roots-Ca_W1S9n.js → roots-CmaWxRVy.js} +112 -22
- package/lib/{sandbox-C-vCJCp3.js → sandbox-aobdyQGi.js} +7 -4
- package/lib/sandbox.js +1 -1
- package/lib/scope.js +162 -12
- package/lib/skills-utyuvmwr.js +104 -0
- package/lib/skills.js +5 -106
- package/lib/{tool-context-CpgIFcCB.js → tool-context-CCeY3mrK.js} +1 -1
- package/lib/types/authority/protocol.d.ts +5 -1
- package/lib/types/client/files-tree/FilesTree.d.ts +49 -6
- package/lib/types/client/files-tree/adapter.d.ts +69 -16
- package/lib/types/client/files-tree/face.d.ts +31 -2
- package/lib/types/client/files-tree/locate.d.ts +74 -0
- package/lib/types/client/files-tree/navigator.d.ts +98 -0
- package/lib/types/client/index.d.ts +7 -0
- package/lib/types/client/locales.d.ts +30 -0
- package/lib/types/client/ops-settings.d.ts +6 -0
- package/lib/types/client/skills-settings.d.ts +3 -2
- package/lib/types/client/styles.d.ts +1 -1
- package/lib/types/command.d.ts +3 -3
- package/lib/types/compat/files-tree.d.ts +8 -1
- package/lib/types/compat/forge-entrypoints.d.ts +44 -0
- package/lib/types/compat/instruction-activity.d.ts +32 -1
- package/lib/types/compat/ops-confirmation.d.ts +17 -0
- package/lib/types/compat/ops-settings.d.ts +11 -0
- package/lib/types/compat/ops-tools.d.ts +25 -0
- package/lib/types/compat/session-existence.d.ts +41 -0
- package/lib/types/compat/skills-client.d.ts +1 -0
- package/lib/types/compat/tool-context.d.ts +4 -0
- package/lib/types/compat.d.ts +2 -2
- package/lib/types/instructions.d.ts +6 -1
- package/lib/types/ops-compatibility.d.ts +34 -0
- package/lib/types/registry-operation.d.ts +7 -0
- package/lib/types/registry.d.ts +67 -8
- package/lib/types/review-context.d.ts +2 -0
- package/lib/types/roots.d.ts +116 -20
- package/lib/types/scope.d.ts +95 -3
- package/lib/workspace-files.js +4 -4
- package/lib/{workspace-routing-YPxaQ4V3.js → workspace-routing-D_qwMUh-.js} +4 -4
- package/package.json +5 -1
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Service, type Context } from '@deepseek-ai/cordis';
|
|
2
|
+
import z from '@deepseek-ai/schemastery';
|
|
3
|
+
export interface Config {
|
|
4
|
+
readonly enabled?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare const Config: z<Schemastery.ObjectS<NoInfer<{
|
|
7
|
+
enabled: z<boolean, boolean, "defined">;
|
|
8
|
+
}>>, Schemastery.ObjectT<NoInfer<{
|
|
9
|
+
enabled: z<boolean, boolean, "defined">;
|
|
10
|
+
}>>, "plain">;
|
|
11
|
+
declare module '@deepseek-ai/cordis' {
|
|
12
|
+
interface Context {
|
|
13
|
+
multiRootOpsCompatibility: MultiRootOpsCompatibility;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export declare class MultiRootOpsCompatibility extends Service {
|
|
17
|
+
private readonly settingsConfig;
|
|
18
|
+
static Config: z<Schemastery.ObjectS<NoInfer<{
|
|
19
|
+
enabled: z<boolean, boolean, "defined">;
|
|
20
|
+
}>>, Schemastery.ObjectT<NoInfer<{
|
|
21
|
+
enabled: z<boolean, boolean, "defined">;
|
|
22
|
+
}>>, "plain">;
|
|
23
|
+
static provide: string;
|
|
24
|
+
static inject: string[];
|
|
25
|
+
constructor(ctx: Context, settingsConfig?: Config);
|
|
26
|
+
private settingsRead;
|
|
27
|
+
private currentToken;
|
|
28
|
+
private enabledNow;
|
|
29
|
+
private closing;
|
|
30
|
+
get token(): object | undefined;
|
|
31
|
+
get enabled(): boolean;
|
|
32
|
+
private sync;
|
|
33
|
+
}
|
|
34
|
+
export default MultiRootOpsCompatibility;
|
|
@@ -2,6 +2,13 @@ import type { SandboxMode } from '@deepseek-ai/dsh-sandbox';
|
|
|
2
2
|
import type { FilesystemScope } from './scope.ts';
|
|
3
3
|
export type RegistryOperationKind = 'fs-write' | 'fs-edit' | 'process' | 'pty' | 'lsp' | 'files' | 'instructions' | 'skills';
|
|
4
4
|
export declare function currentRegistryOperation(proof: object): RegistryOperation | undefined;
|
|
5
|
+
/**
|
|
6
|
+
* The enclosing admission frame's mint disposition for this proof. `expired`
|
|
7
|
+
* frames still occupy the async context of their descendants; an expired
|
|
8
|
+
* admission must never bequeath a launchable profile.
|
|
9
|
+
*/
|
|
10
|
+
export type OperationFrameState = 'active' | 'expired' | 'absent';
|
|
11
|
+
export declare function operationFrameState(proof: object): OperationFrameState;
|
|
5
12
|
export interface RegistryOperationIdentity {
|
|
6
13
|
readonly operationId: string;
|
|
7
14
|
readonly kind: RegistryOperationKind;
|
package/lib/types/registry.d.ts
CHANGED
|
@@ -121,7 +121,12 @@ export type RegistryAuthorityState = {
|
|
|
121
121
|
export interface Config {
|
|
122
122
|
/** Absolute path of the store-wide kernel lock file. */
|
|
123
123
|
readonly leasePath?: string;
|
|
124
|
-
/**
|
|
124
|
+
/**
|
|
125
|
+
* Medium binding for the multi-Host authority service — REQUIRED since the
|
|
126
|
+
* single-process mode was removed (ADR-0028). Absent or empty puts the
|
|
127
|
+
* registry in the fail-closed `config-invalid` state: no lease, no domain,
|
|
128
|
+
* no grants.
|
|
129
|
+
*/
|
|
125
130
|
readonly sharedStorageRoot?: string;
|
|
126
131
|
/**
|
|
127
132
|
* Cap on STORED additional-root records per primary root (every state
|
|
@@ -281,8 +286,15 @@ export declare class MultiRootRegistry extends Service {
|
|
|
281
286
|
/** Keys already warned about a historical over-limit store (one warning each). */
|
|
282
287
|
private readonly warnedOverLimit;
|
|
283
288
|
/**
|
|
284
|
-
*
|
|
285
|
-
*
|
|
289
|
+
* The canonical value each protected boundary resolved to the FIRST time it
|
|
290
|
+
* could be computed, keyed by boundary kind. Never re-pinned: a later
|
|
291
|
+
* rename or symlink swap of the DSH home (or of the lease directory) must
|
|
292
|
+
* not drop the boundary the registration rules were enforced under.
|
|
293
|
+
*/
|
|
294
|
+
private readonly pinnedProtectedPaths;
|
|
295
|
+
/**
|
|
296
|
+
* @param ctx - the host context; `storage` and `multiRootScope` are injected.
|
|
297
|
+
* @param config - the patch row's `sharedStorageRoot`, `leasePath`, and `maxAdditionalRoots`.
|
|
286
298
|
*/
|
|
287
299
|
constructor(ctx: Context, config?: Config);
|
|
288
300
|
/**
|
|
@@ -379,6 +391,44 @@ export declare class MultiRootRegistry extends Service {
|
|
|
379
391
|
*/
|
|
380
392
|
withOperation<T>(policy: SandboxExecutionPolicy, kind: RegistryOperationKind, body: (operation: RegistryOperation) => T | Promise<T>): Promise<T>;
|
|
381
393
|
withPrimaryOperation<T>(policy: SandboxExecutionPolicy, body: () => T): T;
|
|
394
|
+
/**
|
|
395
|
+
* The spellings one protected boundary contributes: the configured path,
|
|
396
|
+
* its current canonical resolution, and the canonical value pinned the
|
|
397
|
+
* first time it resolved — never re-pinned, so a later rename or symlink
|
|
398
|
+
* swap of the protected directory cannot drop the boundary the
|
|
399
|
+
* registration rules were enforced under. The list is deduplicated by
|
|
400
|
+
* {@link samePath}.
|
|
401
|
+
*/
|
|
402
|
+
private boundarySpellings;
|
|
403
|
+
/**
|
|
404
|
+
* The best-known protected-boundary set, plus whether it is COMPLETE.
|
|
405
|
+
*
|
|
406
|
+
* The set comes from trusted host configuration, never from a panel or
|
|
407
|
+
* shell claim: the scope row's `dshHome`, the physical medium
|
|
408
|
+
* `storeIdentity` bound, and the lease directory beside it.
|
|
409
|
+
* `complete` is false — and only NEW grants may then be refused — when a
|
|
410
|
+
* boundary cannot be proven: an invalid `dshHome` config, a configured
|
|
411
|
+
* home that is not a directory right now, or a medium that was never bound. Stored classification always consumes whatever IS known
|
|
412
|
+
* (pinned values included), so an unknown boundary never weakens the
|
|
413
|
+
* revalidation of existing records.
|
|
414
|
+
*/
|
|
415
|
+
private protectedBoundarySet;
|
|
416
|
+
/**
|
|
417
|
+
* The protected DSH directories this Host can prove right now, or
|
|
418
|
+
* `undefined` when the set cannot be proven. `undefined` blocks every NEW
|
|
419
|
+
* grant; it never weakens the classification of stored records.
|
|
420
|
+
*/
|
|
421
|
+
private protectedPaths;
|
|
422
|
+
/**
|
|
423
|
+
* Every protected entry this Host can prove — used wherever a partial set
|
|
424
|
+
* must still protect stored records and received snapshots.
|
|
425
|
+
*/
|
|
426
|
+
private knownProtectedPaths;
|
|
427
|
+
/**
|
|
428
|
+
* The one refusal shared by the owner mutation path and the follower
|
|
429
|
+
* pre-check: the protected set cannot be proven, so no new grant is safe.
|
|
430
|
+
*/
|
|
431
|
+
private static protectedBoundaryUnknown;
|
|
382
432
|
/**
|
|
383
433
|
* Registers one additional root.
|
|
384
434
|
* @param primaryRoot - the session workspace root the root belongs to.
|
|
@@ -463,7 +513,7 @@ export declare class MultiRootRegistry extends Service {
|
|
|
463
513
|
recheck(primaryRoot: string): Promise<readonly RootStatus[]>;
|
|
464
514
|
/**
|
|
465
515
|
* Observe registry changes.
|
|
466
|
-
* @param listener - called with the
|
|
516
|
+
* @param listener - called with the Registry key (`primaryKey()`, case-folded on Windows) after each durable change.
|
|
467
517
|
* @returns the disposer that stops observing.
|
|
468
518
|
*/
|
|
469
519
|
onChange(listener: (primaryRoot: string) => void): () => void;
|
|
@@ -495,10 +545,17 @@ export declare class MultiRootRegistry extends Service {
|
|
|
495
545
|
*/
|
|
496
546
|
private statusesOf;
|
|
497
547
|
/**
|
|
498
|
-
* The stored record set of one key,
|
|
499
|
-
*
|
|
500
|
-
*
|
|
501
|
-
* the
|
|
548
|
+
* The stored record set of one key: `{ record, migrated: false }` when the
|
|
549
|
+
* spelling key holds it; `{ record, migrated: true }` when only the legacy
|
|
550
|
+
* CANONICAL key does — stores written before the upstream primary spelling
|
|
551
|
+
* became the key (v0.1.14 and earlier). The fallback follows the CURRENT
|
|
552
|
+
* resolution ONLY for such a not-yet-migrated store; the first serialized
|
|
553
|
+
* access copies the record forward under the spelling key (see
|
|
554
|
+
* {@link reclassify}), after which resolution can never re-key a grant.
|
|
555
|
+
* `undefined` when there is no record (or when the store itself could not
|
|
556
|
+
* be read: an unusable store reads as "nothing registered" so reads stay
|
|
557
|
+
* answerable and the surfaces can explain the situation, while every write
|
|
558
|
+
* fails loudly in `requireTable()`).
|
|
502
559
|
*/
|
|
503
560
|
private lookupRecords;
|
|
504
561
|
/**
|
|
@@ -562,6 +619,8 @@ export declare class MultiRootRegistry extends Service {
|
|
|
562
619
|
private commit;
|
|
563
620
|
/** Deliver one key's notifications now, or collect them inside a commit. */
|
|
564
621
|
private deliverNotifications;
|
|
622
|
+
/** Narrow owner rows for this Host, identically for projection and admission. */
|
|
623
|
+
private localSnapshotRoots;
|
|
565
624
|
private applySnapshot;
|
|
566
625
|
private remoteUnavailable;
|
|
567
626
|
/**
|
|
@@ -8,8 +8,10 @@ declare module '@deepseek-ai/cordis' {
|
|
|
8
8
|
}
|
|
9
9
|
export interface ReviewExecution {
|
|
10
10
|
readonly agent?: Agent;
|
|
11
|
+
readonly callId?: string;
|
|
11
12
|
readonly name: string;
|
|
12
13
|
readonly arguments: unknown;
|
|
14
|
+
readonly parent?: unknown;
|
|
13
15
|
}
|
|
14
16
|
export interface ReviewContext {
|
|
15
17
|
readonly schemaVersion: 1;
|
package/lib/types/roots.d.ts
CHANGED
|
@@ -14,15 +14,21 @@
|
|
|
14
14
|
* misconfiguration, not a path to guess at.
|
|
15
15
|
* 2. `missing` — the directory does not exist.
|
|
16
16
|
* 3. `not-a-directory` — it exists but is not a directory.
|
|
17
|
-
* 4. `
|
|
17
|
+
* 4. `protected-path` — the canonical candidate equals, lies inside, or
|
|
18
|
+
* contains a protected DSH directory (the DSH home, the registry's
|
|
19
|
+
* physical storage medium, or the lease directory, supplied through
|
|
20
|
+
* {@link RootCandidateContext.protectedPaths}): those hold the store,
|
|
21
|
+
* the lease, profiles and user credentials, so no access level may
|
|
22
|
+
* register them. Applies to every mode; no remote exemption exists.
|
|
23
|
+
* 5. `equals-primary` — the canonical candidate IS the session's workspace
|
|
18
24
|
* root, which is already granted by the upstream policy.
|
|
19
|
-
*
|
|
25
|
+
* 6. `primary-overlap` — the candidate lies under the workspace root, or
|
|
20
26
|
* contains it. The union grants nothing new, and the displayed list would
|
|
21
27
|
* stop matching the enforced root set.
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* Same reasoning as rule
|
|
25
|
-
*
|
|
28
|
+
* 7. `duplicate` — another registered root canonicalizes to the same path.
|
|
29
|
+
* 8. `nested` — the candidate lies under a registered root, or contains one.
|
|
30
|
+
* Same reasoning as rule 6, applied to the additional roots.
|
|
31
|
+
* 9. `unsupported-path` — on Windows, a network or device path (`\\server`,
|
|
26
32
|
* `\\?\…`, `\\.\…`): the ACL confinement cannot be shown to cover it, so it
|
|
27
33
|
* is rejected lexically before any filesystem touch.
|
|
28
34
|
*
|
|
@@ -67,6 +73,23 @@ export declare function additionalRootId(value: string): AdditionalRootId;
|
|
|
67
73
|
* `read-only` session; it does not widen anything beyond its own directory.
|
|
68
74
|
*/
|
|
69
75
|
export type RootAccess = 'inherit' | 'read-write' | 'read-only';
|
|
76
|
+
/**
|
|
77
|
+
* Which protected DSH directory one boundary belongs to: the harness home
|
|
78
|
+
* (`$DSH_HOME`), the registry's physical storage medium, or the directory the
|
|
79
|
+
* store-wide lease occupies.
|
|
80
|
+
*/
|
|
81
|
+
export type ProtectedPathKind = 'dsh-home' | 'registry-medium' | 'registry-lease';
|
|
82
|
+
/**
|
|
83
|
+
* One directory no additional root may equal, lie inside, or contain. `path`
|
|
84
|
+
* is an absolute spelling — the configured one, the current canonical one, or
|
|
85
|
+
* a canonical value pinned the first time the boundary resolved — and is
|
|
86
|
+
* compared with {@link samePath}/{@link isCanonicallyUnder} exactly like
|
|
87
|
+
* every other boundary in this module.
|
|
88
|
+
*/
|
|
89
|
+
export interface ProtectedPath {
|
|
90
|
+
readonly kind: ProtectedPathKind;
|
|
91
|
+
readonly path: string;
|
|
92
|
+
}
|
|
70
93
|
/** One registered additional root exactly as it is persisted. */
|
|
71
94
|
export interface RegisteredRoot {
|
|
72
95
|
/** Stable registry identity. */
|
|
@@ -139,13 +162,20 @@ export interface RootStatus extends RegisteredRoot {
|
|
|
139
162
|
*
|
|
140
163
|
* `'membership-conflict'` means the record passed every structural rule —
|
|
141
164
|
* including an intact, absolute recorded grant — but its recorded boundary
|
|
142
|
-
* overlaps another record's.
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
165
|
+
* overlaps another record's. `'protected-path'` means that same intact
|
|
166
|
+
* grant overlaps a protected DSH directory (the home, the storage medium,
|
|
167
|
+
* or the lease directory) — typically a store written before the boundary
|
|
168
|
+
* existed or on another layout. `'primary-overlap'` means the intact grant
|
|
169
|
+
* equals or overlaps the CURRENT canonical primary root — e.g. a primary
|
|
170
|
+
* spelling retargeted onto (or above) a read-only root — which withholds
|
|
171
|
+
* membership but must never drop the deny. All three reasons mean the same
|
|
172
|
+
* thing: the boundary is intact and trustworthy, so
|
|
173
|
+
* {@link reservedRootBoundary} keeps it reserved and the registry
|
|
174
|
+
* publishes the root as a withheld, deny-only entry — only the membership
|
|
175
|
+
* is withheld. Every other `invalid` verdict left the grant unverifiable,
|
|
176
|
+
* so it contributes nothing.
|
|
147
177
|
*/
|
|
148
|
-
readonly invalidReason?: 'membership-conflict';
|
|
178
|
+
readonly invalidReason?: 'membership-conflict' | 'protected-path' | 'primary-overlap';
|
|
149
179
|
}
|
|
150
180
|
/** One status together with the 1-based ordinal the surfaces accept for it. */
|
|
151
181
|
export interface IndexedRootStatus {
|
|
@@ -172,7 +202,9 @@ export type RootRef = {
|
|
|
172
202
|
readonly addedAt: string;
|
|
173
203
|
};
|
|
174
204
|
/** Every way root handling can fail, as a stable code the surfaces localize. */
|
|
175
|
-
export type RootValidationCode = 'not-absolute' | 'missing' | 'not-a-directory' | 'equals-primary' | 'primary-overlap' | 'duplicate' | 'nested' | 'invalid-alias' | 'invalid-alias-id' | 'not-found' | 'invalid-ref' | 'storage-unavailable' | 'registry-contended' | 'root-limit-reached' | 'config-invalid' | 'reveal-unavailable' | 'session-not-found'
|
|
205
|
+
export type RootValidationCode = 'not-absolute' | 'missing' | 'not-a-directory' | 'equals-primary' | 'primary-overlap' | 'duplicate' | 'nested' | 'invalid-alias' | 'invalid-alias-id' | 'not-found' | 'invalid-ref' | 'storage-unavailable' | 'registry-contended' | 'root-limit-reached' | 'config-invalid' | 'reveal-unavailable' | 'session-not-found'
|
|
206
|
+
/** Synthesized at the panel dispatch catch — the session existed but could not resolve a root during the request (writer-held, mid-resume, or a race resolved after the throw); no RootValidationError is constructed with it. */
|
|
207
|
+
| 'session-not-live' | 'unsupported-path' | 'root-unavailable' | 'protected-path' | 'protected-boundary-unknown' | 'revive-target-changed';
|
|
176
208
|
/** A rejected root operation; `code` is the stable contract, `message` the fallback prose. */
|
|
177
209
|
export declare class RootValidationError extends Error {
|
|
178
210
|
readonly code: RootValidationCode;
|
|
@@ -200,6 +232,21 @@ export declare class RootValidationError extends Error {
|
|
|
200
232
|
* @returns the expanded path (still unvalidated).
|
|
201
233
|
*/
|
|
202
234
|
export declare function expandRootInput(raw: string, home?: string): string;
|
|
235
|
+
/**
|
|
236
|
+
* The Registry/scope lookup key of one primary root: the UPSTREAM spelling,
|
|
237
|
+
* normalized lexically by `path.resolve` and case-folded on Windows — never
|
|
238
|
+
* re-resolved. Upstream `dsh-sandbox-policy` sets `policy.workspaceRoot` to
|
|
239
|
+
* `session.header.cwd` verbatim, so the spelling IS the identity: a symlinked
|
|
240
|
+
* workspace and its realpath target are different sessions and different key
|
|
241
|
+
* spaces. Re-resolving here would let a replaced workspace directory
|
|
242
|
+
* silently re-key the record set onto another identity — the exact failure
|
|
243
|
+
* the redirect rules exist to prevent. This is the ONLY function allowed to
|
|
244
|
+
* turn a primary root into a Registry key; path geometry keeps using
|
|
245
|
+
* {@link canonicalRoot}.
|
|
246
|
+
*/
|
|
247
|
+
export declare function primaryKey(path: string, platform?: NodeJS.Platform): string;
|
|
248
|
+
/** Whether two canonical paths name the same directory on this platform. */
|
|
249
|
+
export declare function samePath(left: string, right: string): boolean;
|
|
203
250
|
/**
|
|
204
251
|
* Whether `candidate` is `root` itself or lies beneath it. Both sides must
|
|
205
252
|
* already be canonical: symlinks are resolved by then, so the lexical test is
|
|
@@ -242,6 +289,15 @@ export interface RootCandidateContext {
|
|
|
242
289
|
* revived exempts only itself.
|
|
243
290
|
*/
|
|
244
291
|
readonly existing: readonly string[];
|
|
292
|
+
/**
|
|
293
|
+
* The protected DSH directories — the home, the registry's storage medium,
|
|
294
|
+
* and the lease directory — that no candidate may equal, lie inside, or
|
|
295
|
+
* contain, at any access level and under any mode. Derived from the trusted
|
|
296
|
+
* host configuration by the registry; absent means "no protected set was
|
|
297
|
+
* provided" and is never a substitute for the boundary being unprovable
|
|
298
|
+
* (the caller refuses that case itself with `protected-boundary-unknown`).
|
|
299
|
+
*/
|
|
300
|
+
readonly protectedPaths?: readonly ProtectedPath[];
|
|
245
301
|
}
|
|
246
302
|
/**
|
|
247
303
|
* Judge one candidate root and return its canonical spelling.
|
|
@@ -251,6 +307,16 @@ export interface RootCandidateContext {
|
|
|
251
307
|
* @throws {RootValidationError} with the first rule that failed.
|
|
252
308
|
*/
|
|
253
309
|
export declare function validateRootCandidate(raw: string, context: RootCandidateContext): string;
|
|
310
|
+
/**
|
|
311
|
+
* The first protected directory `candidate` overlaps — equals, lies inside,
|
|
312
|
+
* or contains. Both sides must already be canonical (or absolute boundary
|
|
313
|
+
* spellings): the comparison is the same lexical three-way test the other
|
|
314
|
+
* boundary rules use, so a symlink alias can never route around it.
|
|
315
|
+
* @param candidate - the canonical candidate or recorded grant to test.
|
|
316
|
+
* @param protectedPaths - the protected DSH directories.
|
|
317
|
+
* @returns the overlapping protected path, or `undefined`.
|
|
318
|
+
*/
|
|
319
|
+
export declare function protectedOverlap(candidate: string, protectedPaths: readonly ProtectedPath[]): ProtectedPath | undefined;
|
|
254
320
|
/**
|
|
255
321
|
* Re-judge every stored record of one primary root — the startup and refresh
|
|
256
322
|
* path, which must never throw: a registration that stopped being usable is
|
|
@@ -262,26 +328,56 @@ export declare function validateRootCandidate(raw: string, context: RootCandidat
|
|
|
262
328
|
* @param primaryRoot - the session workspace root the records belong to.
|
|
263
329
|
* @param records - the stored records, in registry order.
|
|
264
330
|
* @param platform - the platform the rules run for; defaults to `process.platform`.
|
|
331
|
+
* @param protectedPaths - the protected DSH directories a stored grant may not
|
|
332
|
+
* overlap; a record that does is reported `invalid` with
|
|
333
|
+
* `invalidReason: 'protected-path'` — withheld, never deleted, never re-granted.
|
|
265
334
|
* @returns one status per record, in the same order.
|
|
266
335
|
*/
|
|
267
|
-
export declare function classifyStoredRoots(primaryRoot: string, records: readonly RegisteredRoot[], platform?: NodeJS.Platform): RootStatus[];
|
|
336
|
+
export declare function classifyStoredRoots(primaryRoot: string, records: readonly RegisteredRoot[], platform?: NodeJS.Platform, protectedPaths?: readonly ProtectedPath[]): RootStatus[];
|
|
337
|
+
/**
|
|
338
|
+
* The single recorded grant a record or status carries — `undefined` unless
|
|
339
|
+
* EXACTLY one grant field holds a non-empty string. Shared by the conflict
|
|
340
|
+
* pass and {@link reservedRootBoundary}; the stored decoder is the source of
|
|
341
|
+
* the access level, so this never cross-checks the field against `access`.
|
|
342
|
+
*/
|
|
343
|
+
export declare function singleGrantOf(record: {
|
|
344
|
+
readonly recordedPath?: string;
|
|
345
|
+
readonly readOnlyRecordedPath?: string;
|
|
346
|
+
readonly readWriteRecordedPath?: string;
|
|
347
|
+
}): string | undefined;
|
|
268
348
|
/**
|
|
269
349
|
* The canonical roots a status list grants, in registry order.
|
|
270
350
|
* @param statuses - the statuses to project.
|
|
271
351
|
* @returns the canonical paths of every `available` root.
|
|
272
352
|
*/
|
|
273
353
|
export declare function availableRoots(statuses: readonly RootStatus[]): string[];
|
|
354
|
+
/**
|
|
355
|
+
* Apply the protected-path verdict to an already-classified list: a status
|
|
356
|
+
* whose recorded grant overlaps a protected directory becomes `invalid` with
|
|
357
|
+
* `invalidReason: 'protected-path'` — the same verdict the pass-one stored
|
|
358
|
+
* check produces, here applied by a consumer that classified elsewhere (a
|
|
359
|
+
* shared-registry follower re-judging an owner snapshot against ITS OWN
|
|
360
|
+
* protected set, which may differ from the owner's). Statuses already
|
|
361
|
+
* `invalid` keep their own verdict — a structural failure is never healed
|
|
362
|
+
* into a trustworthy boundary.
|
|
363
|
+
* @param statuses - the classified statuses to re-mark.
|
|
364
|
+
* @param protectedPaths - the caller's own protected DSH directories.
|
|
365
|
+
* @returns a new list with the verdict applied.
|
|
366
|
+
*/
|
|
367
|
+
export declare function markProtectedPaths(statuses: readonly RootStatus[], protectedPaths: readonly ProtectedPath[]): RootStatus[];
|
|
274
368
|
/**
|
|
275
369
|
* The recorded boundary one status still RESERVES: the grant it was
|
|
276
370
|
* registered for, which a new or revived registration may not overlap merely
|
|
277
371
|
* because the root is currently unusable.
|
|
278
372
|
*
|
|
279
|
-
* `available`, `missing`, `redirected`, and
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
*
|
|
283
|
-
*
|
|
284
|
-
*
|
|
373
|
+
* `available`, `missing`, `redirected`, and the trustworthy-boundary
|
|
374
|
+
* `invalid` verdicts (`membership-conflict`, `protected-path`, and
|
|
375
|
+
* `primary-overlap`) all carry
|
|
376
|
+
* an intact recorded grant; every other `invalid` verdict means the grant
|
|
377
|
+
* could not be parsed at all, so it reserves — and denies — nothing. This is
|
|
378
|
+
* not an independent authorization table: it derives the boundary from the
|
|
379
|
+
* status list {@link classifyStoredRoots} produced, with the same
|
|
380
|
+
* grant-extraction semantics, so its answer can never disagree with
|
|
285
381
|
* classification.
|
|
286
382
|
* @param status - one classified registration.
|
|
287
383
|
* @returns the absolute recorded boundary, or `undefined` when there is none.
|
package/lib/types/scope.d.ts
CHANGED
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import { Context, Service } from '@deepseek-ai/cordis';
|
|
19
19
|
import type { SandboxExecutionPolicy, SandboxMode } from '@deepseek-ai/dsh-sandbox';
|
|
20
|
-
import type
|
|
20
|
+
import { type RootAccess } from './roots.ts';
|
|
21
|
+
import type { OpsProtectionState } from './compat/ops-tools.ts';
|
|
21
22
|
declare module '@deepseek-ai/cordis' {
|
|
22
23
|
interface Context {
|
|
23
24
|
multiRootScope: MultiRootScopeService;
|
|
@@ -199,6 +200,24 @@ export interface Config {
|
|
|
199
200
|
*/
|
|
200
201
|
readonly dshHome?: string;
|
|
201
202
|
}
|
|
203
|
+
/**
|
|
204
|
+
* The baseline a mint resolved to. Only primary-bounded forms may join the
|
|
205
|
+
* unadmitted ledger: `clamped` (primary-root clamp) and `read-only` (the
|
|
206
|
+
* upstream read-only skeleton — never writable). `ambient` mints —
|
|
207
|
+
* `restrictedExecution`'s ambient-minus-denies profile, and any mint whose
|
|
208
|
+
* scope could not be proven unavailable — may only live bound to an
|
|
209
|
+
* admission frame, and otherwise die.
|
|
210
|
+
*/
|
|
211
|
+
export type MintForm = 'clamped' | 'read-only' | 'ambient';
|
|
212
|
+
/** The authorization basis a mint carried at its synchronous entry, stamped onto its ledger entry. */
|
|
213
|
+
export interface MintStamp {
|
|
214
|
+
/** Publication generation the mint's authorization snapshot was resolved under. */
|
|
215
|
+
readonly version: number;
|
|
216
|
+
/** Tool-cap basis at the same instant. */
|
|
217
|
+
readonly capped: boolean;
|
|
218
|
+
/** The profile form the mint produced; gates ledger admission. */
|
|
219
|
+
readonly form: MintForm;
|
|
220
|
+
}
|
|
202
221
|
/**
|
|
203
222
|
* The `ctx.multiRootScope` service. Providers ask it — never the filesystem —
|
|
204
223
|
* what the current scope is, which is what keeps the fs fence and every kernel
|
|
@@ -210,9 +229,18 @@ export declare class MultiRootScopeService extends Service {
|
|
|
210
229
|
private readonly listeners;
|
|
211
230
|
/** Enclosing {@link MultiRootScopeService.batch} depth; inner calls nest. */
|
|
212
231
|
private batchDepth;
|
|
232
|
+
readonly opsProtection: OpsProtectionState;
|
|
213
233
|
/** True when a batched write coalesced its notification into the flush. */
|
|
214
234
|
private batchDirty;
|
|
215
235
|
private admissionProof;
|
|
236
|
+
/**
|
|
237
|
+
* The publication generation the mint ledger is keyed on: bumped when a
|
|
238
|
+
* root-table write or an authority transition changes the authorization
|
|
239
|
+
* basis (a repeat publish of identical content is not a publication), so a
|
|
240
|
+
* minted profile dies with the state it was computed from.
|
|
241
|
+
*/
|
|
242
|
+
private publicationVersion;
|
|
243
|
+
private mintedProfiles;
|
|
216
244
|
/**
|
|
217
245
|
* The registry-authority fact the registry row publishes. Defaults to
|
|
218
246
|
* `available`: a composition without a registry row can never have
|
|
@@ -233,6 +261,13 @@ export declare class MultiRootScopeService extends Service {
|
|
|
233
261
|
* treated as evidence.
|
|
234
262
|
*/
|
|
235
263
|
private readonly dshHome;
|
|
264
|
+
/**
|
|
265
|
+
* Whether the row carried a `dshHome` value at all — even an invalid one.
|
|
266
|
+
* The registry's protected-boundary set must tell "no home configured"
|
|
267
|
+
* (absent: nothing to protect) from "a home was supplied and rejected"
|
|
268
|
+
* (invalid: the boundary cannot be proven).
|
|
269
|
+
*/
|
|
270
|
+
private readonly dshHomeSupplied;
|
|
236
271
|
/** Observe registry publications; consumers still resolve authority from scopeOf(). */
|
|
237
272
|
subscribe(listener: () => void): () => void;
|
|
238
273
|
/**
|
|
@@ -249,6 +284,8 @@ export declare class MultiRootScopeService extends Service {
|
|
|
249
284
|
batch<T>(body: () => T): T;
|
|
250
285
|
/** Notify subscribers now, or coalesce into the enclosing batch's flush. */
|
|
251
286
|
private notifySubscribers;
|
|
287
|
+
/** A publication superseded the authorization basis: every outstanding minted profile dies. */
|
|
288
|
+
private bumpPublication;
|
|
252
289
|
/**
|
|
253
290
|
* The sorted names of the tools `toolAccess` caps — read-only for the
|
|
254
291
|
* prompt contribution. An invalid config is not enumerated (it caps every
|
|
@@ -257,6 +294,21 @@ export declare class MultiRootScopeService extends Service {
|
|
|
257
294
|
toolCaps(): readonly string[];
|
|
258
295
|
/** Whether malformed toolAccess caps every call, including unknown tools. */
|
|
259
296
|
get toolAccessInvalid(): boolean;
|
|
297
|
+
/**
|
|
298
|
+
* The validated `dshHome` row config, classified for the registry's
|
|
299
|
+
* protected-boundary set: `absent` when the row carried no value (nothing
|
|
300
|
+
* to protect), `invalid` when it carried a value the constructor rejected
|
|
301
|
+
* (the boundary cannot be proven), `configured` with the accepted absolute
|
|
302
|
+
* path. Read-only — this changes no scope or `protectedHome` semantics.
|
|
303
|
+
*/
|
|
304
|
+
get dshHomeConfig(): {
|
|
305
|
+
readonly kind: 'absent';
|
|
306
|
+
} | {
|
|
307
|
+
readonly kind: 'invalid';
|
|
308
|
+
} | {
|
|
309
|
+
readonly kind: 'configured';
|
|
310
|
+
readonly path: string;
|
|
311
|
+
};
|
|
260
312
|
constructor(ctx: Context, config?: Config);
|
|
261
313
|
/**
|
|
262
314
|
* Resolve the scope of one confined call.
|
|
@@ -264,8 +316,46 @@ export declare class MultiRootScopeService extends Service {
|
|
|
264
316
|
* @returns the canonical scope; `additionalRoots` is empty when none are registered.
|
|
265
317
|
*/
|
|
266
318
|
requireAdmission(proof: object): void;
|
|
267
|
-
|
|
319
|
+
/**
|
|
320
|
+
* The stamp an asynchronously completing mint must carry: the publication
|
|
321
|
+
* generation and tool-cap basis at the mint's synchronous entry, before any
|
|
322
|
+
* provider probe could run. An in-flight mint that crosses a real
|
|
323
|
+
* publication keeps its entry generation, so the ledger refuses it rather
|
|
324
|
+
* than re-stamping stale authorization under the current publication.
|
|
325
|
+
* @param form - the profile form the mint will produce (`clamped` and
|
|
326
|
+
* `read-only` are the only ledger-eligible forms).
|
|
327
|
+
*/
|
|
328
|
+
captureMint(form: MintForm): MintStamp;
|
|
329
|
+
/**
|
|
330
|
+
* Record a confined profile this host's provider just minted. Only
|
|
331
|
+
* meaningful under the shared Registry (`admissionProof` set; without it
|
|
332
|
+
* `permitsProfile` never refuses): a live admission binds the profile to
|
|
333
|
+
* the operation and it dies with it; an expired admission never bequeaths
|
|
334
|
+
* one — a mint that outlived its operation is dropped, not demoted into
|
|
335
|
+
* the ledger; only with NO admission frame at all does the profile join
|
|
336
|
+
* the publication-keyed ledger stamped with `mint`, and only when the
|
|
337
|
+
* mint's form is primary-bounded — an `ambient` mint that arrives without
|
|
338
|
+
* a frame is dropped the same way.
|
|
339
|
+
* @param argv - the confined argv just minted.
|
|
340
|
+
* @param mint - the {@link captureMint} stamp taken at the mint's
|
|
341
|
+
* synchronous entry.
|
|
342
|
+
*/
|
|
343
|
+
rememberProfile(argv: readonly string[], mint: MintStamp): void;
|
|
344
|
+
/**
|
|
345
|
+
* Whether a confined argv may launch now: a profile minted inside this live
|
|
346
|
+
* admission, or — for a `confine`→`spawn` composition with no wrapped entry
|
|
347
|
+
* to admit it — a profile this host minted while its authorization
|
|
348
|
+
* publication is still current. Cached argv, argv minted under a superseded
|
|
349
|
+
* publication, and argv minted elsewhere stay refused.
|
|
350
|
+
*/
|
|
268
351
|
permitsProfile(argv: readonly string[]): boolean;
|
|
352
|
+
/**
|
|
353
|
+
* The strict form: only profiles minted inside this live admission. The OPS
|
|
354
|
+
* execution domain owns its profile exclusively and never honors the
|
|
355
|
+
* unadmitted mint ledger.
|
|
356
|
+
*/
|
|
357
|
+
permitsOperationProfile(argv: readonly string[]): boolean;
|
|
358
|
+
private permitsMintedProfile;
|
|
269
359
|
get admissionRequired(): boolean;
|
|
270
360
|
admissionPathsValid(): boolean;
|
|
271
361
|
hasAdmission(): boolean;
|
|
@@ -275,6 +365,7 @@ export declare class MultiRootScopeService extends Service {
|
|
|
275
365
|
resolve(policy: SandboxExecutionPolicy): FilesystemScope;
|
|
276
366
|
/** Read-only pending-dispatch projection. Does not mint an ALS execution grant. */
|
|
277
367
|
previewForTool(policy: SandboxExecutionPolicy, toolName: string): FilesystemScope;
|
|
368
|
+
withToolCap<T>(name: string, fn: () => T): T;
|
|
278
369
|
/** Execution and review use exactly the same parent-narrowing rule. */
|
|
279
370
|
private capForTool;
|
|
280
371
|
private project;
|
|
@@ -324,7 +415,8 @@ export declare class MultiRootScopeService extends Service {
|
|
|
324
415
|
* M1 uses this from tests and smoke scripts only; M3 feeds it from plugin
|
|
325
416
|
* storage. It is a plain setter so the transition swaps the data source
|
|
326
417
|
* without touching any provider.
|
|
327
|
-
* @param primaryRoot -
|
|
418
|
+
* @param primaryRoot - primary root the roots belong to; the Registry key
|
|
419
|
+
* is its upstream spelling via {@link primaryKey}, never re-resolved.
|
|
328
420
|
* @param roots - the additional roots, in display order.
|
|
329
421
|
*/
|
|
330
422
|
setAdditionalRoots(primaryRoot: string, roots: readonly AdditionalWorkspaceRoot[]): void;
|
package/lib/workspace-files.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { t as wrapMethod } from "./method-wrapper-
|
|
2
|
-
import {
|
|
3
|
-
import { l as isCanonicallyUnder } from "./roots-
|
|
4
|
-
import { t as routeWorkspacePath } from "./workspace-routing-
|
|
1
|
+
import { t as wrapMethod } from "./method-wrapper-D4oUCsEF.js";
|
|
2
|
+
import { l as withRegistryRead } from "./registry-operation-CIlmt6_c.js";
|
|
3
|
+
import { l as isCanonicallyUnder } from "./roots-CmaWxRVy.js";
|
|
4
|
+
import { t as routeWorkspacePath } from "./workspace-routing-D_qwMUh-.js";
|
|
5
5
|
import { posix, resolve, win32 } from "node:path";
|
|
6
6
|
//#region src/compat/workspace-files.ts
|
|
7
7
|
function adaptPathWatch(service, wrap) {
|
|
@@ -4,7 +4,7 @@ import { canonicalPath } from "@deepseek-ai/dsh-sandbox";
|
|
|
4
4
|
async function routeWorkspacePath(ctx, primaryRoot, path, signal, cwd = primaryRoot) {
|
|
5
5
|
signal?.throwIfAborted();
|
|
6
6
|
const primary = canonicalPath(primaryRoot);
|
|
7
|
-
const roots = [primary, ...ctx.multiRootScope.scopeOf(
|
|
7
|
+
const roots = [primary, ...ctx.multiRootScope.scopeOf(primaryRoot)];
|
|
8
8
|
const target = await ctx.fs.resolve(path, {
|
|
9
9
|
cwd,
|
|
10
10
|
...signal === void 0 ? {} : { signal }
|
|
@@ -12,11 +12,11 @@ async function routeWorkspacePath(ctx, primaryRoot, path, signal, cwd = primaryR
|
|
|
12
12
|
for (const root of roots) {
|
|
13
13
|
const rootTarget = await ctx.fs.resolve(root, signal === void 0 ? {} : { signal });
|
|
14
14
|
if (!ctx.fs.contains(rootTarget, target)) continue;
|
|
15
|
-
if (root !== primary && !ctx.multiRootScope.scopeOf(
|
|
15
|
+
if (root !== primary && !ctx.multiRootScope.scopeOf(primaryRoot).includes(root)) return void 0;
|
|
16
16
|
signal?.throwIfAborted();
|
|
17
17
|
return {
|
|
18
|
-
primaryRoot
|
|
19
|
-
workspaceRoot: root,
|
|
18
|
+
primaryRoot,
|
|
19
|
+
workspaceRoot: root === primary ? primaryRoot : root,
|
|
20
20
|
target,
|
|
21
21
|
absolutePath: ctx.fs.processPath(target)
|
|
22
22
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dsh-electron/dsh-plugin-multi-root-workspace",
|
|
3
3
|
"description": "Multi-root Workspace for DeepSeek Harness: adds N additional roots to the workspace sandbox scope as an out-of-tree bundle",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.16",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/types/index.d.ts",
|
|
@@ -75,6 +75,10 @@
|
|
|
75
75
|
"./skills": {
|
|
76
76
|
"types": "./lib/types/skills.d.ts",
|
|
77
77
|
"default": "./lib/skills.js"
|
|
78
|
+
},
|
|
79
|
+
"./ops-compatibility": {
|
|
80
|
+
"types": "./lib/types/ops-compatibility.d.ts",
|
|
81
|
+
"default": "./lib/ops-compatibility.js"
|
|
78
82
|
}
|
|
79
83
|
},
|
|
80
84
|
"files": [
|