@sidequest-007/dsh-atlas 1.0.0
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.md +269 -0
- package/CONTRIBUTING.md +50 -0
- package/LICENSE +21 -0
- package/LICENSE-ORIGINAL +21 -0
- package/NOTICE +29 -0
- package/README.md +297 -0
- package/README.zh.md +289 -0
- package/assets/diagrams/atlas-overview.svg +53 -0
- package/assets/diagrams/atlas-seam.svg +32 -0
- package/assets/screenshots/file-mention-composer.png +0 -0
- package/assets/screenshots/file-mention-settings.png +0 -0
- package/assets/screenshots/menu-mixed.png +0 -0
- package/cordis.patch.yml +14 -0
- package/dsh.plugin.json +12 -0
- package/lib/client.js +20615 -0
- package/lib/index.js +17458 -0
- package/lib/invariant.js +13 -0
- package/lib/types/abort.d.ts +22 -0
- package/lib/types/atlas.d.ts +181 -0
- package/lib/types/client/DraftLinks.d.ts +21 -0
- package/lib/types/client/FilesDock.d.ts +75 -0
- package/lib/types/client/FolderPicker.d.ts +66 -0
- package/lib/types/client/FolderTab.d.ts +50 -0
- package/lib/types/client/MentionNavigator.d.ts +96 -0
- package/lib/types/client/MenuIcons.d.ts +16 -0
- package/lib/types/client/ReferenceLinks.d.ts +85 -0
- package/lib/types/client/SettingsSection.d.ts +31 -0
- package/lib/types/client/draft-links.d.ts +205 -0
- package/lib/types/client/draft-scope.d.ts +22 -0
- package/lib/types/client/git-provider.d.ts +45 -0
- package/lib/types/client/icons.d.ts +48 -0
- package/lib/types/client/index.d.ts +8 -0
- package/lib/types/client/locales.d.ts +267 -0
- package/lib/types/client/model.d.ts +38 -0
- package/lib/types/client/reference-links.d.ts +100 -0
- package/lib/types/client/remote.d.ts +54 -0
- package/lib/types/client/search.d.ts +9 -0
- package/lib/types/client/source.d.ts +325 -0
- package/lib/types/client/styles.d.ts +15 -0
- package/lib/types/contract.d.ts +512 -0
- package/lib/types/defaults.d.ts +115 -0
- package/lib/types/external.d.ts +58 -0
- package/lib/types/files.d.ts +76 -0
- package/lib/types/git.d.ts +44 -0
- package/lib/types/index.d.ts +88 -0
- package/lib/types/invariant.d.ts +15 -0
- package/lib/types/mention.d.ts +80 -0
- package/lib/types/paste.d.ts +13 -0
- package/lib/types/reference.d.ts +16 -0
- package/lib/types/references.d.ts +126 -0
- package/lib/types/runtime-info.d.ts +30 -0
- package/lib/types/runtime.d.ts +180 -0
- package/lib/types/settings.d.ts +25 -0
- package/lib/types/tokens.d.ts +35 -0
- package/lib/types/tools.d.ts +94 -0
- package/lib/types/typert.d.ts +13 -0
- package/lib/types/types.d.ts +12 -0
- package/package.json +208 -0
package/lib/invariant.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// src/invariant.ts
|
|
2
|
+
var PACKAGE_NAME = "@sidequest-007/dsh-atlas";
|
|
3
|
+
var name = "dsh-atlas-invariant";
|
|
4
|
+
var inject = ["invariants"];
|
|
5
|
+
var install = () => {
|
|
6
|
+
};
|
|
7
|
+
var apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
|
|
8
|
+
export {
|
|
9
|
+
apply,
|
|
10
|
+
inject,
|
|
11
|
+
name
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=invariant.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cancellation versus failure, stated once.
|
|
3
|
+
*
|
|
4
|
+
* The plugin cancels its own work all the time: the browser aborts a superseded
|
|
5
|
+
* menu search on every keystroke, and the Host aborts the expansions of a turn
|
|
6
|
+
* the user stopped. Those cancellations surface as ordinary catchable errors
|
|
7
|
+
* (`gateway/cancelled`, `AbortError`), and reporting them as failures is noise
|
|
8
|
+
* that hides the real ones — so every containment site asks this first.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Whether a caught error is the cancellation of the plugin's own request.
|
|
12
|
+
*
|
|
13
|
+
* The caller's own signal is the authority: when the request the plugin handed
|
|
14
|
+
* down was aborted, whatever came back is that abort. An `AbortError` name is
|
|
15
|
+
* honoured too, because a layer may re-throw the reason detached from the
|
|
16
|
+
* signal. Message text is deliberately NOT sniffed — a real failure whose text
|
|
17
|
+
* happens to say "cancelled" must still be reported.
|
|
18
|
+
* @param error - the caught value.
|
|
19
|
+
* @param signal - the signal the plugin handed down, when it has one.
|
|
20
|
+
* @returns true when this is the plugin's own cancellation.
|
|
21
|
+
*/
|
|
22
|
+
export declare function isCancellation(error: unknown, signal?: AbortSignal): boolean;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `@` data-source seam — one contract, validated per half.
|
|
3
|
+
*
|
|
4
|
+
* ATLAS defines the capability (this file), third-party plugins provide it, and
|
|
5
|
+
* the `@` menu is the single consumer, the same three-layer split the official
|
|
6
|
+
* `dsh-shell` seam uses. A provider hands over a declaration plus its callbacks;
|
|
7
|
+
* it never hands over its data, and this registry never reaches for anything the
|
|
8
|
+
* provider did not explicitly declare.
|
|
9
|
+
*
|
|
10
|
+
* The seam has two halves because DSH plugins do: the browser half answers
|
|
11
|
+
* `list` while the menu is open, and the Host half answers `resolve` when a
|
|
12
|
+
* committed reference is turned into model-visible text. A plugin may register
|
|
13
|
+
* either half; `id` ties them together. Registration IS the authorization — a
|
|
14
|
+
* plugin that never registers is invisible here.
|
|
15
|
+
*/
|
|
16
|
+
import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
17
|
+
/** One selectable row a provider offers to the menu. */
|
|
18
|
+
export interface AtlasItem {
|
|
19
|
+
/** Provider-owned id, handed back to `resolve` verbatim; token-safe. */
|
|
20
|
+
readonly id: string;
|
|
21
|
+
/** Menu title. */
|
|
22
|
+
readonly title: string;
|
|
23
|
+
/** One-line preview shown in the list only; never injected on its own. */
|
|
24
|
+
readonly preview?: string;
|
|
25
|
+
/** Compact right-hand badge (severity, count, status). */
|
|
26
|
+
readonly badge?: string;
|
|
27
|
+
}
|
|
28
|
+
/** What a provider learns about the call it is answering. */
|
|
29
|
+
export interface AtlasCallContext {
|
|
30
|
+
readonly sessionId: SessionId;
|
|
31
|
+
/**
|
|
32
|
+
* The answered session's workspace directory, when the Host knows it. A
|
|
33
|
+
* provider that reaches outside the conversation (the built-in `@git` source
|
|
34
|
+
* runs Git there) must not guess it.
|
|
35
|
+
*/
|
|
36
|
+
readonly cwd?: string;
|
|
37
|
+
/** Superseded when the query changes or the menu closes. */
|
|
38
|
+
readonly signal: AbortSignal;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* One registered data source.
|
|
42
|
+
*
|
|
43
|
+
* `list` runs while the menu is open and must stay cheap; `resolve` runs once,
|
|
44
|
+
* after the user commits, and may be expensive. `scopes` and `testedOn` are the
|
|
45
|
+
* governance layer the framework does not provide: what the provider will reach
|
|
46
|
+
* for, and which DSH versions it has actually been proven on.
|
|
47
|
+
*/
|
|
48
|
+
/**
|
|
49
|
+
* What a provider's `open` may report back.
|
|
50
|
+
*
|
|
51
|
+
* `'gone'` says the item's target no longer exists — the menu then draws the
|
|
52
|
+
* mention's chip as stale rather than as a link, which is the honest answer to a
|
|
53
|
+
* click that could not open anything. `'opened'`, or no return value at all,
|
|
54
|
+
* means the click did its thing.
|
|
55
|
+
*/
|
|
56
|
+
export type AtlasOpenOutcome = 'opened' | 'gone';
|
|
57
|
+
export interface AtlasProvider {
|
|
58
|
+
/** The `@<id>` handle; unique across providers. */
|
|
59
|
+
readonly id: string;
|
|
60
|
+
readonly display: string;
|
|
61
|
+
/** Browser half: candidates for the open menu. Required when registering a `list` half. */
|
|
62
|
+
list?(query: string, ctx: AtlasCallContext): Promise<readonly AtlasItem[]>;
|
|
63
|
+
/**
|
|
64
|
+
* Browser half: open ONE of this provider's items for the user — the click
|
|
65
|
+
* action of a committed `@atlas:<id>/<item>` mention in an already sent
|
|
66
|
+
* message. Optional, and deliberately the provider's own business: an item id
|
|
67
|
+
* means whatever the provider says it means, so the menu never interprets one
|
|
68
|
+
* (a `@git` item happens to be a workspace path, a `@docs` item would be a
|
|
69
|
+
* URL). A provider without this callback contributes an inert chip that is
|
|
70
|
+
* never drawn as clickable.
|
|
71
|
+
*
|
|
72
|
+
* Returning `'gone'` is how a provider tells the menu its item no longer
|
|
73
|
+
* exists, so the chip can be drawn stale instead of as a live link. Anything
|
|
74
|
+
* else (including the usual `void`) means the click did its thing.
|
|
75
|
+
*/
|
|
76
|
+
open?(item: string, ctx: AtlasCallContext): AtlasOpenOutcome | void | Promise<AtlasOpenOutcome | void>;
|
|
77
|
+
/** Host half: the model-visible text for one committed item. */
|
|
78
|
+
resolve?(item: AtlasItem, ctx: AtlasCallContext): Promise<string>;
|
|
79
|
+
/** What this provider reaches for; `[]` means it reaches nothing external. */
|
|
80
|
+
readonly scopes: readonly string[];
|
|
81
|
+
/** DSH versions this provider was verified against. */
|
|
82
|
+
readonly testedOn: readonly string[];
|
|
83
|
+
}
|
|
84
|
+
/** One accepted provider, with the registry's own verdict attached. */
|
|
85
|
+
export interface AtlasRegistration {
|
|
86
|
+
readonly id: string;
|
|
87
|
+
readonly display: string;
|
|
88
|
+
readonly scopes: readonly string[];
|
|
89
|
+
readonly testedOn: readonly string[];
|
|
90
|
+
/**
|
|
91
|
+
* Whether the running DSH version appears in `testedOn`. False also when the
|
|
92
|
+
* runtime version is unknown: an unproven claim must never read as proven.
|
|
93
|
+
*/
|
|
94
|
+
readonly verified: boolean;
|
|
95
|
+
readonly provider: AtlasProvider;
|
|
96
|
+
}
|
|
97
|
+
/** The running Harness facts the registry needs for its version gate. */
|
|
98
|
+
export interface AtlasRuntimeInfo {
|
|
99
|
+
/** The DSH version of this session, when the Host could report it. */
|
|
100
|
+
readonly dshVersion?: string;
|
|
101
|
+
}
|
|
102
|
+
/** Which half a registry instance accepts. */
|
|
103
|
+
export interface AtlasHalf {
|
|
104
|
+
/** Require and keep `list` (the browser/menu half). */
|
|
105
|
+
readonly list: boolean;
|
|
106
|
+
/**
|
|
107
|
+
* Keep the optional `open` callback (the browser/menu half only). Optional so
|
|
108
|
+
* an existing registry keeps its meaning: absent means a declaration carrying
|
|
109
|
+
* `open` is refused rather than silently never called.
|
|
110
|
+
*/
|
|
111
|
+
readonly open?: boolean;
|
|
112
|
+
/** Require and keep `resolve` (the Host/injection half). */
|
|
113
|
+
readonly resolve: boolean;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* The handle the seam publishes as `ctx.get('atlas')`.
|
|
117
|
+
*
|
|
118
|
+
* Both halves publish this same shape, so a provider author writes one
|
|
119
|
+
* declaration either way — which callbacks it must carry depends on the half it
|
|
120
|
+
* is registering into, and the registry says so by throwing.
|
|
121
|
+
*/
|
|
122
|
+
export interface AtlasSeam {
|
|
123
|
+
/**
|
|
124
|
+
* Accept one provider declaration for the half this handle belongs to.
|
|
125
|
+
* @param candidate - the declaration.
|
|
126
|
+
* @returns the disposer that withdraws it.
|
|
127
|
+
*/
|
|
128
|
+
register(candidate: unknown): () => void;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Validate one candidate declaration, naming the first field that fails.
|
|
132
|
+
* @param candidate - the value handed to `register`.
|
|
133
|
+
* @param half - which callbacks this registry requires.
|
|
134
|
+
* @returns the same value typed as a provider.
|
|
135
|
+
*/
|
|
136
|
+
export declare function validateAtlasDeclaration(candidate: unknown, half: AtlasHalf): AtlasProvider;
|
|
137
|
+
/**
|
|
138
|
+
* Validate one item a provider returned.
|
|
139
|
+
* @param item - the value from `list`.
|
|
140
|
+
* @returns the same value typed as an item.
|
|
141
|
+
*/
|
|
142
|
+
export declare function validateAtlasItem(item: unknown): AtlasItem;
|
|
143
|
+
/**
|
|
144
|
+
* The `@` seam's registry: accepts declarations for one half, enforces the
|
|
145
|
+
* governance rules, and hands the accepted providers to that half's consumer.
|
|
146
|
+
*/
|
|
147
|
+
export declare class AtlasRegistry {
|
|
148
|
+
private readonly registrations;
|
|
149
|
+
private readonly readRuntime;
|
|
150
|
+
private readonly half;
|
|
151
|
+
/**
|
|
152
|
+
* @param readRuntime - the running Harness facts used by the version gate;
|
|
153
|
+
* accepted as a thunk (read at verdict time) or as a plain record.
|
|
154
|
+
* @param half - which callbacks this instance requires (defaults to both).
|
|
155
|
+
*/
|
|
156
|
+
constructor(readRuntime?: (() => AtlasRuntimeInfo) | AtlasRuntimeInfo, half?: AtlasHalf);
|
|
157
|
+
/**
|
|
158
|
+
* Accept one provider declaration.
|
|
159
|
+
* @param candidate - the declaration handed over by a plugin.
|
|
160
|
+
* @returns the disposer that withdraws it.
|
|
161
|
+
*/
|
|
162
|
+
register(candidate: unknown): () => void;
|
|
163
|
+
/** Every accepted provider, in registration order. */
|
|
164
|
+
entries(): readonly AtlasRegistration[];
|
|
165
|
+
/**
|
|
166
|
+
* One accepted provider by handle.
|
|
167
|
+
* @param id - the provider handle.
|
|
168
|
+
* @returns the registration, or undefined when nothing claimed it.
|
|
169
|
+
*/
|
|
170
|
+
get(id: string): AtlasRegistration | undefined;
|
|
171
|
+
/**
|
|
172
|
+
* Re-read the version verdict for one stored registration.
|
|
173
|
+
*
|
|
174
|
+
* The verdict is deliberately not a snapshot: the browser learns the running
|
|
175
|
+
* version asynchronously and a plugin may register before it lands, so a
|
|
176
|
+
* registration made a moment too early must not stay unverified forever.
|
|
177
|
+
* @param entry - the stored registration.
|
|
178
|
+
* @returns the same registration with the current verdict.
|
|
179
|
+
*/
|
|
180
|
+
private judge;
|
|
181
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { InjectFace, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
2
|
+
import type { ReferenceInfoSource } from './FilesDock.tsx';
|
|
3
|
+
import type { ReferenceLink } from './reference-links.ts';
|
|
4
|
+
import type { ReferenceOutcome } from './ReferenceLinks.tsx';
|
|
5
|
+
/** What the session's overlay shares with the draft bridge. */
|
|
6
|
+
export interface DraftLinksInjected {
|
|
7
|
+
/** The action a click on this mention performs, or undefined when this build has none. */
|
|
8
|
+
readonly actionFor: (link: ReferenceLink) => (() => ReferenceOutcome | Promise<ReferenceOutcome>) | undefined;
|
|
9
|
+
/** The dock's own inspection verdicts: the bridge never re-asks for the same path. */
|
|
10
|
+
readonly hooks: {
|
|
11
|
+
readonly referenceInfo: ReferenceInfoSource;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/** Overlay entry props: the composer runtime face plus the opener and the verdicts. */
|
|
15
|
+
export type DraftLinksProps = PropsRuntime<'conversation.input.overlay'> & InjectFace<DraftLinksInjected>;
|
|
16
|
+
/**
|
|
17
|
+
* Invisible bridge: opens the reference token a composer click lands in.
|
|
18
|
+
* @param props - the injected opener and the dock's verdict source.
|
|
19
|
+
* @returns nothing; this entry renders no DOM of its own.
|
|
20
|
+
*/
|
|
21
|
+
export declare function DraftLinks({ actionFor, useReferenceInfo }: DraftLinksProps): null;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
2
|
+
import type { ObservableSnapshot } from '@deepseek-ai/dsh-client-store';
|
|
3
|
+
import type { AtFileSettings, ReferenceInfo } from '../contract.ts';
|
|
4
|
+
import type { ReferenceLink } from './reference-links.ts';
|
|
5
|
+
import type { MentionKind } from './source.ts';
|
|
6
|
+
export interface AtFileSettingsSnapshot {
|
|
7
|
+
readonly value: AtFileSettings;
|
|
8
|
+
}
|
|
9
|
+
export type AtFileSettingsSource = ObservableSnapshot<AtFileSettingsSnapshot>;
|
|
10
|
+
/** Snapshot of the host-inspected reference facts (existence, kind, size). */
|
|
11
|
+
export interface ReferenceInfoSnapshot {
|
|
12
|
+
readonly value: readonly ReferenceInfo[];
|
|
13
|
+
}
|
|
14
|
+
export type ReferenceInfoSource = ObservableSnapshot<ReferenceInfoSnapshot>;
|
|
15
|
+
/** Injected business face: open one mention, inspect the draft's references, and the live sources. */
|
|
16
|
+
export interface AtFileDockInjected {
|
|
17
|
+
/**
|
|
18
|
+
* Open one mention of the draft (the same action a click on the sent-message
|
|
19
|
+
* chip performs, so both surfaces behave alike).
|
|
20
|
+
*/
|
|
21
|
+
onOpen: (link: ReferenceLink) => void;
|
|
22
|
+
/** Ask the Host for existence, kind, and size of the draft's path references. */
|
|
23
|
+
requestInspect: (targets: readonly string[]) => void;
|
|
24
|
+
hooks: {
|
|
25
|
+
scope: AtFileSettingsSource;
|
|
26
|
+
referenceInfo: ReferenceInfoSource;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/** Approximate bytes per token for text files (the dock's cost hint). */
|
|
30
|
+
export declare const BYTES_PER_TOKEN = 4;
|
|
31
|
+
/** References heavier than this many tokens are flagged in the dock. */
|
|
32
|
+
export declare const COST_WARN_TOKENS = 8000;
|
|
33
|
+
/**
|
|
34
|
+
* Approximate context cost of one reference, or undefined when there is nothing
|
|
35
|
+
* to price (missing path, directory, or a file whose size is unknown).
|
|
36
|
+
* @param info - the host-inspected facts for this reference, when available.
|
|
37
|
+
* @returns the token estimate and whether it exceeds the warning threshold.
|
|
38
|
+
*/
|
|
39
|
+
export declare function referenceCost(info: ReferenceInfo | undefined): {
|
|
40
|
+
readonly tokens: number;
|
|
41
|
+
readonly warn: boolean;
|
|
42
|
+
} | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* Compact token count for the dock badge (`820`, `1.2k`).
|
|
45
|
+
* @param tokens - the estimated token count.
|
|
46
|
+
* @returns the badge text.
|
|
47
|
+
*/
|
|
48
|
+
export declare function formatTokenCount(tokens: number): string;
|
|
49
|
+
/** Full dock entry props: InputZone owner share + session standard kit + injected face + locale seat. */
|
|
50
|
+
export type AtFileDockProps = PropsRuntime<'conversation.input.dock'> & InjectFace<AtFileDockInjected> & PropsLocale<'atlas'>;
|
|
51
|
+
/** One parsed mention token in the draft, with its span for precise removal. */
|
|
52
|
+
export interface DraftMention {
|
|
53
|
+
readonly kind: Exclude<MentionKind, 'category' | 'back'>;
|
|
54
|
+
/** Stable removal key (the token start index). */
|
|
55
|
+
readonly key: number;
|
|
56
|
+
/** Display label. */
|
|
57
|
+
readonly label: string;
|
|
58
|
+
/** Workspace-relative path for file/dir rows (inspection and open action). */
|
|
59
|
+
readonly relative?: string;
|
|
60
|
+
/** The click action's target, for rows this build can open. */
|
|
61
|
+
readonly link?: ReferenceLink;
|
|
62
|
+
readonly start: number;
|
|
63
|
+
readonly end: number;
|
|
64
|
+
}
|
|
65
|
+
/** Parse the draft's mention tokens in order, deduplicating by kind + span. */
|
|
66
|
+
export declare function draftMentions(draft: string): readonly DraftMention[];
|
|
67
|
+
/** Draft text with one token span removed. */
|
|
68
|
+
export declare function withoutToken(draft: string, start: number, end: number): string;
|
|
69
|
+
/**
|
|
70
|
+
* Render the referenced-item rows; null while the draft has no mention tokens
|
|
71
|
+
* or the settings switch is off.
|
|
72
|
+
* @param props - runtime (input currency + actions), inject, and locale shares.
|
|
73
|
+
* @returns the dock strip, or null.
|
|
74
|
+
*/
|
|
75
|
+
export declare function FilesDock({ input, inputActions, onOpen, requestInspect, useScope, useReferenceInfo, t }: AtFileDockProps): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The folder chooser dialog: a modal picker over the product's own browse
|
|
3
|
+
* backend.
|
|
4
|
+
*
|
|
5
|
+
* Why not the product's dialog itself: `BrowseDirectoryFlow` is not a service, it
|
|
6
|
+
* is a component filling the workspace picker's PRIVATE child slots
|
|
7
|
+
* (`conversation.hero.workspace.directoryFlow` / `sidebar.workspaces.directoryFlow`),
|
|
8
|
+
* and rendering a slot belongs to the entry that declared it — a plugin can
|
|
9
|
+
* neither declare the same hole nor render someone else's. And
|
|
10
|
+
* `directoryPicker/pick` is the NATIVE chooser, which a profile serving the
|
|
11
|
+
* in-app browse capability refuses (`directory-picker/unavailable`).
|
|
12
|
+
*
|
|
13
|
+
* What CAN be borrowed is the backend both of those use:
|
|
14
|
+
* `ctx.uiWorkspace.listDirectory` / `createDirectory` (the `directoryPicker`
|
|
15
|
+
* namespace's browse verbs). This dialog is that backend with our own face: the
|
|
16
|
+
* path line is editable, a row with a chevron walks into that folder, and 打开
|
|
17
|
+
* confirms the folder the path line is showing.
|
|
18
|
+
*/
|
|
19
|
+
import { type ReactNode } from 'react';
|
|
20
|
+
import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
21
|
+
/** One row of a browse level, as the product's picker backend reports it. */
|
|
22
|
+
export interface PickerEntry {
|
|
23
|
+
readonly name: string;
|
|
24
|
+
readonly path: string;
|
|
25
|
+
readonly hidden: boolean;
|
|
26
|
+
}
|
|
27
|
+
/** One browse level: the folder itself, its ancestry, and its child folders. */
|
|
28
|
+
export interface PickerListing {
|
|
29
|
+
readonly path: string;
|
|
30
|
+
readonly home: string;
|
|
31
|
+
readonly crumbs: readonly PickerEntry[];
|
|
32
|
+
readonly entries: readonly PickerEntry[];
|
|
33
|
+
readonly truncated: boolean;
|
|
34
|
+
}
|
|
35
|
+
/** The open/closed share between the source's picker row and this dialog. */
|
|
36
|
+
export interface FolderPickerSnapshot {
|
|
37
|
+
/** Set while the dialog is open; the token the pick will replace travels with it. */
|
|
38
|
+
readonly value: unknown | null;
|
|
39
|
+
}
|
|
40
|
+
/** Read-only observable face the dialog subscribes to. */
|
|
41
|
+
export interface FolderPickerSource {
|
|
42
|
+
getSnapshot(): FolderPickerSnapshot;
|
|
43
|
+
subscribe(listener: () => void): () => void;
|
|
44
|
+
}
|
|
45
|
+
/** What the session's shell shares with the dialog. */
|
|
46
|
+
export interface FolderPickerInjected {
|
|
47
|
+
/** One directory level from the product's picker backend; undefined starts at home. */
|
|
48
|
+
readonly list: (path: string | undefined, signal: AbortSignal) => Promise<PickerListing>;
|
|
49
|
+
/** Create one child folder and answer its absolute path. */
|
|
50
|
+
readonly create: (path: string, name: string) => Promise<string>;
|
|
51
|
+
/** Adopt the folder the path line shows (inserts the reference and closes). */
|
|
52
|
+
readonly confirm: (path: string) => void;
|
|
53
|
+
/** Dismiss without adopting anything. */
|
|
54
|
+
readonly close: () => void;
|
|
55
|
+
readonly hooks: {
|
|
56
|
+
readonly folderPicker: FolderPickerSource;
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/** The dialog's props: the overlay seat, this plugin's copy, and the shell's face. */
|
|
60
|
+
export type FolderPickerProps = PropsRuntime<'conversation.input.overlay'> & PropsLocale<'atlas'> & InjectFace<FolderPickerInjected>;
|
|
61
|
+
/**
|
|
62
|
+
* Render the folder chooser while one is requested.
|
|
63
|
+
* @param props - the open state, the browse backend, and the adoption callbacks.
|
|
64
|
+
* @returns the modal, or null while closed.
|
|
65
|
+
*/
|
|
66
|
+
export declare function FolderPicker({ useFolderPicker, list, create, confirm, close, t }: FolderPickerProps): ReactNode;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The folder tab: one directory, one level, in the right Sidebar.
|
|
3
|
+
*
|
|
4
|
+
* Why a type of this plugin's own: a `dsh-resource://file/…` address is claimed
|
|
5
|
+
* by the file viewers, and a directory makes them answer `"…" is a directory`;
|
|
6
|
+
* the built-in `files` tab is a page (a tree rooted at the session workspace)
|
|
7
|
+
* that claims no address at all. So a folder click has nowhere to go in the
|
|
8
|
+
* shipped product, and this tab is that destination — for a workspace folder and
|
|
9
|
+
* for an out-of-workspace one alike, because the Host gates the listing with the
|
|
10
|
+
* same rule it uses for a reference.
|
|
11
|
+
*
|
|
12
|
+
* The body walks on its own: a directory row descends in this tab, a file row
|
|
13
|
+
* opens the file's own viewer, and the header carries the way back up plus the
|
|
14
|
+
* OS opener. Nothing here reads a file — `atFile/list` returns names and kinds.
|
|
15
|
+
*/
|
|
16
|
+
import { type ReactNode } from 'react';
|
|
17
|
+
import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
18
|
+
import type { SidebarRightTabDefinition } from '@deepseek-ai/dsh-client-ui-sidebar-right/client';
|
|
19
|
+
import type { DirectoryListing } from '../contract.ts';
|
|
20
|
+
import type { AtFileKey } from './locales.ts';
|
|
21
|
+
import { childOf } from './model.ts';
|
|
22
|
+
/** This type's identity in the tab registry, and the key its body registers under. */
|
|
23
|
+
export declare const FOLDER_TAB_ID = "dsh-atlas/folder";
|
|
24
|
+
/** The tab kind this plugin owns. */
|
|
25
|
+
export declare const FOLDER_TAB_KIND = "atlas-folder";
|
|
26
|
+
/**
|
|
27
|
+
* The folder tab's registry definition: it claims every address of our own
|
|
28
|
+
* `folder` resource type, so nothing else can be asked to open one.
|
|
29
|
+
* @param t - namespace-bound translate for the chip title.
|
|
30
|
+
* @returns the definition to register.
|
|
31
|
+
*/
|
|
32
|
+
export declare function folderTabDefinition(t: (key: AtFileKey, params?: Record<string, string>) => string): SidebarRightTabDefinition;
|
|
33
|
+
/** What the session's shell shares with the folder tab. */
|
|
34
|
+
export interface FolderTabInjected {
|
|
35
|
+
/** List one directory (one level); `''` means the session workspace root. */
|
|
36
|
+
readonly list: (path: string, signal: AbortSignal) => Promise<DirectoryListing>;
|
|
37
|
+
/** Open a file the way every other reference does. */
|
|
38
|
+
readonly openFile: (path: string) => void;
|
|
39
|
+
/** Hand a path to the OS file manager. */
|
|
40
|
+
readonly openNative: (path: string) => void;
|
|
41
|
+
}
|
|
42
|
+
/** The folder tab's props: the tab seat, the copy seat, and the shell's face. */
|
|
43
|
+
export type FolderTabProps = PropsRuntime<'sidebar.right.pane.tab'> & PropsLocale<'atlas'> & InjectFace<FolderTabInjected>;
|
|
44
|
+
export { childOf };
|
|
45
|
+
/**
|
|
46
|
+
* Render one directory's contents.
|
|
47
|
+
* @param props - the tab's live info, the Host listing call, and the openers.
|
|
48
|
+
* @returns the folder's rows, or the reason it could not be listed.
|
|
49
|
+
*/
|
|
50
|
+
export declare function FolderTab({ useTabInfo, list, openFile, openNative, t }: FolderTabProps): ReactNode;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { InjectFace, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
2
|
+
import type { InputTriggerCandidate, MenuState, TriggerGuard } from '@deepseek-ai/dsh-client-ui-input-trigger/client';
|
|
3
|
+
import type { SnapshotStore } from '@deepseek-ai/dsh-client-store';
|
|
4
|
+
import type { AtFileSettingsSource } from './FilesDock.tsx';
|
|
5
|
+
/** Controller surface required by the completion bridge. */
|
|
6
|
+
export interface MentionNavigationController {
|
|
7
|
+
readonly menu: SnapshotStore<MenuState>;
|
|
8
|
+
track(draft: string, caret: number, guard: TriggerGuard, draftRev: number): void;
|
|
9
|
+
/** Close the menu without a pick (used to force a refresh). */
|
|
10
|
+
dismiss(): void;
|
|
11
|
+
}
|
|
12
|
+
/** Injected controller for the current session. */
|
|
13
|
+
export interface MentionNavigatorInjected {
|
|
14
|
+
readonly controller: MentionNavigationController;
|
|
15
|
+
readonly hooks: {
|
|
16
|
+
scope: AtFileSettingsSource;
|
|
17
|
+
};
|
|
18
|
+
readonly sessionId: string;
|
|
19
|
+
/** Toggle one group header's collapse state (chat:/skill: keys). */
|
|
20
|
+
readonly toggleGroup: (key: string) => void;
|
|
21
|
+
/** Synchronously rebuild rows for a category query from settled caches. */
|
|
22
|
+
readonly rebuildRows: (sessionId: string, query: string) => readonly InputTriggerCandidate[] | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* A message was submitted. The Host records referenced external paths in its
|
|
25
|
+
* ledger during the send, so any cached out-of-workspace scope is now suspect.
|
|
26
|
+
*/
|
|
27
|
+
readonly onSend?: () => void;
|
|
28
|
+
}
|
|
29
|
+
/** Overlay entry props: session input state/actions plus the trigger controller. */
|
|
30
|
+
export type MentionNavigatorProps = PropsRuntime<'conversation.input.overlay'> & InjectFace<MentionNavigatorInjected>;
|
|
31
|
+
/** Input facts needed to validate a menu-time completion. */
|
|
32
|
+
export interface MentionNavigationInput {
|
|
33
|
+
readonly draft: string;
|
|
34
|
+
readonly draftRev: number;
|
|
35
|
+
readonly phase: 'plain' | 'adjudicating' | 'claimed' | 'submitting';
|
|
36
|
+
}
|
|
37
|
+
/** The group row currently highlighted in the @ menu, if any. */
|
|
38
|
+
export declare function highlightedGroup(menu: MenuState): {
|
|
39
|
+
key: string;
|
|
40
|
+
index: number;
|
|
41
|
+
} | undefined;
|
|
42
|
+
/** The group row at a menu index, or undefined. */
|
|
43
|
+
export declare function groupAt(menu: MenuState, index: number): {
|
|
44
|
+
key: string;
|
|
45
|
+
} | undefined;
|
|
46
|
+
/** Whether the row at index is the back-to-categories row. */
|
|
47
|
+
export declare function backRowAt(menu: MenuState, index: number): boolean;
|
|
48
|
+
/** The category row at index (its draft prefix), or undefined. */
|
|
49
|
+
export declare function categoryRowAt(menu: MenuState, index: number): {
|
|
50
|
+
prefix: string;
|
|
51
|
+
} | undefined;
|
|
52
|
+
/** Whether a menu item is a section header (category/back/group) rather than a result leaf. */
|
|
53
|
+
export declare function isHeaderRow(item: {
|
|
54
|
+
mentionKind?: string;
|
|
55
|
+
} | undefined): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* The index of the first result leaf in the atlas group (skipping every
|
|
58
|
+
* section header), or -1 when the menu has no result rows yet.
|
|
59
|
+
*/
|
|
60
|
+
export declare function firstLeafIndex(menu: MenuState): number;
|
|
61
|
+
/**
|
|
62
|
+
* The row a freshly settled query's default highlight belongs on.
|
|
63
|
+
*
|
|
64
|
+
* A query that uniquely names a category — a shortcut letter (F/D/S/C/P) or an
|
|
65
|
+
* English prefix (fi/fo/sk/ch/pl) — is an explicit "I want this category"
|
|
66
|
+
* intent, so the highlight goes to that category's own row: the gray hint row
|
|
67
|
+
* the source pins on top, whose pick enters `@<category>:`. Landing on the
|
|
68
|
+
* first result leaf instead would make Enter insert whichever most-used entry
|
|
69
|
+
* happens to contain the typed letter, which is the opposite of what typing a
|
|
70
|
+
* category shortcut asks for. Every other query keeps the best-match behavior
|
|
71
|
+
* and lands on the first result leaf.
|
|
72
|
+
* @param menu - the live menu state.
|
|
73
|
+
* @returns the row index, or -1 when no row qualifies yet.
|
|
74
|
+
*/
|
|
75
|
+
export declare function defaultHighlightIndex(menu: MenuState): number;
|
|
76
|
+
/**
|
|
77
|
+
* Rows one PageUp/PageDown covers when the list cannot be measured.
|
|
78
|
+
*
|
|
79
|
+
* jsdom implements no layout, and a browser can be asked before the list has one:
|
|
80
|
+
* a fixed page is better than a division by zero, and eight rows is what the menu
|
|
81
|
+
* shows at its usual height anyway.
|
|
82
|
+
*/
|
|
83
|
+
export declare const PAGE_ROWS = 8;
|
|
84
|
+
/**
|
|
85
|
+
* How many rows one page covers in the open menu.
|
|
86
|
+
*
|
|
87
|
+
* Measured from the live list rather than assumed, so a taller window pages
|
|
88
|
+
* further and a short one does not jump past what the user can see.
|
|
89
|
+
* @param box - the menu's scrolling listbox, or null when it is not rendered.
|
|
90
|
+
* @returns the row count of one page (at least one).
|
|
91
|
+
*/
|
|
92
|
+
export declare function pageStep(box: Element | null): number;
|
|
93
|
+
/** Whether this is a plain Enter: no IME ownership, no modifier, not already handled. */
|
|
94
|
+
export declare function isPlainEnter(event: Pick<KeyboardEvent, 'key' | 'keyCode' | 'defaultPrevented' | 'isComposing' | 'altKey' | 'ctrlKey' | 'metaKey' | 'shiftKey'>): boolean;
|
|
95
|
+
/** Invisible overlay entry: folds group headers and keeps category rows in the menu. */
|
|
96
|
+
export declare function MentionNavigator({ controller, useInput, inputActions, toggleGroup, sessionId, rebuildRows, onSend }: MentionNavigatorProps): null;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { InjectFace, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
2
|
+
import type { MenuState } from '@deepseek-ai/dsh-client-ui-input-trigger/client';
|
|
3
|
+
import type { SnapshotStore } from '@deepseek-ai/dsh-client-store';
|
|
4
|
+
/** The attribute this overlay sets on an icon slot it covers. */
|
|
5
|
+
export declare const HOST_ATTRIBUTE = "data-dsh-atlas-menu-icon-host";
|
|
6
|
+
/** What the session's overlay shares with the menu layer. */
|
|
7
|
+
export interface MenuIconsInjected {
|
|
8
|
+
/** The trigger controller's menu store: the rows, their order, and the open state. */
|
|
9
|
+
readonly hooks: {
|
|
10
|
+
readonly menu: SnapshotStore<MenuState>;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
/** Overlay entry props: the composer runtime face plus the menu store. */
|
|
14
|
+
export type MenuIconsProps = PropsRuntime<'conversation.input.overlay'> & InjectFace<MenuIconsInjected>;
|
|
15
|
+
/** The glyph layer. */
|
|
16
|
+
export declare function MenuIcons({ useMenu }: MenuIconsProps): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { InjectFace, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
2
|
+
import { type ReferenceLink } from './reference-links.ts';
|
|
3
|
+
/** The attribute this bridge sets on a chip it can open (hover affordance hook). */
|
|
4
|
+
export declare const LINK_ATTRIBUTE = "data-dsh-atlas-link";
|
|
5
|
+
/** The attribute this bridge sets on a chip whose target is gone (stale styling hook). */
|
|
6
|
+
export declare const MISSING_ATTRIBUTE = "data-dsh-atlas-missing";
|
|
7
|
+
/** What a click on one reference did, as far as the chip is concerned. */
|
|
8
|
+
export type ReferenceOutcome = 'opened' | 'gone';
|
|
9
|
+
/** What the session's overlay shares with the bridge. */
|
|
10
|
+
export interface ReferenceLinksInjected {
|
|
11
|
+
/**
|
|
12
|
+
* The action a click on this mention performs, or undefined when this build
|
|
13
|
+
* has none (an unknown provider, a provider that declared no `open`, a
|
|
14
|
+
* session with no client scope). The same answer drives the hover affordance,
|
|
15
|
+
* so a chip is never drawn as clickable unless clicking really does something.
|
|
16
|
+
*
|
|
17
|
+
* The outcome is what the chip has to show: a reference whose target no longer
|
|
18
|
+
* exists is a normal state — the user deleted or renamed the file long after
|
|
19
|
+
* the mention was sent — so the bridge marks that chip stale instead of
|
|
20
|
+
* pretending the click opened something.
|
|
21
|
+
*/
|
|
22
|
+
readonly actionFor: (link: ReferenceLink) => (() => ReferenceOutcome | Promise<ReferenceOutcome>) | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* Map one composer chip's label to a workspace-relative path.
|
|
25
|
+
*
|
|
26
|
+
* A source that inserts chips may label them with a bare file name (the
|
|
27
|
+
* sidebar plugin does: the chip shows `@index.ts` while it serializes to
|
|
28
|
+
* `@src/client/index.ts`), so a bare name is resolved through the plugin's own
|
|
29
|
+
* index before the click. `undefined` means the label alone cannot name one
|
|
30
|
+
* file (two indexed paths answer to it), and the bridge then falls back to the
|
|
31
|
+
* draft — never to a guess.
|
|
32
|
+
*/
|
|
33
|
+
readonly resolveLabel: (label: string) => string | undefined;
|
|
34
|
+
}
|
|
35
|
+
/** Overlay entry props: the chat overlay's runtime face plus the opener. */
|
|
36
|
+
export type ReferenceLinksProps = PropsRuntime<'conversation.input.overlay'> & InjectFace<ReferenceLinksInjected>;
|
|
37
|
+
/**
|
|
38
|
+
* The openable mention of one chip element, or undefined.
|
|
39
|
+
*
|
|
40
|
+
* A `<button>` chip means the framework passed its reference actions and wires
|
|
41
|
+
* the click itself; acting as well would open the resource twice. A chip of any
|
|
42
|
+
* other kind (session, folder, skill-slash, command) is not ours to open.
|
|
43
|
+
* @param chip - the chip element.
|
|
44
|
+
* @returns the decoded action, or undefined when the bridge must not act.
|
|
45
|
+
*/
|
|
46
|
+
export declare function chipLink(chip: Element): ReferenceLink | undefined;
|
|
47
|
+
/** The visual body of a composer chip inside its host element. */
|
|
48
|
+
export declare function composerChipBody(host: Element): Element;
|
|
49
|
+
/**
|
|
50
|
+
* The openable mention of one composer chip, or undefined.
|
|
51
|
+
*
|
|
52
|
+
* The label is the chip body's `title` (the framework renders the visible `@` as
|
|
53
|
+
* a separate marker span, and uses a domain icon instead for some sources), with
|
|
54
|
+
* the host's own text as the fallback. A label that is not a mention of ours — a
|
|
55
|
+
* slash chip (`/skill`), an empty label, a provider handle — stays inert, exactly
|
|
56
|
+
* as an inert sent chip does.
|
|
57
|
+
*
|
|
58
|
+
* Unlike a draft TOKEN, this is not checked against the Host first: the label is
|
|
59
|
+
* another source's display text, not necessarily a workspace spelling the dock
|
|
60
|
+
* inspected. `dsh-better-sidebar`, which inserts these chips, labels a chip with
|
|
61
|
+
* the file's BASENAME while its serialization keeps the full relative path, so
|
|
62
|
+
* the caller resolves a bare name through the plugin's own index before the
|
|
63
|
+
* click, and the click itself discovers a vanished target the way a sent chip
|
|
64
|
+
* does — the action reports `gone` and the chip is marked stale.
|
|
65
|
+
* @param host - one `[data-composer-chip]` host element.
|
|
66
|
+
* @param resolve - maps one chip label to a workspace-relative path.
|
|
67
|
+
* @returns the decoded mention, or undefined when the bridge must not act.
|
|
68
|
+
*/
|
|
69
|
+
export declare function composerChipLink(host: Element, resolve?: (label: string) => string | undefined): ReferenceLink | undefined;
|
|
70
|
+
/**
|
|
71
|
+
* The one draft token a bare chip label names, or undefined.
|
|
72
|
+
*
|
|
73
|
+
* A chip's full reference lives in its Lexical node, not in the DOM — but the
|
|
74
|
+
* DRAFT keeps exactly what that source serializes (the whole mention), so a
|
|
75
|
+
* label the index could not place is still recoverable when the draft holds
|
|
76
|
+
* precisely one token with that basename. Two candidates mean the label cannot
|
|
77
|
+
* say which one is meant, and the answer is then undefined: an inert chip beats
|
|
78
|
+
* opening the wrong file.
|
|
79
|
+
* @param draft - the composer's draft text.
|
|
80
|
+
* @param label - the chip's visible label.
|
|
81
|
+
* @returns the workspace-relative path the draft spells, or undefined.
|
|
82
|
+
*/
|
|
83
|
+
export declare function draftPathForLabel(draft: string, label: string): string | undefined;
|
|
84
|
+
/** Invisible bridge: opens the reference chips the product renders on click. */
|
|
85
|
+
export declare function ReferenceLinks({ actionFor, resolveLabel, useInput }: ReferenceLinksProps): null;
|