@civilization/deepseek-harness-skill-mcp 0.1.7 → 0.1.8

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/README.md CHANGED
@@ -153,9 +153,9 @@ pnpm run web
153
153
 
154
154
  ## 验证
155
155
 
156
- 自动测试覆盖配置持久化、Skill 多路径、编辑和删除、单技能内容编辑与删除、用户分组、并发 revision、Skill 创建与调用权限、MCP 工具发现和卸载、轮询状态以及中英文词典一致性。测试不需要模型 API key。
156
+ 自动测试覆盖配置持久化、Skill 多路径、编辑和删除、单技能内容编辑与删除、用户分组、并发 revision、Skill 创建与调用权限、MCP 工具发现和卸载、轮询状态、中英文词典一致性,以及 DSH `webServer` 前缀路由、OPTIONS 预检和 RPC 信封协议桥接。测试不需要模型 API key。
157
157
 
158
- 浏览器端 `/` 候选缓存失效兼容逻辑会使用公开的 `connection/reset` 客户端事件;自动测试验证 Host/RPC 和浏览器 bundle 构建,但没有在本仓库内启动完整 Harness Web 做端到端菜单点击验证。外部文件改动仍依赖 filesystem provider 先完成 watcher 失效,必要时可在变更后点击管理页“刷新”。
158
+ 浏览器端 `/` 候选缓存失效兼容逻辑会使用公开的 `connection/reset` 客户端事件;服务端通过注入 `webServer` 与 `connection` 双重挂载 `/extensions` 路由并由 Cordis effect 托管生命周期,防止动态请求掉入前端静态资源的 405 fallback。自动测试验证 Host/RPC、HTTP 桥接和浏览器 bundle 构建,但没有在本仓库内启动完整 Harness Web 做端到端菜单点击验证。外部文件改动仍依赖 filesystem provider 先完成 watcher 失效,必要时可在变更后点击管理页“刷新”。
159
159
 
160
160
  ## 发布
161
161
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@civilization/deepseek-harness-skill-mcp",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "Skill path and MCP server management for DeepSeek Harness Web",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
package/src/index.js CHANGED
@@ -4,7 +4,7 @@ import { homedir } from 'node:os'
4
4
  import { join } from 'node:path'
5
5
  import { defineTool } from '@deepseek-ai/dsh-tools'
6
6
  import { ExtensionStore } from './store.js'
7
- import { createWebHandler } from './web.js'
7
+ import { createWebHandler, createWebHttpHandler } from './web.js'
8
8
 
9
9
  export const name = 'extension-manager'
10
10
  export const inject = ['tools', 'skills']
@@ -21,8 +21,19 @@ export async function apply(ctx, config) {
21
21
  const home = process.env.DSH_HOME || join(homedir(), '.dsh')
22
22
  const store = new ExtensionStore(config.patchPath || join(home, 'profiles', 'web', 'cordis.patch.yml'))
23
23
  await store.ensureNonFatalMcpStartup()
24
+
25
+ const webHandler = createWebHandler(store, ctx)
26
+ const httpHandler = createWebHttpHandler(store, ctx)
27
+
24
28
  ctx.inject(['connection'], web => {
25
- web.connection.rpc.handle('/extensions', createWebHandler(store, ctx))
29
+ web.connection.rpc.handle('/extensions', webHandler)
30
+ })
31
+ ctx.inject(['webServer'], hostCtx => {
32
+ hostCtx.effect(() => hostCtx.webServer.register({
33
+ kind: 'prefix',
34
+ path: '/extensions',
35
+ handler: httpHandler,
36
+ }), 'extension-manager: /extensions route')
26
37
  })
27
38
  const register = (name, description, parameters, execute) => ctx.tools.register(defineTool({ name, description, parameters, output, execute }))
28
39
 
package/src/web.js CHANGED
@@ -61,7 +61,7 @@ export function createWebHandler(store, ctx) {
61
61
  try {
62
62
  if (!Object.hasOwn(schemas, endpoint)) throw new Error('Unknown operation.')
63
63
  const args = schemas[endpoint](payload)
64
- signal.throwIfAborted()
64
+ signal?.throwIfAborted()
65
65
  if (endpoint === 'add-skill') await store.addSkillPath(args.directory, signal, args.revision)
66
66
  if (endpoint === 'add-mcp') {
67
67
  const input = JSON.parse(args.configuration)
@@ -97,3 +97,68 @@ export function createWebHandler(store, ctx) {
97
97
  }
98
98
  }
99
99
  }
100
+
101
+ /** HTTP server route handler that satisfies DSH WebServer and RPC envelope protocols. */
102
+ export function createWebHttpHandler(store, ctx) {
103
+ const handler = createWebHandler(store, ctx)
104
+ return async (req, res) => {
105
+ if (req.method === 'OPTIONS') {
106
+ res.writeHead(204, {
107
+ 'access-control-allow-origin': '*',
108
+ 'access-control-allow-methods': 'POST, OPTIONS',
109
+ 'access-control-allow-headers': 'content-type, *',
110
+ })
111
+ res.end()
112
+ return
113
+ }
114
+ if (req.method !== 'POST') {
115
+ res.writeHead(405, { 'content-type': 'application/json; charset=utf-8' })
116
+ res.end(JSON.stringify({ ok: false, error: { message: 'Method Not Allowed' } }))
117
+ return
118
+ }
119
+ const pathname = new URL(req.url ?? '/', 'http://dsh.internal').pathname
120
+ const endpoint = pathname.startsWith('/extensions/') ? pathname.slice('/extensions/'.length)
121
+ : pathname === '/extensions' ? 'list' : undefined
122
+ if (!endpoint) {
123
+ res.writeHead(404, { 'content-type': 'application/json; charset=utf-8' })
124
+ res.end(JSON.stringify({ ok: false, error: { message: 'Not Found' } }))
125
+ return
126
+ }
127
+
128
+ let raw = ''
129
+ try {
130
+ for await (const chunk of req) raw += chunk.toString('utf8')
131
+ } catch {
132
+ res.writeHead(400, { 'content-type': 'application/json; charset=utf-8' })
133
+ res.end(JSON.stringify({ ok: false, error: { message: 'Failed to read request body.' } }))
134
+ return
135
+ }
136
+
137
+ let body = {}
138
+ if (raw.trim()) {
139
+ try { body = JSON.parse(raw) }
140
+ catch {
141
+ res.writeHead(400, { 'content-type': 'application/json; charset=utf-8' })
142
+ res.end(JSON.stringify({ ok: false, error: { message: 'Invalid JSON body.' } }))
143
+ return
144
+ }
145
+ }
146
+
147
+ const isRpcEnvelope = body && body.type === 'client-request' && typeof body.rpcId === 'string'
148
+ const actualEndpoint = isRpcEnvelope ? (body.method || endpoint) : endpoint
149
+ const payload = isRpcEnvelope ? (body.payload ?? {}) : body
150
+
151
+ const result = await handler(actualEndpoint, payload, req.signal)
152
+
153
+ const responseBody = isRpcEnvelope
154
+ ? { type: 'server-response', rpcId: body.rpcId, result }
155
+ : result
156
+
157
+ const data = JSON.stringify(responseBody)
158
+ res.writeHead(200, {
159
+ 'content-type': 'application/json; charset=utf-8',
160
+ 'content-length': Buffer.byteLength(data),
161
+ })
162
+ res.end(data)
163
+ }
164
+ }