@chestnut23/dsh-conversation-outline 0.1.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/LICENSE +21 -0
- package/README.md +113 -0
- package/README.zh.md +105 -0
- package/assets/banner.png +0 -0
- package/assets/banner.svg +106 -0
- package/cordis.patch.yml +12 -0
- package/docs/implementation-spec.md +455 -0
- package/docs/publishing-guide.md +384 -0
- package/docs/security.md +47 -0
- package/docs/troubleshooting.md +65 -0
- package/docs/usage.md +62 -0
- package/lib/client/OutlinePanel.js +246 -0
- package/lib/client/index.js +27 -0
- package/lib/client/locales.js +32 -0
- package/lib/client/outline.js +83 -0
- package/lib/client/styles.js +304 -0
- package/lib/client.js +750 -0
- package/lib/client.js.map +1 -0
- package/lib/index.js +15 -0
- package/lib/types/client/OutlinePanel.d.ts +23 -0
- package/lib/types/client/index.d.ts +7 -0
- package/lib/types/client/locales.d.ts +36 -0
- package/lib/types/client/outline.d.ts +82 -0
- package/lib/types/client/styles.d.ts +14 -0
- package/lib/types/index.d.ts +14 -0
- package/package.json +82 -0
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
# dsh-conversation-outline — Implementation Spec
|
|
2
|
+
|
|
3
|
+
A DeepSeek Harness (DSH) client plugin that adds a **Codex-style conversation outline**:
|
|
4
|
+
a floating panel listing every user question in the current conversation, with
|
|
5
|
+
search and **click-to-jump** (switches to the Chat view, scrolls to the message,
|
|
6
|
+
flashes a highlight). Bilingual zh-CN / en.
|
|
7
|
+
|
|
8
|
+
This spec is the single source of truth for the team. All paths are relative to
|
|
9
|
+
the repo root `/Users/liziqing/Programs/agents/dsh-conversation-outline`.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 1. Facts about DSH (verified against installed rc.6 packages)
|
|
14
|
+
|
|
15
|
+
Reference material on disk (READ ONLY):
|
|
16
|
+
- DSH packages (runtime + types): `/Users/liziqing/.npm/_npx/1e7f6d9597241db0/node_modules/@deepseek-ai/`
|
|
17
|
+
- Web profile flat node_modules (symlinks to the above, plus react):
|
|
18
|
+
`/Users/liziqing/.dsh/profiles/node_modules/`
|
|
19
|
+
- Working third-party plugin example (published, MIT): `@nanmicoder/dsh-agent-teams`
|
|
20
|
+
installed at `/Users/liziqing/.dsh/profiles/web/node_modules/@nanmicoder/dsh-agent-teams/`
|
|
21
|
+
— its `lib/client.js` shows the exact loader-wrapper shape, panel CSS patterns,
|
|
22
|
+
and `apply(ctx)` portal pattern.
|
|
23
|
+
- Authoritative dev skill: `/tmp/dsh-plugin-skill.md` (fetched from
|
|
24
|
+
NanmiCoder/dsh-agent-teams `skills/dsh-plugin-development/SKILL.md`) and
|
|
25
|
+
`/tmp/developing-dsh-plugins.md` (the long-form guide). Read both.
|
|
26
|
+
|
|
27
|
+
### 1.1 Client plugin contract
|
|
28
|
+
|
|
29
|
+
- A bundle = npm package whose `package.json` has:
|
|
30
|
+
- `dsh.bundle.patch` → `./cordis.patch.yml` (bundle patch layer)
|
|
31
|
+
- `dsh.client` = `{ platform: "web", inject: ["@deepseek-ai/dsh-client-runtime", ...] }`
|
|
32
|
+
(`inject` is informational metadata; the REAL activation deps come from the
|
|
33
|
+
client bundle's exported `inject` array)
|
|
34
|
+
- `exports["./client"]` → the built browser bundle
|
|
35
|
+
- `cordis.patch.yml` is a top-level YAML array: `- insert: [{id, name, config}]`.
|
|
36
|
+
`name` MUST equal the package name (roster resolves `require.resolve('<name>/package.json')`).
|
|
37
|
+
- The browser bundle is a CJS closure factory:
|
|
38
|
+
```js
|
|
39
|
+
window.__ModuleLoader__.load({ id: "<package-name>", factory: (require) => {
|
|
40
|
+
var module = { exports: {} }; var exports = module.exports;
|
|
41
|
+
/* ...bundled code... */
|
|
42
|
+
return module.exports;
|
|
43
|
+
} });
|
|
44
|
+
```
|
|
45
|
+
Built with tsdown@0.22 (cjs, platform browser), `external` = the platform
|
|
46
|
+
module table (see 1.4), everything else inlined, sourcemap on, `clean: false`
|
|
47
|
+
(must not wipe the tsc host output).
|
|
48
|
+
- Client plugin entry (`src/client/index.tsx`, MUST be `.tsx` to use JSX) exports:
|
|
49
|
+
```ts
|
|
50
|
+
export const inject = ['slots', 'sessions', 'locale'] // cordis services to wait for
|
|
51
|
+
export function apply(ctx: ClientContext): void { ... }
|
|
52
|
+
```
|
|
53
|
+
- HMR/dispose: every DOM node, React root, style tag, listener must be owned by
|
|
54
|
+
`ctx.effect(() => disposer, 'label')`.
|
|
55
|
+
|
|
56
|
+
### 1.2 The host half
|
|
57
|
+
|
|
58
|
+
Even a client-only plugin needs a host row in the composition (the roster scans
|
|
59
|
+
Loader entries for `dsh.client` packages). The host module (`src/index.ts`, tsc
|
|
60
|
+
→ `lib/index.js`) must export a plugin body — a function or `{apply}` object
|
|
61
|
+
(cordis throws otherwise). Use a minimal:
|
|
62
|
+
```ts
|
|
63
|
+
import type { Context } from '@deepseek-ai/cordis'
|
|
64
|
+
export const name = 'dsh-conversation-outline'
|
|
65
|
+
export const inject: string[] = []
|
|
66
|
+
export function apply(ctx: Context): void {
|
|
67
|
+
// client-only plugin: nothing to do on the host
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
(Keep a `Config` schema optional with a `z.object({})` default so the row config
|
|
71
|
+
is valid; follow the agent-teams shape.)
|
|
72
|
+
|
|
73
|
+
### 1.3 UI seams (verified in rc.6)
|
|
74
|
+
|
|
75
|
+
- `shell.overlay` — **the** seat for frame-wide floating surfaces:
|
|
76
|
+
`kind: 'list'`, `scope: 'root'`, declared by `@deepseek-ai/dsh-client-ui-layout`.
|
|
77
|
+
The layer is click-through by default; entries opt back into pointer events.
|
|
78
|
+
Register with a fresh `id` (e.g. `dsh-conversation-outline.badge`), no `key`.
|
|
79
|
+
Types: `@deepseek-ai/dsh-client-ui-layout/client` (module augmentation only).
|
|
80
|
+
Render site verified in layout `lib/client.js` line ~236.
|
|
81
|
+
- Conversation scrollport: `[data-conversation-scroll]` (ui-conversation).
|
|
82
|
+
- Chat flow rows: each row div carries `data-chat-anchor-key=<node.key>` and
|
|
83
|
+
`data-chat-flow-kind=<kind>` (ui-conversation `ChatNodeSeat`).
|
|
84
|
+
- Chat view tab id is `"chat"`, registered at `order: 0` (the FIRST tab in
|
|
85
|
+
`[role="tablist"]`); trajectory is `order: 10`. Tab buttons are
|
|
86
|
+
`button[role="tab"]` inside `[role="tablist"]`; clicking one calls
|
|
87
|
+
`setView(id)`.
|
|
88
|
+
- Layout phases: the active app column has `[data-phase="active"]`; the CSS var
|
|
89
|
+
`--dsh-sidebar-width` exists. Panel CSS may rely on these (see agent-teams
|
|
90
|
+
panel CSS for the pattern), but do NOT depend on hashed class names of other
|
|
91
|
+
packages.
|
|
92
|
+
|
|
93
|
+
### 1.4 Platform module table (externals for tsdown)
|
|
94
|
+
|
|
95
|
+
Verified `require(...)` calls inside official client bundles (rc.6):
|
|
96
|
+
`@deepseek-ai/cordis`, `@deepseek-ai/dsh-client-runtime/client`,
|
|
97
|
+
`@deepseek-ai/dsh-client-ui-slots`, `@deepseek-ai/dsh-client-ui-primitives`,
|
|
98
|
+
`@deepseek-ai/dsh-client-ui-attachment`, `react`, `react/jsx-runtime`,
|
|
99
|
+
`react-dom`, `react-dom/client`.
|
|
100
|
+
|
|
101
|
+
Use these as `external` in tsdown. Do NOT value-import any other cross-plugin
|
|
102
|
+
package (purity gate; the browser module table would reject it). Type-only
|
|
103
|
+
imports are erased and allowed, e.g.
|
|
104
|
+
`import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'` and
|
|
105
|
+
`import type {} from '@deepseek-ai/dsh-client-ui-layout/client'` (declaration
|
|
106
|
+
merge).
|
|
107
|
+
|
|
108
|
+
### 1.5 Runtime data (verified)
|
|
109
|
+
|
|
110
|
+
- `ctx.sessions.list` is an `ObservableSnapshot<SessionListState>`:
|
|
111
|
+
`getSnapshot()` → `{ current: SessionId | undefined, byId, ... }`, `subscribe`.
|
|
112
|
+
(agent-teams passes it into its panel component and subscribes with
|
|
113
|
+
`useSyncExternalStore`.)
|
|
114
|
+
- Current session object: `ctx.sessions.binding(sessionId)?.session` → `Session`
|
|
115
|
+
with `subscribe(listener)` (uSES) and `getSnapshot()` →
|
|
116
|
+
`ConversationSnapshot` with:
|
|
117
|
+
- `chat.nodes` — Map-like `ChatNodeStore` (`get(key)`, `values()`)
|
|
118
|
+
- `chat.order` — `readonly string[]` of node keys in flow order
|
|
119
|
+
- `chat.legacy.nodes` — array form
|
|
120
|
+
- `hasMore: boolean`, `loadingOlder: boolean`
|
|
121
|
+
- `running`, `blank`, `openState`, `pending`, `queue`, ...
|
|
122
|
+
- `session.loadOlder()` — pages up (returns Promise, guarded by hasMore).
|
|
123
|
+
- Chat node shape (from `chatNode()` in ui-conversation):
|
|
124
|
+
`{ key, kind, id, target: 'chat', anchorSeq, location, visibility, data }`.
|
|
125
|
+
- user-message node: `kind: 'user'` (also `'steering'` for mid-turn steers,
|
|
126
|
+
`'context'` for injected context). `data` =
|
|
127
|
+
`{ kind, seq, time (unix ms), content: ContentBlock[], source }` where
|
|
128
|
+
`ContentBlock` is `{ type: 'text', text } | { type: 'image', ... } | ...`.
|
|
129
|
+
- `node.key` = `conversationContextKey(defKind, businessId)` with format
|
|
130
|
+
`<kindLen>:<kind><id>` — e.g. `13:input-message<uuid>` for user messages.
|
|
131
|
+
Use `node.key` verbatim for DOM lookup.
|
|
132
|
+
- `node.location`: `{kind:'turn', turn: TurnLocation} | {kind:'step', turn, step} |
|
|
133
|
+
{kind:'session'} | {kind:'unresolved'}`; turn number =
|
|
134
|
+
`location.kind === 'step' ? location.turn.turn : location.kind === 'turn' ? location.turn.turn : undefined`.
|
|
135
|
+
- Locale: `ctx.locale.register(NS, { zh, en })` then `ctx.locale.bind(NS)` → `t(key)`.
|
|
136
|
+
- Open a session: `ctx.sessions.open(id)`.
|
|
137
|
+
|
|
138
|
+
### 1.6 Versions (peerDependencies / devDependencies)
|
|
139
|
+
|
|
140
|
+
- **peerDependencies: `react` `^18.2.0` ONLY.** The `@deepseek-ai/*` packages are
|
|
141
|
+
deliberately NOT peers (REVISED after the first install report): the registry's
|
|
142
|
+
`latest` tag for the client sub-packages is stale (`0.0.1-rc.1`) while `0.1.0-rc.6`
|
|
143
|
+
lives on `next`, so a `^0.1.0-rc.6` peer makes pnpm fail with
|
|
144
|
+
`ERR_PNPM_NO_MATCHING_VERSION` on a fresh profile. Nothing imports those packages at
|
|
145
|
+
runtime anyway — the browser module loader resolves platform modules from the
|
|
146
|
+
profile's own (channel-matched) installation, and the host half imports nothing.
|
|
147
|
+
`react-dom` is also dropped (unused since the shell.overlay rewrite).
|
|
148
|
+
- Typechecking still links the local `@deepseek-ai/*` types via `scripts/link-types.mjs`
|
|
149
|
+
(dev-only symlinks, never shipped — see §1.7).
|
|
150
|
+
- devDeps from public npm: `typescript@^5.9.3`, `tsdown@0.22.2`, `lightningcss@^1.33.0`,
|
|
151
|
+
`@types/react@~18.3.1`, `@types/react-dom@^19.2.4`, `react@^18.2.0`,
|
|
152
|
+
`react-dom@^18.2.0` (react needed as devDep for jsx types).
|
|
153
|
+
- Node `^22.19.0 || >=24` (engines).
|
|
154
|
+
|
|
155
|
+
### 1.7 Typecheck strategy (no registry access to @deepseek-ai)
|
|
156
|
+
|
|
157
|
+
`@deepseek-ai/*` packages are NOT re-installable from the public registry here;
|
|
158
|
+
the profile flat dir already has everything. Symlink (do NOT commit):
|
|
159
|
+
```sh
|
|
160
|
+
mkdir -p node_modules/@deepseek-ai
|
|
161
|
+
for p in cordis dsh-client-runtime dsh-client-ui-layout dsh-client-ui-slots dsh-client-ui-primitives dsh-client-locale dsh-client-web-react; do
|
|
162
|
+
ln -sfn /Users/liziqing/.dsh/profiles/node_modules/@deepseek-ai/$p node_modules/@deepseek-ai/$p
|
|
163
|
+
done
|
|
164
|
+
```
|
|
165
|
+
Provide a `scripts/link-types.mjs` that does this (idempotent), and a
|
|
166
|
+
`pnpm dev:types` script. `.gitignore` must exclude `node_modules/` so the
|
|
167
|
+
symlinks never ship.
|
|
168
|
+
|
|
169
|
+
Two tsc programs (host and client) — see skill §6.1 / guide §3.1. The client
|
|
170
|
+
program compiles `src/client/**` with `lib: ["ES2022","DOM","DOM.Iterable"]`,
|
|
171
|
+
`jsx: "react-jsx"`, `types: []`, `allowImportingTsExtensions` +
|
|
172
|
+
`rewriteRelativeImportExtensions` (TS 5.7+). Output: host → `lib/`, client →
|
|
173
|
+
`lib/client/`. tsdown then bundles `lib/client/index.js` → `lib/client.js`
|
|
174
|
+
(with banner/footer wrapper + sourcemap; keep `lib/client/*.js` files — the
|
|
175
|
+
verify script imports the pure-logic module from there).
|
|
176
|
+
|
|
177
|
+
### 1.8 CSS
|
|
178
|
+
|
|
179
|
+
Keep it simple and HMR-safe: plain CSS string in a `.ts` module, injected in
|
|
180
|
+
`apply()` via `ctx.effect` with a `<style data-plugin="dsh-conversation-outline"
|
|
181
|
+
data-plugin-css="dsh-conversation-outline/panel.css">` tag (same pattern the
|
|
182
|
+
shipped bundles generate; HMR removes owned `style[data-plugin]` tags).
|
|
183
|
+
Use stable prefixed class names (`dso_*`) and DSH theme vars (`--dsw-alias-*`,
|
|
184
|
+
`--dsw-specific-*`, `--dsh-sidebar-width`) — see agent-teams' injected CSS for
|
|
185
|
+
the exact vars. CSS Modules via lightningcss is allowed but optional; the
|
|
186
|
+
manual style-tag approach is preferred for lower build risk.
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## 2. Product spec (the panel)
|
|
191
|
+
|
|
192
|
+
### 2.1 Surfaces (REVISED — rail + hover panel, no badge)
|
|
193
|
+
|
|
194
|
+
Per user feedback the top-right capsule badge was replaced: the AgentTeams
|
|
195
|
+
plugin already occupies that corner. The outline now lives on the right EDGE.
|
|
196
|
+
|
|
197
|
+
1. **Rail** (always mounted, `shell.overlay` entry, id
|
|
198
|
+
`dsh-conversation-outline.rail`): a thin fixed strip on the right edge
|
|
199
|
+
(`right: 0`, vertically centered `top: 50%`), one small horizontal bar per
|
|
200
|
+
user question in chronological order top→bottom — a conversation minimap.
|
|
201
|
+
Capped at 60 bars; overflow folds into a top `+N` marker. Bars are real
|
|
202
|
+
`<button>`s with localized `aria-label` (`跳到第 {n} 个问题`); clicking a
|
|
203
|
+
bar jumps. The strip itself is `role="group"` with `aria-label`, focusable
|
|
204
|
+
(focus/Enter opens the panel for keyboard users). Hidden (renders nothing)
|
|
205
|
+
when there is no current session or the session is blank.
|
|
206
|
+
2. **Panel** (rendered only while open): fixed right overlay
|
|
207
|
+
(`right: 16px`, vertically centered), `width: min(340px, calc(100vw - 24px))`,
|
|
208
|
+
`max-height: min(70vh, 600px)`, rounded 16px card, internal scroll, backdrop
|
|
209
|
+
blur, DSH theme vars. Sections:
|
|
210
|
+
- Header: title `会话大纲 / Outline` + count + close button (×).
|
|
211
|
+
- Search input (localized placeholder; case-insensitive filter).
|
|
212
|
+
- Question list, **chronological**: one row per user question — `#<turn>`
|
|
213
|
+
badge, **single-line truncated** text preview (the question's opening
|
|
214
|
+
words; longer text ellipsizes), trailing `HH:MM` time, copy button,
|
|
215
|
+
`追问`/`steer` tag for steering messages. Clicking a row = jump (2.2).
|
|
216
|
+
- Footer: `加载更早 / Load older` when `snapshot.hasMore` (disabled while
|
|
217
|
+
`loadingOlder`); calls `session.loadOlder()`.
|
|
218
|
+
- Empty state when no user messages in the loaded window.
|
|
219
|
+
3. Behavior rules:
|
|
220
|
+
- **Hover-open**: mouseenter on the rail (or panel) opens the panel;
|
|
221
|
+
mouseleave schedules a collapse after a 240ms grace (canceled on re-enter,
|
|
222
|
+
so the pointer can travel rail→panel). Touch devices: tapping the strip
|
|
223
|
+
(non-bar area) pins/unpins the panel; `Escape` or × closes. The panel is
|
|
224
|
+
a pure OVERLAY — no column yield, no layout shift.
|
|
225
|
+
- Follows `sessions.list.current`; on session change the panel closes and
|
|
226
|
+
pending jump work is canceled (navigate → collapse).
|
|
227
|
+
- While a session is running, new user messages appear live (snapshot
|
|
228
|
+
subscription covers it; the rail grows a bar per question).
|
|
229
|
+
|
|
230
|
+
### 2.2 Jump-to-message algorithm (core feature)
|
|
231
|
+
|
|
232
|
+
On row click, given the target `node.key`:
|
|
233
|
+
|
|
234
|
+
1. Ensure the conversation page is mounted: abort when
|
|
235
|
+
`document.querySelector('[data-conversation-scroll]')` is missing. If a
|
|
236
|
+
conversation-root header tablist exists (found by walking up from
|
|
237
|
+
`scrollport.parentElement` — the scrollport itself may contain other
|
|
238
|
+
tablists such as the trajectory event-details tabs), click its first
|
|
239
|
+
`button[role="tab"]` (chat is `order: 0` — always first; `setView("chat")`
|
|
240
|
+
is idempotent). A profile without extra views has no tablist — fine, the
|
|
241
|
+
rows are already visible.
|
|
242
|
+
2. Wait for the target row to render: poll with `requestAnimationFrame` up to
|
|
243
|
+
~1500ms for `[data-chat-anchor-key="<node.key>"]` inside the chat list. The
|
|
244
|
+
row is guaranteed to be in the loaded window (it comes from the snapshot),
|
|
245
|
+
so this is a render-timing wait.
|
|
246
|
+
3. Scroll: `const scrollport = row.closest('[data-conversation-scroll]') ?? document.querySelector('[data-conversation-scroll]')`; compute
|
|
247
|
+
`row.getBoundingClientRect().top - scrollport.getBoundingClientRect().top`
|
|
248
|
+
and set `scrollport.scrollTop += flowTop - 96` (leave ~96px headroom under
|
|
249
|
+
the sticky header/composer area). Use smooth behavior unless
|
|
250
|
+
`prefers-reduced-motion`.
|
|
251
|
+
4. Flash highlight: set `row.dataset.dshOutlineFlash = 'true'`; CSS
|
|
252
|
+
`[data-dsh-outline-flash]{ animation: dso-flash 1.8s ease-out }` (a
|
|
253
|
+
background/outline pulse using `--dsw-alias-state-business-primary`);
|
|
254
|
+
remove the attribute after ~1.9s (timeout, cleaned on unmount).
|
|
255
|
+
5. Close the panel.
|
|
256
|
+
|
|
257
|
+
All DOM work must be guarded (elements may be missing) and cleaned up on
|
|
258
|
+
dispose (timeouts/RAF cancelled in the effect disposer).
|
|
259
|
+
|
|
260
|
+
### 2.3 Pure logic module (`src/client/outline.ts`, NO DOM, NO React)
|
|
261
|
+
|
|
262
|
+
Keep the derivations as importable pure functions so `scripts/verify.mjs` can
|
|
263
|
+
test them in Node by importing `lib/client/outline.js`:
|
|
264
|
+
|
|
265
|
+
- `flattenQuestionText(content: ContentBlock[]): string` — join text blocks,
|
|
266
|
+
image blocks → `[图片]` / `[image]`, trim, collapse whitespace.
|
|
267
|
+
- `collectQuestions(snapshot): OutlineItem[]` — walk `snapshot.chat.order`,
|
|
268
|
+
look up `snapshot.chat.nodes.get(key)`, keep `kind === 'user' | 'steering'`,
|
|
269
|
+
skip empty text; item =
|
|
270
|
+
`{ key, kind, seq, time, turn: number | undefined, text }`.
|
|
271
|
+
- `filterQuestions(items, query): OutlineItem[]` — case-insensitive substring.
|
|
272
|
+
- `formatTime(ms): string` — `HH:MM` local.
|
|
273
|
+
- `isJumpTargetRow(row: Element, key: string): boolean` — pure DOM predicate
|
|
274
|
+
(used by the jump loop; testable with a fake object in node).
|
|
275
|
+
|
|
276
|
+
### 2.4 Localization
|
|
277
|
+
|
|
278
|
+
Namespace `dsh-conversation-outline`; keys at least: title, count (with
|
|
279
|
+
`{count}`), searchPlaceholder, empty, loadOlder, loadingOlder, copy, copied,
|
|
280
|
+
steerTag, jumpFailed (console only). zh-CN + en dictionaries, registered via
|
|
281
|
+
`ctx.locale.register(NS, { zh, en })`.
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## 3. Repo layout (create exactly this)
|
|
286
|
+
|
|
287
|
+
```
|
|
288
|
+
dsh-conversation-outline/
|
|
289
|
+
├── package.json
|
|
290
|
+
├── cordis.patch.yml
|
|
291
|
+
├── tsconfig.json # host program (excludes src/client)
|
|
292
|
+
├── tsconfig.client.json # client program
|
|
293
|
+
├── tsdown.config.ts # client bundle (wrapper banner/footer)
|
|
294
|
+
├── .gitignore # node_modules, lib, *.log, .DS_Store
|
|
295
|
+
├── LICENSE # MIT (owner placeholder "your name")
|
|
296
|
+
├── README.md # written by docs member
|
|
297
|
+
├── docs/
|
|
298
|
+
│ ├── implementation-spec.md # this file
|
|
299
|
+
│ └── publishing-guide.md # written by docs member
|
|
300
|
+
├── scripts/
|
|
301
|
+
│ ├── link-types.mjs # symlink @deepseek-ai types (dev-only)
|
|
302
|
+
│ └── verify.mjs # offline smoke: manifest/patch/bundle shape + pure logic
|
|
303
|
+
└── src/
|
|
304
|
+
├── index.ts # minimal host apply
|
|
305
|
+
├── event-types.ts # NOT needed (no custom events) — skip
|
|
306
|
+
└── client/
|
|
307
|
+
├── index.tsx # apply(): locale, style tag, shell.overlay registration, badge+panel
|
|
308
|
+
├── OutlinePanel.tsx # badge + panel components
|
|
309
|
+
├── outline.ts # pure logic (2.3)
|
|
310
|
+
├── locales.ts # zh/en dictionaries
|
|
311
|
+
└── styles.ts # CSS string + injectStyle(ctx) helper
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### 3.1 package.json essentials
|
|
315
|
+
|
|
316
|
+
```jsonc
|
|
317
|
+
{
|
|
318
|
+
"name": "dsh-conversation-outline",
|
|
319
|
+
"version": "0.1.0",
|
|
320
|
+
"type": "module",
|
|
321
|
+
"main": "lib/index.js",
|
|
322
|
+
"types": "lib/types/index.d.ts",
|
|
323
|
+
"exports": {
|
|
324
|
+
".": { "types": "./lib/types/index.d.ts", "default": "./lib/index.js" },
|
|
325
|
+
"./client": { "types": "./lib/types/client/index.d.ts", "default": "./lib/client.js" },
|
|
326
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
327
|
+
"./package.json": "./package.json"
|
|
328
|
+
},
|
|
329
|
+
"files": ["lib", "cordis.patch.yml", "README.md", "LICENSE"],
|
|
330
|
+
"dsh": {
|
|
331
|
+
"bundle": { "patch": "./cordis.patch.yml" },
|
|
332
|
+
"client": { "inject": ["@deepseek-ai/dsh-client-runtime", "@deepseek-ai/dsh-client-ui-layout"], "platform": "web" }
|
|
333
|
+
},
|
|
334
|
+
"scripts": {
|
|
335
|
+
"build": "tsc -p tsconfig.json && tsc -p tsconfig.client.json && tsdown",
|
|
336
|
+
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.client.json --noEmit",
|
|
337
|
+
"verify": "node scripts/verify.mjs",
|
|
338
|
+
"dev:types": "node scripts/link-types.mjs"
|
|
339
|
+
},
|
|
340
|
+
"peerDependencies": {
|
|
341
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
342
|
+
"@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.6",
|
|
343
|
+
"@deepseek-ai/dsh-client-ui-layout": "^0.1.0-rc.6",
|
|
344
|
+
"react": "^18.2.0",
|
|
345
|
+
"react-dom": "^18.2.0"
|
|
346
|
+
},
|
|
347
|
+
"devDependencies": {
|
|
348
|
+
"@types/react": "~18.3.1",
|
|
349
|
+
"@types/react-dom": "^19.2.4",
|
|
350
|
+
"lightningcss": "^1.33.0",
|
|
351
|
+
"react": "^18.2.0",
|
|
352
|
+
"react-dom": "^18.2.0",
|
|
353
|
+
"tsdown": "0.22.2",
|
|
354
|
+
"typescript": "^5.9.3"
|
|
355
|
+
},
|
|
356
|
+
"engines": { "node": "^22.19.0 || >=24" },
|
|
357
|
+
"license": "MIT",
|
|
358
|
+
"keywords": ["dsh", "dsh-plugin", "deepseek-harness", "conversation", "outline", "navigation"],
|
|
359
|
+
"repository": { "type": "git", "url": "git+https://github.com/<owner>/dsh-conversation-outline.git" }
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### 3.2 cordis.patch.yml
|
|
364
|
+
|
|
365
|
+
```yaml
|
|
366
|
+
- insert:
|
|
367
|
+
- id: dsh-conversation-outline
|
|
368
|
+
name: dsh-conversation-outline
|
|
369
|
+
config: {}
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### 3.3 tsdown.config.ts
|
|
373
|
+
|
|
374
|
+
Entry `{ client: 'lib/client/index.js' }`, outDir `lib`, `format: 'cjs'`,
|
|
375
|
+
`platform: 'browser'`, `dts: false`, `sourcemap: true`, `clean: false`,
|
|
376
|
+
`external` = the platform table (1.4), `define` NODE_ENV production,
|
|
377
|
+
`outputOptions`: `entryFileNames: 'client.js'`, `banner: 'window.__ModuleLoader__.load({ id: "dsh-conversation-outline", factory: (require) => {'`,
|
|
378
|
+
`footer: 'return module.exports; } });'`,
|
|
379
|
+
`intro: 'var module = { exports: {} }; var exports = module.exports;'`.
|
|
380
|
+
No CSS-modules plugin needed (manual style tag); keep a simple purity guard
|
|
381
|
+
plugin that throws on value-imports of `@deepseek-ai/*` packages outside the
|
|
382
|
+
external table.
|
|
383
|
+
|
|
384
|
+
---
|
|
385
|
+
|
|
386
|
+
## 4. Verification plan
|
|
387
|
+
|
|
388
|
+
### 4.1 Offline (engineer, then researcher re-runs)
|
|
389
|
+
|
|
390
|
+
1. `pnpm dev:types` (symlink), `pnpm install`, `pnpm typecheck`, `pnpm build`.
|
|
391
|
+
2. `node scripts/verify.mjs`:
|
|
392
|
+
- manifest/exports/files consistency (every exported path exists);
|
|
393
|
+
- `cordis.patch.yml` parses, first insert id/name match package name;
|
|
394
|
+
- `lib/client.js` starts with the `window.__ModuleLoader__.load` wrapper;
|
|
395
|
+
- no absolute machine paths inside `lib/` (grep `/Users/`);
|
|
396
|
+
- pure-logic assertions from `lib/client/outline.js`: flatten text incl.
|
|
397
|
+
image blocks, collect/filter/sort questions from a fixture snapshot,
|
|
398
|
+
formatTime, turn extraction.
|
|
399
|
+
|
|
400
|
+
### 4.2 Integration (researcher)
|
|
401
|
+
|
|
402
|
+
Use a SCRATCH profile + temporary `DSH_HOME` + different port — NEVER touch the
|
|
403
|
+
running instance (the user's GUI at 127.0.0.1:3080 serves the current session;
|
|
404
|
+
restarting it would kill the session. Also do not start a replacement server on
|
|
405
|
+
3080).
|
|
406
|
+
|
|
407
|
+
1. `pnpm build` in the repo.
|
|
408
|
+
2. `tmp=$(mktemp -d)`; `DSH_HOME=$tmp npx -p @deepseek-ai/dsh dsh plugin --profile scratch add /Users/liziqing/Programs/agents/dsh-conversation-outline`
|
|
409
|
+
(network: npm registry reachable for the dsh CLI; if the npx download fails,
|
|
410
|
+
fall back to the already-installed CLI:
|
|
411
|
+
`DSH_HOME=$tmp node /Users/liziqing/.npm/_npx/1e7f6d9597241db0/node_modules/@deepseek-ai/dsh/lib/bin.js plugin --profile scratch add <path>`).
|
|
412
|
+
3. `DSH_HOME=$tmp <dsh> --profile scratch --dump-config` — assert the
|
|
413
|
+
`dsh-conversation-outline` row appears with id/name/config.
|
|
414
|
+
4. Boot the scratch web instance on a free port (e.g. 3199) in the background:
|
|
415
|
+
`DSH_HOME=$tmp <dsh> --profile scratch --port 3199` (check `--help` flags if
|
|
416
|
+
needed), then `curl http://127.0.0.1:3199/` and assert
|
|
417
|
+
`window.__DSH_BOOT__` contains an entry
|
|
418
|
+
`{"id":"dsh-conversation-outline","url":"/plugins/dsh-conversation-outline/client.js"...}`,
|
|
419
|
+
and `curl http://127.0.0.1:3199/plugins/dsh-conversation-outline/client.js`
|
|
420
|
+
returns 200 with the loader wrapper. Kill the instance afterwards.
|
|
421
|
+
5. Report exact commands + outputs in the task output.
|
|
422
|
+
|
|
423
|
+
### 4.3 Review (reviewer)
|
|
424
|
+
|
|
425
|
+
Check against the skill checklist (§9 完成标准): minimal surface; manifest/
|
|
426
|
+
exports/patch/products consistent; inject boundaries; effect ownership
|
|
427
|
+
(dispose of root/DOM/style/listeners/timeouts/RAF); purity of client imports;
|
|
428
|
+
no host/client type pollution (two programs); a11y (aria, focus-visible,
|
|
429
|
+
Escape, reduced motion); the jump algorithm's failure modes (missing row,
|
|
430
|
+
missing tablist, session switched mid-jump, blank session); live updates;
|
|
431
|
+
load-older; i18n coverage. Mark findings as must-fix / nice-to-have and hand
|
|
432
|
+
back to the engineer for fixes.
|
|
433
|
+
|
|
434
|
+
### 4.4 Final (captain)
|
|
435
|
+
|
|
436
|
+
Install into the user's real web profile (`dsh plugin --profile web add <path>`)
|
|
437
|
+
— this only edits profile files; the plugin takes effect after the user
|
|
438
|
+
restarts their GUI (document this). Keep the scratch instance killed and temp
|
|
439
|
+
dirs cleaned.
|
|
440
|
+
|
|
441
|
+
---
|
|
442
|
+
|
|
443
|
+
## 5. Order of work
|
|
444
|
+
|
|
445
|
+
1. engineer: scaffold (3.x) → implement (2.x) → build green (4.1).
|
|
446
|
+
2. researcher: 4.2 integration.
|
|
447
|
+
3. reviewer: 4.3; engineer fixes must-fix findings.
|
|
448
|
+
4. docs: README (install via `dsh plugin --profile web add <pkg|github:...>`,
|
|
449
|
+
usage, dev loop with HMR: `tsdown --watch` + `pnpm run dev:web` note,
|
|
450
|
+
screenshots placeholder) + `docs/publishing-guide.md` (GitHub publish:
|
|
451
|
+
create repo, license choice — MIT recommended, .gitignore, README, release
|
|
452
|
+
workflow: npm publish with `prepublishOnly: pnpm build && pnpm verify`,
|
|
453
|
+
GitHub-only install without npm, versioning, CHANGELOG, issues/PR templates,
|
|
454
|
+
publishing scoped vs unscoped, npm name collision fallback to a scope).
|
|
455
|
+
5. captain: final assembly, install into web profile, wrap-up report.
|