@dsh-plus/agent-preset-rea 0.1.1 → 0.1.2
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/lib/client.js +363 -105
- package/lib/index.d.ts +127 -20
- package/lib/index.js +510 -196
- package/package.json +2 -2
- package/src/client/api.ts +7 -6
- package/src/client/card.tsx +8 -4
- package/src/client/client.ts +2 -2
- package/src/client/draft.ts +21 -2
- package/src/client/i18n.ts +42 -2
- package/src/client/install.tsx +157 -58
- package/src/client/toggles.tsx +57 -0
- package/src/config.ts +34 -5
- package/src/definition.ts +6 -6
- package/src/dto.ts +29 -3
- package/src/exec.ts +34 -12
- package/src/index.ts +21 -1
- package/src/manager.ts +14 -9
- package/src/reverse.ts +335 -0
- package/src/routes.ts +108 -66
- package/src/skill.ts +23 -8
package/src/routes.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 受管安装/更新的 HTTP 通道:状态查询 +
|
|
2
|
+
* 受管安装/更新的 HTTP 通道:状态查询 + 四类动作(安装/更新 REA、更新 REA skill、
|
|
3
|
+
* 更新 reverse-skill、取消作业)。
|
|
3
4
|
*
|
|
4
5
|
* 只挂在 dsh web 同源(webserver 默认 127.0.0.1,与 notify-email 的测试端点同等暴露面):
|
|
5
|
-
* 这些端点会 spawn npm 并写插件数据目录,不做鉴权以外的额外暴露面扩张。
|
|
6
|
+
* 这些端点会 spawn npm/curl 并写插件数据目录,不做鉴权以外的额外暴露面扩张。
|
|
6
7
|
* 配置读写仍走官方 settings(卡片直连 remote.settings),这里只补官方传输没有的动作。
|
|
7
8
|
*
|
|
8
9
|
* 请求体只接受一个可选字段 `proxyPort`(0..65535);其余一律来自插件配置。
|
|
@@ -15,10 +16,11 @@ import type { Context } from '@deepseek-ai/cordis'
|
|
|
15
16
|
import type {} from '@deepseek-ai/dsh-host-webserver'
|
|
16
17
|
|
|
17
18
|
import type { AgentPresetReaConfig } from './config.ts'
|
|
18
|
-
import type { JobActionDto, StatusDto } from './dto.ts'
|
|
19
|
+
import type { JobActionDto, JobKind, StatusDto } from './dto.ts'
|
|
19
20
|
import type { JobBoard } from './jobs.ts'
|
|
20
21
|
import type { ReaManager } from './manager.ts'
|
|
21
22
|
import { type McpLaunch, reaMcpLaunch } from './mcp.ts'
|
|
23
|
+
import { reversePaths, reverseSkillDirs } from './reverse.ts'
|
|
22
24
|
|
|
23
25
|
/** 路由前缀。 */
|
|
24
26
|
export const ROUTE_BASE = '/dsh-plus/agent-preset-rea'
|
|
@@ -33,6 +35,18 @@ export interface ReaRoutesOptions {
|
|
|
33
35
|
config: () => AgentPresetReaConfig
|
|
34
36
|
}
|
|
35
37
|
|
|
38
|
+
/** 路由处理器形态。 */
|
|
39
|
+
type RouteHandler = (req: IncomingMessage, res: ServerResponse) => Promise<void>
|
|
40
|
+
|
|
41
|
+
/** 响应发送器。 */
|
|
42
|
+
type Send = (res: ServerResponse, status: number, body: unknown) => void
|
|
43
|
+
|
|
44
|
+
/** 日志窄面。 */
|
|
45
|
+
interface RouteLogger {
|
|
46
|
+
info(message: string): void
|
|
47
|
+
warn(message: string): void
|
|
48
|
+
}
|
|
49
|
+
|
|
36
50
|
/** 读请求体(超限即拒绝;空体按空对象)。 */
|
|
37
51
|
async function readBody(req: IncomingMessage): Promise<unknown> {
|
|
38
52
|
const chunks: Buffer[] = []
|
|
@@ -66,11 +80,18 @@ export function statusPayload(options: ReaRoutesOptions): StatusDto {
|
|
|
66
80
|
const launch: McpLaunch | null = reaMcpLaunch(config, runtime.install)
|
|
67
81
|
const mode =
|
|
68
82
|
config.mcpCommand.trim() !== '' ? 'custom' : launch === null ? 'missing' : 'installed'
|
|
83
|
+
const reverse = reversePaths()
|
|
84
|
+
const activeDirs = reverseSkillDirs(config, runtime.reverse)
|
|
69
85
|
return {
|
|
70
86
|
config: {
|
|
71
87
|
reaVersion: config.reaVersion,
|
|
72
88
|
proxyPort: config.proxyPort,
|
|
73
89
|
mcpEnabled: config.mcpEnabled,
|
|
90
|
+
skillEnabled: config.skillEnabled,
|
|
91
|
+
reverseSkillEnabled: config.reverseSkillEnabled,
|
|
92
|
+
reverseSkillRepo: config.reverseSkillRepo,
|
|
93
|
+
reverseSkillRef: config.reverseSkillRef,
|
|
94
|
+
reverseSkillGroups: [...config.reverseSkillGroups],
|
|
74
95
|
installRoot: config.installRoot,
|
|
75
96
|
},
|
|
76
97
|
install: {
|
|
@@ -80,102 +101,123 @@ export function statusPayload(options: ReaRoutesOptions): StatusDto {
|
|
|
80
101
|
entry: runtime.install?.entry ?? null,
|
|
81
102
|
},
|
|
82
103
|
skill: { ...runtime.skill },
|
|
104
|
+
reverse: {
|
|
105
|
+
installed: runtime.reverse.installed,
|
|
106
|
+
repo: runtime.reverse.repo,
|
|
107
|
+
ref: runtime.reverse.ref,
|
|
108
|
+
sha: runtime.reverse.sha,
|
|
109
|
+
version: runtime.reverse.version,
|
|
110
|
+
root: reverse.root,
|
|
111
|
+
tree: reverse.tree,
|
|
112
|
+
files: runtime.reverse.files,
|
|
113
|
+
groups: [...runtime.reverse.groups],
|
|
114
|
+
activeGroups: activeDirs.map((dir) =>
|
|
115
|
+
dir.startsWith(`${reverse.tree}/`) ? dir.slice(reverse.tree.length + 1) : dir,
|
|
116
|
+
),
|
|
117
|
+
updatedAt: runtime.reverse.updatedAt,
|
|
118
|
+
},
|
|
83
119
|
mcp: {
|
|
84
120
|
mode,
|
|
85
121
|
enabled: config.mcpEnabled && launch !== null,
|
|
86
122
|
command: launch?.command ?? null,
|
|
87
123
|
args: launch?.args ?? [],
|
|
88
124
|
},
|
|
89
|
-
paths,
|
|
125
|
+
paths: { ...paths, reverseRoot: reverse.root },
|
|
90
126
|
job: options.board.snapshot(),
|
|
91
127
|
}
|
|
92
128
|
}
|
|
93
129
|
|
|
94
|
-
/**
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const send = (res: ServerResponse, status: number, body: unknown): void => {
|
|
130
|
+
/** 发送 JSON 响应。 */
|
|
131
|
+
function makeSend(): Send {
|
|
132
|
+
return (res, status, body) => {
|
|
98
133
|
res.writeHead(status, { 'content-type': 'application/json; charset=utf-8' })
|
|
99
134
|
res.end(JSON.stringify(body))
|
|
100
135
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** 异常兜底:日志 + 400(响应头已发则只收尾)。 */
|
|
139
|
+
function makeGuard(logger: RouteLogger, send: Send): (handler: RouteHandler) => RouteHandler {
|
|
140
|
+
return (handler) => async (req, res) => {
|
|
141
|
+
try {
|
|
142
|
+
await handler(req, res)
|
|
143
|
+
} catch (error) {
|
|
144
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
145
|
+
logger.warn(`${req.method ?? '?'} ${req.url ?? '?'} 失败:${message}`)
|
|
146
|
+
if (!res.headersSent) send(res, 400, { job: null, error: message } satisfies JobActionDto)
|
|
147
|
+
else res.end()
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** 方法门:不匹配即 405。 */
|
|
153
|
+
function withMethod(method: string, send: Send, handler: RouteHandler): RouteHandler {
|
|
154
|
+
return async (req, res) => {
|
|
155
|
+
if (req.method !== method) {
|
|
156
|
+
send(res, 405, { job: null, error: 'method not allowed' } satisfies JobActionDto)
|
|
157
|
+
return
|
|
113
158
|
}
|
|
159
|
+
await handler(req, res)
|
|
114
160
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** 起作业的处理器工厂(409 = 已有作业在跑)。 */
|
|
164
|
+
function startJobHandler(
|
|
165
|
+
kind: JobKind,
|
|
166
|
+
options: ReaRoutesOptions,
|
|
167
|
+
logger: RouteLogger,
|
|
168
|
+
send: Send,
|
|
169
|
+
): RouteHandler {
|
|
170
|
+
return async (req, res) => {
|
|
120
171
|
const overrides = proxyPortOf(await readBody(req))
|
|
121
172
|
const operations = options.manager.operations(
|
|
122
173
|
overrides === undefined ? undefined : { proxyPort: overrides },
|
|
123
174
|
)
|
|
124
175
|
const job = options.board.start(kind, operations[kind])
|
|
125
176
|
if (job === null) {
|
|
126
|
-
send(res, 409, {
|
|
177
|
+
send(res, 409, {
|
|
178
|
+
job: options.board.snapshot(),
|
|
179
|
+
error: '已有作业在运行',
|
|
180
|
+
} satisfies JobActionDto)
|
|
127
181
|
return
|
|
128
182
|
}
|
|
129
183
|
logger.info(`已启动 ${kind} 作业`)
|
|
130
184
|
send(res, 200, { job } satisfies JobActionDto)
|
|
131
185
|
}
|
|
186
|
+
}
|
|
132
187
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
188
|
+
/** 全部路由(path 末段 → 处理器)。 */
|
|
189
|
+
function buildRoutes(
|
|
190
|
+
options: ReaRoutesOptions,
|
|
191
|
+
logger: RouteLogger,
|
|
192
|
+
send: Send,
|
|
193
|
+
): Record<string, RouteHandler> {
|
|
194
|
+
return {
|
|
195
|
+
status: withMethod('GET', send, async (_req, res) => {
|
|
141
196
|
send(res, 200, statusPayload(options))
|
|
142
197
|
}),
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
handler: guard(async (req, res) => {
|
|
148
|
-
if (req.method !== 'POST') {
|
|
149
|
-
send(res, 405, { job: null, error: 'method not allowed' })
|
|
150
|
-
return
|
|
151
|
-
}
|
|
152
|
-
await startJob(req, res, 'install')
|
|
153
|
-
}),
|
|
154
|
-
})
|
|
155
|
-
ctx.webServer.register({
|
|
156
|
-
kind: 'exact',
|
|
157
|
-
path: `${ROUTE_BASE}/skill`,
|
|
158
|
-
handler: guard(async (req, res) => {
|
|
159
|
-
if (req.method !== 'POST') {
|
|
160
|
-
send(res, 405, { job: null, error: 'method not allowed' })
|
|
161
|
-
return
|
|
162
|
-
}
|
|
163
|
-
await startJob(req, res, 'skill')
|
|
164
|
-
}),
|
|
165
|
-
})
|
|
166
|
-
ctx.webServer.register({
|
|
167
|
-
kind: 'exact',
|
|
168
|
-
path: `${ROUTE_BASE}/cancel`,
|
|
169
|
-
handler: guard(async (req, res) => {
|
|
170
|
-
if (req.method !== 'POST') {
|
|
171
|
-
send(res, 405, { job: null, error: 'method not allowed' })
|
|
172
|
-
return
|
|
173
|
-
}
|
|
198
|
+
install: withMethod('POST', send, startJobHandler('install', options, logger, send)),
|
|
199
|
+
skill: withMethod('POST', send, startJobHandler('skill', options, logger, send)),
|
|
200
|
+
reverse: withMethod('POST', send, startJobHandler('reverse', options, logger, send)),
|
|
201
|
+
cancel: withMethod('POST', send, async (_req, res) => {
|
|
174
202
|
const cancelled = options.board.cancel()
|
|
175
203
|
send(res, 200, {
|
|
176
204
|
job: options.board.snapshot(),
|
|
177
205
|
...(cancelled ? {} : { error: '没有正在运行的作业' }),
|
|
178
206
|
} satisfies JobActionDto)
|
|
179
207
|
}),
|
|
180
|
-
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** 注册全部路由。 */
|
|
212
|
+
export function registerReaRoutes(ctx: Context, options: ReaRoutesOptions): void {
|
|
213
|
+
const logger = ctx.logger('agent-preset-rea')
|
|
214
|
+
const send = makeSend()
|
|
215
|
+
const guard = makeGuard(logger, send)
|
|
216
|
+
for (const [name, handler] of Object.entries(buildRoutes(options, logger, send))) {
|
|
217
|
+
ctx.webServer.register({
|
|
218
|
+
kind: 'exact',
|
|
219
|
+
path: `${ROUTE_BASE}/${name}`,
|
|
220
|
+
handler: guard(handler),
|
|
221
|
+
})
|
|
222
|
+
}
|
|
181
223
|
}
|
package/src/skill.ts
CHANGED
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
import { fileURLToPath } from 'node:url'
|
|
12
12
|
|
|
13
13
|
import type { AgentPresetReaConfig } from './config.ts'
|
|
14
|
+
import type { RuntimeSnapshot } from './manager.ts'
|
|
15
|
+
import { reverseSkillDirs } from './reverse.ts'
|
|
14
16
|
|
|
15
17
|
/**
|
|
16
18
|
* 随包 REA 官方 skill 根(包内 `skills/`)。
|
|
@@ -21,14 +23,27 @@ import type { AgentPresetReaConfig } from './config.ts'
|
|
|
21
23
|
export const BUNDLED_SKILL_ROOT = fileURLToPath(new URL('../skills', import.meta.url))
|
|
22
24
|
|
|
23
25
|
/**
|
|
24
|
-
* skill
|
|
26
|
+
* REA skill 根(随包副本,或手动更新副本——后者存在即替代前者)。
|
|
27
|
+
* @param updatedRoot - 手动更新副本根(来自 manager 现探)。
|
|
28
|
+
*/
|
|
29
|
+
export function reaSkillDirs(updatedRoot: string | null = null): string[] {
|
|
30
|
+
return updatedRoot === null ? [BUNDLED_SKILL_ROOT] : [updatedRoot]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 本预设 `skill-filesystem` 行的完整追加根清单,按三个开关裁剪:
|
|
35
|
+
* - `skillEnabled=false` → 不挂 REA 官方 skill(安装副本仍留在磁盘);
|
|
36
|
+
* - `reverseSkillEnabled=false` → 不挂 reverse-skill 的技能组;
|
|
37
|
+
* - `extraSkillDirs` 是部署方追加项,不受开关影响(关掉的是本插件自带的两个源)。
|
|
25
38
|
* @param config - 插件配置。
|
|
26
|
-
* @param
|
|
39
|
+
* @param runtime - 现探运行期事实。
|
|
27
40
|
*/
|
|
28
|
-
export function
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
41
|
+
export function presetSkillDirs(config: AgentPresetReaConfig, runtime: RuntimeSnapshot): string[] {
|
|
42
|
+
const rea =
|
|
43
|
+
config.skillEnabled === false
|
|
44
|
+
? []
|
|
45
|
+
: reaSkillDirs(runtime.skill.source === 'updated' ? runtime.skill.root : null)
|
|
46
|
+
const reverse =
|
|
47
|
+
config.reverseSkillEnabled === false ? [] : reverseSkillDirs(config, runtime.reverse)
|
|
48
|
+
return [...rea, ...reverse, ...config.extraSkillDirs]
|
|
34
49
|
}
|