@sparkelf/dsh-dataops-managed 0.3.20 → 0.3.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.
Files changed (35) hide show
  1. package/README.md +8 -0
  2. package/lib/client.js +3996 -267
  3. package/lib/client.js.map +1 -1
  4. package/lib/index.js +2355 -181
  5. package/lib/types/api-docs.d.ts +80 -0
  6. package/lib/types/auth-state.d.ts +50 -0
  7. package/lib/types/client/NotificationBell.d.ts +19 -0
  8. package/lib/types/client/PermissionCenterIcon.d.ts +9 -0
  9. package/lib/types/client/PermissionCenterSection.d.ts +15 -0
  10. package/lib/types/client/ReloginPrompt.d.ts +27 -0
  11. package/lib/types/client/ReviewSettingsCard.d.ts +12 -0
  12. package/lib/types/client/SkillPlazaSection.d.ts +3 -5
  13. package/lib/types/client/WanxiangBrand.d.ts +7 -1
  14. package/lib/types/client/WorkspaceUpgradePrompt.d.ts +21 -0
  15. package/lib/types/client/combobox.d.ts +17 -0
  16. package/lib/types/client/index.d.ts +17 -2
  17. package/lib/types/client/locales.d.ts +109 -6
  18. package/lib/types/client/review-client.d.ts +155 -0
  19. package/lib/types/client/review-demo.d.ts +13 -0
  20. package/lib/types/client/review-notification-store.d.ts +23 -0
  21. package/lib/types/client/review-types.d.ts +114 -0
  22. package/lib/types/client/settings-gate.d.ts +21 -0
  23. package/lib/types/client/skill-plaza-client.d.ts +21 -0
  24. package/lib/types/client/workspace-upgrade-client.d.ts +29 -0
  25. package/lib/types/connector-state.d.ts +51 -0
  26. package/lib/types/index.d.ts +36 -0
  27. package/lib/types/review-api.d.ts +32 -0
  28. package/lib/types/skill-plaza.d.ts +31 -0
  29. package/lib/types/tool-manager.d.ts +81 -0
  30. package/lib/types/tool-output.d.ts +25 -0
  31. package/lib/types/tool-scope.d.ts +156 -0
  32. package/lib/types/tools.d.ts +41 -0
  33. package/lib/types/workspace-upgrade-state.d.ts +66 -0
  34. package/lib/types/workspace-upgrade.d.ts +83 -0
  35. package/package.json +1 -1
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Shared vocabulary for the review, permission and notification surfaces.
3
+ *
4
+ * The backend contract is landing in parallel; every field that a legacy row cannot carry
5
+ * yet is optional here and normalized to the product default (approved/global) rather than
6
+ * guessed per component. Keeping the rules in one place is what lets the skill plaza, the
7
+ * permission centre and the notification bell agree on what "pending" means.
8
+ */
9
+ /** Review state of a shared skill or workflow. */
10
+ export type ReviewStatus = 'pending' | 'approved' | 'rejected';
11
+ /** Who may see a shared resource outside its owner. */
12
+ export type ResourceVisibility = 'global' | 'department' | 'private';
13
+ /** The skill-plaza filter tabs, in display order. */
14
+ export type ReviewTab = 'approved' | 'pending' | 'all';
15
+ /** The four DataOps roles the permission centre knows. */
16
+ export type PortalRole = 'SUPER_ADMIN' | 'ADMIN' | 'DEPT_ADMIN' | 'APP_USER';
17
+ /** One node of a department ancestry path. */
18
+ export interface DepartmentPathEntry {
19
+ code: string;
20
+ name: string;
21
+ }
22
+ /** A department as a card tag needs it: code, label, and the ancestry for the tooltip. */
23
+ export interface DepartmentSummary {
24
+ code: string;
25
+ name: string;
26
+ /** Root-first path, in either the legacy string form or the backend object form. */
27
+ path?: readonly (string | DepartmentPathEntry)[];
28
+ }
29
+ /** One AI risk finding. */
30
+ export interface RiskFinding {
31
+ category: string;
32
+ severity: 'low' | 'medium' | 'high';
33
+ evidence?: string;
34
+ recommendation?: string;
35
+ }
36
+ /** The AI report attached to a pending resource. */
37
+ export interface RiskReport {
38
+ score?: number;
39
+ /** Overall severity, which is wider than a single finding's severity. */
40
+ severity?: 'low' | 'medium' | 'high' | 'critical';
41
+ summary?: string;
42
+ findings: readonly RiskFinding[];
43
+ scannedAt?: string;
44
+ }
45
+ /** The caller's review permissions, as the Host reports them. */
46
+ export interface ReviewContext {
47
+ userId?: string;
48
+ role: PortalRole;
49
+ departmentCode?: string;
50
+ departmentPath?: readonly string[];
51
+ canReview: boolean;
52
+ canManagePermissions: boolean;
53
+ }
54
+ /** One notification row. */
55
+ export interface ReviewNotification {
56
+ id: string;
57
+ type: string;
58
+ title: string;
59
+ body?: string;
60
+ resourceKind?: string;
61
+ resourceId?: string | number;
62
+ read: boolean;
63
+ createdAt: string;
64
+ }
65
+ /** A reviewable row as the cards read it. */
66
+ export interface ReviewableSummary {
67
+ reviewStatus?: ReviewStatus;
68
+ visibility?: ResourceVisibility;
69
+ departmentCode?: string | null;
70
+ departmentName?: string;
71
+ departmentPath?: readonly (string | DepartmentPathEntry)[];
72
+ riskReport?: RiskReport | null;
73
+ reviewNote?: string | null;
74
+ submittedAt?: string;
75
+ }
76
+ /** Treat a missing status as approved: rows written before the review state existed were live. */
77
+ export declare function normalizeReviewStatus(value: unknown): ReviewStatus;
78
+ /** Treat a missing visibility as global: a shared row with no scope was visible to everyone. */
79
+ export declare function normalizeVisibility(value: unknown): ResourceVisibility;
80
+ /** Whether the caller may see and act on the review queue. */
81
+ export declare function canReview(context: ReviewContext | null | undefined): boolean;
82
+ /** Whether the caller may open the permission centre's write controls. */
83
+ export declare function canManagePermissions(context: ReviewContext | null | undefined): boolean;
84
+ /**
85
+ * The tabs this caller may see.
86
+ *
87
+ * A department administrator sees the queue too, but the Host narrows it to their subtree;
88
+ * an ordinary user gets only the approved list, and no tab strip at all when there is one tab.
89
+ */
90
+ export declare function reviewTabsFor(context: ReviewContext | null | undefined): readonly ReviewTab[];
91
+ /** Filter rows by the selected review tab. */
92
+ export declare function filterByReview<T extends ReviewableSummary>(rows: readonly T[], tab: ReviewTab): readonly T[];
93
+ /** Counts for the tab labels. */
94
+ export declare function reviewCounts(rows: readonly ReviewableSummary[]): Record<ReviewTab, number>;
95
+ /** The status badge shown on a local skill card. */
96
+ export declare function reviewStatusLabel(status: ReviewStatus): string;
97
+ /**
98
+ * The visibility tag in a card's top-right corner.
99
+ *
100
+ * A department tag names the department and keeps the full path in the tooltip; a global tag
101
+ * is one word because it is the default and should not compete with the skill name.
102
+ */
103
+ /** Read a department path from either the legacy string form or the backend object form. */
104
+ export declare function departmentPathNames(path: ReviewableSummary['departmentPath']): string[];
105
+ export declare function visibilityTag(row: ReviewableSummary): {
106
+ label: string;
107
+ detail: string;
108
+ };
109
+ /** Unread count for a bell badge. */
110
+ export declare function unreadCount(items: readonly ReviewNotification[]): number;
111
+ /** Newest first. Read state never reorders rows, so marking one read cannot move it to another page. */
112
+ export declare function sortNotifications(items: readonly ReviewNotification[]): readonly ReviewNotification[];
113
+ /** The tab a notification should open, when its resource has one. */
114
+ export declare function notificationTab(item: ReviewNotification): ReviewTab | undefined;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Whether the managed DataOps settings section may be registered at all.
3
+ *
4
+ * Registration is the gate, not a CSS class: an ordinary user's profile should not contain a
5
+ * section whose controls they may not use. The identity record is presentation-only and arrives
6
+ * from the portal after the iframe loads, so the registration follows it instead of reading it
7
+ * once at startup. Authorization still lives in DataOps; this only keeps a forbidden panel out of
8
+ * the interface.
9
+ */
10
+ /** Read the current role label without trusting any other field. */
11
+ export declare function currentManagedSettingsRole(): string | null;
12
+ /** Whether the current identity may see the managed DataOps settings section. */
13
+ export declare function canSeeManagedSettings(): boolean;
14
+ /**
15
+ * Observe identity changes.
16
+ *
17
+ * The account plugin keeps one store on `window`; subscribing to its listener set catches the
18
+ * in-page update even before the portal's DOM event. The event is kept as a second signal because
19
+ * a future portal may publish the event without touching the store this plugin reads.
20
+ */
21
+ export declare function subscribeManagedSettingsVisibility(listener: () => void): () => void;
@@ -1,4 +1,5 @@
1
1
  /** Same-origin skill plaza calls from the DataOps skill center section. */
2
+ import type { ReviewStatus, ResourceVisibility } from './review-types.ts';
2
3
  /** Route the plugin's Host half serves for the plaza. */
3
4
  export declare const SKILL_PLAZA_PATH = "/integrations/dataops/skill-plaza";
4
5
  /** One plaza entry as the section reads it. */
@@ -10,11 +11,29 @@ export interface PlazaSkill {
10
11
  tags: string[];
11
12
  sharedByMe: boolean;
12
13
  updatedAt: string;
14
+ reviewStatus: ReviewStatus;
15
+ visibility: ResourceVisibility;
16
+ departmentCode?: string | null;
17
+ departmentName?: string;
18
+ departmentPath?: readonly {
19
+ code: string;
20
+ name: string;
21
+ }[];
22
+ ownerName?: string;
23
+ riskReport?: import('./review-types.ts').RiskReport | null;
24
+ reviewNote?: string | null;
25
+ submittedAt?: string;
13
26
  }
14
27
  /** The plaza listing and the tags in use. */
15
28
  export interface PlazaListing {
16
29
  skills: PlazaSkill[];
17
30
  tags: string[];
31
+ counts?: {
32
+ pending: number;
33
+ approved: number;
34
+ rejected: number;
35
+ total: number;
36
+ };
18
37
  }
19
38
  /**
20
39
  * Read the plaza.
@@ -24,6 +43,8 @@ export interface PlazaListing {
24
43
  export declare function fetchPlaza(filter?: {
25
44
  tag?: string;
26
45
  query?: string;
46
+ status?: string;
47
+ scope?: string;
27
48
  }): Promise<PlazaListing | null>;
28
49
  /**
29
50
  * Change the plaza.
@@ -0,0 +1,29 @@
1
+ /** Same-origin calls for the workspace image upgrade banner. */
2
+ /** Route the plugin's Host half serves for upgrade detection and start. */
3
+ export declare const WORKSPACE_UPGRADE_PATH = "/integrations/dataops/workspace-upgrade";
4
+ /** A DSH session that will receive "继续" after the restart. */
5
+ export interface WorkspaceUpgradeSession {
6
+ sessionId: string;
7
+ title?: string;
8
+ cwd?: string;
9
+ }
10
+ /** The status the DSH page reads before offering an upgrade. */
11
+ export interface WorkspaceUpgradeStatus {
12
+ imageStatus: 'CURRENT' | 'UPDATE_AVAILABLE' | 'NOT_RUNNING' | 'UNKNOWN';
13
+ configuredImage: string;
14
+ activeSessions: WorkspaceUpgradeSession[];
15
+ }
16
+ /**
17
+ * Read the workspace upgrade preview.
18
+ * @returns the status, or null when the Host route is not ready or not connected.
19
+ */
20
+ export declare function fetchWorkspaceUpgrade(): Promise<WorkspaceUpgradeStatus | null>;
21
+ /**
22
+ * Ask the Host to snapshot sessions and recreate the workspace container.
23
+ * @returns the accepted hand-off summary.
24
+ */
25
+ export declare function requestWorkspaceUpgrade(): Promise<{
26
+ accepted: boolean;
27
+ upgradeId: string;
28
+ sessions: number;
29
+ }>;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * The DataOps connector's workspace-visible state and skill-center catalogue.
3
+ *
4
+ * The skill center is skill-first, with connectors as its fourth tab. DataOps contributes one
5
+ * connector whose capability rows are derived from the same scope that drives tool disclosure;
6
+ * a capability the account cannot use is omitted rather than rendered as a disabled placeholder.
7
+ *
8
+ * This module owns only state and presentation. It never authorizes a tool call or a DataOps
9
+ * request: the backend remains the final boundary.
10
+ *
11
+ * @module connector-state
12
+ */
13
+ import { type DataOpsScope } from './tool-scope.ts';
14
+ /** Same-origin route the skill center's connector tab reads and writes. */
15
+ export declare const CONNECTORS_PATH = "/api/dsh-skill-center/connectors";
16
+ /** Connector id shown in the skill center. */
17
+ export declare const DATAOPS_CONNECTOR_ID = "dataops-managed";
18
+ /** Persisted enable state; a missing file means the connector is off. */
19
+ export interface DataOpsConnectorState {
20
+ enabled: boolean;
21
+ capabilities: Record<string, boolean>;
22
+ }
23
+ /** One capability row the skill center renders. */
24
+ export interface ConnectorCapabilityView {
25
+ id: string;
26
+ name: string;
27
+ description: string;
28
+ enabled: boolean;
29
+ permitted: boolean;
30
+ unavailableReason?: string;
31
+ }
32
+ /** One connector row the skill center renders. */
33
+ export interface ConnectorView {
34
+ id: string;
35
+ name: string;
36
+ vendor: string;
37
+ description: string;
38
+ enabled: boolean;
39
+ connected: boolean;
40
+ capabilities: ConnectorCapabilityView[];
41
+ }
42
+ /** Resolve the persisted state file without depending on a global DSH path. */
43
+ export declare const CONNECTOR_STATE_PATH: string;
44
+ /** Read state; a missing or corrupt file is a safe disabled state. */
45
+ export declare function readConnectorState(path?: string): Promise<DataOpsConnectorState>;
46
+ /** Persist state atomically. */
47
+ export declare function writeConnectorState(state: DataOpsConnectorState, path?: string): Promise<void>;
48
+ /** Build the connector row for one current scope. */
49
+ export declare function buildConnectorView(scope: DataOpsScope | undefined, state: DataOpsConnectorState): ConnectorView;
50
+ /** Whether one capability id is in the fixed catalogue. */
51
+ export declare function hasCapability(id: string): boolean;
@@ -1,5 +1,7 @@
1
1
  /** DataOps-managed JWT intake and credential-backed MCP composition. */
2
+ import type { IncomingMessage } from 'node:http';
2
3
  import type { Context } from '@deepseek-ai/cordis';
4
+ import { credentialRef } from '@deepseek-ai/dsh-credentials';
3
5
  import z from '@deepseek-ai/schemastery';
4
6
  import { ModelSync } from './model-sync.ts';
5
7
  import { SettingsSync, type SectionSyncState } from './settings-sync.ts';
@@ -16,10 +18,26 @@ export declare const MANAGED_PLUGIN_ID = "dataops-managed";
16
18
  export declare const MODEL_SYNC_PATH = "/integrations/dataops/model-sync";
17
19
  /** Same-origin route the skill center uses to read and change plaza sharing. */
18
20
  export declare const SKILL_PLAZA_PATH = "/integrations/dataops/skill-plaza";
21
+ /** Caller role and review capability. */
22
+ export declare const REVIEW_CONTEXT_PATH = "/integrations/dataops/review-context";
23
+ /** Notification inbox and read receipts. */
24
+ export declare const REVIEW_NOTIFICATIONS_PATH = "/integrations/dataops/review-notifications";
25
+ /** AI scan and the administrator's decision. */
26
+ export declare const REVIEW_ACTION_PATH = "/integrations/dataops/review";
27
+ /** Permission-centre reads and writes. */
28
+ export declare const RBAC_PATH = "/integrations/dataops/rbac";
29
+ /** The three plaza-review switches. */
30
+ export declare const REVIEW_SETTINGS_PATH = "/integrations/dataops/review-settings";
31
+ /** Department CRUD bridge (task-67). */
32
+ export declare const DEPARTMENTS_PATH = "/integrations/dataops/departments";
19
33
  /** Same-origin route the Settings panel uses to read and change this workspace's limits. */
20
34
  export declare const WORKSPACE_LIMITS_PATH = "/integrations/dataops/workspace-limits";
21
35
  /** Same-origin route the Settings panel uses to read and change the tool-call timeout. */
22
36
  export declare const TOOL_TIMEOUT_PATH = "/integrations/dataops/tool-timeout";
37
+ /** Same-origin route reporting what the last DataOps answer said about the credential. */
38
+ export declare const AUTH_STATE_PATH = "/integrations/dataops/auth-state";
39
+ /** Same-origin route the DSH page uses to detect and start a workspace image upgrade. */
40
+ export declare const WORKSPACE_UPGRADE_PATH = "/integrations/dataops/workspace-upgrade";
23
41
  /** The ends a tool-call timeout may take, in whole seconds. */
24
42
  export declare const TOOL_TIMEOUT_MIN_SECONDS = 1;
25
43
  export declare const TOOL_TIMEOUT_MAX_SECONDS = 3600;
@@ -103,6 +121,24 @@ export interface ModelSyncState {
103
121
  export declare const DEFAULT_TOOL_CALL_TIMEOUT_MS = 300000;
104
122
  /** Schemastery parser for managed DataOps configuration. */
105
123
  export declare const Config: z<Config>;
124
+ /**
125
+ * Adopt the JWT the browser gateway attached to one request.
126
+ *
127
+ * Every browser request reaches this workspace through the DataOps gateway, which resolves the
128
+ * caller's session and forwards a fresh `authorization: Bearer <jwt>` — so the current credential
129
+ * arrives continuously, without any push, handshake or refresh protocol. The routes below are
130
+ * the only place this plugin can see that header, and reading it here is what keeps the stored
131
+ * credential from going stale: the value in it was captured once, while the caller's session is
132
+ * renewed by DataOps for as long as they stay signed in.
133
+ *
134
+ * A request without the header is normal rather than an error (same-origin fetches from inside
135
+ * the workspace do carry it, but a probe or a health check may not), so this only ever improves
136
+ * the stored value and never rejects the request.
137
+ *
138
+ * @param request - the incoming request, whose headers the gateway filled in.
139
+ * @returns once the credential has been updated, or immediately when there is nothing to adopt.
140
+ */
141
+ export declare function adoptRequestJwt(ctx: Context, accessRef: ReturnType<typeof credentialRef>, request: IncomingMessage): Promise<void>;
106
142
  /**
107
143
  * Accept the current DataOps JWT and expose DataOps MCP tools through its credential.
108
144
  * @param ctx - DSH Host context with credentials, Web routes, and tools.
@@ -0,0 +1,32 @@
1
+ /**
2
+ * The DataOps review, notification, permission and settings API.
3
+ *
4
+ * One transport for the four new surfaces keeps the JWT handling, 401 observation and error
5
+ * mapping identical; the browser routes only translate same-origin actions into these calls.
6
+ */
7
+ import type { Context } from '@deepseek-ai/cordis';
8
+ /** Configuration shared with the skill plaza: where DataOps is and which credential holds its JWT. */
9
+ export interface ReviewApiConfig {
10
+ baseUrl: string;
11
+ credentialRef: string;
12
+ }
13
+ /** One upstream answer, status included so a 404/503 can reach the browser unchanged. */
14
+ export interface ReviewApiResult {
15
+ status: number;
16
+ body: unknown;
17
+ }
18
+ /** Cookie-free same-origin bridge to the review endpoints. */
19
+ export declare class ReviewApi {
20
+ private readonly ctx;
21
+ private readonly config;
22
+ constructor(ctx: Context, config: ReviewApiConfig);
23
+ private apiBase;
24
+ private token;
25
+ /**
26
+ * Call one DataOps endpoint with the workspace JWT.
27
+ * @param path - API path below the DataOps origin.
28
+ * @param init - Method, body and extra headers.
29
+ * @returns The upstream status and parsed body; 503 when this workspace has no session.
30
+ */
31
+ request(path: string, init?: RequestInit): Promise<ReviewApiResult>;
32
+ }
@@ -11,6 +11,18 @@ export interface PlazaSkill {
11
11
  /** Whether the current user shared this entry. */
12
12
  sharedByMe: boolean;
13
13
  updatedAt: string;
14
+ reviewStatus: 'pending' | 'approved' | 'rejected';
15
+ visibility: 'global' | 'department' | 'private';
16
+ departmentCode?: string | null;
17
+ departmentName?: string;
18
+ departmentPath?: {
19
+ code: string;
20
+ name: string;
21
+ }[];
22
+ ownerName?: string;
23
+ riskReport?: unknown;
24
+ reviewNote?: string | null;
25
+ submittedAt?: string;
14
26
  }
15
27
  /** A skill the local workspace holds, offered by name for sharing. */
16
28
  export interface LocalSkillCandidate {
@@ -74,7 +86,26 @@ export declare class SkillPlaza {
74
86
  list(options?: {
75
87
  tag?: string;
76
88
  query?: string;
89
+ status?: string;
90
+ scope?: string;
91
+ departmentCode?: string;
77
92
  }): Promise<PlazaSkill[]>;
93
+ /** List with the server's tab counts, which the plaza shows without re-counting a page. */
94
+ listPage(options?: {
95
+ tag?: string;
96
+ query?: string;
97
+ status?: string;
98
+ scope?: string;
99
+ departmentCode?: string;
100
+ }): Promise<{
101
+ skills: PlazaSkill[];
102
+ counts?: {
103
+ pending: number;
104
+ approved: number;
105
+ rejected: number;
106
+ total: number;
107
+ };
108
+ }>;
78
109
  /**
79
110
  * Every tag in use, so the browser offers existing labels.
80
111
  * @returns Distinct tags.
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Owns the managed tool set for the workspace's current DataOps principal.
3
+ *
4
+ * A DSH workspace belongs to exactly one DataOps user, so the model's tool list can be derived
5
+ * from that user's current scope. The manager is the only writer of that list: it reads
6
+ * `/api/ai/review/context` when the credential changes (the `credentials/reference-updated`
7
+ * event, which is also what JWT adoption and `managed-auth` produce), on the first connect, and
8
+ * when the review-context route observes a different role set. It then reconciles the registry:
9
+ * newly allowed tools are registered, tools the role lost are disposed, and tools that remain
10
+ * allowed keep their registration.
11
+ *
12
+ * The manager never runs inside a tool call. Execution-time authorization belongs to DataOps;
13
+ * this is disclosure, so a scope sync that fails transiently keeps the current set rather than
14
+ * flapping, while a definitive 401/403/404/503 clears it.
15
+ *
16
+ * @module tool-manager
17
+ */
18
+ import type { Context } from '@deepseek-ai/cordis';
19
+ import type { ReviewApi, ReviewApiResult } from './review-api.ts';
20
+ import { type AiToolName, type DataOpsScope } from './tool-scope.ts';
21
+ /**
22
+ * Reconcile the managed tools with the current DataOps scope.
23
+ *
24
+ * Construct once per plugin activation. `refresh` is idempotent and coalesces concurrent calls;
25
+ * the constructor also subscribes to the credential observation so an expired session removes
26
+ * the tools without waiting for another explicit sync.
27
+ */
28
+ export declare class AiToolManager {
29
+ private readonly ctx;
30
+ private readonly reviewApi;
31
+ private readonly definitions;
32
+ private readonly registrations;
33
+ private readonly unsubscribeExpiry;
34
+ private disposeViewer;
35
+ private scope;
36
+ private revision;
37
+ private inflight;
38
+ private disposed;
39
+ constructor(ctx: Context, reviewApi: ReviewApi);
40
+ /** The scope the current registration set was derived from, when one has been read. */
41
+ currentScope(): DataOpsScope | undefined;
42
+ /** The tool names currently registered, for diagnostics and tests. */
43
+ registeredToolNames(): AiToolName[];
44
+ /**
45
+ * Re-read the workspace's DataOps scope and reconcile the tool list.
46
+ *
47
+ * Definitive denials clear the list; transient failures (network, 5xx) keep what the last good
48
+ * observation produced. The method never throws: sync happens in the background of a browser
49
+ * request and must not fail that request.
50
+ * @param force - Start a new read even when one is already in flight. Credential adoption uses
51
+ * this because an older read may have captured the previous token; its answer must not win
52
+ * over the scope the new token produces.
53
+ */
54
+ refresh(force?: boolean): Promise<void>;
55
+ /**
56
+ * Apply a context answer already fetched by a same-origin route.
57
+ *
58
+ * The review-context route is how the UI learns about a role change, so its answer is also the
59
+ * freshest scope this plugin will see. Reusing it avoids a second fetch on a page that polls.
60
+ * @param result - The upstream status and parsed body.
61
+ */
62
+ observeContext(result: ReviewApiResult): void;
63
+ /**
64
+ * Apply one parsed context body without contacting DataOps.
65
+ * @param body - Parsed `/api/ai/review/context` body.
66
+ */
67
+ applyContext(body: unknown): void;
68
+ /** Drop every managed tool because no usable DataOps scope is known. */
69
+ clear(): void;
70
+ /** Dispose every tool this manager registered and stop observing auth state. */
71
+ dispose(): void;
72
+ private loadScope;
73
+ /** Store the scope and make the registry match it. */
74
+ private applyScope;
75
+ /** Map one DataOps scope to the skill registry's audience filter. */
76
+ private applyViewer;
77
+ /** Dispose one registration, tolerating a disposer that has already run. */
78
+ private disposeRegistration;
79
+ /** Dispose the whole current set. */
80
+ private disposeRegistrations;
81
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The canonical output contract shared by this plugin's model tools.
3
+ *
4
+ * Every managed tool returns one JSON value and renders it as pretty-printed text. Keeping the
5
+ * schema and renderer in one place means a new tool cannot accidentally return a shape the tool
6
+ * registry rejects.
7
+ *
8
+ * @module tool-output
9
+ */
10
+ /** JSON-safe content a tool may return. */
11
+ export type Json = null | boolean | number | string | Json[] | {
12
+ [key: string]: Json;
13
+ };
14
+ /** One text block as the tool registry accepts it. */
15
+ export type ToolContent = {
16
+ type: 'text';
17
+ text: string;
18
+ };
19
+ /** An open object schema: tools declare their canonical shape in code and docs. */
20
+ export declare const OPEN_OBJECT_SCHEMA: {
21
+ readonly type: "object";
22
+ readonly additionalProperties: true;
23
+ };
24
+ /** Render one canonical value as pretty JSON text. */
25
+ export declare const renderJson: (_args: unknown, value: unknown) => ToolContent[];
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Which DataOps tools this workspace's model may see.
3
+ *
4
+ * This module is disclosure, not authorization. DataOps remains the only authority for every
5
+ * API call: its AuthGuard and PermissionGuard resolve the caller's current roles and grants,
6
+ * and every managed tool call still crosses that boundary. What happens here is narrower — a
7
+ * DSH workspace belongs to one DataOps principal, so the tool schemas offered to the model can
8
+ * follow that principal's current scope instead of advertising operations the role cannot use.
9
+ *
10
+ * The scope is the answer `/api/ai/review/context` gives for the stored JWT. It is read when the
11
+ * credential changes, on the first connect, and when the review-context route observes a new
12
+ * role set — never inside a tool `execute`. A tool that is already registered and whose role
13
+ * loses the capability is disposed by the manager.
14
+ *
15
+ * @module tool-scope
16
+ */
17
+ /** The subset of `/api/ai/review/context` that tool disclosure needs. */
18
+ export interface DataOpsScope {
19
+ /** Stable DataOps user id. */
20
+ userId?: string;
21
+ /** Login name, used only for diagnostics. */
22
+ username?: string;
23
+ /** Role display codes such as 超级管理员/管理员/部门管理员/应用人员. */
24
+ roleCodes?: string[];
25
+ /** The primary role code; DataOps normalizes it to one of the four portal roles. */
26
+ primaryRole?: string;
27
+ /** Stable role keys such as SUPER_ADMIN, when the context carries them. */
28
+ systemKeys?: string[];
29
+ /** The caller's department, when one is assigned. */
30
+ departmentCode?: string | null;
31
+ /**
32
+ * Effective permission keys from every role the caller holds.
33
+ *
34
+ * `permissions` is the current backend field; `permissionKeys` is accepted while a deployment
35
+ * still serves the older shape. A present but empty array is a denial, not a reason to fall
36
+ * back to role names.
37
+ */
38
+ permissions?: string[];
39
+ /** Older spelling of {@link permissions}. */
40
+ permissionKeys?: string[];
41
+ /** Data scope DataOps resolved for the caller. */
42
+ dataScope?: {
43
+ all?: boolean;
44
+ self?: boolean;
45
+ departmentCodes?: string[];
46
+ };
47
+ /** Review capability as the current context reports it (older nested shape). */
48
+ review?: {
49
+ canReview?: boolean;
50
+ canReviewGlobal?: boolean;
51
+ canManagePermissions?: boolean;
52
+ };
53
+ /** Review capability as the current context reports it (top-level shape). */
54
+ canReview?: boolean;
55
+ /** Whether the caller may review global (department-less) resources. */
56
+ canReviewGlobal?: boolean;
57
+ }
58
+ /** The permission each managed tool needs before it is offered to the model. */
59
+ export interface ToolAccessPolicy {
60
+ /** One `skill:*` capability; checked against the context's effective permission list. */
61
+ readonly permission?: string;
62
+ /** Review capability from the context, independent of a permission key. */
63
+ readonly reviewer?: boolean;
64
+ /** Only the DataOps 超级管理员 role sees this tool. */
65
+ readonly superAdminOnly?: boolean;
66
+ }
67
+ /**
68
+ * Tool-to-capability disclosure map.
69
+ *
70
+ * `review_notifications_list/read` intentionally carry no policy: every authenticated principal
71
+ * owns their own notification inbox. `dataops_api_docs` is gated by the role itself because the
72
+ * backend grants the OpenAPI reader to 超级管理员 by default; an administrator who happens to hold
73
+ * `developer:openapi:read` is still outside that role and does not receive the tool.
74
+ */
75
+ export declare const AI_TOOL_ACCESS: {
76
+ readonly skill_list: {
77
+ readonly permission: "skill:read";
78
+ };
79
+ readonly skill_get: {
80
+ readonly permission: "skill:read";
81
+ };
82
+ readonly skill_submit: {
83
+ readonly permission: "skill:submit";
84
+ };
85
+ readonly skill_install: {
86
+ readonly permission: "skill:install";
87
+ };
88
+ readonly skill_uninstall: {
89
+ readonly permission: "skill:install";
90
+ };
91
+ readonly skill_enable: {
92
+ readonly permission: "skill:manage";
93
+ };
94
+ readonly skill_disable: {
95
+ readonly permission: "skill:manage";
96
+ };
97
+ readonly skill_unshare: {
98
+ readonly permission: "skill:manage";
99
+ };
100
+ readonly skill_delete: {
101
+ readonly permission: "skill:manage";
102
+ };
103
+ readonly review_scan: {
104
+ readonly permission: "skill:read";
105
+ readonly reviewer: true;
106
+ };
107
+ readonly review_decide: {
108
+ readonly permission: "skill:review:decide";
109
+ readonly reviewer: true;
110
+ };
111
+ readonly review_notifications_list: {};
112
+ readonly review_notifications_read: {};
113
+ readonly dataops_api_docs: {
114
+ readonly superAdminOnly: true;
115
+ };
116
+ };
117
+ /** Every managed tool name, in the order the manager considers them. */
118
+ export declare const AI_TOOL_NAMES: AiToolName[];
119
+ /** One managed tool name. */
120
+ export type AiToolName = keyof typeof AI_TOOL_ACCESS;
121
+ /** Whether the context carries the stable super-administrator role. */
122
+ export declare const isSuperAdmin: (scope: DataOpsScope) => boolean;
123
+ /** Whether the context carries an administrator or super-administrator role. */
124
+ export declare const isAdmin: (scope: DataOpsScope) => boolean;
125
+ /** Whether the context carries a department-administrator role. */
126
+ export declare const isDepartmentAdmin: (scope: DataOpsScope) => boolean;
127
+ /**
128
+ * Whether the caller may run review operations.
129
+ *
130
+ * The current backend returns `canReview` at the top level; the nested `review` shape is
131
+ * accepted for older contexts. Role membership is a fallback, not an override.
132
+ */
133
+ export declare const canReview: (scope: DataOpsScope) => boolean;
134
+ /**
135
+ * Whether the caller holds one capability.
136
+ *
137
+ * A context that carries a permission list is authoritative. Before the list existed, the
138
+ * four-role fallback kept the old behavior; it is only used when neither spelling is present.
139
+ */
140
+ export declare const hasPermission: (scope: DataOpsScope, key: string) => boolean;
141
+ /**
142
+ * Whether one tool should be registered for this scope.
143
+ *
144
+ * Every check is disclosure metadata, not an execution gate: a visible tool still lets DataOps
145
+ * answer with 403 when the underlying grant changed between a scope sync and the call.
146
+ */
147
+ export declare function isToolVisible(name: AiToolName, scope: DataOpsScope): boolean;
148
+ /**
149
+ * Validate one `/api/ai/review/context` body.
150
+ *
151
+ * A body without `roleCodes` is not a usable scope: treating it as "no roles" would let the
152
+ * role-name fallback hand an ordinary capability set to an answer this plugin cannot interpret.
153
+ * @param body - Parsed JSON from the context endpoint.
154
+ * @returns The normalized scope, or undefined when the answer is not a context document.
155
+ */
156
+ export declare function normalizeScope(body: unknown): DataOpsScope | undefined;