@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/README.md
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
# dsh-ATLAS
|
|
2
|
+
|
|
3
|
+
**English** · [简体中文](README.zh.md)
|
|
4
|
+
|
|
5
|
+
A **Side Quest** project (工作室:支线任务, **Side Quest Labs**) — npm scope `@sidequest-007/*`, repository [`jameswatt139240-crypto/dsh-ATLAS`](https://github.com/jameswatt139240-crypto/dsh-ATLAS); the DSH plugin id keeps the ecosystem's unscoped `dsh-*` form.
|
|
6
|
+
|
|
7
|
+
<img src="assets/diagrams/atlas-overview.svg" alt="dsh-ATLAS: one @ trigger, five built-in categories, plus a category any plugin can register" width="880">
|
|
8
|
+
|
|
9
|
+
**@ Last, All Sources.** One `@` . Any plugin can register.
|
|
10
|
+
|
|
11
|
+
Unified `@` mentions for the DeepSeek Harness web GUI: type `@` in the composer to reference **workspace files and folders**, **discoverable skills**, **past chats**, and **installed plugins** — and any other plugin can add its own category to the same menu.
|
|
12
|
+
|
|
13
|
+
- **One `@`, every source**: five built-in categories plus every registered provider, in one list; typing letters searches all of them at once.
|
|
14
|
+
- **A platform, not a picker**: third-party plugins register their own `@` category through the `ctx.atlas` seam. The menu rebuilds from the live registry on every open, so **this package never has to change for a provider** — see [Write your own `@` source](#write-your-own--source).
|
|
15
|
+
- **Provenance, never content**: a committed mention injects a marker (`<workspace-reference>`, `<skill-reference>`, `<atlas-reference>`, …) and nothing else. The plugin reads directory metadata only; the agent reads the file itself, if and when it needs to.
|
|
16
|
+
- **Governed by construction**: `scopes` and `testedOn` are required, a duplicate `id` throws, a version mismatch registers as `verified: false`, and every provider body is budgeted (16 KiB per reference, 48 KiB per step).
|
|
17
|
+
- **`@git` ships as the worked example**: a real provider, built the way a third party would build one, in ~200 lines across its two halves.
|
|
18
|
+
|
|
19
|
+
<p align="center"><img src="assets/diagrams/atlas-seam.svg" alt="The @ data-source seam: browser half lists candidates, host half resolves one reference at send, and the registry gates the declaration" width="880"></p>
|
|
20
|
+
|
|
21
|
+
It extends [`dsh-at-file`](https://github.com/FSMargoo/dsh-at-file) (MIT), keeping its workspace path index, file filters, and paste protection, and adding four more categories, a category menu with shortcuts, mixed results, collapsible groups, and three model-facing query tools.
|
|
22
|
+
|
|
23
|
+
> `dsh-atlas` and `dsh-at-file` both claim the composer's `@` trigger. Install **one of them**, not both.
|
|
24
|
+
|
|
25
|
+
## Install
|
|
26
|
+
|
|
27
|
+
Requires a DSH install (`dsh` on `PATH`) with a web profile.
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
# from GitHub (this repository; lib/ is committed, so no build runs)
|
|
31
|
+
dsh plugin --profile web add github:jameswatt139240-crypto/dsh-ATLAS
|
|
32
|
+
|
|
33
|
+
# a local clone (development)
|
|
34
|
+
git clone https://github.com/jameswatt139240-crypto/dsh-ATLAS
|
|
35
|
+
dsh plugin --profile web add link:/path/to/dsh-ATLAS
|
|
36
|
+
|
|
37
|
+
# npm (once published; the scope is the studio's, the plugin id stays `dsh-atlas`)
|
|
38
|
+
dsh plugin --profile web add @sidequest-007/dsh-atlas
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Restart `dsh web` after installing or updating so the Host and the browser client load the same version. Then type `@` in the composer.
|
|
42
|
+
|
|
43
|
+
## The five categories
|
|
44
|
+
|
|
45
|
+
The menu keeps five category rows in its **bottom band** — the rows nearest the composer — so pressing `@` opens on them rather than on whatever else shares the trigger.
|
|
46
|
+
|
|
47
|
+
| Category | Menu prefix | Shortcut | Candidates from | Written into the draft |
|
|
48
|
+
|---|---|---|---|---|
|
|
49
|
+
| File | `file:` | `F` | The session workspace index | `@src/index.ts` |
|
|
50
|
+
| Folder | `folder:` | `D` | Same index | `@src/client/` |
|
|
51
|
+
| Skill | `skill:` | `S` | The `ctx.skills` registry | `@skill:blender-modeling` |
|
|
52
|
+
| Past chat | `chat:` | `C` | The official session-reference resolver | `@[title](dsh-session:<id>)` |
|
|
53
|
+
| Plugin | `plugin:` | `P` | The official plugin inventory | `@plugin:dsh-atlas` |
|
|
54
|
+
|
|
55
|
+
**One shortcut letter is a statement of intent, not a query.** Typing it shows the five categories plus a `Tab 补全 → category:` hint as the **last** row, with the highlight already on that hint; <kbd>Tab</kbd> or <kbd>Enter</kbd> then enters the `category:` prefix. **A registered source can claim a shortcut letter too**: the first letter of its id (`@git` → <kbd>G</kbd>), and only while no built-in category or earlier provider answers to that letter (the `@<id>:` prefix always works). Filtering starts on anything more specific — a first letter that is not a shortcut, or a second character (which is what the `fi`/`fo`/`sk`/`ch`/`pl` spellings are). Clicking a category row or the back row enters/returns the same way and **keeps the menu open** — that is this plugin's own override; the normal pick path closes it.
|
|
56
|
+
|
|
57
|
+
**Registered sources are the sixth row and beyond.** `@git` ships built in (the workspace's changed files, with each file's diff as the referenced content), and any plugin can add its own category the same way — see [Write your own `@` source](#write-your-own--source). They appear after the five, in registration order, and disappear when the plugin that registered them is disposed. The plugin's group itself is registered with a **positive order**, so it is the last group in the menu: the bottom band belongs to this plugin, and the stock `dsh-client-ui-reference` source's file list stays above it.
|
|
58
|
+
|
|
59
|
+
## Interaction
|
|
60
|
+
|
|
61
|
+
- **Typing letters** searches every category at once, split into a "recently referenced" section and an "all matches" section, with a category tag on each row; the category rows stay pinned and clickable.
|
|
62
|
+
- **Folders**: the `@folder:` category lists directories; picking one writes `@path/` into the draft (the trailing slash is what marks a directory, and the plugin still reads directory metadata only). Keep typing after the slash to narrow the search inside it. While filtering, the rows are ranked in **two tiers**: folders whose NAME matches first — the workspace's own, then the ones outside it — and then, in a group of their own (`路径匹配`), the folders that only their PATH matches. Folding that group is your gesture; it opens by default and shows whatever fits.
|
|
63
|
+
- **A folder you name first scopes the file list**: `@e:/work/docs/ @file:` lists **that folder's own files** (one level, through the same directory listing the folder tab uses) under a group header that says which folder they came from and that it is outside the workspace, before the workspace's own matches. The scope is positional — the nearest folder reference typed before the category token — so **no connective word is needed**, and a draft with no folder reference behaves exactly as before.
|
|
64
|
+
- **Grouping**: skills group by management tier (系统 / 用户 / 项目 / 自定义 / 插件) and then by domain (blender, competition, dsh, …); plugins group by npm scope (`@deepseek-ai/…` or 其他); past chats group by workspace with the sidebar's relative times and children indented under their parent. Every parent row collapses or expands instantly on click, or on <kbd>Enter</kbd> while that group header is the highlighted row. <kbd>Tab</kbd> completion, <kbd>↑</kbd><kbd>↓</kbd>, and <kbd>Esc</kbd> are the harness's own keymap. Beyond those, the plugin adds exactly three keys of its own: <kbd>Enter</kbd> on a highlighted group header folds it, the category and back rows keep the menu open when picked, and <kbd>PageUp</kbd>/<kbd>PageDown</kbd> page the list by one measured screen — those two move the highlight (or scroll the list when the highlight belongs to another source), so the focused row is always the one you can see.
|
|
65
|
+
- **Long lists scroll instead of hiding rows**: a category shows at most `MAX_CANDIDATES` (20) rows, and when they do not fit the menu scrolls — PageUp/PageDown walk it a screen at a time, and the plugin re-asserts the focused row's visibility whenever the content changes (a scoped folder's listing arriving late, a group being folded). Rows are therefore never dropped merely to keep the menu short; the tiers above decide what comes first.
|
|
66
|
+
- **Initial view**: `@file:` lists recently referenced files first (most recent first, persisted per workspace), then alphabetical order.
|
|
67
|
+
- **Cost and staleness**: each file reference in the dock shows its approximate token cost (`≈1.2k tokens`, highlighted above 8 000); a reference whose path no longer resolves is marked **Missing**. Cost comes from the file size (~4 bytes per token) and the plugin reads entry metadata only — **never file content**.
|
|
68
|
+
- **Typo tolerance**: a lightly mistyped filename (`veiw` → `view.ts`, `clinet` → `client/…`) still matches. **Exact ranking wins**: the fuzzy pass only runs when the exact ranking found nothing, and queries shorter than three characters or containing `/` stay exact.
|
|
69
|
+
- **Line ranges**: type `@src/a.ts:12-40` to reference lines 12–40 of that file (a single line is `@src/a.ts:7`; a reversed `40-12` is normalized to `12-40`). Directories take no range. The Host validates syntax and path kind only and **never opens the file**, so whether the lines exist is the agent's business when it reads.
|
|
70
|
+
- **Reference dock**: every reference in the draft appears above the composer; file, folder and provider rows open on click, and each row's <kbd>×</kbd> removes its token.
|
|
71
|
+
- **Every draft reference the plugin can open is a link**: the test is the PLUGIN's own — the token decodes into a reference and the Host has confirmed the target exists — not whether the framework happened to decorate it. A hand-typed `@AGENTS.md` behaves exactly like one the editor recognised: the whole token is blue, a click anywhere inside it opens the reference, and the pointer turns into a hand. The colour is painted with the CSS Custom Highlight API (a Lexical text span expects exactly one text child, so wrapping any part of a token would break typing inside it) in the very colour the framework uses for its own references. A token whose target is **gone** is not left as ordinary text either: it is painted the dock's own **Missing** way — dimmed and struck through — because it is a reference that cannot open. **When** that verdict is drawn matters: only a **finished** token is judged (one followed by whitespace, or by another line, i.e. after Enter), because a token still being typed is every prefix of a path at once and `@N` is always "missing" — striking it through mid-word would judge a word the user has not written yet. A token that CAN open turns blue the moment its name is complete, exactly as the framework's own decoration does. A token whose verdict has not arrived, whose provider declared no `open`, or whose name cannot be placed claims nothing and stays ordinary text. On a browser without the highlight API nothing is painted at all — there only the part the framework itself coloured acts as a link, because nothing else is drawn as one.
|
|
72
|
+
- **A reference another source inserted as a chip is clickable too**: the sidebar's file tree writes one into the draft as an atomic chip, which this client build draws in the framework's chip blue and leaves inert. Clicking one opens the file it names, and the chip then carries the plugin's link language. That source labels a chip with the file's **basename** (the full relative path lives only in the draft text), so a bare name is placed in two steps: first the plugin's own index, where a unique basename settles it; then, when the index itself is ambiguous (`index.ts` exists twice in this workspace), the **draft** — one token with that basename decides, two leave the chip inert. Only a name the index does not know at all is handed to the click's own existence check, which marks the chip stale rather than opening something wrong.
|
|
73
|
+
- **References in sent messages are clickable**: a chip in a message bubble opens on click (files in the right Sidebar, the Host opener when there is none; skills through the skill source; provider items through the `open` their own provider declared). Only a chip that really opens is drawn as a link — the framework's own `--dsw-alias-link` blue with a hover underline — and a build that wires the chips itself renders `<button>`, which this plugin leaves alone. The plugin also implements the framework's `openReference` hook, so a client that activates a reference token in the composer asks its owning source to open it. The installed client activates nothing yet, so the plugin supplies that click itself, through the very action the chips run.
|
|
74
|
+
- **Icons**: every row this plugin emits carries **its own** glyph — the same modern line set the reference dock uses, including per-file-type and per-language marks (TypeScript, Rust, PDF, image, archive, …) and a branch glyph for `@git`. The framework's menu row can only draw three glyphs of its own, so the plugin reserves the slot (which is what indents every name by the same amount) and draws the glyph itself, monochrome, in a fixed layer above the list: nothing is inserted into a row the framework owns, and the framework's own glyph is hidden only in the slots this layer actually covers.
|
|
75
|
+
|
|
76
|
+

|
|
77
|
+
|
|
78
|
+
*The menu (scrolled to its bottom band): a mixed, tagged result list — file rows with their directory, a collapsed plugin group — above the five category rows plus `@git`, each with the plugin's own glyph. The band is the last group, nearest the composer, and the menu is registered with a positive order so the stock reference source stays above it.*
|
|
79
|
+
|
|
80
|
+

|
|
81
|
+
|
|
82
|
+
*The reference dock above the composer: one row per mention in the draft, each with its own glyph, its approximate token cost (`≈26k tokens` in red is past the 8 000 highlight) and its remove button — and every token in the draft painted as a link.*
|
|
83
|
+
|
|
84
|
+
## Injected references
|
|
85
|
+
|
|
86
|
+
Before each agent step, the plugin validates every reference in the draft and appends one message carrying **only the reference** — never file content. The agent reads referenced content on demand with the tools available to the session.
|
|
87
|
+
|
|
88
|
+
```text
|
|
89
|
+
Review @docs/spec.pdf
|
|
90
|
+
Use @skill:blender-modeling
|
|
91
|
+
Recall @[Payment rates](dsh-session:xxx)
|
|
92
|
+
With @plugin:dsh-atlas
|
|
93
|
+
Take the diff of @atlas:git/src/extract.ts
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
| Category | Injected marker | Message source |
|
|
97
|
+
|---|---|---|
|
|
98
|
+
| File / Folder | `<workspace-reference path="docs/spec.pdf" kind="file" />` (adds `lines="12-40"` for a range) | `at-file-mention` |
|
|
99
|
+
| Skill | `<skill-reference name="blender-modeling" />` | `atlas-skill` |
|
|
100
|
+
| Plugin | `<plugin-reference name="dsh-atlas" />` | `atlas-plugin` |
|
|
101
|
+
| Past chat | The official `session-reference` read-only snapshot (replayable, budgeted) | `session-reference` |
|
|
102
|
+
| A registered `@` source | `<atlas-reference provider="git" item="src/a.ts" scopes="process:git" verified="true">…</atlas-reference>` | `atlas-provider` |
|
|
103
|
+
|
|
104
|
+
- A referenced directory injects **that directory only**; its contents are never expanded here.
|
|
105
|
+
- Absolute paths and paths escaping the workspace are ignored.
|
|
106
|
+
- A referenced path must still exist; unknown or out-of-workspace tokens produce no marker.
|
|
107
|
+
- Skill bodies are not injected by default (see `injectSkillBody`); the agent loads them through its skill tool.
|
|
108
|
+
- Past-chat snapshots are budgeted by the official implementation (64 KB per source, at most 3 references, self-reference refused).
|
|
109
|
+
- An `@atlas:` reference is the one marker that carries a body. That body is the provider's own answer, never a file this plugin opened: it is bounded to 16 KiB per reference and 48 KiB per step, and an over-long body is cut and marked `truncated="true"`.
|
|
110
|
+
|
|
111
|
+
## Write your own `@` source
|
|
112
|
+
|
|
113
|
+
The menu is the seam's only consumer. It rebuilds its category list from the live registry on every open, so a plugin adds a `@` category by registering one — nothing in this package enumerates providers, and no rebuild of it is needed.
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
// Host half — what a committed reference turns into. `ctx.get('atlas')` is the seam.
|
|
117
|
+
import type { AtlasProvider, AtlasSeam } from '@sidequest-007/dsh-atlas'
|
|
118
|
+
|
|
119
|
+
const provider: AtlasProvider = {
|
|
120
|
+
id: 'diag', // becomes @atlas:diag/… in the draft
|
|
121
|
+
display: 'Diagnostics',
|
|
122
|
+
scopes: ['process:lsp'], // what you reach for (required)
|
|
123
|
+
testedOn: ['0.1.5-rc.1'], // DSH builds you actually ran (required)
|
|
124
|
+
async resolve(item, context) {
|
|
125
|
+
// context.cwd is the answered session's workspace; context.sessionId names it.
|
|
126
|
+
return await diagnosticsFor(context.cwd, item.id, context.signal)
|
|
127
|
+
},
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const atlas = ctx.get('atlas') as AtlasSeam
|
|
131
|
+
atlas.register(provider) // returns a disposer
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
// Browser half — candidates for the open menu, from your own client bundle.
|
|
136
|
+
const provider: AtlasProvider = {
|
|
137
|
+
id: 'diag',
|
|
138
|
+
display: 'Diagnostics',
|
|
139
|
+
scopes: ['process:lsp'],
|
|
140
|
+
testedOn: ['0.1.5-rc.1'],
|
|
141
|
+
async list(query, context) {
|
|
142
|
+
return await askYourHost(query, context.sessionId, context.signal) // must stay cheap
|
|
143
|
+
},
|
|
144
|
+
open(item, context) {
|
|
145
|
+
// Optional: what a click on `@atlas:diag/…` in an ALREADY SENT message does.
|
|
146
|
+
// Only you know what your item means (a file path? a URL? a record id?), so
|
|
147
|
+
// the menu never interprets it — without `open`, the chip stays inert.
|
|
148
|
+
openInYourViewer(item)
|
|
149
|
+
},
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
A provider may register either half or both; `id` ties them together. What the user commits is plain text (`@atlas:diag/src/a.ts:12`), so provider content never enters the draft, and only the Host half decides what the model sees.
|
|
154
|
+
|
|
155
|
+
| Rule | Why |
|
|
156
|
+
|---|---|
|
|
157
|
+
| Registration *is* authorization | A plugin that never registers is invisible: nothing is scanned, guessed or discovered |
|
|
158
|
+
| `list` runs while the menu is open | It decides how the menu feels; cache it yourself if it is not free |
|
|
159
|
+
| `resolve` runs once, at send | It may be expensive, and it receives the session's `cwd` and an `AbortSignal` |
|
|
160
|
+
| `open` is optional and menu-half only | The click happens in the browser; without it the chip is inert, and never drawn as clickable |
|
|
161
|
+
| `scopes` and `testedOn` are required | `register()` refuses a declaration without them |
|
|
162
|
+
| `testedOn` is compared for equality | A mismatch registers as `verified: false`; an unknown running version is *never* verified |
|
|
163
|
+
| `id` is unique | A duplicate throws and names the first registrant — no silent overwrite |
|
|
164
|
+
| The seam bounds every body | 16 KiB per reference, 48 KiB per step; the overflow is marked, not passed on |
|
|
165
|
+
| An empty body injects nothing | A provider saying "nothing to add" costs no tokens |
|
|
166
|
+
|
|
167
|
+
The verdict is re-read every time the registry is read, so a provider that registers before the browser has learned the running version does not stay unverified.
|
|
168
|
+
|
|
169
|
+
### What ATLAS will not carry
|
|
170
|
+
|
|
171
|
+
A source passes all three, or the answer is no:
|
|
172
|
+
|
|
173
|
+
| Refuse | Why |
|
|
174
|
+
|---|---|
|
|
175
|
+
| **What the model can already get** | `@file` and `@session` are built in; a second route to the same data is noise |
|
|
176
|
+
| **What cannot be narrowed** | A whole logcat or a full database dump poisons the context; a reference is a selection |
|
|
177
|
+
| **What is expensive to `resolve`** | Lazy loading is the contract, not a preference |
|
|
178
|
+
|
|
179
|
+
The test: **narrowable · previewable · useful the moment it lands.**
|
|
180
|
+
|
|
181
|
+
`@git` is the worked example, and it is built the way a third party would build one. See `src/git.ts` (Host: reads the change list and produces one diff, never opening a file) and `src/client/git-provider.ts` (browser: asks the Host over `atlas/gitChanges`, filters per keystroke, caps the list).
|
|
182
|
+
|
|
183
|
+
## Model-facing tools
|
|
184
|
+
|
|
185
|
+
Beyond the markers injected at send time, the model can query these categories on demand:
|
|
186
|
+
|
|
187
|
+
| Tool | Purpose |
|
|
188
|
+
|---|---|
|
|
189
|
+
| `past_chats` | List past sessions by title or workspace — follow-up to an `@past chat` reference |
|
|
190
|
+
| `read_past_chat` | Read one referenced session's current user/assistant surface |
|
|
191
|
+
| `plugin_info` | List installed plugins and whether each is enabled, filtered by module name |
|
|
192
|
+
|
|
193
|
+
## Settings
|
|
194
|
+
|
|
195
|
+
Managed in **Settings → Workspace file mentions**:
|
|
196
|
+
|
|
197
|
+
| Setting | Default | Meaning |
|
|
198
|
+
|---|---|---|
|
|
199
|
+
| Enable @ file mentions | on | Master switch; turning it off hides the `@` menu and dock and stops injecting markers |
|
|
200
|
+
| Enable @Skill mentions | on | Show skills in the menu |
|
|
201
|
+
| Enable @past chats mentions | on | Show past sessions in the menu |
|
|
202
|
+
| Enable @plugin mentions | on | Show installed plugins in the menu |
|
|
203
|
+
| Candidate limit | 50 | Maximum candidates returned per request (1–200) |
|
|
204
|
+
| Ignore @ mentions in pasted text | on | `@tokens` pasted from other applications stay plain text |
|
|
205
|
+
| File filters | — | Global and per-workspace rules; each rule is Exact or Regex, with its own case setting |
|
|
206
|
+
|
|
207
|
+

|
|
208
|
+
|
|
209
|
+
File filters match **basenames only** (never directory paths). Workspace rules apply alongside global rules; editing rules clears the index cache, so the next `@` uses them.
|
|
210
|
+
|
|
211
|
+
## Configuration
|
|
212
|
+
|
|
213
|
+
These parameters live in the profile's `cordis.patch.yml` (usually `~/.dsh/profiles/web/cordis.patch.yml`):
|
|
214
|
+
|
|
215
|
+
```yaml
|
|
216
|
+
- id: dsh-atlas
|
|
217
|
+
config:
|
|
218
|
+
maxIndexedFiles: 2000 # workspace index entry cap
|
|
219
|
+
ignoreDirs: [] # replaces the built-in ignore list; [] indexes every directory
|
|
220
|
+
injectSkillBody: false # also inject the skill body when a skill is referenced
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Omitting `ignoreDirs` keeps the built-in list (version-control directories, IDE metadata, dependency directories, caches, and build output).
|
|
224
|
+
|
|
225
|
+
## Design constraints
|
|
226
|
+
|
|
227
|
+
- **Paths only, never content**: the Host validates and injects paths, categories, and provenance markers. It never opens a referenced file and never lists a referenced directory.
|
|
228
|
+
- **Paste protection**: external text cannot forge a reference — only `source.kind === 'user'` messages are scanned, and pasted content is marked as plain text by default.
|
|
229
|
+
- **Workspace confinement**: references stay inside the session workspace; absolute paths and `..` escapes are refused.
|
|
230
|
+
- **Model-visible means logged**: every injected marker carries its source and is persisted with the session log.
|
|
231
|
+
- **The display slot stays out of matching**: the display slot beside the `@` menu is fully decoupled from candidate computation. It never blocks or slows a search, and it reads no session data.
|
|
232
|
+
|
|
233
|
+
## Development
|
|
234
|
+
|
|
235
|
+
**Prerequisite**: the dev dependencies are `link:` entries into a DSH **source checkout**, expected at `../deepseek-harness` beside this repository. Without it `pnpm install` cannot resolve them, so a plain clone is not buildable on its own (the published packages — e.g. `@deepseek-ai/dsh-client-ui-conversation@0.1.6-alpha.1` — are on npm, but this setup is pinned to the checkout on purpose: it is how the plugin is tested against the same source the Harness is built from).
|
|
236
|
+
|
|
237
|
+
```sh
|
|
238
|
+
git clone https://github.com/jameswatt139240-crypto/dsh-ATLAS
|
|
239
|
+
git clone <dsh source checkout> ../deepseek-harness # or repoint the links in package.json
|
|
240
|
+
cd dsh-ATLAS
|
|
241
|
+
pnpm install
|
|
242
|
+
pnpm run check # typecheck + tests + ad-free build + publish-surface gate
|
|
243
|
+
pnpm run build # build only (no ad panel; lib/ is committed)
|
|
244
|
+
pnpm run build:ads # build the ad-bearing variant (not for the first release)
|
|
245
|
+
pnpm run verify:publish # publish-surface gate on its own
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
`lib/` is committed, so profile installs run without a build. See [CONTRIBUTING.md](CONTRIBUTING.md) for the ladder a change has to pass.
|
|
249
|
+
|
|
250
|
+
### Publishing
|
|
251
|
+
|
|
252
|
+
- **The default build carries no ad.** `pnpm run build` replaces the ad panel and its
|
|
253
|
+
banner images with stubs, so neither ad code nor image bytes reach `lib/client.js`
|
|
254
|
+
(~726 KB ad-free vs ~978 KB with ads). Use `pnpm run build:ads` for a later
|
|
255
|
+
ad-bearing release.
|
|
256
|
+
- **The publish-surface gate** runs automatically before `npm publish`
|
|
257
|
+
(`prepublishOnly`) and refuses: the internal plan directory (kept out of git and
|
|
258
|
+
out of the tarball), AI-assistant files
|
|
259
|
+
(`AGENTS.md`, `CLAUDE.md`, `.agents/`, `.claude/`, `.cursor/`, `skills/`),
|
|
260
|
+
TypeScript sources and tests, sourcemaps (they inline sources and local paths),
|
|
261
|
+
a bundle that still inlines an ad banner, a bundle that leaks a local machine path,
|
|
262
|
+
and any of the four places that must agree on the package name
|
|
263
|
+
(`package.json`, `dsh.plugin.json`, `cordis.patch.yml`, the client bundle id, plus
|
|
264
|
+
the invariant companion). Set `DSH_ATLAS_ALLOW_ADS=1` for a deliberate ad release.
|
|
265
|
+
- **Releasing**: the tag is the decision.
|
|
266
|
+
|
|
267
|
+
```sh
|
|
268
|
+
pnpm run check # full ladder, needs ../deepseek-harness
|
|
269
|
+
git tag -a v1.0.0 -m "dsh-ATLAS 1.0.0"
|
|
270
|
+
git push origin main --tags # the release workflow publishes
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
`.github/workflows/publish-surface.yml` runs the self-contained gate on every push:
|
|
274
|
+
it needs no registry and no DSH checkout, because it packs the committed `lib/` and
|
|
275
|
+
inspects the tarball. `.github/workflows/release.yml` publishes on a `v*` tag with
|
|
276
|
+
**trusted publishing (OIDC)** — no `NPM_TOKEN`, no OTP: GitHub mints a short-lived
|
|
277
|
+
identity (`permissions: id-token: write`) and npm verifies it against the trusted
|
|
278
|
+
publisher configured for this package. That configuration is one-time, lives at
|
|
279
|
+
`https://www.npmjs.com/package/@sidequest-007/dsh-atlas/access` → *Trusted
|
|
280
|
+
Publisher* (user `jameswatt139240-crypto`, repository `dsh-ATLAS`, workflow filename
|
|
281
|
+
`release.yml`, "publish directly" allowed), and could only be added **after** the
|
|
282
|
+
first version existed — npm attaches a trusted publisher to a package, so 1.0.0
|
|
283
|
+
itself was published interactively. The job also skips itself when the tagged
|
|
284
|
+
version is already on npm, and runs `npm publish --ignore-scripts` (the full ladder
|
|
285
|
+
needs the DSH source checkout, which CI does not have). To publish by hand instead,
|
|
286
|
+
run `npm publish --registry https://registry.npmjs.org` (a mirror such as
|
|
287
|
+
`registry.npmmirror.com` cannot accept a publish).
|
|
288
|
+
|
|
289
|
+
## Compatibility
|
|
290
|
+
|
|
291
|
+
The plugin satisfies both generations of the Harness Typert codec validation (the installed build checks `codec.schema.parse`; the source checkout checks `codec.create()`), and opens referenced paths through `ctx.remote.session.openWorkspacePath`.
|
|
292
|
+
|
|
293
|
+
## License
|
|
294
|
+
|
|
295
|
+
**MIT** (see [LICENSE](LICENSE)) — use it, modify it, ship it, sell it; keep the copyright notice with the copies you distribute. It is an OSI-approved license, so plugin markets that require one are fine with it.
|
|
296
|
+
|
|
297
|
+
This project is a derivative work of [`dsh-at-file`](https://github.com/FSMargoo/dsh-at-file) (MIT). Its original copyright notice and license text are preserved in [LICENSE-ORIGINAL](LICENSE-ORIGINAL), and [NOTICE](NOTICE) maps which files came from it.
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
# dsh-ATLAS
|
|
2
|
+
|
|
3
|
+
[English](README.md) · **简体中文**
|
|
4
|
+
|
|
5
|
+
本项目属于 **Side Quest(支线任务)** 工作室(**Side Quest Labs**)—— npm 范围 `@sidequest-007/*`,仓库 [`jameswatt139240-crypto/dsh-ATLAS`](https://github.com/jameswatt139240-crypto/dsh-ATLAS);**DSH 插件 id 沿用生态惯例的无 scope 形式 `dsh-*`**。
|
|
6
|
+
|
|
7
|
+
<img src="assets/diagrams/atlas-overview.svg" alt="dsh-ATLAS:一个 @ 触发位、五个内置类别,以及任何插件都能注册的自己的类别" width="880">
|
|
8
|
+
|
|
9
|
+
**@ Last, All Sources.** 一个 `@` 。任何插件都能注册。
|
|
10
|
+
|
|
11
|
+
DeepSeek Harness Web 界面的**统一 `@` 提及**插件:在输入框输入 `@`,即可引用工作区**文件与文件夹**、可发现的 **Skill**、**过去的聊天记录**、**已安装插件** —— 而且任何其它插件都能把自己的类别加进同一个菜单。
|
|
12
|
+
|
|
13
|
+
- **一个 `@`,所有来源**:五个内置类别 + 所有已注册的数据源,同一个列表;输入字母即跨类别一起搜。
|
|
14
|
+
- **是平台,不是取词器**:第三方插件通过 `ctx.atlas` seam 注册自己的 `@` 类别。菜单每次打开都从**实时注册表**重建,所以**本包永远不需要为某个 provider 改代码** —— 见 [自己接一个 `@` 数据源](#自己接一个--数据源)。
|
|
15
|
+
- **只给来源,不给内容**:提交的引用只注入标记(`<workspace-reference>`、`<skill-reference>`、`<atlas-reference>` …)。插件只读目录项元数据;文件内容由 agent 在需要时自己去读。
|
|
16
|
+
- **治理写在代码里**:`scopes` 与 `testedOn` 必填、`id` 重复直接抛错、版本不匹配注册为 `verified: false`、provider 正文有预算(单条 16 KiB、单步 48 KiB)。
|
|
17
|
+
- **`@git` 就是范例**:一个真实 provider,完全按第三方的方式实现,两个半边加起来约 200 行。
|
|
18
|
+
|
|
19
|
+
<p align="center"><img src="assets/diagrams/atlas-seam.svg" alt="@ 数据源 seam:浏览器半边在菜单打开时列候选,Host 半边在发送时解析一次,注册表负责门禁" width="880"></p>
|
|
20
|
+
|
|
21
|
+
它基于 [`dsh-at-file`](https://github.com/FSMargoo/dsh-at-file)(MIT)扩展而来:保留原有的工作区路径索引、文件过滤规则与粘贴保护,并新增四个类别、类别菜单与快捷键、混合结果、分组折叠,以及三个模型侧查询工具。
|
|
22
|
+
|
|
23
|
+
> `dsh-atlas` 与 `dsh-at-file` 都占用输入框的 `@` 触发位。请**二选一**,不要同时安装。
|
|
24
|
+
|
|
25
|
+
## 安装
|
|
26
|
+
|
|
27
|
+
前置:装好 DSH(`dsh` 在 `PATH` 上)并有一个 web profile。
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
# 从 GitHub 装(本仓库;lib/ 已随仓库提交,安装时不会触发构建)
|
|
31
|
+
dsh plugin --profile web add github:jameswatt139240-crypto/dsh-ATLAS
|
|
32
|
+
|
|
33
|
+
# 本地克隆(开发)
|
|
34
|
+
git clone https://github.com/jameswatt139240-crypto/dsh-ATLAS
|
|
35
|
+
dsh plugin --profile web add link:/path/to/dsh-ATLAS
|
|
36
|
+
|
|
37
|
+
# npm 包(发布后;scope 是工作室的,插件 id 仍是 `dsh-atlas`)
|
|
38
|
+
dsh plugin --profile web add @sidequest-007/dsh-atlas
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
安装或更新后**重启 `dsh web`**,确保 Host 与浏览器客户端都加载同一版本。然后在输入框打 `@`。
|
|
42
|
+
|
|
43
|
+
## 五个类别
|
|
44
|
+
|
|
45
|
+
输入 `@` 后,菜单顶部是五个常驻类别行;也可以直接输入关键字跨类别混合搜索。
|
|
46
|
+
|
|
47
|
+
| 类别 | 菜单前缀 | 快捷键 | 候选来源 | 选中后写入草稿 |
|
|
48
|
+
|---|---|---|---|---|
|
|
49
|
+
| 文件 | `file:` | `F` | 当前会话工作区索引 | `@src/index.ts` |
|
|
50
|
+
| 文件夹 | `folder:` | `D` | 同上 | `@src/client/` |
|
|
51
|
+
| Skill | `skill:` | `S` | `ctx.skills` 技能注册表 | `@skill:blender-modeling` |
|
|
52
|
+
| 过去的聊天 | `chat:` | `C` | 官方 session-reference 解析器 | `@[标题](dsh-session:<id>)` |
|
|
53
|
+
| 插件 | `plugin:` | `P` | 官方 plugin inventory | `@plugin:dsh-atlas` |
|
|
54
|
+
|
|
55
|
+
**单个快捷键字母是"意图"而不是"查询"**:输入它只显示五个类别 + 一行「Tab 补全 → `类别:`」提示,且这行提示在**最下面**(高亮也已经落在它上面),按 <kbd>Tab</kbd> 或 <kbd>Enter</kbd> 即进入对应的 `类别:` 前缀。**注册的数据源也能有快捷键字母**:取 provider id 的首字母(`@git` → <kbd>G</kbd>),只有在该字母没被内置类别或更早注册的 provider 占用时才生效(`@<id>:` 前缀永远可用)。**过滤从更具体的东西开始**:不是快捷键的首字母,或第二个字符(`fi`/`fo`/`sk`/`ch`/`pl` 这类前缀本来就是两个字符)。点击类别行或「返回类别」行同样进入/返回,且**菜单保持打开**(这是本插件的两处覆写:正常 pick 路径会关闭菜单)。
|
|
56
|
+
|
|
57
|
+
**第六行起是"已注册数据源"。** `@git` 已内置(工作区的变更文件,引用内容就是该文件的 diff),任何插件都能用同样的方式加自己的类别——见[自己接一个 `@` 数据源](#自己接一个--数据源)。它们排在五个类别之后、按注册顺序出现,注册它们的插件被销毁时一起消失。插件自身的分组以**正的 `order`** 注册,因此它是菜单里的**最后一个分组**:底部那条工作带归本插件,框架自带 `dsh-client-ui-reference` 数据源的文件列表留在它上面。
|
|
58
|
+
|
|
59
|
+
## 交互
|
|
60
|
+
|
|
61
|
+
- **直接输入字母**:跨类别混合搜索,结果按「最近引用」与「全部匹配」两段展示,每行带类别标签;五个类别行始终在**最下面的工作带**里可点选。
|
|
62
|
+
- **文件夹**:`@folder:` 类别列出目录,选中后写入 `@路径/`(末尾的 `/` 才是「目录」的标记,插件依然只读目录项元数据);在斜杠后继续输入即可在该目录内收窄搜索。过滤时结果分**两档**排序:先按**名称**命中的文件夹(工作区内的在前,工作区外的在后),再是只有**路径**命中的那些,收进它们自己的 `路径匹配` 组。那一组默认展开、装得下就显示,折叠与否是你的手势。
|
|
63
|
+
- **先写的文件夹会成为后面文件列表的作用域**:`@e:/work/docs/ @file:` 会先列出**该文件夹自己的文件**(一层,走的是文件夹 tab 同一套目录列表),组标题写明它来自哪个文件夹、并标明在工作区之外,然后才是工作区里的命中项。作用域是**位置**决定的——当前 token 之前最近的那个文件夹引用——所以**不需要连接词**;草稿里没有文件夹引用时行为与以前完全一致。
|
|
64
|
+
- **分组折叠**:Skill 按管理层级(系统 / 用户 / 项目 / 自定义 / 插件)再按领域分组,插件按 npm scope(`@deepseek-ai/…` 或「其他」)分组,过去的聊天按工作区分组、子会话缩进在父会话下;组标题**点击即可折叠/展开;当高亮行正是该组标题时按 <kbd>Enter</kbd> 同样折叠/展开**。<kbd>Tab</kbd> 补全、<kbd>↑</kbd><kbd>↓</kbd>、<kbd>Esc</kbd> 全部由框架自己的 keymap 提供。除此之外插件只加三个键:高亮组标题上的 <kbd>Enter</kbd> 折叠、类别行/返回行选中后保持菜单打开、以及 <kbd>PageUp</kbd>/<kbd>PageDown</kbd> 按**实测一屏**翻页——翻页移动的是高亮(高亮属于别的数据源时就只滚列表),所以"看得见的那一行"永远是你正在操作的那一行。
|
|
65
|
+
- **列表长了就滚动,不再靠隐藏行数**:单个类别最多 `MAX_CANDIDATES`(20)行,装不下就由菜单滚动——<kbd>PageUp</kbd>/<kbd>PageDown</kbd> 一屏一屏走,且内容变化时(作用域目录列表后到、某组被折叠)插件会把焦点行重新拉回可视区。也就是说**不会为了菜单短一点而丢行**;谁排在前面由上面的分档决定。
|
|
66
|
+
- **相对时间**:历史会话与工作区行显示「刚刚 / 5分钟 / 3小时 / 2天」等相对时间。
|
|
67
|
+
- **初始视图**:`@file:` 先列出最近引用过的文件(最近的在前,按工作区持久化),其后按字母序。
|
|
68
|
+
- **引用成本与失效提示**:引用栏里的每个文件引用会显示约 token 成本(如 `≈1.2k tokens`;超过 8000 token 时高亮);若引用的路径在工作区内已不存在,会标出「已失效」。成本取自文件大小(约 4 字节 / token),插件只读目录项元数据,**不读取文件内容**。
|
|
69
|
+
- **错字容错**:文件名的轻微拼写错误(如 `veiw` → `view.ts`、`clinet` → `client/…`)仍能命中。**精确匹配优先**——只要精确排序有结果,就不会混入模糊结果;长度小于 3 或含 `/` 的查询保持精确。
|
|
70
|
+
- **行范围引用**:直接手打 `@src/a.ts:12-40` 即可引用该文件的第 12–40 行(单行写 `@src/a.ts:7`;倒序 `40-12` 会自动归一为 `12-40`)。目录不接受行范围。Host 只校验语法与路径类型,**不会打开文件**,因此行号是否越界由 agent 读取时判断。
|
|
71
|
+
- **引用栏**:输入框上方按顺序显示当前草稿里的每个引用;文件、文件夹与 provider 行可点击打开,每行的 <kbd>×</kbd> 移除对应 token。
|
|
72
|
+
- **草稿里任何能打开的 `@路径` 都是链接**:判断依据是**插件自己**——token 能解码成引用、并且宿主确认该目标存在——而不是框架有没有把它装饰成引用。所以手打的 `@AGENTS.md` 和框架认出来的 token 一样:**整条**变蓝、点任意位置都打开、鼠标变手型。上色用 CSS Custom Highlight API 逐段绘制(Lexical 的文本 span 只允许一个文本子节点,包裹尾巴会破坏输入),颜色取框架给引用用的同一个变量。**目标已不存在**的 token 也不会被当成普通文本,而是按引用栏那套「已失效」语言画成**暗色 + 删除线**——它是引用,只是打不开。**判定时机**:只有**写完的 token** 才会被判失效(后面跟着空白,或后面还有下一行=按过回车);正在输入的 token 不下结论——`@N` 这种中间态必然"不存在",边打边画删除线等于替用户还没写完的词下判决。**能打开**的 token 则在名字写成的那一刻就变蓝(与框架自己的引用装饰一样即时)。引用栏还没回答、provider 没声明 `open`、或名字放不下的 token 什么都不说、保持普通文本。浏览器不支持 highlight API 时什么都不绘制,此时**只有框架自己上过色的那部分**可点(没画成链接就不表现得像链接)。
|
|
73
|
+
- **草稿里由其它数据源插入的 chip 也能点**:右侧栏的文件树就是往草稿里插一条原子 chip,这个客户端把它画成框架自己的 chip 蓝、并且不接任何点击。点它即打开它命名的文件,随后 chip 会带上插件的链接语言。那个数据源给 chip 的标签是**文件名**(完整相对路径只存在于草稿文本里),所以裸文件名按两步还原:①在插件索引里找同名 basename,唯一命中即用;②索引里就有歧义(例如 `index.ts` 在工作区里有两处)时回退到**草稿**——草稿里只有一个同名 token 就用它,仍有两个则**保持不可点**。索引完全不认识的名字才原样交给点击时的存在性检查(结果是标「已失效」,而不是打开一个错的目标)。
|
|
74
|
+
- **已发送消息里的引用可点**:消息气泡里的引用 chip 点击即打开(文件进右侧栏,无右栏时交宿主打开器;Skill 交给 Skill 源;provider item 交给它自己声明的 `open`)。只有真的能打开的 chip 会被画成链接(用框架自己的 `--dsw-alias-link` 蓝 + 悬停下划线),框架自己接好点击的版本会渲染 `<button>`,本插件自动让位。插件同时实现了框架的 `openReference` 钩子:客户端在草稿里激活一个引用 token 时,由拥有它的 source 打开。安装版客户端还没有接上这一步,因此这个点击同样由插件补上,走的是 chip 用的那同一个动作。
|
|
75
|
+
- **图标**:本插件发出的每一行都带**自己的**图标——与引用栏同一套现代线稿,含按文件类型与语言的标记(TypeScript、Rust、PDF、图片、压缩包…),`@git` 用分支图标。框架的菜单行只会画它自己的三种字形,所以插件**预定那个图标槽**(这决定了所有名字缩进一致),再由自己在列表上方的一层固定层里、用单色线条画出图标:不往框架拥有的行里插任何东西,框架自己的字形也只在被这一层真正覆盖的槽里隐藏。
|
|
76
|
+
|
|
77
|
+

|
|
78
|
+
|
|
79
|
+
*菜单(滚到最底部的工作带):上面是带类别标签的混合结果列表——文件行带所在目录、还有折叠的插件分组——最下面是五个类别加 `@git`,每行都是插件自己画的图标。工作带是最后一组、离输入框最近;本插件的分组用正序注册,所以框架自带的数据源仍在它上方。*
|
|
80
|
+
|
|
81
|
+

|
|
82
|
+
|
|
83
|
+
*输入框上方的引用栏:草稿里每个引用一行,各带自己的图标、≈token 成本(红色的 `≈26k tokens` 是超过 8 000 的高亮)与移除按钮——草稿里的每个 token 同时都被画成链接。*
|
|
84
|
+
|
|
85
|
+
## 注入语义
|
|
86
|
+
|
|
87
|
+
每次 agent 开始处理前,插件会校验草稿中的每个引用,并追加一条**只含引用信息、不含文件内容**的消息。文件内容始终由 agent 使用当前会话的工具按需读取。
|
|
88
|
+
|
|
89
|
+
```text
|
|
90
|
+
看下 @docs/spec.pdf
|
|
91
|
+
用 @skill:blender-modeling
|
|
92
|
+
回忆 @[付款费率](dsh-session:xxx)
|
|
93
|
+
配合 @plugin:dsh-atlas
|
|
94
|
+
取出 @atlas:git/src/extract.ts 的 diff
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
| 类别 | 注入的标记 | 消息来源 |
|
|
98
|
+
|---|---|---|
|
|
99
|
+
| 文件 / 文件夹 | `<workspace-reference path="docs/spec.pdf" kind="file" />`(带行范围时追加 `lines="12-40"`) | `at-file-mention` |
|
|
100
|
+
| Skill | `<skill-reference name="blender-modeling" />` | `atlas-skill` |
|
|
101
|
+
| 插件 | `<plugin-reference name="dsh-atlas" />` | `atlas-plugin` |
|
|
102
|
+
| 过去的聊天 | 官方 `session-reference` 只读快照(可回放、带预算) | `session-reference` |
|
|
103
|
+
| 已注册的 `@` 数据源 | `<atlas-reference provider="git" item="src/a.ts" scopes="process:git" verified="true">…</atlas-reference>` | `atlas-provider` |
|
|
104
|
+
|
|
105
|
+
- 引用目录时**只注入该目录**,不展开其中的内容;需要时由 agent 自行列出。
|
|
106
|
+
- 绝对路径与越出工作区的路径会被忽略。
|
|
107
|
+
- 引用的文件必须仍然存在;不存在或越界的 token 不会产生标记。
|
|
108
|
+
- Skill 正文默认不注入(见 `injectSkillBody`),由 agent 的技能工具按需加载。
|
|
109
|
+
- 历史会话快照由官方实现负责预算与上限(单源 64 KB、最多 3 个、拒绝自引用)。
|
|
110
|
+
- `@atlas:` 是唯一带正文的标记。正文完全来自 provider 自己的回答,不是本插件打开的任何文件:单条上限 16 KiB、单步合计 48 KiB,超长部分被截断并标记 `truncated="true"`。
|
|
111
|
+
|
|
112
|
+
## 自己接一个 `@` 数据源
|
|
113
|
+
|
|
114
|
+
菜单是这个 seam 唯一的消费方。它每次打开都从实时注册表重建类别行,所以插件只要注册一条声明就多出一个 `@` 类别——本包里没有任何 provider 清单,也不需要重新构建它。
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
// Host 半边:已提交的引用最终变成什么。`ctx.get('atlas')` 就是 seam。
|
|
118
|
+
import type { AtlasProvider, AtlasSeam } from '@sidequest-007/dsh-atlas'
|
|
119
|
+
|
|
120
|
+
const provider: AtlasProvider = {
|
|
121
|
+
id: 'diag', // 草稿里就是 @atlas:diag/…
|
|
122
|
+
display: 'Diagnostics',
|
|
123
|
+
scopes: ['process:lsp'], // 你会碰什么(必填)
|
|
124
|
+
testedOn: ['0.1.5-rc.1'], // 你真正跑过的 DSH 版本(必填)
|
|
125
|
+
async resolve(item, context) {
|
|
126
|
+
// context.cwd 是被回答会话的工作区;context.sessionId 是它的身份。
|
|
127
|
+
return await diagnosticsFor(context.cwd, item.id, context.signal)
|
|
128
|
+
},
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const atlas = ctx.get('atlas') as AtlasSeam
|
|
132
|
+
atlas.register(provider) // 返回一个 disposer
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
// 浏览器半边:菜单打开时的候选,来自你自己的客户端 bundle。
|
|
137
|
+
const provider: AtlasProvider = {
|
|
138
|
+
id: 'diag',
|
|
139
|
+
display: 'Diagnostics',
|
|
140
|
+
scopes: ['process:lsp'],
|
|
141
|
+
testedOn: ['0.1.5-rc.1'],
|
|
142
|
+
async list(query, context) {
|
|
143
|
+
return await askYourHost(query, context.sessionId, context.signal) // 必须便宜
|
|
144
|
+
},
|
|
145
|
+
open(item, context) {
|
|
146
|
+
// 可选:用户在**已发送**的消息里点 `@atlas:diag/…` 时做什么。
|
|
147
|
+
// 你的 item 是什么意思只有你知道(文件路径?URL?记录 id?),
|
|
148
|
+
// 所以菜单永远不会替你解释它——没写 open,那颗 chip 保持不可点。
|
|
149
|
+
openInYourViewer(item)
|
|
150
|
+
},
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
一个 provider 可以只注册其中半边,也可以两半都注册,`id` 把两半绑在一起。用户提交进草稿的是纯文本(`@atlas:diag/src/a.ts:12`),所以 provider 的内容永远不会进入草稿;模型看到什么,只由 Host 半边的 `resolve` 决定。
|
|
155
|
+
|
|
156
|
+
| 规则 | 原因 |
|
|
157
|
+
|---|---|
|
|
158
|
+
| 注册**就是**授权 | 没注册的插件在这里不可见:不扫描、不猜测、不发现 |
|
|
159
|
+
| `list` 在菜单打开期间跑 | 它决定手感;不便宜就自己缓存 |
|
|
160
|
+
| `resolve` 每次发送只跑一次 | 可以贵,并且会拿到会话的 `cwd` 与 `AbortSignal` |
|
|
161
|
+
| `open` 可选,且只属于菜单半边 | 点的动作发生在浏览器里;没声明 `open` 的 provider,chip 不可点(也不会画成可点) |
|
|
162
|
+
| `scopes` 与 `testedOn` 必填 | 缺少任一项,`register()` 直接拒绝 |
|
|
163
|
+
| `testedOn` 按相等比较 | 不匹配则以 `verified: false` 注册;运行版本未知时**永远**不算已验证 |
|
|
164
|
+
| `id` 唯一 | 重复会抛错并指名先注册者,不静默覆盖 |
|
|
165
|
+
| 正文由 seam 统一限量 | 单条 16 KiB、单步 48 KiB;超出部分被标记,不会被放行 |
|
|
166
|
+
| 空正文不注入任何东西 | provider 说"没什么可加"就不该花 token |
|
|
167
|
+
|
|
168
|
+
版本裁决在每次读注册表时重新判定,所以"比浏览器拿到运行版本更早注册"的 provider 不会永久停留在未验证。
|
|
169
|
+
|
|
170
|
+
### 三类不接
|
|
171
|
+
|
|
172
|
+
三条全中才接,否则不接:
|
|
173
|
+
|
|
174
|
+
| 不接 | 理由 |
|
|
175
|
+
|---|---|
|
|
176
|
+
| **模型自己能拿到的** | `@file` / `@session` 官方已内置;同一份数据的第二条路只是噪音 |
|
|
177
|
+
| **不可窄选的** | 全量 logcat 或整个数据库 dump 会污染上下文;引用必须是一次选择 |
|
|
178
|
+
| **`resolve` 贵的** | 懒加载是契约,不是偏好 |
|
|
179
|
+
|
|
180
|
+
判定标准:**可窄选 · 可预览 · 注入即有用。**
|
|
181
|
+
|
|
182
|
+
`@git` 就是照这个标准写出来的范例,且完全按第三方的方式实现:`src/git.ts`(Host:读变更列表、产出单个文件的 diff,全程不打开任何文件)与 `src/client/git-provider.ts`(浏览器:通过 `atlas/gitChanges` 向 Host 要数据,逐键筛选、限量)。
|
|
183
|
+
|
|
184
|
+
## 模型侧工具
|
|
185
|
+
|
|
186
|
+
除了发送时注入的标记,模型还可以在需要时主动查询这些类别:
|
|
187
|
+
|
|
188
|
+
| 工具 | 用途 |
|
|
189
|
+
|---|---|
|
|
190
|
+
| `past_chats` | 按标题或工作区列出历史会话,用于 `@过去的聊天` 之后的追问 |
|
|
191
|
+
| `read_past_chat` | 读取某个已引用会话的当前用户/助手消息面 |
|
|
192
|
+
| `plugin_info` | 列出已安装插件及其启用状态,可按模块名过滤 |
|
|
193
|
+
|
|
194
|
+
## 设置
|
|
195
|
+
|
|
196
|
+
在 **设置 → 工作区文件提及** 中管理:
|
|
197
|
+
|
|
198
|
+
| 设置项 | 默认 | 说明 |
|
|
199
|
+
|---|---|---|
|
|
200
|
+
| 启用 @ 文件提及 | 开 | 总开关;关闭后隐藏 `@` 菜单与引用栏,并停止注入标记 |
|
|
201
|
+
| 启用 @Skill 提及 | 开 | 是否在菜单中显示 Skill |
|
|
202
|
+
| 启用 @过去的聊天 提及 | 开 | 是否在菜单中显示历史会话 |
|
|
203
|
+
| 启用 @插件 提及 | 开 | 是否在菜单中显示已安装插件 |
|
|
204
|
+
| 候选数量上限 | 50 | 每次请求返回的候选条目上限(1–200) |
|
|
205
|
+
| 忽略粘贴文本中的 @ | 开 | 从其他应用粘贴的 `@内容` 保持普通文本,不触发引用 |
|
|
206
|
+
| 文件过滤 | — | 全局与工作区两套规则;每条可选 Exact / Regex 与是否区分大小写 |
|
|
207
|
+
|
|
208
|
+

|
|
209
|
+
|
|
210
|
+
文件过滤只匹配**文件名**(不含目录路径)。工作区规则与全局规则同时生效;修改规则会清除索引缓存,下一次输入 `@` 即生效。
|
|
211
|
+
|
|
212
|
+
## 配置
|
|
213
|
+
|
|
214
|
+
以下参数在所选 profile 的 `cordis.patch.yml` 中配置(常用路径 `~/.dsh/profiles/web/cordis.patch.yml`):
|
|
215
|
+
|
|
216
|
+
```yaml
|
|
217
|
+
- id: dsh-atlas
|
|
218
|
+
config:
|
|
219
|
+
maxIndexedFiles: 2000 # 工作区索引条目上限
|
|
220
|
+
ignoreDirs: [] # 替换内置忽略目录列表;留空数组表示索引所有目录
|
|
221
|
+
injectSkillBody: false # 引用 Skill 时是否连同正文一起注入
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
`ignoreDirs` 省略时使用内置列表(版本控制目录、IDE 元数据、依赖目录、缓存与构建产物等)。
|
|
225
|
+
|
|
226
|
+
## 设计约束
|
|
227
|
+
|
|
228
|
+
- **只传路径,不读内容**:Host 端只校验并注入路径、类别与来源标记,从不打开引用的文件,也从不列出目录内容。
|
|
229
|
+
- **粘贴保护**:外部文本无法伪造引用——只有 `source.kind === 'user'` 的消息才会被扫描,且粘贴内容默认被标记为普通文本。
|
|
230
|
+
- **工作区边界**:引用被限制在当前会话的工作区内;绝对路径与 `..` 逃逸会被拒绝。
|
|
231
|
+
- **模型可见即可回放**:注入的标记都带来源,随会话日志持久化。
|
|
232
|
+
- **展示位不参与匹配**:`@` 菜单旁的展示位与候选计算完全解耦,不会阻塞或影响搜索;它不读取任何会话数据。
|
|
233
|
+
|
|
234
|
+
## 开发
|
|
235
|
+
|
|
236
|
+
**前置条件**:devDependencies 是指向 DSH **源码检出**的 `link:` 条目,默认期望在本仓库同级目录 `../deepseek-harness`。没有它 `pnpm install` 无法解析依赖,因此**单纯 clone 本仓库不能直接构建**(这些包在 npm 上也有,例如 `@deepseek-ai/dsh-client-ui-conversation@0.1.6-alpha.1`;这里刻意指向检出,是为了让插件始终对着与 Harness 构建同源的代码做测试)。
|
|
237
|
+
|
|
238
|
+
```sh
|
|
239
|
+
git clone https://github.com/jameswatt139240-crypto/dsh-ATLAS
|
|
240
|
+
git clone <DSH 源码检出> ../deepseek-harness # 或者改 package.json 里的 link 指向你的路径
|
|
241
|
+
cd dsh-ATLAS
|
|
242
|
+
pnpm install
|
|
243
|
+
pnpm run check # typecheck + 测试 + 无广告构建 + 发布面门禁
|
|
244
|
+
pnpm run build # 仅构建(默认不含广告面板)
|
|
245
|
+
pnpm run build:ads # 构建含广告面板的版本(不用于首次发布)
|
|
246
|
+
pnpm run verify:publish # 单独运行发布面门禁
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
`lib/` 中的构建产物会提交到仓库,因此从 profile 安装时不需要执行构建脚本。改动需要过的检查见 [CONTRIBUTING.md](CONTRIBUTING.md)。
|
|
250
|
+
|
|
251
|
+
### 发布
|
|
252
|
+
|
|
253
|
+
- **默认构建不含广告**:`pnpm run build` 会把广告面板与横幅图片一并替换为桩,因此
|
|
254
|
+
`lib/client.js` 里既没有广告代码也没有图片字节(无广告约 726 KB,含广告约 978 KB)。
|
|
255
|
+
后续要出带广告的版本,使用 `pnpm run build:ads`。
|
|
256
|
+
- **发布面门禁**在 `npm publish` 前自动运行(`prepublishOnly`),拒绝以下内容进入 npm 包:
|
|
257
|
+
内部计划目录(既不进 git 也不进 tarball)、AI 助手说明文件(`AGENTS.md`、`CLAUDE.md`、`.agents/`、`.claude/`、
|
|
258
|
+
`.cursor/`、`skills/`)、TypeScript 源码与测试、sourcemap(内联源码与本机路径)、
|
|
259
|
+
仍然内联广告图的构建产物、泄漏本机路径的产物,以及**四处包名不一致**
|
|
260
|
+
(`package.json`、`dsh.plugin.json`、`cordis.patch.yml`、客户端 bundle id,外加 invariant 伴生包)。
|
|
261
|
+
确需发布广告版本时设置 `DSH_ATLAS_ALLOW_ADS=1`。
|
|
262
|
+
- **发版**:tag 就是发布决定。
|
|
263
|
+
|
|
264
|
+
```sh
|
|
265
|
+
pnpm run check # 完整梯子,需要 ../deepseek-harness
|
|
266
|
+
git tag -a v1.0.0 -m "dsh-ATLAS 1.0.0"
|
|
267
|
+
git push origin main --tags # release workflow 自动发布
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
`.github/workflows/publish-surface.yml` 在每次 push 上跑**自足**门禁(不需要 registry、也不需要 DSH 检出,
|
|
271
|
+
它只是把已提交的 `lib/` 打成 tarball 再检查);`.github/workflows/release.yml` 在 `v*` tag 上用
|
|
272
|
+
**可信发布(OIDC / Trusted Publishing)**:**既不要 `NPM_TOKEN` 也不要 OTP** —— GitHub 用
|
|
273
|
+
`permissions: id-token: write` 签发一次性身份,npm 按该包配置的 trusted publisher 校验后授权发布。
|
|
274
|
+
这项配置是一次性的,位置在 `https://www.npmjs.com/package/@sidequest-007/dsh-atlas/access` →
|
|
275
|
+
*Trusted Publisher*(user `jameswatt139240-crypto`、repository `dsh-ATLAS`、workflow 文件名 `release.yml`、
|
|
276
|
+
勾选"允许直接 publish"),而且**只能在包已存在之后添加** —— trusted publisher 是挂在包上的,所以 1.0.0 本身
|
|
277
|
+
是交互式发布的。该 job 还会在"该版本已在 npm 上"时自动跳过,并以 `--ignore-scripts` 发布(完整梯子依赖
|
|
278
|
+
DSH 源码检出,CI 里没有)。若要手动发布:`npm publish --registry https://registry.npmjs.org`
|
|
279
|
+
(镜像如 `registry.npmmirror.com` 不能接收发布)。
|
|
280
|
+
|
|
281
|
+
## 兼容性
|
|
282
|
+
|
|
283
|
+
插件同时兼容两代 Harness 的 Typert codec 校验(安装版校验 `codec.schema.parse`,源码检出校验 `codec.create()`),并在运行时通过 `ctx.remote.session.openWorkspacePath` 打开引用路径。
|
|
284
|
+
|
|
285
|
+
## License
|
|
286
|
+
|
|
287
|
+
**MIT**(见 [LICENSE](LICENSE))——随便用、改、发、卖,只要在你分发出去的副本里保留版权声明。它也是 OSI 认可的开源许可,要求开源许可的插件市场不会卡它。
|
|
288
|
+
|
|
289
|
+
本项目是 [`dsh-at-file`](https://github.com/FSMargoo/dsh-at-file)(MIT)的衍生作品:上游的版权声明与许可原文保留在 [LICENSE-ORIGINAL](LICENSE-ORIGINAL),哪些文件来自上游见 [NOTICE](NOTICE)。
|