@sparkelf/dsh-knowledge-base 0.1.21 → 0.1.22
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.md +32 -0
- package/lib/client.js +1234 -857
- package/lib/client.js.map +1 -1
- package/lib/index.js +497 -115
- package/lib/types/index.d.ts +24 -1
- package/lib/types/knowledgeTools.d.ts +12 -10
- package/lib/types/managementTools.d.ts +11 -7
- package/lib/types/toolDisclosure.d.ts +188 -0
- package/package.json +7 -6
package/lib/types/index.d.ts
CHANGED
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
*
|
|
11
11
|
* It owns no authorization logic. DataOps AuthGuard and PermissionGuard resolve
|
|
12
12
|
* the caller's roles and grants from the session credential; this plugin only
|
|
13
|
-
* forwards requests (AGENTS.md "Managed DataOps Authorization").
|
|
13
|
+
* forwards requests (AGENTS.md "Managed DataOps Authorization"). Which model-facing
|
|
14
|
+
* tools exist is decided from that same permission projection in `toolDisclosure.ts`;
|
|
15
|
+
* path-level checks and the backend remain the enforcement boundary.
|
|
14
16
|
*
|
|
15
17
|
* Content retrieval is deliberately absent. Once the view tree is mounted the
|
|
16
18
|
* official `grep`, `read`, `write`, and `edit` tools operate on it as an
|
|
@@ -19,6 +21,8 @@
|
|
|
19
21
|
* @module @sparkelf/dsh-knowledge-base
|
|
20
22
|
*/
|
|
21
23
|
import type { Context } from '@deepseek-ai/cordis';
|
|
24
|
+
import { type ManagementContext } from './managementTools.ts';
|
|
25
|
+
import { type ToolDisclosureCoordinator } from './toolDisclosure.ts';
|
|
22
26
|
import { type WorkflowActor } from './workflowPublish.ts';
|
|
23
27
|
import z from '@deepseek-ai/schemastery';
|
|
24
28
|
import { type KnowledgeConfig, type KnowledgeGrant, type KnowledgeGrantRequest, type KnowledgeRevokeRequest, type KnowledgeSshView, type KnowledgeStatus } from './protocol.ts';
|
|
@@ -189,6 +193,18 @@ export declare function isTeamDeployment(config: Config): boolean;
|
|
|
189
193
|
* "no user".
|
|
190
194
|
*/
|
|
191
195
|
export declare const LOCAL_USER_ID = "local";
|
|
196
|
+
/**
|
|
197
|
+
* The permission context a personal deployment discloses tools from.
|
|
198
|
+
*
|
|
199
|
+
* A personal deployment has no accounts, so there is nobody to ask: the local
|
|
200
|
+
* operator owns every capability the product knows about. Kept as a function rather
|
|
201
|
+
* than the inline object inside the management host so the disclosure coordinator
|
|
202
|
+
* can register the personal tool surface synchronously at startup, instead of
|
|
203
|
+
* waiting for a backend round trip that does not exist.
|
|
204
|
+
*
|
|
205
|
+
* @returns the full-capability context a local owner holds.
|
|
206
|
+
*/
|
|
207
|
+
export declare function localManagementContext(): ManagementContext;
|
|
192
208
|
/**
|
|
193
209
|
* Why a path was refused in a personal deployment.
|
|
194
210
|
*
|
|
@@ -1152,6 +1168,13 @@ export interface SkillCandidateLike {
|
|
|
1152
1168
|
export interface SkillDefinitionLike extends SkillCandidateLike {
|
|
1153
1169
|
readonly content: string;
|
|
1154
1170
|
}
|
|
1171
|
+
/**
|
|
1172
|
+
* The permission-based tool disclosure coordinator this plugin instance registered.
|
|
1173
|
+
*
|
|
1174
|
+
* @param host - the Host context the plugin was applied to.
|
|
1175
|
+
* @returns the coordinator, or undefined when this context has no knowledge-base plugin.
|
|
1176
|
+
*/
|
|
1177
|
+
export declare function knowledgeBaseToolDisclosure(host: object): ToolDisclosureCoordinator | undefined;
|
|
1155
1178
|
export declare function apply(ctx: Context, deploymentConfig: Config): void;
|
|
1156
1179
|
/** Re-exported for tests and for callers that need the host shape. */
|
|
1157
1180
|
export type { RemoteHost, RemoteRunner };
|
|
@@ -7,11 +7,14 @@
|
|
|
7
7
|
* bypasses all of that, so the model gets tools that go through the same host
|
|
8
8
|
* operations as the HTTP routes.
|
|
9
9
|
*
|
|
10
|
-
* This module owns argument validation,
|
|
11
|
-
* shape.
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
10
|
+
* This module owns argument validation, path-level permission checks and the tool
|
|
11
|
+
* result shape. Which tools exist is decided once per permission context by
|
|
12
|
+
* `toolDisclosure.ts`; the path checks here decide which data a visible tool may
|
|
13
|
+
* touch and are therefore kept. The host object is supplied by `index.ts`; it is the
|
|
14
|
+
* seam that reuses the existing `canReadPath` / `canWritePath` /
|
|
15
|
+
* `listDirectoryWithPermissions` / `readKnowledgeFile` / `createKnowledgeEntry` /
|
|
16
|
+
* `moveEntry` implementations, and it is what tests replace without importing the
|
|
17
|
+
* whole plugin.
|
|
15
18
|
*
|
|
16
19
|
* @module @sparkelf/dsh-knowledge-base/knowledgeTools
|
|
17
20
|
*/
|
|
@@ -117,8 +120,6 @@ export type KnowledgeViewSync = {
|
|
|
117
120
|
export interface KnowledgeFileToolHost {
|
|
118
121
|
/** Resolve the caller (team: DataOps `/auth/me`; personal: the local operator). */
|
|
119
122
|
resolveCaller(): Promise<KnowledgeToolCaller>;
|
|
120
|
-
/** Backend DSH permission for this operation, when the host has a permission context. */
|
|
121
|
-
hasPermission?(permission: string): Promise<boolean>;
|
|
122
123
|
/** FTS5 search over the sidecar index, already scoped to the caller. */
|
|
123
124
|
searchIndex?(input: {
|
|
124
125
|
query: string;
|
|
@@ -231,9 +232,10 @@ export declare const asKnowledgeToolError: (error: unknown, path?: string) => Kn
|
|
|
231
232
|
*/
|
|
232
233
|
export declare const assertToolPath: (value: unknown, allowRoot?: boolean) => string;
|
|
233
234
|
/**
|
|
234
|
-
* Build the
|
|
235
|
+
* Build the knowledge-base model-facing tools.
|
|
235
236
|
*
|
|
236
|
-
* Returned definitions are registered by `apply
|
|
237
|
-
*
|
|
237
|
+
* Returned definitions are registered by `apply` through the permission-based
|
|
238
|
+
* disclosure coordinator; tests call the same definitions with a fake host to assert
|
|
239
|
+
* path, audit and result behaviour without a live backend or a permission backend.
|
|
238
240
|
*/
|
|
239
241
|
export declare const createKnowledgeFileTools: (host: KnowledgeFileToolHost) => import("@deepseek-ai/dsh-tools").ToolDefinition[];
|
|
@@ -2,11 +2,17 @@
|
|
|
2
2
|
* Model-facing management tools for the knowledge base and the workflow plaza.
|
|
3
3
|
*
|
|
4
4
|
* The UI already exposes these operations. This module gives the model the same
|
|
5
|
-
* operations without re-implementing authorization:
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
5
|
+
* operations without re-implementing authorization: which tools exist is decided
|
|
6
|
+
* once from the caller's review context by `toolDisclosure.ts`, so a tool body does
|
|
7
|
+
* not re-read the permission tree on every call. The context is still read where it
|
|
8
|
+
* decides WHICH rows a visible tool may return (department scope, own audit trail)
|
|
9
|
+
* and for the actor recorded in an audit line. Writes additionally require an
|
|
10
|
+
* explicit human authorization flag, because a model deciding on its own must never
|
|
11
|
+
* be indistinguishable from an operator approving a review.
|
|
12
|
+
*
|
|
13
|
+
* DataOps remains the final authority: every host call below reaches a backend
|
|
14
|
+
* route that enforces the JWT's current roles and grants, so a stale disclosure can
|
|
15
|
+
* cause a refusal but never an unauthorized operation.
|
|
10
16
|
*
|
|
11
17
|
* Secrets are write-only through the UI; these tools deliberately reject secret-shaped
|
|
12
18
|
* fields and redact them on reads.
|
|
@@ -147,8 +153,6 @@ export declare const DEFAULT_DSH_PERMISSIONS: Readonly<Record<string, readonly s
|
|
|
147
153
|
export declare function defaultPermissionsForRoles(roleKeys: readonly string[]): string[];
|
|
148
154
|
/** True when the caller holds one backend permission key. */
|
|
149
155
|
export declare function hasPermission(context: ManagementContext, permission: string): boolean;
|
|
150
|
-
/** Require one backend permission key; a denial is a structured 403. */
|
|
151
|
-
export declare function requirePermission(context: ManagementContext, permission: string, action?: string): void;
|
|
152
156
|
/** Global administrators: all resources and all review queues. */
|
|
153
157
|
export declare function isGlobalAdmin(context: ManagementContext): boolean;
|
|
154
158
|
/** Department reviewers: their department and its descendants. */
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Permission-based disclosure of the model-facing knowledge-base tools.
|
|
3
|
+
*
|
|
4
|
+
* A tool the caller may never use should not be in the model's tool list at all.
|
|
5
|
+
* Registering every tool and refusing inside `execute()` wastes a schema slot,
|
|
6
|
+
* invites a call that will fail, and hides the real capability from the surface
|
|
7
|
+
* that decides what to offer. Disclosure is therefore decided ONCE per permission
|
|
8
|
+
* context: the coordinator registers the tools a context allows and disposes the
|
|
9
|
+
* rest. Execution never re-reads the full permission tree; the backend remains the
|
|
10
|
+
* final authority for every request the tools then make.
|
|
11
|
+
*
|
|
12
|
+
* ## What is decided where
|
|
13
|
+
*
|
|
14
|
+
* - **Disclosure (this module).** A tool's group and its required permission keys
|
|
15
|
+
* or role capability. Evaluated from the caller's `/api/ai/review/context`
|
|
16
|
+
* projection (`ManagementContext`), the same object the Settings panel and route
|
|
17
|
+
* gate already read.
|
|
18
|
+
* - **Data filtering (the tools themselves).** Path-level `canRead` / `canWrite`
|
|
19
|
+
* calls stay in `execute()`: they decide WHICH rows a visible tool may touch, not
|
|
20
|
+
* whether the tool exists. The same holds for department-scope filtering of
|
|
21
|
+
* grants, audit entries and review queues.
|
|
22
|
+
* - **Final authorization (DataOps).** Every backend route the tools call enforces
|
|
23
|
+
* the JWT's current roles and grants. A stale tool registration can therefore
|
|
24
|
+
* cause a refusal, never an unauthorized read or write.
|
|
25
|
+
*
|
|
26
|
+
* ## Groups
|
|
27
|
+
*
|
|
28
|
+
* `read` tools exist for a caller with the knowledge-base read key; `write` tools
|
|
29
|
+
* for the editors; `manage` for the administrator/reviewer surfaces; `publish` for
|
|
30
|
+
* workflow publication. Groups exist so a permission change can be applied as a
|
|
31
|
+
* set, and so a fault in one group is reflected in one place rather than in every
|
|
32
|
+
* tool body.
|
|
33
|
+
*
|
|
34
|
+
* @module @sparkelf/dsh-knowledge-base/toolDisclosure
|
|
35
|
+
*/
|
|
36
|
+
import type { ToolDefinition } from '@deepseek-ai/dsh-tools';
|
|
37
|
+
import { type ManagementContext } from './managementTools.ts';
|
|
38
|
+
/** The four disclosure groups a knowledge-base tool can belong to. */
|
|
39
|
+
export declare const TOOL_GROUPS: readonly ["read", "write", "manage", "publish"];
|
|
40
|
+
/** One disclosure group. */
|
|
41
|
+
export type KnowledgeToolGroup = (typeof TOOL_GROUPS)[number];
|
|
42
|
+
/**
|
|
43
|
+
* One tool's disclosure rule.
|
|
44
|
+
*
|
|
45
|
+
* `permissions` lists keys that must ALL be held. `capability` names a role
|
|
46
|
+
* capability that must also be present; it is how the tools whose product
|
|
47
|
+
* description is "reviewer only" or "global admin only" are filtered without
|
|
48
|
+
* inventing a permission key the backend does not issue.
|
|
49
|
+
*/
|
|
50
|
+
export interface ToolAccessRule {
|
|
51
|
+
/** Which group the tool is registered and disposed with. */
|
|
52
|
+
readonly group: KnowledgeToolGroup;
|
|
53
|
+
/** Every permission key the caller must hold. */
|
|
54
|
+
readonly permissions?: readonly string[];
|
|
55
|
+
/** Reviewer/admin capability the caller must have, in addition to permissions. */
|
|
56
|
+
readonly capability?: 'reviewer' | 'admin';
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Disclosure rules for every model-facing tool this package registers.
|
|
60
|
+
*
|
|
61
|
+
* The map is the single list of what exists; the coordinator rejects a tool
|
|
62
|
+
* definition without a rule and a rule without a definition at construction time,
|
|
63
|
+
* so a new tool cannot ship undisclosed or leave a stale policy behind.
|
|
64
|
+
*/
|
|
65
|
+
export declare const KB_TOOL_ACCESS: Readonly<Record<string, ToolAccessRule>>;
|
|
66
|
+
/** Tool names per group, derived from {@link KB_TOOL_ACCESS}. */
|
|
67
|
+
export declare const TOOL_GROUPS_OF: Readonly<Record<KnowledgeToolGroup, readonly string[]>>;
|
|
68
|
+
/** Every model-facing tool name this package discloses. */
|
|
69
|
+
export declare const KNOWLEDGE_TOOL_NAMES: readonly string[];
|
|
70
|
+
/** One tool's disclosure decision. */
|
|
71
|
+
export interface ToolAccessDecision {
|
|
72
|
+
/** Whether the tool may be registered for this context. */
|
|
73
|
+
readonly allowed: boolean;
|
|
74
|
+
/** The tool's group, when a rule exists. */
|
|
75
|
+
readonly group?: KnowledgeToolGroup;
|
|
76
|
+
/**
|
|
77
|
+
* What the context lacks, as permission keys (`kb:file:write`) or capability
|
|
78
|
+
* labels (`reviewer`, `admin`, `permission-context`). Empty when allowed.
|
|
79
|
+
*/
|
|
80
|
+
readonly missing: readonly string[];
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Decide whether one tool is visible for a permission context.
|
|
84
|
+
*
|
|
85
|
+
* An absent context is a denial: disclosure happens before execution, so an
|
|
86
|
+
* unresolved permission tree must withhold the surface rather than expose it.
|
|
87
|
+
* Unknown tool names are denied too; the coordinator rejects them at construction,
|
|
88
|
+
* and this function keeps the same fail-closed answer if one reaches it anyway.
|
|
89
|
+
*
|
|
90
|
+
* @param name - model-facing tool name.
|
|
91
|
+
* @param context - the caller's review context, or undefined while it is unavailable.
|
|
92
|
+
* @returns the decision and the requirements the context did not satisfy.
|
|
93
|
+
*/
|
|
94
|
+
export declare function decideToolAccess(name: string, context: ManagementContext | undefined): ToolAccessDecision;
|
|
95
|
+
/** One withheld tool and the requirements that withheld it. */
|
|
96
|
+
export interface ToolDenial {
|
|
97
|
+
readonly name: string;
|
|
98
|
+
readonly group?: KnowledgeToolGroup;
|
|
99
|
+
readonly missing: readonly string[];
|
|
100
|
+
}
|
|
101
|
+
/** The disclosure state after one pass. */
|
|
102
|
+
export interface ToolDisclosureSnapshot {
|
|
103
|
+
/** Tool names registered after this pass, in definition order. */
|
|
104
|
+
readonly registered: readonly string[];
|
|
105
|
+
/** Tools withheld from the model, with the unsatisfied requirement per tool. */
|
|
106
|
+
readonly denied: readonly ToolDenial[];
|
|
107
|
+
/** Groups that currently have at least one registered tool. */
|
|
108
|
+
readonly activeGroups: readonly KnowledgeToolGroup[];
|
|
109
|
+
/** Set when the permission context could not be resolved; every tool is disposed. */
|
|
110
|
+
readonly error?: string;
|
|
111
|
+
}
|
|
112
|
+
/** The tool registry seam this coordinator needs. */
|
|
113
|
+
export interface ToolRegistryLike {
|
|
114
|
+
/** Register one tool; returns the exact disposer that unregisters it. */
|
|
115
|
+
register(definition: ToolDefinition): () => void;
|
|
116
|
+
}
|
|
117
|
+
/** Construction options for a {@link ToolDisclosureCoordinator}. */
|
|
118
|
+
export interface ToolDisclosureOptions {
|
|
119
|
+
/** Every model-facing tool this plugin can register. */
|
|
120
|
+
readonly tools: readonly ToolDefinition[];
|
|
121
|
+
/** The host registry to register into and dispose from. */
|
|
122
|
+
readonly registry: ToolRegistryLike;
|
|
123
|
+
/**
|
|
124
|
+
* Resolve the caller's current review context. Called on an explicit refresh,
|
|
125
|
+
* never from a tool's `execute()`.
|
|
126
|
+
*/
|
|
127
|
+
readonly resolveContext: () => Promise<ManagementContext>;
|
|
128
|
+
/** Diagnostic sink; a failed refresh is reported here instead of thrown. */
|
|
129
|
+
readonly log?: (message: string) => void;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Register and dispose knowledge-base tools as the caller's permission context changes.
|
|
133
|
+
*
|
|
134
|
+
* The coordinator owns every tool registration. A pass computes one decision per
|
|
135
|
+
* tool, registers the newly allowed ones, disposes the newly denied ones and
|
|
136
|
+
* leaves unchanged tools untouched, so an unchanged context causes no registry
|
|
137
|
+
* churn. Concurrent refreshes share one in-flight context read; a pass that is
|
|
138
|
+
* superseded by a newer context never applies its stale answer.
|
|
139
|
+
*
|
|
140
|
+
* On a context-resolution failure every gated tool is disposed. That is the
|
|
141
|
+
* fail-closed direction: an unavailable permission tree cannot prove a capability,
|
|
142
|
+
* and a tool surface that outlives its proof is exactly the leak disclosure exists
|
|
143
|
+
* to prevent.
|
|
144
|
+
*/
|
|
145
|
+
export declare class ToolDisclosureCoordinator {
|
|
146
|
+
private readonly definitions;
|
|
147
|
+
/** Registered tools and their exact registry disposers. */
|
|
148
|
+
private readonly disposers;
|
|
149
|
+
private readonly options;
|
|
150
|
+
/** Monotonic pass counter; a slower older read must not overwrite a newer one. */
|
|
151
|
+
private generation;
|
|
152
|
+
private inFlight?;
|
|
153
|
+
private disposed;
|
|
154
|
+
private snapshot?;
|
|
155
|
+
constructor(options: ToolDisclosureOptions);
|
|
156
|
+
/** Tool names currently registered, in definition order. */
|
|
157
|
+
get registeredTools(): readonly string[];
|
|
158
|
+
/** The most recent disclosure pass, if one has completed. */
|
|
159
|
+
get lastSnapshot(): ToolDisclosureSnapshot | undefined;
|
|
160
|
+
/**
|
|
161
|
+
* Recompute the tool surface.
|
|
162
|
+
*
|
|
163
|
+
* @param context - a context already read by the caller (a route gate or status
|
|
164
|
+
* request). Passing it avoids a second backend round trip. Omit it to resolve
|
|
165
|
+
* one through {@link ToolDisclosureOptions.resolveContext}.
|
|
166
|
+
* @returns the resulting snapshot; a context failure is reported in `error`
|
|
167
|
+
* rather than thrown, because a refresh must never fail the request that
|
|
168
|
+
* triggered it.
|
|
169
|
+
*/
|
|
170
|
+
syncToolDisclosure(context?: ManagementContext): Promise<ToolDisclosureSnapshot>;
|
|
171
|
+
/** Dispose every tool this coordinator registered; later refreshes are no-ops. */
|
|
172
|
+
dispose(): void;
|
|
173
|
+
/** Register allowed tools and dispose denied ones for one resolved context. */
|
|
174
|
+
private apply;
|
|
175
|
+
/** Dispose everything and report why the surface is empty. */
|
|
176
|
+
private fail;
|
|
177
|
+
/** Dispose every registered tool; safe to call more than once. */
|
|
178
|
+
private disposeRegistered;
|
|
179
|
+
/** Snapshot for a pass that cannot run at all. */
|
|
180
|
+
private emptySnapshot;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Build a coordinator.
|
|
184
|
+
*
|
|
185
|
+
* @param options - the definitions, registry, context resolver and log sink.
|
|
186
|
+
* @returns the coordinator the plugin stores and refreshes.
|
|
187
|
+
*/
|
|
188
|
+
export declare function createToolDisclosure(options: ToolDisclosureOptions): ToolDisclosureCoordinator;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sparkelf/dsh-knowledge-base",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.22",
|
|
4
4
|
"description": "DataOps-backed knowledge base for DeepSeek Harness: a left-sidebar destination, Settings configuration, and index tooling over a mounted NAS view.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -48,6 +48,11 @@
|
|
|
48
48
|
"skills/**/SKILL.md",
|
|
49
49
|
"skills/**/i18n/*.json"
|
|
50
50
|
],
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "rm -rf lib && tsc -p tsconfig.build.json && tsdown",
|
|
53
|
+
"bundle": "tsdown",
|
|
54
|
+
"prepack": "pnpm run build"
|
|
55
|
+
},
|
|
51
56
|
"engines": {
|
|
52
57
|
"node": "^22.19.0 || >=24.0.0"
|
|
53
58
|
},
|
|
@@ -103,9 +108,5 @@
|
|
|
103
108
|
"dsh-better-sidebar": {
|
|
104
109
|
"optional": true
|
|
105
110
|
}
|
|
106
|
-
},
|
|
107
|
-
"scripts": {
|
|
108
|
-
"build": "rm -rf lib && tsc -p tsconfig.build.json && tsdown",
|
|
109
|
-
"bundle": "tsdown"
|
|
110
111
|
}
|
|
111
|
-
}
|
|
112
|
+
}
|