dsh-env-manager-plugin 0.0.0-stage → 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 +249 -2
- package/client.js +2581 -0
- package/cordis.patch.yml +4 -0
- package/docs/00-project/current-state.md +246 -0
- package/docs/00-project/glossary.md +215 -0
- package/docs/00-project/project-brief.md +114 -0
- package/docs/00-project/roadmap.md +89 -0
- package/docs/01-architecture/DSH_Environment_Service_Memory_Skill_Brainstorm.md +493 -0
- package/docs/01-architecture/api-verification.md +416 -0
- package/docs/01-architecture/architecture-overview.md +222 -0
- package/docs/01-architecture/data-model.md +930 -0
- package/docs/01-architecture/decisions/ADR-0001-plugin-as-bundle.md +29 -0
- package/docs/01-architecture/decisions/ADR-0002-file-based-storage.md +25 -0
- package/docs/01-architecture/decisions/ADR-0003-skill-router-over-search.md +25 -0
- package/docs/01-architecture/decisions/ADR-0004-typescript-host-js-client.md +31 -0
- package/docs/01-architecture/decisions/ADR-0005-layered-rule-resolution.md +36 -0
- package/docs/01-architecture/decisions/ADR-0006-credentials-as-references.md +30 -0
- package/docs/01-architecture/decisions/ADR-0007-session-scoped-default-environment.md +28 -0
- package/docs/01-architecture/decisions/ADR-0008-memory-bridge-not-second-store.md +31 -0
- package/docs/01-architecture/decisions/ADR-0009-client-host-http-transport.md +87 -0
- package/docs/01-architecture/decisions/ADR-0010-environment-plane-model.md +87 -0
- package/docs/01-architecture/decisions/ADR-0011-k8s-node-execution-model.md +92 -0
- package/docs/01-architecture/design.md +666 -0
- package/docs/01-architecture/detailed-design.md +961 -0
- package/docs/01-architecture/implementation-plan.md +323 -0
- package/docs/01-architecture/module-boundaries.md +306 -0
- package/docs/01-architecture/security-model.md +227 -0
- package/docs/01-architecture/storage-contract.md +224 -0
- package/docs/01-architecture/tool-contract.md +438 -0
- package/docs/01-architecture/ui-design.md +587 -0
- package/docs/02-harness/agent-behavior.md +36 -0
- package/docs/02-harness/change-snapshot-policy.md +29 -0
- package/docs/02-harness/client-ui-integration.md +110 -0
- package/docs/02-harness/context-policy.md +43 -0
- package/docs/02-harness/harness-standard.md +91 -0
- package/docs/02-harness/task-execution-policy.md +26 -0
- package/docs/02-harness/verification-policy.md +97 -0
- package/docs/03-openspec/archive-rule.md +25 -0
- package/docs/03-openspec/bugfix-rule.md +30 -0
- package/docs/03-openspec/design-rule.md +33 -0
- package/docs/03-openspec/openspec-usage-guide.md +31 -0
- package/docs/03-openspec/proposal-rule.md +70 -0
- package/docs/03-openspec/spec-rule.md +33 -0
- package/docs/03-openspec/tasks-rule.md +31 -0
- package/docs/04-development/coding-style.md +67 -0
- package/docs/04-development/dependency-policy.md +43 -0
- package/docs/04-development/development-guide.md +85 -0
- package/docs/04-development/directory-convention.md +105 -0
- package/docs/04-development/local-run-guide.md +55 -0
- package/docs/04-development/testing-guide.md +102 -0
- package/docs/05-domain/deployment-template.md +76 -0
- package/docs/05-domain/environment-template.md +165 -0
- package/docs/05-domain/operation-binding-template.md +55 -0
- package/docs/05-domain/operation-record-template.md +55 -0
- package/docs/05-domain/prompt-template.md +90 -0
- package/docs/05-domain/rule-template.md +73 -0
- package/docs/05-domain/service-template.md +39 -0
- package/docs/06-operations/deployment-guide.md +54 -0
- package/docs/06-operations/release.md +188 -0
- package/docs/06-operations/security-policy.md +53 -0
- package/docs/06-operations/troubleshooting.md +91 -0
- package/docs/07-templates/adr-template.md +21 -0
- package/docs/07-templates/bugfix-template.md +11 -0
- package/docs/07-templates/case-template.md +15 -0
- package/docs/07-templates/design-template.md +29 -0
- package/docs/07-templates/proposal-template.md +19 -0
- package/docs/07-templates/spec-template.md +15 -0
- package/docs/07-templates/tasks-template.md +12 -0
- package/docs/99-archive/README.md +13 -0
- package/docs/README.md +142 -0
- package/icon.svg +4 -0
- package/index.js +8 -0
- package/lib/app/app.d.ts +67 -0
- package/lib/app/app.js +494 -0
- package/lib/app/app.js.map +1 -0
- package/lib/config.d.ts +55 -0
- package/lib/config.js +42 -0
- package/lib/config.js.map +1 -0
- package/lib/domain/constants.d.ts +21 -0
- package/lib/domain/constants.js +59 -0
- package/lib/domain/constants.js.map +1 -0
- package/lib/domain/errors.d.ts +20 -0
- package/lib/domain/errors.js +22 -0
- package/lib/domain/errors.js.map +1 -0
- package/lib/domain/schemas.d.ts +28 -0
- package/lib/domain/schemas.js +445 -0
- package/lib/domain/schemas.js.map +1 -0
- package/lib/domain/types.d.ts +412 -0
- package/lib/domain/types.js +2 -0
- package/lib/domain/types.js.map +1 -0
- package/lib/index.d.ts +18 -0
- package/lib/index.js +89 -0
- package/lib/index.js.map +1 -0
- package/lib/logger.d.ts +8 -0
- package/lib/logger.js +13 -0
- package/lib/logger.js.map +1 -0
- package/lib/memory/bridge.d.ts +18 -0
- package/lib/memory/bridge.js +13 -0
- package/lib/memory/bridge.js.map +1 -0
- package/lib/registry/integrity.d.ts +10 -0
- package/lib/registry/integrity.js +204 -0
- package/lib/registry/integrity.js.map +1 -0
- package/lib/registry/registry.d.ts +14 -0
- package/lib/registry/registry.js +182 -0
- package/lib/registry/registry.js.map +1 -0
- package/lib/resolver/merge.d.ts +23 -0
- package/lib/resolver/merge.js +90 -0
- package/lib/resolver/merge.js.map +1 -0
- package/lib/resolver/resolve.d.ts +18 -0
- package/lib/resolver/resolve.js +288 -0
- package/lib/resolver/resolve.js.map +1 -0
- package/lib/resolver/template.d.ts +10 -0
- package/lib/resolver/template.js +42 -0
- package/lib/resolver/template.js.map +1 -0
- package/lib/router/params.d.ts +3 -0
- package/lib/router/params.js +83 -0
- package/lib/router/params.js.map +1 -0
- package/lib/router/router.d.ts +11 -0
- package/lib/router/router.js +204 -0
- package/lib/router/router.js.map +1 -0
- package/lib/session/session.d.ts +16 -0
- package/lib/session/session.js +64 -0
- package/lib/session/session.js.map +1 -0
- package/lib/skill.d.ts +17 -0
- package/lib/skill.js +56 -0
- package/lib/skill.js.map +1 -0
- package/lib/storage/atomic.d.ts +10 -0
- package/lib/storage/atomic.js +107 -0
- package/lib/storage/atomic.js.map +1 -0
- package/lib/storage/paths.d.ts +10 -0
- package/lib/storage/paths.js +50 -0
- package/lib/storage/paths.js.map +1 -0
- package/lib/storage/records.d.ts +16 -0
- package/lib/storage/records.js +112 -0
- package/lib/storage/records.js.map +1 -0
- package/lib/storage/sessions.d.ts +17 -0
- package/lib/storage/sessions.js +65 -0
- package/lib/storage/sessions.js.map +1 -0
- package/lib/tools/tools.d.ts +14 -0
- package/lib/tools/tools.js +472 -0
- package/lib/tools/tools.js.map +1 -0
- package/lib/transport/http.d.ts +3 -0
- package/lib/transport/http.js +217 -0
- package/lib/transport/http.js.map +1 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +97 -4
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
# API Verification
|
|
2
|
+
|
|
3
|
+
> Status: verified against the DSH runtime shipped as `DeepSeek Harness 0.2.1-rc.5`.
|
|
4
|
+
> Method: `cordis_inspect_*` providers plus package documentation and bundled source
|
|
5
|
+
> extracted from the installed `app.asar`. This document records what the V1 design
|
|
6
|
+
> may rely on, and what it must verify again at implementation time.
|
|
7
|
+
|
|
8
|
+
## 1. Purpose
|
|
9
|
+
|
|
10
|
+
The first design draft left eight open questions about DSH integration. This document
|
|
11
|
+
answers them from the runtime itself so that `detailed-design.md`,
|
|
12
|
+
`tool-contract.md`, `storage-contract.md` and `ui-design.md` describe APIs that exist,
|
|
13
|
+
not APIs that are assumed.
|
|
14
|
+
|
|
15
|
+
Every finding has an ID (`F1`…`F21`). The design documents reference these IDs.
|
|
16
|
+
|
|
17
|
+
## 2. Findings
|
|
18
|
+
|
|
19
|
+
### F1 — Host plugin entry
|
|
20
|
+
|
|
21
|
+
A Host package is an ES module exporting any of:
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
export const name = 'dsh-env-manager'
|
|
25
|
+
export const inject = ['tools']
|
|
26
|
+
export const Config = z.object({ dataDir: z.string() })
|
|
27
|
+
export function apply(ctx, config) { /* ... */ }
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Evidence: `@deepseek-ai/dsh-shell-env` (`lib/index.js`) and `@deepseek-ai/dsh-tool-todo`.
|
|
31
|
+
`Config` is a `@deepseek-ai/schemastery` schema, not Zod. `inject` is a plain string
|
|
32
|
+
array. The loader supplies the parsed config as the second `apply` argument.
|
|
33
|
+
|
|
34
|
+
Design consequence: the Host entry keeps `apply(ctx, config)` and declares `inject`
|
|
35
|
+
explicitly; `Config` is authored with schemastery, while record validation uses Zod.
|
|
36
|
+
|
|
37
|
+
### F2 — Tool registration
|
|
38
|
+
|
|
39
|
+
`ctx.tools.register(definition)` registers globally or in the calling Agent scope and
|
|
40
|
+
returns an exact disposer. Duplicate names within one layer throw.
|
|
41
|
+
|
|
42
|
+
Tool authors should use `defineTool` from `@deepseek-ai/dsh-tools`:
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
ctx.tools.register(defineTool({
|
|
46
|
+
name: 'env_list',
|
|
47
|
+
description: '...',
|
|
48
|
+
parameters: { kind: { type: 'string', enum: [...] } },
|
|
49
|
+
output: { schema: { ... }, render: (args, value) => [{ type: 'text', text: '...' }] },
|
|
50
|
+
async execute(args, exec) { /* ... */ },
|
|
51
|
+
}))
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Evidence: `dsh-tools` README §"Register a tool"; `dsh-tool-todo` source.
|
|
55
|
+
|
|
56
|
+
Design consequence: all ten V1 tools are `defineTool` definitions, registered inside
|
|
57
|
+
`apply` and disposed with the plugin fiber.
|
|
58
|
+
|
|
59
|
+
### F3 — Parameter schema DSL
|
|
60
|
+
|
|
61
|
+
`defineTool.parameters` is DSH's own value-schema DSL, not raw JSON Schema. Supported
|
|
62
|
+
node types: `string`, `number`, `integer`, `boolean`, `null`, `array`, `object`,
|
|
63
|
+
author-only `json`, and exact-one `oneOf`. Object property `required: true` marks
|
|
64
|
+
membership; `additionalProperties` must be explicitly `true` or `false`.
|
|
65
|
+
|
|
66
|
+
Model arguments are validated before `execute` runs; invalid input becomes a normal
|
|
67
|
+
error result (`ToolArgsError`).
|
|
68
|
+
|
|
69
|
+
Design consequence: tool parameter tables in `tool-contract.md` use this DSL verbatim.
|
|
70
|
+
|
|
71
|
+
### F4 — Tool output contract
|
|
72
|
+
|
|
73
|
+
`output.schema` is a `JsonSchemaNode` restricted to:
|
|
74
|
+
`type`, `oneOf`, `properties`, `required`, `additionalProperties`, `items`, `enum`,
|
|
75
|
+
`const`, plus annotations (`description`, `title`, `default`, `examples`).
|
|
76
|
+
An **annotation-only schema** (`{ description: '…' }`) is the supported way to declare
|
|
77
|
+
unconstrained JSON.
|
|
78
|
+
|
|
79
|
+
The registry validates the returned value against `output.schema` after execution and
|
|
80
|
+
throws `ToolOutputError` on mismatch. `render(args, value)` returns `ContentBlock[]`;
|
|
81
|
+
one `{ type: 'text', text }` block is enough for a JSON-shaped tool.
|
|
82
|
+
|
|
83
|
+
Evidence: `dsh-tools` `assertSupportedJsonSchema`, `checkSchemaNode`,
|
|
84
|
+
`validateJsonSchemaValue`, and the output check at dispatch.
|
|
85
|
+
|
|
86
|
+
Design consequence: every tool output is a precise object schema where practical, and
|
|
87
|
+
uses `additionalProperties: true` for open record payloads. `render` emits a short
|
|
88
|
+
human summary, because the canonical value is what the model and programmatic callers
|
|
89
|
+
receive.
|
|
90
|
+
|
|
91
|
+
### F5 — Structured tool errors
|
|
92
|
+
|
|
93
|
+
Only a thrown `HarnessError` produces structured `{ name, code }` failure info;
|
|
94
|
+
plain `Error` produces a message only. Constructor:
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
new HarnessError(message, code, options?)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
imported from `@deepseek-ai/dsh-llm`. `name` defaults to the subclass constructor name.
|
|
101
|
+
|
|
102
|
+
Evidence: `dsh-tools` `errorInfo()` and `@deepseek-ai/dsh-llm` `HarnessError`.
|
|
103
|
+
|
|
104
|
+
Design consequence: all expected failures throw a single `EnvManagerError extends
|
|
105
|
+
HarnessError` carrying a stable code from the diagnostics catalogue. Success values
|
|
106
|
+
carry no `ok` flag; failure is the error path.
|
|
107
|
+
|
|
108
|
+
### F6 — Tool execution context
|
|
109
|
+
|
|
110
|
+
`execute(args, exec)` receives `ToolRunContext` extending `ToolExecution`:
|
|
111
|
+
|
|
112
|
+
| Member | Use |
|
|
113
|
+
|---|---|
|
|
114
|
+
| `exec.signal` | cooperative cancellation; must be observed |
|
|
115
|
+
| `exec.callId`, `exec.rootCallId` | correlation ids |
|
|
116
|
+
| `exec.agent` | optional `Agent`; `exec.agent.session.header.id` is the session id |
|
|
117
|
+
| `exec.deferContext(msg)` | attach extra model context |
|
|
118
|
+
| `exec.concludeTurn()` | end the turn (not used in V1) |
|
|
119
|
+
|
|
120
|
+
Evidence: Service contract `tools`, referenced type `ToolRunContext`; `dsh-shell-env`
|
|
121
|
+
reads `execution.agent.session.header.id`.
|
|
122
|
+
|
|
123
|
+
Design consequence: session-scoped tools require `exec.agent`; without it they fail
|
|
124
|
+
with `SESSION_CONTEXT_UNAVAILABLE` instead of guessing a session.
|
|
125
|
+
|
|
126
|
+
### F7 — Approval
|
|
127
|
+
|
|
128
|
+
`ctx.approval.request(req)` returns
|
|
129
|
+
`'allowed-once' | 'rejected' | 'cancelled' | 'unavailable'`.
|
|
130
|
+
Only `'allowed-once'` is a grant; every other outcome must be treated as denial.
|
|
131
|
+
The request needs `agent`, `toolName`, optional `callId`, `reason`, `displayReason`
|
|
132
|
+
(localized), and a `signal`. It requires an open turn.
|
|
133
|
+
|
|
134
|
+
Evidence: Service contract `approval`.
|
|
135
|
+
|
|
136
|
+
Design consequence: `env_delete` and production `env_save` call
|
|
137
|
+
`ctx.approval.request` with a localized `displayReason`, and fail closed on any
|
|
138
|
+
non-grant. Approval is per call; nothing is cached.
|
|
139
|
+
|
|
140
|
+
### F8 — Credentials
|
|
141
|
+
|
|
142
|
+
`ctx.credentials.describe(ref)` returns `{ configured, source?, writable }` without
|
|
143
|
+
the value. `set(ref, value)` writes to a provider-managed writable source and
|
|
144
|
+
`unset(ref)` removes it. `resolve(ref)` returns the value and must not be called by this
|
|
145
|
+
plugin. `CredentialRef` is a branded string; at runtime an ordinary string is accepted.
|
|
146
|
+
|
|
147
|
+
Evidence: Service contract `credentials`; live `POST /env-manager/api` with
|
|
148
|
+
`op: "credential"` returned `{ ref, configured: true }`, and `op: "credentialUnset"`
|
|
149
|
+
returned `{ ref, removed: true }`. The panel used the same path to create an environment,
|
|
150
|
+
then the record was read back and contained only the ref.
|
|
151
|
+
|
|
152
|
+
Design consequence: `env_resolve` reports each `credentialRef` with
|
|
153
|
+
`configured`/`source` only. Credential values may appear in the request body of
|
|
154
|
+
`credential`, but never in a tool result, HTTP response, log line, diagnostic or registry
|
|
155
|
+
file.
|
|
156
|
+
|
|
157
|
+
### F9 — Filesystem capability gap
|
|
158
|
+
|
|
159
|
+
`ctx.fs` exposes `resolve`, `stat`, `listDir`, `readText`, `writeText` (atomic),
|
|
160
|
+
`editText`, `readBytes`, `readByteRange`, `watch`, `contains` — but **no** `mkdir`,
|
|
161
|
+
`unlink`, `rename`, or `copy`. `resolve` follows real paths, so it cannot target a
|
|
162
|
+
file whose parent directory does not exist yet.
|
|
163
|
+
|
|
164
|
+
Evidence: Service contract `fs`.
|
|
165
|
+
|
|
166
|
+
Design consequence: registry storage uses `node:fs/promises` directly, confined to
|
|
167
|
+
the resolved `dataDir`. The plugin is trusted Host code, not a sandboxed tool. Every
|
|
168
|
+
path is built from a validated id and checked with `path.resolve` containment; no
|
|
169
|
+
model-supplied path reaches the filesystem.
|
|
170
|
+
|
|
171
|
+
### F10 — Data directory
|
|
172
|
+
|
|
173
|
+
`ctx.get('profileContext')` exposes at least `name` and `dir` at runtime
|
|
174
|
+
(`dsh-shell-env` reads both), although the inspect contract lists only four other
|
|
175
|
+
members. `dir` is the profile directory that owns `node_modules`, patches and data.
|
|
176
|
+
|
|
177
|
+
Evidence: `dsh-shell-env` `collect()`; Service contract `profileContext`.
|
|
178
|
+
|
|
179
|
+
Design consequence: `dataDir` resolution order:
|
|
180
|
+
|
|
181
|
+
1. `config.dataDir` (absolute, or relative to the profile directory)
|
|
182
|
+
2. `<profileContext.dir>/data/dsh-env-manager`
|
|
183
|
+
3. `$DSH_PROFILE_DIR/data/dsh-env-manager`
|
|
184
|
+
4. `<cwd>/data`
|
|
185
|
+
|
|
186
|
+
The resolved path is logged once and exposed read-only through `env_get_context` and
|
|
187
|
+
the UI overview. Task T1 verifies `profileContext.dir` on a live profile.
|
|
188
|
+
|
|
189
|
+
### F11 — Session identity and state
|
|
190
|
+
|
|
191
|
+
`exec.agent.session` is a live `Session` with `header.id`, `append(type, data)` and
|
|
192
|
+
`snapshotEvents()`. The session id is available for every agent-initiated tool call.
|
|
193
|
+
|
|
194
|
+
There is a `sessionProjections` service (used by `todo_write`) for replayable plugin
|
|
195
|
+
state, but it is designed around declared session event types and projection schemas;
|
|
196
|
+
`SessionEventMap` is a closed TypeScript interface.
|
|
197
|
+
|
|
198
|
+
Design consequence: V1 stores the default environment in
|
|
199
|
+
`<dataDir>/sessions/<sessionId>.json` (small, explicit, testable, no session-log
|
|
200
|
+
schema changes). A projection-based implementation is recorded as a P2 alternative.
|
|
201
|
+
|
|
202
|
+
### F12 — HTTP carrier
|
|
203
|
+
|
|
204
|
+
`ctx.webServer.register({ kind: 'prefix' | 'exact', path, handler })` registers a
|
|
205
|
+
route and returns a disposer; duplicate `(kind, path)` throws.
|
|
206
|
+
`getOrigin()` returns the local origin. `tapIndex(transform)` can inject markup.
|
|
207
|
+
|
|
208
|
+
Evidence: Service contract `webServer`.
|
|
209
|
+
|
|
210
|
+
Design consequence: the Client panel talks to the Host through
|
|
211
|
+
`POST /env-manager/api` registered by this plugin, authenticated by a required custom
|
|
212
|
+
header plus an `Origin` check. See ADR-0009 and `ui-design.md` §4.
|
|
213
|
+
|
|
214
|
+
### F13 — Client module
|
|
215
|
+
|
|
216
|
+
A bundle's `client.js` is served as `dsh.client`'s `./client` export and loaded by
|
|
217
|
+
`window.__ModuleLoader__.load({ id, factory(require) { ... } })`. The factory may
|
|
218
|
+
`require('react')`. The returned plugin declares `inject` services (`slots`,
|
|
219
|
+
`locale`, …) and `apply(ctx)`.
|
|
220
|
+
|
|
221
|
+
The dynamic-Cordis builtins (`React`, `host.call`, `styles`, `console`) belong to
|
|
222
|
+
runtime-defined packages, **not** to installed bundle client halves.
|
|
223
|
+
|
|
224
|
+
Evidence: `client.js` skeleton; `Slots`/`Service` client catalog; `dsh-cordis-client-runner`.
|
|
225
|
+
|
|
226
|
+
Design consequence: the Client uses `slots` and `locale` only, plus browser `fetch`
|
|
227
|
+
for the plugin's own HTTP route.
|
|
228
|
+
|
|
229
|
+
### F14 — Client-to-Host Remote is not usable here
|
|
230
|
+
|
|
231
|
+
DSH's native Client↔Host RPC is the Typert Remote system:
|
|
232
|
+
`ctx.remote.$mount()` mounts a **generated** Host-for-Client contribution; strict
|
|
233
|
+
descriptors come from `@Remote`/`@RemoteScope` decorators processed by the build-time
|
|
234
|
+
Typert loader into `ctx.typert.local` (`/remote` artifacts imported by the Client).
|
|
235
|
+
Client-supplied fields without strict generated codecs fail before methods become
|
|
236
|
+
callable. SRC mode is a development fallback for endpoints without a strict
|
|
237
|
+
definition and does not give a standalone bundle a typed namespace.
|
|
238
|
+
|
|
239
|
+
Evidence: `dsh-api-gateway` README (Host and Client sections), `dsh-api-remotes`
|
|
240
|
+
README ("capability set is fixed by explicit build-time value imports"), client
|
|
241
|
+
Service catalog (no `remote` service until the gateway Client face mounts one).
|
|
242
|
+
|
|
243
|
+
Design consequence: a standalone plugin bundle cannot add a Remote namespace without
|
|
244
|
+
adopting DSH's build toolchain. V1 therefore uses the plugin-owned HTTP route
|
|
245
|
+
(ADR-0009), which is same-origin, token-free, header-gated, and reuses the exact same
|
|
246
|
+
application handlers as the tools.
|
|
247
|
+
|
|
248
|
+
### F15 — Slots
|
|
249
|
+
|
|
250
|
+
Live topology confirms:
|
|
251
|
+
|
|
252
|
+
| Slot | Kind | Registration |
|
|
253
|
+
|---|---|---|
|
|
254
|
+
| `sidebar.panellist` | list | `{ id, order?, label? }` — one sidebar icon |
|
|
255
|
+
| `main` | keyed | `{ key }` — the selected panel body |
|
|
256
|
+
| `plugins.bundle.config` | keyed | `{ key: '<package name>' }`, `view: 'page'` only |
|
|
257
|
+
| `conversation.composer.dock` | list | `{ id, order?, label? }` — ambient composer entry |
|
|
258
|
+
|
|
259
|
+
`ctx.slots.inject(slotName, () => ctx.slots.register({ name, ... }, Component))` waits
|
|
260
|
+
for the slot to exist and disposes with the fiber.
|
|
261
|
+
|
|
262
|
+
Design consequence: the manager registers a `sidebar.panellist` icon
|
|
263
|
+
(id `env-manager`), a `main` panel keyed `env-manager`, and optionally a bundle
|
|
264
|
+
config page. The composer dock is not used for the main UI.
|
|
265
|
+
|
|
266
|
+
### F16 — Client services
|
|
267
|
+
|
|
268
|
+
Available to the Client half: `layout`, `locale`, `sessions`, `slots`, `theme`,
|
|
269
|
+
`timer`, `uiWorkspace`, `workspaces`.
|
|
270
|
+
|
|
271
|
+
- `ctx.locale.register(ns, { en, zh })` and `ctx.locale.bind(ns)` provide copy.
|
|
272
|
+
- `ctx.theme` owns theme selection; the plugin only consumes CSS variables.
|
|
273
|
+
- `ctx.layout.selectPanel(id)` can select the panel programmatically.
|
|
274
|
+
|
|
275
|
+
Design consequence: UI copy is registered as namespace `envManager`; no string is
|
|
276
|
+
hardcoded twice.
|
|
277
|
+
|
|
278
|
+
### F17 — Theme tokens
|
|
279
|
+
|
|
280
|
+
The current theme exposes `--dsw-alias-*` tokens:
|
|
281
|
+
`bg-base`, `bg-layer-1`, `bg-layer-2`, `bg-overlay`, `border-l1`, `border-l2`,
|
|
282
|
+
`brand-primary`, `label-primary`, `label-secondary`, `state-error-primary`,
|
|
283
|
+
`state-idle-primary`, `state-success-primary`, `state-warn-primary`, and
|
|
284
|
+
`--dsw-specific-sidebar-fill`.
|
|
285
|
+
|
|
286
|
+
Design consequence: the panel styles itself only with these variables; no hardcoded
|
|
287
|
+
palette, including the sidebar icon (except its own brand mark color).
|
|
288
|
+
|
|
289
|
+
### F18 — Config schema shape
|
|
290
|
+
|
|
291
|
+
`Config` is `@deepseek-ai/schemastery` (`z.object({...})`), and the loader rejects
|
|
292
|
+
invalid config before `apply`. There is no runtime config *write* API for a plugin;
|
|
293
|
+
users edit the profile patch. The Client may show the effective config read-only.
|
|
294
|
+
|
|
295
|
+
Design consequence: the bundle config page is informational; `dataDir` is changed in
|
|
296
|
+
the profile patch or `cordis.patch.yml`, not through the UI.
|
|
297
|
+
|
|
298
|
+
### F19 — Package layout and bundling
|
|
299
|
+
|
|
300
|
+
The bundle needs `index.js` (Host entry), `client.js` (Client entry),
|
|
301
|
+
`cordis.patch.yml`, `package.json` with `dsh.bundle.patch` and `dsh.client`.
|
|
302
|
+
DSH itself uses prebuilt ESM in `lib/` and ships no bundler requirement for the Host.
|
|
303
|
+
The Client half is a single plain-JS module.
|
|
304
|
+
|
|
305
|
+
Design consequence: TypeScript Host compiled to `lib/`, `index.js` as a thin
|
|
306
|
+
re-export shim, no client bundler. This matches ADR-0004.
|
|
307
|
+
|
|
308
|
+
### F20 — Skill catalog
|
|
309
|
+
|
|
310
|
+
The Host exposes a `skills` service:
|
|
311
|
+
|
|
312
|
+
```ts
|
|
313
|
+
ctx.skills.list({ scope?, cwd?, signal? }): Promise<SkillSummary[]>
|
|
314
|
+
ctx.skills.snapshot({ scope?, cwd?, signal? }): Promise<{ skills: SkillSummary[]; complete: boolean }>
|
|
315
|
+
ctx.skills.get(name, { scope?, cwd? }): Promise<SkillSummary | undefined>
|
|
316
|
+
ctx.skills.register(...), ctx.skills.registerProvider(...)
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
`SkillSummary` fields: `name`, `description`, `whenToUse?`, `invocation`, `source`,
|
|
320
|
+
`provider`, `path?`, `resourceBase?`. There is **no digest field**, and no API returns
|
|
321
|
+
Skill file contents or a content hash.
|
|
322
|
+
|
|
323
|
+
`snapshot.complete === false` means the catalog is incomplete for the requested scope;
|
|
324
|
+
absence of a name then proves nothing.
|
|
325
|
+
|
|
326
|
+
Design consequence:
|
|
327
|
+
|
|
328
|
+
- Availability checks use `snapshot`, not a name-only `get`.
|
|
329
|
+
- A missing/service-unavailable/incomplete catalog yields `available: 'unknown'` with a
|
|
330
|
+
warning, never a silent pass.
|
|
331
|
+
- Digest pinning is unsupported. `SkillReference` uses `expectedSource` and
|
|
332
|
+
`expectedProvider`; `expectedDigest` was removed.
|
|
333
|
+
|
|
334
|
+
### F21 — Runtime Skill registration from a plugin
|
|
335
|
+
|
|
336
|
+
`ctx.skills.register(skill)` is the official Host API for a plugin-owned runtime Skill:
|
|
337
|
+
|
|
338
|
+
```ts
|
|
339
|
+
const dispose = ctx.skills.register({
|
|
340
|
+
name: 'dsh-env-manager',
|
|
341
|
+
description: '...',
|
|
342
|
+
whenToUse: '...',
|
|
343
|
+
content: '# ...',
|
|
344
|
+
source: 'runtime',
|
|
345
|
+
invocation: { modelInvocable: true, userInvocable: true },
|
|
346
|
+
})
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
Evidence: Service contract `skills` (methods `register`, `registerProvider`, `list`,
|
|
350
|
+
`snapshot`, `get`; types `SkillRegistration`, `SkillDefinition`, `SkillSummary`,
|
|
351
|
+
`SkillInvocationPolicy`). The service description states that an unscoped registration
|
|
352
|
+
files into the **global layer**, while a registration made in a scoped context (for
|
|
353
|
+
example an agent preset) files into that scope. The disposer unregisters the provider
|
|
354
|
+
and invalidates catalog caches.
|
|
355
|
+
|
|
356
|
+
A live registration was verified in the `desktop` profile: after rebuilding and
|
|
357
|
+
reloading `dsh-env-manager`, the Skill catalog added `dsh-env-manager`, and the `skill`
|
|
358
|
+
tool returned the registered content. This is runtime registration, not a filesystem
|
|
359
|
+
`SKILL.md`; no workspace file or separate Skill installation is involved.
|
|
360
|
+
|
|
361
|
+
Design consequence:
|
|
362
|
+
|
|
363
|
+
- The bundled `dsh-env-manager` discovery Skill is registered in `apply()` with
|
|
364
|
+
`ctx.get('skills')`, inside the plugin effect so it is disposed with the fiber.
|
|
365
|
+
- The Skill is visible in the same profile's other workspaces; it remains a catalog
|
|
366
|
+
summary until invoked, so it does not put the full workflow in every system prompt.
|
|
367
|
+
- `config.registerSkill` defaults to `true`; setting it to `false` suppresses the entry.
|
|
368
|
+
- `ctx.skills` is optional. If the service is absent or lacks `register`, the plugin
|
|
369
|
+
keeps its tools and panel and skips the Skill.
|
|
370
|
+
- Operation execution still requires an `operationBinding`; the discovery Skill does
|
|
371
|
+
not execute the routed Skill.
|
|
372
|
+
|
|
373
|
+
## 3. Consequences for the V1 design
|
|
374
|
+
|
|
375
|
+
1. **Tool contract is concrete.** Ten `defineTool` definitions with a parameter DSL,
|
|
376
|
+
output schema, render function and `HarnessError` codes.
|
|
377
|
+
2. **Transport is plugin-owned HTTP.** No Typert codegen; one same-origin route; the
|
|
378
|
+
same application service answers tools and UI.
|
|
379
|
+
3. **Storage uses Node fs.** `ctx.fs` cannot create or delete directories/files, so
|
|
380
|
+
the plugin owns `<dataDir>` with `node:fs/promises` and its own atomic writes.
|
|
381
|
+
4. **Approval is per tool call.** Delete and production writes ask the human, and any
|
|
382
|
+
outcome other than `allowed-once` denies.
|
|
383
|
+
5. **Session context is file-backed.** No session-log schema changes in V1.
|
|
384
|
+
6. **UI is a real panel.** Sidebar entry + `main` panel; no composer-dock widget.
|
|
385
|
+
|
|
386
|
+
## 4. Remaining verification tasks (implementation phase)
|
|
387
|
+
|
|
388
|
+
| ID | Verify | Fallback | Status |
|
|
389
|
+
|---|---|---|---|
|
|
390
|
+
| V1 | `profileContext.dir` present and writable | `$DSH_PROFILE_DIR`, then `<cwd>/data` | Verified: live `meta` reports `C:\Users\18811\.dsh\profiles\desktop\data` via the env fallback |
|
|
391
|
+
| V2 | `slots.register` accepts `main` with `key` | register under `main.conversation` | Verified live: `sidebar.panellist` occupant `dsh-env-manager` and `main` key `dsh-env-manager` are active |
|
|
392
|
+
| V3 | `fetch('/env-manager/api')` works from the bundle | fall back to Remote when DSH exposes runtime descriptors | Verified: live `POST /env-manager/api` returns `200` and a save/list/remove round-trip succeeds |
|
|
393
|
+
| V4 | `approval.request` from a tool body resolves | show a UI confirmation and deny without approval | Implemented fail-closed; not exercised live |
|
|
394
|
+
| V5 | Output schema accepts `additionalProperties: true` objects | annotation-only schema | Verified locally: all ten schemas pass `assertObjectJsonSchema` |
|
|
395
|
+
| V6 | `CredentialRef` accepts a plain string at runtime | import the branded factory | Implemented with string refs; live `describe` not exercised |
|
|
396
|
+
| V7 | Directory watcher parity on Windows | reload per request (already the V1 plan) | Verified: each request reloads the registry |
|
|
397
|
+
| V8 | `webServer` route prefix does not collide with shell routes | choose a more specific prefix | Verified live: `/env-manager/api` is served beside the GUI |
|
|
398
|
+
|
|
399
|
+
## 5. Evidence index
|
|
400
|
+
|
|
401
|
+
| Source | What it proves |
|
|
402
|
+
|---|---|
|
|
403
|
+
| `cordis_inspect_query(host, Service, tools)` | `register` signature, `ToolDefinition`, `ToolRunContext` |
|
|
404
|
+
| `cordis_inspect_query(host, Service, approval)` | approval outcomes, request fields |
|
|
405
|
+
| `cordis_inspect_query(host, Service, credentials)` | describe/resolve split |
|
|
406
|
+
| `cordis_inspect_query(host, Service, fs)` | capability gap (no mkdir/unlink) |
|
|
407
|
+
| `cordis_inspect_query(host, Service, skills)` | `snapshot`/`list`/`get`/`register`, `SkillRegistration`, no digest |
|
|
408
|
+
| Live `dsh-env-manager` registration | Skill catalog gained `dsh-env-manager`; `skill` tool loaded its content |
|
|
409
|
+
| `cordis_inspect_query(host, Service, webServer)` | route registration, origin |
|
|
410
|
+
| `cordis_inspect_query(client, Slots, listSubTree)` | slot topology and registration fields |
|
|
411
|
+
| `cordis_inspect_query(client, Builtin, listBuiltins)` | dynamic-half builtins (`host.call`) |
|
|
412
|
+
| `cordis_inspect_query(client, Theme, listTokens)` | `--dsw-alias-*` tokens |
|
|
413
|
+
| `dsh-tools` README + `lib/index.js` | `defineTool`, schema subset, output validation, `HarnessError` |
|
|
414
|
+
| `dsh-shell-env` `lib/index.js` | `Config` schemastery, `profileContext.dir`, session id |
|
|
415
|
+
| `dsh-api-gateway` / `dsh-api-remotes` README | Typert Remote requires generated descriptors |
|
|
416
|
+
| `dsh-tool-todo` `lib/index.js` | a complete tool definition in practice |
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# Architecture Overview
|
|
2
|
+
|
|
3
|
+
Status: V1.
|
|
4
|
+
|
|
5
|
+
Related: [design.md](./design.md), [detailed-design.md](./detailed-design.md), [module-boundaries.md](./module-boundaries.md), [data-model.md](./data-model.md).
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. Layers
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
DSH Web / Desktop UI
|
|
13
|
+
└── Client module (client.js): panels, context preview, YAML editor
|
|
14
|
+
│
|
|
15
|
+
▼ POST /env-manager/api
|
|
16
|
+
DSH Host process
|
|
17
|
+
├── transport header/origin/size checks, op dispatch
|
|
18
|
+
├── tools Agent-facing schemas, approval gates
|
|
19
|
+
├── app use cases shared by tools and transport
|
|
20
|
+
├── session per-session default environment
|
|
21
|
+
├── router operation → Skill matching
|
|
22
|
+
├── resolver context + rule resolution with provenance
|
|
23
|
+
├── registry validated in-memory snapshot
|
|
24
|
+
├── storage atomic YAML persistence
|
|
25
|
+
└── memory-bridge (P1 contract, null adapter in V1)
|
|
26
|
+
│
|
|
27
|
+
▼
|
|
28
|
+
Plugin data directory (YAML records + session JSON)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`tools` and `transport` are sibling adapters over `app`; neither contains business
|
|
32
|
+
logic. This is the core boundary that keeps the Agent path and the UI path consistent.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 2. Major components
|
|
37
|
+
|
|
38
|
+
### 2.1 Client module
|
|
39
|
+
|
|
40
|
+
Runs inside DSH Web/Desktop as a `sidebar.panellist` entry plus the `main` panel keyed
|
|
41
|
+
`env-manager`.
|
|
42
|
+
|
|
43
|
+
Responsibilities:
|
|
44
|
+
|
|
45
|
+
- Environment, service, deployment, rule and binding lists and YAML editors.
|
|
46
|
+
- Context preview with provenance badges.
|
|
47
|
+
- Operation preview with skill availability and conflict display.
|
|
48
|
+
- Validation and diagnostics display.
|
|
49
|
+
- Locale-aware text (`en`, `zh`).
|
|
50
|
+
|
|
51
|
+
It calls the plugin HTTP API and renders the results. It holds no business logic and no
|
|
52
|
+
durable state.
|
|
53
|
+
|
|
54
|
+
### 2.2 Tools
|
|
55
|
+
|
|
56
|
+
The Agent-facing surface: ten `env_*` tools.
|
|
57
|
+
|
|
58
|
+
Read tools answer questions about the registry. Management tools validate and write
|
|
59
|
+
records, with approval gates for production.
|
|
60
|
+
|
|
61
|
+
Tool handlers are thin; they resolve ids, call `app`, and map results to the declared
|
|
62
|
+
JSON value and `render` text. Expected failures throw `EnvManagerError`.
|
|
63
|
+
|
|
64
|
+
### 2.3 Transport
|
|
65
|
+
|
|
66
|
+
The Client-facing surface: one same-origin route, `POST /env-manager/api`.
|
|
67
|
+
|
|
68
|
+
- Requires `x-env-manager-request: 1`, JSON content type and a same-origin `Origin`.
|
|
69
|
+
- Body limit 2 MiB; no CORS, no cookies.
|
|
70
|
+
- Dispatches `{ op, payload }` to `app` and maps codes to HTTP statuses.
|
|
71
|
+
|
|
72
|
+
### 2.4 App
|
|
73
|
+
|
|
74
|
+
The use-case layer shared by both adapters.
|
|
75
|
+
|
|
76
|
+
- Composes storage, registry, resolver, router and session.
|
|
77
|
+
- Applies the record cap, timestamps, actor policy and dry-run.
|
|
78
|
+
- Reloads the registry after a write.
|
|
79
|
+
- Returns domain results or throws `EnvManagerError`.
|
|
80
|
+
|
|
81
|
+
### 2.5 Registry
|
|
82
|
+
|
|
83
|
+
Loads all record files into an in-memory snapshot and validates them.
|
|
84
|
+
|
|
85
|
+
- Ids are unique per record type.
|
|
86
|
+
- References resolve.
|
|
87
|
+
- Invalid files are skipped with diagnostics, never deleted.
|
|
88
|
+
- V1 loads at the start of every application request; a write triggers a reload before
|
|
89
|
+
returning.
|
|
90
|
+
|
|
91
|
+
### 2.6 Resolver
|
|
92
|
+
|
|
93
|
+
Pure functions that combine records into a `ServiceContext`.
|
|
94
|
+
|
|
95
|
+
- Merges rule layers field by field.
|
|
96
|
+
- Resolves `${...}` templates with cycle and undefined-variable detection.
|
|
97
|
+
- Rejects path traversal.
|
|
98
|
+
- Attaches provenance to every returned field.
|
|
99
|
+
|
|
100
|
+
### 2.7 Router
|
|
101
|
+
|
|
102
|
+
Pure functions that map an operation to a bound Skill.
|
|
103
|
+
|
|
104
|
+
- Groups bindings by match level.
|
|
105
|
+
- Fails on same-level conflicts.
|
|
106
|
+
- Checks Skill availability through an injected interface (`ctx.skills.snapshot`).
|
|
107
|
+
- Evaluates approval and production policy.
|
|
108
|
+
- Maps parameters and returns diagnostics.
|
|
109
|
+
|
|
110
|
+
The router never executes anything.
|
|
111
|
+
|
|
112
|
+
### 2.8 Session
|
|
113
|
+
|
|
114
|
+
Stores the current session's default environment.
|
|
115
|
+
|
|
116
|
+
- Per-session isolation.
|
|
117
|
+
- V1 persists `sessions/<sessionId>.json`; a projection is a later refinement.
|
|
118
|
+
- Never affects other sessions or registry files.
|
|
119
|
+
|
|
120
|
+
### 2.9 Memory bridge
|
|
121
|
+
|
|
122
|
+
P1 integration with the memory plugin.
|
|
123
|
+
|
|
124
|
+
- V1 defines types and a null adapter.
|
|
125
|
+
- Emits redacted operation records.
|
|
126
|
+
- Proposes rule/skill changes for review.
|
|
127
|
+
- Never applies changes.
|
|
128
|
+
|
|
129
|
+
### 2.10 Storage
|
|
130
|
+
|
|
131
|
+
Atomic YAML persistence.
|
|
132
|
+
|
|
133
|
+
- Directory per record type.
|
|
134
|
+
- Temp file + fsync + rename.
|
|
135
|
+
- Rolling backups (5 per record).
|
|
136
|
+
- Bounded and validated.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## 3. Request flows
|
|
141
|
+
|
|
142
|
+
### 3.1 Resolve a service context
|
|
143
|
+
|
|
144
|
+
```text
|
|
145
|
+
env_resolve(environmentId?, serviceId, deploymentId?)
|
|
146
|
+
→ tools: validate input, resolve session default if needed
|
|
147
|
+
→ app: fetch env, service, deployments from the registry
|
|
148
|
+
→ resolver: select deployment, merge rules, resolve templates
|
|
149
|
+
→ app: attach credential status (describe only)
|
|
150
|
+
→ tools: return ServiceContext
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### 3.2 Route an operation
|
|
154
|
+
|
|
155
|
+
```text
|
|
156
|
+
env_resolve_operation(operation, environmentId?, serviceId?, deploymentId?)
|
|
157
|
+
→ resolve context (above)
|
|
158
|
+
→ router: classify bindings, detect conflict, snapshot skills, evaluate policy
|
|
159
|
+
→ app: resolve parameters
|
|
160
|
+
→ tools: return OperationResolution
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### 3.3 Save a record
|
|
164
|
+
|
|
165
|
+
```text
|
|
166
|
+
env_save(kind, record, dryRun?)
|
|
167
|
+
→ tools: parse the JSON parameter
|
|
168
|
+
→ app: validate with Zod, stamp timestamps, enforce the record cap
|
|
169
|
+
→ approval gate when production is affected
|
|
170
|
+
→ storage: atomic write + backup
|
|
171
|
+
→ app: reload registry, re-run integrity checks
|
|
172
|
+
→ tools: return id, created flag, warnings
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### 3.4 Validate the registry
|
|
176
|
+
|
|
177
|
+
```text
|
|
178
|
+
env_validate
|
|
179
|
+
→ app: load snapshot and integrity diagnostics
|
|
180
|
+
→ tools: summarize counts and diagnostics
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### 3.5 UI read and write
|
|
184
|
+
|
|
185
|
+
```text
|
|
186
|
+
client: call('record.get', { kind, id })
|
|
187
|
+
→ transport: header + origin + size checks
|
|
188
|
+
→ app.get(kind, id)
|
|
189
|
+
→ transport: { ok: true, data: { record, yaml } }
|
|
190
|
+
→ client: render the editor
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Writes follow the same path with actor `'ui'` and the production policy in
|
|
194
|
+
`writePolicy.allowUiProductionWrites`.
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## 4. Cross-cutting concerns
|
|
199
|
+
|
|
200
|
+
| Concern | Approach |
|
|
201
|
+
|---|---|
|
|
202
|
+
| Credentials | references only; status via `describe` |
|
|
203
|
+
| Approval | policy in router; `ctx.approval` for management writes |
|
|
204
|
+
| Rule safety | interpolation only, cycle/traversal detection |
|
|
205
|
+
| Redaction | shared function before diagnostics and P1 records |
|
|
206
|
+
| Persistence | atomic write, bounded backups, no hidden state |
|
|
207
|
+
| Performance | small YAML scans; no indexes in V1 |
|
|
208
|
+
| Compatibility | `schemaVersion` gate; unknown versions skipped |
|
|
209
|
+
| Client security | same-origin route, custom header, no CORS, actor policy |
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## 5. What is deliberately absent
|
|
214
|
+
|
|
215
|
+
- No network calls.
|
|
216
|
+
- No remote execution.
|
|
217
|
+
- No database.
|
|
218
|
+
- No vector search.
|
|
219
|
+
- No background jobs.
|
|
220
|
+
- No telemetry.
|
|
221
|
+
|
|
222
|
+
Each absence is a V1 scope decision, not an oversight.
|