# Architecture & Technical Specification: `@anoslide/dsh-vscode-workspace`
This document provides a comprehensive technical breakdown of `@anoslide/dsh-vscode-workspace`, the unified Dual-Face Cordis plugin for DeepSeek Harness (`dsh`).
---
## 1. Architectural Philosophy: Dual-Face Cordis Model
Historically, workspace capabilities in DeepSeek Harness were split across two distinct packages:
1. `@anoslide/dsh-host-files`: A Node.js backend handling filesystem operations and Git porcelain.
2. `@anoslide/dsh-client-vscode-layout`: A browser frontend bundle delivering the 3-column VS Code shell.
This separation created synchronization overhead, split versioning, brittle multi-step deployment scripts, and risks of API divergence.
`@anoslide/dsh-vscode-workspace` consolidates both domains into a single **Dual-Face** Cordis package:
- **Host Half (`lib/index.js`)**: An ESM Node.js plugin executing in the Cordis backend container, injecting the `webServer` service to serve all `/vscode-files/*` REST endpoints.
- **Client Half (`lib/client.js`)**: A lazy-CJS closure factory consumed by the DeepSeek Harness client module loader (`window.__ModuleLoader__`), injecting slots into the browser runtime.
### Package Manifest Alignment (`package.json`)
The package exports both entrypoints through standard Node module resolution and DeepSeek Harness platform metadata:
```json
{
"name": "@anoslide/dsh-vscode-workspace",
"version": "1.0.0",
"type": "module",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"exports": {
".": {
"types": "./lib/index.d.ts",
"default": "./lib/index.js"
},
"./client": {
"types": "./lib/index.d.ts",
"default": "./lib/client.js"
},
"./package.json": "./package.json"
},
"dsh": {
"client": {
"platform": "web",
"inject": [
"@deepseek-ai/dsh-client-ui-theme"
]
},
"bundle": {
"patch": "./cordis.patch.yml"
}
}
}
```
The `cordis.patch.yml` profile layer introduces the plugin cleanly into the runtime bundle list:
```yaml
- insert:
- id: vscode-workspace
name: '@anoslide/dsh-vscode-workspace'
```
---
## 2. Host Service Architecture (`src/host/`)
### 2.1 Cordis `webServer` Injection & Lifecycle
The host entrypoint (`src/index.ts`) declares its dependency on the Cordis `webServer` service:
```typescript
import type { Context } from '@deepseek-ai/cordis'
import { registerHostRoutes } from './host/routes.ts'
export const name = 'dsh-vscode-workspace'
export const inject = ['webServer']
export function apply(ctx: Context): void {
registerHostRoutes(ctx)
}
```
When Cordis initializes the plugin:
1. `registerPersonaPrompt(ctx)`: Hooks into the session controller to inject the workspace persona into the AI agent's system prompt.
2. `ctx.effect(...)`: Binds a route handler prefix `/vscode-files` onto `ctx.webServer`. The lifecycle hook automatically unregisters the route if the plugin is unloaded.
```typescript
export function registerHostRoutes(ctx: Context): void {
registerPersonaPrompt(ctx)
const handler = createHostRequestHandler()
ctx.effect?.(() => {
return ctx.webServer?.register({
kind: 'prefix',
path: '/vscode-files',
handler,
}, 'dsh-vscode-workspace: /vscode-files routes')
})
}
```
### 2.2 Endpoint Specification (`/vscode-files/*`)
All HTTP communication between the client workbench and host service occurs over JSON REST endpoints (with binary streaming for media/raw files).
| Method | Endpoint | Description | Request Parameters / Body | Response Schema |
| :--- | :--- | :--- | :--- | :--- |
| `GET` | `/vscode-files/list` | Lists directory contents and file metadata. | Query: `path` (relative or absolute) | `{ ok: true, path: string, items: Array<{ name, path, isDir, size, mtime }> }` |
| `GET` | `/vscode-files/read` | Reads file content as text. | Query: `path` | `{ ok: true, content: string, binary: false }` or `{ ok: true, binary: true }` |
| `GET` | `/vscode-files/raw` | Streams raw file content with MIME headers. | Query: `path` | Raw byte stream with appropriate `Content-Type` |
| `POST` | `/vscode-files/write` | Atomically writes content to a file. | Body: `{ path: string, content: string }` | `{ ok: true, size: number }` |
| `POST` | `/vscode-files/mkdir` | Creates a new directory recursively. | Body: `{ path: string }` | `{ ok: true, path: string }` |
| `POST` | `/vscode-files/mkfile` | Creates an empty file. | Body: `{ path: string }` | `{ ok: true, path: string }` |
| `POST` | `/vscode-files/rename` | Renames or moves a file or directory. | Body: `{ oldPath: string, newPath: string }` | `{ ok: true }` |
| `POST` | `/vscode-files/delete` | Moves item to OS Trash (or unlinks). | Body: `{ path: string }` | `{ ok: true, trashed: boolean }` |
| `GET` | `/vscode-files/search` | Fast file or content search (`ripgrep`). | Query: `q`, `type` (`filename` \| `content`), `caseSensitive`, `isRegex` | `{ ok: true, results: Array<{ path, line?, preview? }> }` |
| `GET` | `/vscode-files/highlight` | Server-side syntax highlighting with Shiki. | Query: `path`, `theme` (`light` \| `dark`) | `{ ok: true, html: string }` |
| `GET` | `/vscode-files/git` | Retrieves repository status and branch. | Query: `path` (repo path) | `{ ok: true, repo: true, branch: string, staged: [], unstaged: [] }` |
| `GET` | `/vscode-files/git/log` | Returns recent Git commit history. | Query: `path`, `limit` (default 50, max 200) | `{ ok: true, commits: Array<{ hash, message, author, date }> }` |
| `POST` | `/vscode-files/git/stage` | Stages file(s) into Git index. | Body: `{ repo: string, file: string }` | `{ ok: true }` |
| `POST` | `/vscode-files/git/unstage`| Unstages file(s) from Git index. | Body: `{ repo: string, file: string }` | `{ ok: true }` |
| `POST` | `/vscode-files/git/discard`| Discards unstaged modifications. | Body: `{ repo: string, file: string }` | `{ ok: true }` |
| `POST` | `/vscode-files/git/commit` | Creates a Git commit. | Body: `{ repo: string, message: string }` | `{ ok: true, commit: string }` |
| `POST` | `/vscode-files/git/push` | Pushes commits to Git remote. | Body: `{ repo: string }` | `{ ok: true }` |
| `POST` | `/vscode-files/git/pull` | Pulls commits from Git remote. | Body: `{ repo: string }` | `{ ok: true }` |
| `POST` | `/vscode-files/git/fetch`| Fetches updates from Git remote. | Body: `{ repo: string }` | `{ ok: true }` |
| `POST` | `/vscode-files/upload-image` | Uploads an image (local or Cloudflare R2). | Body: `{ file: string (base64), filename: string }` | `{ ok: true, url: string }` |
| `GET` | `/vscode-files/sandbox-info` | Returns current sandbox root and boundaries. | None | `{ ok: true, root: string, isSandboxed: boolean }` |
| `GET` | `/vscode-files/persona` | Reads global workspace persona markdown. | None | `{ ok: true, persona: string }` |
| `POST` | `/vscode-files/persona` | Updates workspace persona markdown. | Body: `{ persona: string }` | `{ ok: true }` |
### 2.3 Security Sandboxing & Path Traversal Guards
All filesystem operations are strictly sandboxed:
1. **Sandbox Root**: Defined by `process.env.DSH_SANDBOX_ROOT` or falling back to `process.cwd()`.
2. **Canonical Resolution**: Incoming paths are resolved using `path.resolve()` and checked against the sandbox root.
3. **Traversal Prevention**:
```typescript
export function isInsideSandbox(targetPath: string, root = getSandboxRoot()): boolean {
const rel = relative(root, resolve(root, targetPath))
return !rel.startsWith('..') && !isAbsolute(rel)
}
```
4. Attempts to escape the sandbox boundary immediately reject with `403 Forbidden` (`{ ok: false, error: "Access outside sandbox forbidden" }`).
---
## 3. Client Service & Slot Architecture (`src/client/`)
### 3.1 DeepSeek Harness Micro-Frontend Loader Mechanics
DeepSeek Harness compiles its client-side applications as micro-frontends. The browser runtime provides a frozen module loader: `window.__ModuleLoader__`.
A client plugin bundle must not use generic browser script semantics. Instead, it adheres to the **Lazy-CJS Closure Factory Protocol**:
```javascript
window.__ModuleLoader__.load({
id: "@anoslide/dsh-vscode-workspace",
factory: (require) => {
var module = { exports: {} };
var exports = module.exports;
// ... compiled code ...
return module.exports;
}
});
```
### 3.2 Platform Modules Alignment (`PLATFORM_MODULES`)
To prevent multiple instances of React hooks, context, or Cordis state engines from co-existing (which causes runtime crashes), the browser runtime provides shared singleton modules via `require(...)`.
Our client build pipeline (`build/tsdown.client.ts`) guarantees that `PLATFORM_MODULES` are never bundled into the client artifact:
```typescript
export const PLATFORM_MODULES = [
'react',
'react/jsx-runtime',
'react-dom',
'react-dom/client',
'@deepseek-ai/cordis',
'@deepseek-ai/dsh-client-store',
'@deepseek-ai/dsh-client-ui-slots',
'@deepseek-ai/dsh-client-ui-primitives',
'@deepseek-ai/dsh-client-ui-dockkit',
] as const
```
The build pipeline enforces a **Purity Gate** (`purityGate` plugin in `build/tsdown.client.ts`). Any value import from `@deepseek-ai/*` that is not in `PLATFORM_MODULES` triggers a compile-time error, ensuring cross-plugin communication occurs strictly via Cordis services and slot registrations.
### 3.3 Slot Composition Model
DeepSeek Harness interfaces are composed entirely of Cordis Slots.
```mermaid
graph TD
RootSlot["Slot: 'root'"] --> AppFrame["AppFrame.tsx"]
AppFrame --> SlotLeft["Slot: 'frame.main.left'"]
AppFrame --> SlotCenter["Slot: 'frame.main.center'"]
AppFrame --> SlotRight["Slot: 'frame.main.right'"]
AppFrame --> SlotStatus["Slot: 'frame.main.status'"]
SlotLeft --> LeftColumn["RailViews (Explorer, Search, Git)"]
SlotCenter --> CenterWorkbench["Workbench (Tabs, CodeMirror, TipTap)"]
SlotRight --> RightChat["Chat & Trajectory Drawer"]
SlotStatus --> StatusBar["Status Bar (Git branch, line/col, auto-save)"]
SidebarSeat["Slot: 'sidebar.footer.action'"] --> RailAction["RailSwitcher (View toggle buttons)"]
```
1. **`root` Slot Replacement**:
`AppFrame.tsx` registers into the built-in `'root'` slot. It declares exclusive render authority over its child slots:
- `frame.main.left`: Host rail and view panels.
- `frame.main.center`: Multi-tab workbench editor.
- `frame.main.right`: AI conversation and tool inspection stream.
- `frame.main.status`: Bottom status bar.
2. **`sidebar.footer.action` Rail Integration**:
The native `ui-sidebar` maintains the left-most 56px action rail. Rather than replacing the rail, `@anoslide/dsh-vscode-workspace` seats a toggle button into `sidebar.footer.action` to switch between Explorer, Search, Git, and Sessions.
### 3.4 State Management & Data Channels
In accordance with Cordis client standards, state is partitioned across three explicit channels:
1. **Owner Props**: Downward data passing from parent slot containers.
2. **Local Component State**: Ephemeral UI state (e.g. menu open/closed, hovering, drag handles).
3. **Cordis Stores**: Persistent shared application state created via `createLayoutStore` and `createViewState`, synchronized with the Cordis context.
---
## 4. TipTap Notion WYSIWYG & Editor Subsystem
The center editor workbench (`src/client/tiptap/`) implements a complete Notion-style block editor for Markdown:
```
src/client/tiptap/
├── ai/ # Inline AI assist integration
├── bubble/ # Floating formatting bubble menu
├── callouts/ # Notion-style callout blocks (/callout)
├── clipboard/ # Markdown, HTML, and image paste handlers
├── codeblock/ # Syntax-highlighted code blocks
├── details/ # Collapsible toggle lists (/toggle)
├── dragHandle/ # Block hover drag & drop handles
├── excalidraw/ # Embedded Excalidraw whiteboards
├── findBar/ # In-editor find and replace widget
├── frontmatter/ # YAML frontmatter parser and renderer
├── headingFold/ # Section collapse / fold controls
├── html/ # Safe raw HTML preservation
├── image/ # Image upload and rendering pipeline
├── markdown/ # Bidirectional Markdown AST parser/serializer
├── math/ # KaTeX inline ($...$) and block ($$...$$) math
├── mermaid/ # Live Mermaid diagram renderer
├── table/ # Interactive tables with row/column controls
├── toc/ # Table of Contents outline drawer
└── wiki/ # Internal wiki-link resolution ([[note]])
```
### Key Subsystem Behaviors:
- **Bidirectional Markdown Pipeline**: Automatically transforms raw Markdown into ProseMirror AST nodes and serializes changes back without information loss.
- **Debounced Auto-Save**: An internal 1.5-second debounce automatically syncs dirty documents to the host via `POST /vscode-files/write`.
- **Yjs Collaborative Readiness**: Built-in support for `y-tiptap` collaborative editing and presence cursor tracking over WebSockets.
---
## 5. AI Chat & Reference Protocol
The chat integration (`src/client/chat/`, `src/client/inputTriggers/`) provides a deeply connected AI pair programming workflow:
1. **`@` Mention Provider (`inputTriggers/fileSource.ts`)**:
Registers into the host's mention autocomplete system. When `@` is typed in the composer, it searches workspace files via `/vscode-files/search` and surfaces candidate files.
2. **Authentic Blue Reference Chips (`OccurrenceChip`)**:
Selecting a candidate or pressing `Ctrl+L` with code selected inserts an authentic reference chip into the composer. The chip encodes:
- File path relative to workspace root (`toWorkspaceRelative`).
- Line range specifier (e.g. `#L15-45`).
- Clean UI pill styling with click-to-preview capability.
3. **Turn Review Card (`chat/TurnReviewCard.tsx`)**:
Inspects settled tool calls from the AI session (`extractSettledDiffs.ts`). Any file edits performed by the AI agent render as interactive diff review cards with 1-click apply/revert.
4. **Inline AI Assist (`Ctrl+K`)**:
Renders a floating prompt above selected editor text, communicating directly with the AI backend to execute polish, formatting, refactoring, or code transformations inline.
---
## 6. Build Pipeline & `tsdown` Engine
The build pipeline is coordinated by `tsdown.config.ts` and `build/tsdown.client.ts`.
```mermaid
flowchart LR
SourcesHost["src/index.ts
(Host Node Source)"]
SourcesClient["src/client/index.ts
(Client UI Source)"]
subgraph tsdown["tsdown Multi-Target Compiler"]
HostTarget["Target 1: Node22 ESM
- dts: true
- external: node:*"]
ClientTarget["Target 2: Browser CJS
- external: PLATFORM_MODULES
- lightningcss inline
- closure banner/footer"]
end
OutputHost["lib/index.js
lib/index.d.ts"]
OutputClient["lib/client.js
lib/client.js.map"]
SourcesHost --> HostTarget --> OutputHost
SourcesClient --> ClientTarget --> OutputClient
```
### 6.1 Host Target Configuration
- **Entry**: `src/index.ts`
- **Format**: `esm`
- **Target**: `node22`
- **Outputs**: `lib/index.js` (JavaScript) and `lib/index.d.ts` (TypeScript declarations).
### 6.2 Client Target Configuration
- **Entry**: `src/client/index.ts`
- **Format**: `cjs`
- **Platform**: `browser`
- **Outputs**: `lib/client.js` and `lib/client.js.map`.
- **CSS Modules & LightningCSS**:
- `cssModules()` plugin intercepts `*.module.css` and `*.css`.
- Transforms styles with `lightningcss`, hashing class names.
- Injects self-registering `