@dsh-plus/agent-preset-rea 0.1.0 → 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 +870 -52
- package/lib/index.d.ts +402 -9
- package/lib/index.js +1319 -203
- package/package.json +4 -3
- package/src/client/api.ts +77 -0
- package/src/client/card.tsx +39 -10
- package/src/client/client.ts +3 -3
- package/src/client/draft.ts +52 -6
- package/src/client/i18n.ts +135 -4
- package/src/client/install.tsx +373 -0
- package/src/client/styles.ts +27 -2
- package/src/client/toggles.tsx +57 -0
- package/src/config.ts +54 -14
- package/src/definition.ts +193 -136
- package/src/dto.ts +113 -0
- package/src/exec.ts +177 -0
- package/src/index.ts +115 -4
- package/src/jobs.ts +145 -0
- package/src/manager.ts +424 -0
- package/src/mcp.ts +47 -6
- package/src/reverse.ts +335 -0
- package/src/routes.ts +223 -0
- package/src/skill.ts +32 -7
package/src/index.ts
CHANGED
|
@@ -18,6 +18,16 @@
|
|
|
18
18
|
* 本插件据此换代注册(注销旧声明 → 按新值重建声明),**新建会话**即用新提示词,
|
|
19
19
|
* 已存在会话保持既有 composition(注册表语义:运行中会话的装配不随换代改变)。
|
|
20
20
|
*
|
|
21
|
+
* 卡片上还有三个能力开关(活动字段:REA MCP 行、REA skill、reverse-skill 技能包)
|
|
22
|
+
* 与两个受管技能源的手动更新:REA 官方 skill(npm 包内 `skills/`)与 GitHub 的
|
|
23
|
+
* `zhaoxuya520/reverse-skill`(路由包,按技能组挂进 `customSkillDirs`)。开关只管
|
|
24
|
+
* 挂不挂,安装副本始终留在插件数据目录。
|
|
25
|
+
*
|
|
26
|
+
* REA 的安装与更新也在卡片上(受管安装 + 后台作业 + 实时进度 + 可选代理端口):
|
|
27
|
+
* 宿主半的 manager 把 `rea-agents` 装进插件数据目录、把 skill 拉成可替换副本,
|
|
28
|
+
* 作业完成后同样换代注册——因此预设声明里**不会**在 boot 期 spawn `npx`
|
|
29
|
+
* (那会跑 npm 安装脚本,原生依赖 prebuild 下载一卡就吊死预设激活与整个名册)。
|
|
30
|
+
*
|
|
21
31
|
* 设计取舍:
|
|
22
32
|
* - 独立插件而非 cordis.patch.yml 里的裸 `@deepseek-ai/dsh-agent-preset`
|
|
23
33
|
* 行:便于纳入本仓的构建/测试/文档体系,并把显示面与 MCP/skill 参数开放为
|
|
@@ -36,7 +46,12 @@ import { unwrapVolatile } from '@dsh-plus/shared'
|
|
|
36
46
|
|
|
37
47
|
import { type AgentPresetReaConfig, type AgentPresetReaConfigFields, Config } from './config.ts'
|
|
38
48
|
import { reaPresetDefinition } from './definition.ts'
|
|
49
|
+
import { createJobBoard } from './jobs.ts'
|
|
50
|
+
import { createManager } from './manager.ts'
|
|
51
|
+
import { REA_PINNED_VERSION } from './mcp.ts'
|
|
39
52
|
import { createPresetRegistrar } from './registration.ts'
|
|
53
|
+
import { registerReaRoutes } from './routes.ts'
|
|
54
|
+
import { BUNDLED_SKILL_ROOT } from './skill.ts'
|
|
40
55
|
|
|
41
56
|
export const name = 'dsh-plus-agent-preset-rea'
|
|
42
57
|
|
|
@@ -44,18 +59,87 @@ export const name = 'dsh-plus-agent-preset-rea'
|
|
|
44
59
|
export const inject = [] as const
|
|
45
60
|
|
|
46
61
|
export { SETTINGS_NS } from './config.ts'
|
|
47
|
-
export {
|
|
62
|
+
export {
|
|
63
|
+
ADDED_ROWS,
|
|
64
|
+
CONFIG_DELTAS,
|
|
65
|
+
emptyRuntime,
|
|
66
|
+
REA_PRESET_ID,
|
|
67
|
+
reaPresetDefinition,
|
|
68
|
+
} from './definition.ts'
|
|
69
|
+
export type {
|
|
70
|
+
InstallDto,
|
|
71
|
+
JobActionDto,
|
|
72
|
+
JobDto,
|
|
73
|
+
JobKind,
|
|
74
|
+
JobProgressDto,
|
|
75
|
+
JobState,
|
|
76
|
+
McpDto,
|
|
77
|
+
ReverseSkillDto,
|
|
78
|
+
SkillDto,
|
|
79
|
+
StatusDto,
|
|
80
|
+
} from './dto.ts'
|
|
81
|
+
export {
|
|
82
|
+
type CommandRunner,
|
|
83
|
+
JobCancelled,
|
|
84
|
+
type JobOperation,
|
|
85
|
+
type JobTaskContext,
|
|
86
|
+
NPM,
|
|
87
|
+
npmEnv,
|
|
88
|
+
safeVersion,
|
|
89
|
+
spawnRunner,
|
|
90
|
+
} from './exec.ts'
|
|
91
|
+
export { createJobBoard, type JobBoard, type JobSnapshot } from './jobs.ts'
|
|
92
|
+
export {
|
|
93
|
+
configWith,
|
|
94
|
+
createManager,
|
|
95
|
+
INSTALL_PHASES,
|
|
96
|
+
type JobDeps,
|
|
97
|
+
type JobOverrides,
|
|
98
|
+
type ManagerPaths,
|
|
99
|
+
managerPaths,
|
|
100
|
+
probeInstall,
|
|
101
|
+
probeRuntime,
|
|
102
|
+
probeSkill,
|
|
103
|
+
REA_INSTALL_DIR,
|
|
104
|
+
REA_PACKAGE,
|
|
105
|
+
REA_SKILL_DIR,
|
|
106
|
+
REA_SKILL_NAME,
|
|
107
|
+
type ReaInstall,
|
|
108
|
+
type ReaManager,
|
|
109
|
+
type RuntimeSnapshot,
|
|
110
|
+
SKILL_PHASES,
|
|
111
|
+
type SkillManifest,
|
|
112
|
+
type SkillState,
|
|
113
|
+
} from './manager.ts'
|
|
48
114
|
export {
|
|
49
115
|
DEFAULT_MCP_COMMAND,
|
|
50
116
|
INPUT_SCHEMA_PROFILES,
|
|
117
|
+
type InstalledEntry,
|
|
118
|
+
type McpLaunch,
|
|
51
119
|
OUTPUT_SCHEMA_PROFILES,
|
|
52
120
|
REA_PINNED_VERSION,
|
|
53
121
|
reaMcpArgs,
|
|
54
122
|
reaMcpEnv,
|
|
123
|
+
reaMcpLaunch,
|
|
55
124
|
} from './mcp.ts'
|
|
56
125
|
export { PLAN_MODE_SECTION } from './prompt.ts'
|
|
57
126
|
export { createPresetRegistrar, type PresetRegistrar, type Unregister } from './registration.ts'
|
|
58
|
-
export {
|
|
127
|
+
export {
|
|
128
|
+
discoverSkillGroups,
|
|
129
|
+
probeReverse,
|
|
130
|
+
REVERSE_DEFAULT_REPO,
|
|
131
|
+
REVERSE_DIR,
|
|
132
|
+
REVERSE_PHASES,
|
|
133
|
+
REVERSE_TREE_DIR,
|
|
134
|
+
type ReverseManifest,
|
|
135
|
+
type ReversePaths,
|
|
136
|
+
type ReverseState,
|
|
137
|
+
reversePaths,
|
|
138
|
+
reverseSkillDirs,
|
|
139
|
+
runReverseSkillJob,
|
|
140
|
+
} from './reverse.ts'
|
|
141
|
+
export { ROUTE_BASE, registerReaRoutes, statusPayload } from './routes.ts'
|
|
142
|
+
export { BUNDLED_SKILL_ROOT, presetSkillDirs, reaSkillDirs } from './skill.ts'
|
|
59
143
|
export type { AgentPresetReaConfig, AgentPresetReaConfigFields }
|
|
60
144
|
export { Config }
|
|
61
145
|
|
|
@@ -76,20 +160,47 @@ export function apply(ctx: Context, config: AgentPresetReaConfigFields): void {
|
|
|
76
160
|
const detail = error instanceof Error ? error.message : String(error)
|
|
77
161
|
logger.warn(`register rea preset failed: ${detail}`)
|
|
78
162
|
}
|
|
163
|
+
const manager = createManager({
|
|
164
|
+
config: current,
|
|
165
|
+
bundledRoot: BUNDLED_SKILL_ROOT,
|
|
166
|
+
bundledVersion: REA_PINNED_VERSION,
|
|
167
|
+
})
|
|
168
|
+
const board = createJobBoard()
|
|
169
|
+
// 换代入口:安装/更新作业完成、或活动配置提交后,都要按新事实重建声明。
|
|
170
|
+
let reregister: (() => void) | null = null
|
|
171
|
+
const refresh = (): void => {
|
|
172
|
+
manager.refresh()
|
|
173
|
+
reregister?.()
|
|
174
|
+
}
|
|
175
|
+
ctx.effect(
|
|
176
|
+
() =>
|
|
177
|
+
board.onSettled((snapshot) => {
|
|
178
|
+
refresh()
|
|
179
|
+
logger.info(`rea ${snapshot.kind} 作业结束:${snapshot.state}`)
|
|
180
|
+
}),
|
|
181
|
+
'agent-preset-rea: refresh after job',
|
|
182
|
+
)
|
|
79
183
|
|
|
80
184
|
ctx.inject(['agentPresets'], (scope) => {
|
|
81
185
|
scope.effect(() => {
|
|
82
186
|
const registrar = createPresetRegistrar({
|
|
83
187
|
register: (definition) => scope.agentPresets.register(definition),
|
|
84
|
-
build: () => reaPresetDefinition(current()),
|
|
188
|
+
build: () => reaPresetDefinition(current(), manager.snapshot()),
|
|
85
189
|
onError,
|
|
86
190
|
})
|
|
191
|
+
reregister = () => registrar.refresh()
|
|
87
192
|
// 监听器挂在插件自身 fiber 上:loader 正是对该 fiber 广播 volatile 提交。
|
|
88
|
-
const off = ctx.events.on('loader/volatile-update', () =>
|
|
193
|
+
const off = ctx.events.on('loader/volatile-update', () => refresh())
|
|
89
194
|
return () => {
|
|
195
|
+
reregister = null
|
|
90
196
|
off()
|
|
91
197
|
registrar.dispose()
|
|
92
198
|
}
|
|
93
199
|
}, 'agent-preset-rea: register rea preset')
|
|
94
200
|
})
|
|
201
|
+
|
|
202
|
+
// 安装/更新端点:webServer 缺席(headless)即不注册,插件其余能力照常。
|
|
203
|
+
ctx.inject(['webServer'], (scope) => {
|
|
204
|
+
registerReaRoutes(scope as unknown as Context, { manager, board, config: current })
|
|
205
|
+
})
|
|
95
206
|
}
|
package/src/jobs.ts
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 单作业作业板:同一时刻只允许一个安装/更新作业,快照供 HTTP 轮询,
|
|
3
|
+
* 完成后回调订阅者(宿主据此换代注册预设声明)。
|
|
4
|
+
*
|
|
5
|
+
* 进度按「阶段」推进(`percent = step / steps`):安装类作业的外部命令不产出可靠
|
|
6
|
+
* 字节进度,硬造百分比是骗人;阶段 + 实时日志尾部 + 起止时间才是可核对的事实。
|
|
7
|
+
* 日志只保留尾部 {@link JOB_LOG_LIMIT} 行(回传浏览器,必须有界)。
|
|
8
|
+
*
|
|
9
|
+
* 状态放在 {@link BoardState} 里、函数都提到模块级:卡片/端点/测试三方共享同一份
|
|
10
|
+
* 语义,单个函数也保持在可读长度内(仓库门禁:函数 60/150 行)。
|
|
11
|
+
* @module @dsh-plus/agent-preset-rea/jobs
|
|
12
|
+
*/
|
|
13
|
+
import type { JobDto, JobKind, JobProgressDto } from './dto.ts'
|
|
14
|
+
import { JobCancelled, type JobOperation, type JobTaskContext } from './exec.ts'
|
|
15
|
+
|
|
16
|
+
/** 作业进度(与 wire DTO 同形)。 */
|
|
17
|
+
export type JobProgress = JobProgressDto
|
|
18
|
+
|
|
19
|
+
/** 作业快照(与 wire DTO 同形,HTTP 直接回传)。 */
|
|
20
|
+
export type JobSnapshot = JobDto
|
|
21
|
+
|
|
22
|
+
/** 作业板。 */
|
|
23
|
+
export interface JobBoard {
|
|
24
|
+
/** 启动作业;已有作业在跑时返回 null(调用方回 409)。 */
|
|
25
|
+
start(kind: JobKind, operation: JobOperation): JobSnapshot | null
|
|
26
|
+
/** 取消当前作业(返回是否真的发出取消)。 */
|
|
27
|
+
cancel(): boolean
|
|
28
|
+
/** 当前/最近一次作业快照。 */
|
|
29
|
+
snapshot(): JobSnapshot | null
|
|
30
|
+
/** 订阅作业完成(返回退订函数)。 */
|
|
31
|
+
onSettled(listener: (snapshot: JobSnapshot) => void): () => void
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** 日志尾部保留行数。 */
|
|
35
|
+
export const JOB_LOG_LIMIT = 200
|
|
36
|
+
|
|
37
|
+
/** 作业板可变状态(函数按引用操作它,不靠闭包嵌套)。 */
|
|
38
|
+
interface BoardState {
|
|
39
|
+
seq: number
|
|
40
|
+
current: JobSnapshot | null
|
|
41
|
+
abort: AbortController | null
|
|
42
|
+
listeners: Set<(snapshot: JobSnapshot) => void>
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** 阶段序号 → 进度(进入该阶段即报该阶段起点百分比)。 */
|
|
46
|
+
function progressOf(operation: JobOperation, step: number): JobProgress {
|
|
47
|
+
const steps = operation.phases.length
|
|
48
|
+
return {
|
|
49
|
+
step: step + 1,
|
|
50
|
+
steps,
|
|
51
|
+
phase: operation.phases[step] ?? '',
|
|
52
|
+
percent: steps === 0 ? 0 : Math.round((step / steps) * 100),
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** 落定:写状态 + 通知订阅者(订阅者异常不得影响状态机)。 */
|
|
57
|
+
function settleJob(state: BoardState, patch: Partial<JobSnapshot>): void {
|
|
58
|
+
const base = state.current
|
|
59
|
+
if (base === null) return
|
|
60
|
+
state.current = { ...base, ...patch, finishedAt: new Date().toISOString() }
|
|
61
|
+
for (const listener of [...state.listeners]) {
|
|
62
|
+
try {
|
|
63
|
+
listener(state.current)
|
|
64
|
+
} catch {
|
|
65
|
+
// 订阅者异常不影响作业板状态
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** 兜底进度形状(正常路径下 `state.current` 必有值,这里只防类型上的缺席)。 */
|
|
71
|
+
const ZERO_PROGRESS: JobProgress = { step: 1, steps: 1, phase: '', percent: 0 }
|
|
72
|
+
|
|
73
|
+
/** 收尾:成功记 ok + 版本,失败区分「取消」与「真失败」。 */
|
|
74
|
+
function finishJob(state: BoardState, error: unknown, version: string | null): void {
|
|
75
|
+
if (version !== null) {
|
|
76
|
+
const progress = { ...(state.current?.progress ?? ZERO_PROGRESS), percent: 100 }
|
|
77
|
+
settleJob(state, { state: 'ok', version, progress })
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
const cancelled = error instanceof JobCancelled || state.abort?.signal.aborted === true
|
|
81
|
+
settleJob(state, {
|
|
82
|
+
state: cancelled ? 'cancelled' : 'error',
|
|
83
|
+
error: cancelled ? null : error instanceof Error ? error.message : String(error),
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** 起一个作业并挂上状态机(调用方已确认当前无运行中作业)。 */
|
|
88
|
+
function startJob(state: BoardState, kind: JobKind, operation: JobOperation): JobSnapshot {
|
|
89
|
+
const controller = new AbortController()
|
|
90
|
+
state.abort = controller
|
|
91
|
+
state.seq += 1
|
|
92
|
+
const snapshot: JobSnapshot = {
|
|
93
|
+
id: state.seq,
|
|
94
|
+
kind,
|
|
95
|
+
state: 'running',
|
|
96
|
+
progress: progressOf(operation, 0),
|
|
97
|
+
log: [],
|
|
98
|
+
startedAt: new Date().toISOString(),
|
|
99
|
+
finishedAt: null,
|
|
100
|
+
version: null,
|
|
101
|
+
error: null,
|
|
102
|
+
}
|
|
103
|
+
state.current = snapshot
|
|
104
|
+
const push = (patch: Partial<JobSnapshot>): void => {
|
|
105
|
+
if (state.current !== null) state.current = { ...state.current, ...patch }
|
|
106
|
+
}
|
|
107
|
+
const ctx: JobTaskContext = {
|
|
108
|
+
phase: (step) => push({ progress: progressOf(operation, step) }),
|
|
109
|
+
log: (line) => push({ log: [...(state.current?.log ?? []), line].slice(-JOB_LOG_LIMIT) }),
|
|
110
|
+
signal: controller.signal,
|
|
111
|
+
}
|
|
112
|
+
void operation
|
|
113
|
+
.run(ctx)
|
|
114
|
+
.then(
|
|
115
|
+
(version) => finishJob(state, null, version),
|
|
116
|
+
(error: unknown) => finishJob(state, error, null),
|
|
117
|
+
)
|
|
118
|
+
.finally(() => {
|
|
119
|
+
state.abort = null
|
|
120
|
+
})
|
|
121
|
+
return snapshot
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** 取消当前作业。 */
|
|
125
|
+
function cancelJob(state: BoardState): boolean {
|
|
126
|
+
// 只看状态:落定后 finally 还没跑(微任务次序),按 abort 句柄判断会误报「已取消」。
|
|
127
|
+
if (state.abort === null || state.current?.state !== 'running') return false
|
|
128
|
+
state.abort.abort()
|
|
129
|
+
return true
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** 建一个作业板。 */
|
|
133
|
+
export function createJobBoard(): JobBoard {
|
|
134
|
+
const state: BoardState = { seq: 0, current: null, abort: null, listeners: new Set() }
|
|
135
|
+
return {
|
|
136
|
+
start: (kind, operation) =>
|
|
137
|
+
state.current?.state === 'running' ? null : startJob(state, kind, operation),
|
|
138
|
+
cancel: () => cancelJob(state),
|
|
139
|
+
snapshot: () => state.current,
|
|
140
|
+
onSettled: (listener) => {
|
|
141
|
+
state.listeners.add(listener)
|
|
142
|
+
return () => state.listeners.delete(listener)
|
|
143
|
+
},
|
|
144
|
+
}
|
|
145
|
+
}
|