@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 +2 -2
- package/package.json +1 -1
- package/src/index.js +13 -2
- package/src/web.js +66 -1
package/README.md
CHANGED
|
@@ -153,9 +153,9 @@ pnpm run web
|
|
|
153
153
|
|
|
154
154
|
## 验证
|
|
155
155
|
|
|
156
|
-
自动测试覆盖配置持久化、Skill 多路径、编辑和删除、单技能内容编辑与删除、用户分组、并发 revision、Skill 创建与调用权限、MCP
|
|
156
|
+
自动测试覆盖配置持久化、Skill 多路径、编辑和删除、单技能内容编辑与删除、用户分组、并发 revision、Skill 创建与调用权限、MCP 工具发现和卸载、轮询状态、中英文词典一致性,以及 DSH `webServer` 前缀路由、OPTIONS 预检和 RPC 信封协议桥接。测试不需要模型 API key。
|
|
157
157
|
|
|
158
|
-
浏览器端 `/` 候选缓存失效兼容逻辑会使用公开的 `connection/reset`
|
|
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
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',
|
|
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
|
|
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
|
+
}
|