agentworkshop 0.7.12 → 0.7.14
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/.output/nitro.json +1 -1
- package/.output/public/_nuxt/CD1SlCoI.js +14 -0
- package/.output/public/_nuxt/CYBq3kS7.js +1 -0
- package/.output/public/_nuxt/Cr9DkD1d.js +1 -0
- package/.output/public/_nuxt/DGVAuScD.js +1 -0
- package/.output/public/_nuxt/DMdVWIGz.js +19 -0
- package/.output/public/_nuxt/DVoWWlUW.js +1 -0
- package/.output/public/_nuxt/agents.CB9Ck7VQ.css +1 -0
- package/.output/public/_nuxt/builds/latest.json +1 -1
- package/.output/public/_nuxt/builds/meta/23c30854-f9e1-4651-bc3a-b695ef5faca4.json +1 -0
- package/.output/public/_nuxt/channel-templates.C-M5nKcC.css +1 -0
- package/.output/public/_nuxt/{Ds8JbqGB.js → ed5qX-x5.js} +1 -1
- package/.output/public/_nuxt/{entry.Bv_GCmPP.css → entry.BNApuuEW.css} +1 -1
- package/.output/public/_nuxt/logs.BarkrvUp.css +1 -0
- package/.output/public/_nuxt/pages.CG8UJ2TK.css +1 -0
- package/.output/public/_nuxt/teams.DN0pAGGu.css +1 -0
- package/.output/public/_nuxt/tokens.BVXU1jOK.css +1 -0
- package/.output/server/chunks/_/nitro.mjs +849 -752
- package/.output/server/chunks/_/nitro.mjs.map +1 -1
- package/.output/server/chunks/_/user.schema.mjs +1 -1
- package/.output/server/chunks/_/user.schema.mjs.map +1 -1
- package/.output/server/chunks/build/TownView-CunmEMzS.mjs +1 -1
- package/.output/server/chunks/build/_id_-CVnMdSeG.mjs +1 -1
- package/.output/server/chunks/build/_id_-D265Wh_k.mjs +1 -1
- package/.output/server/chunks/build/{agents-B10wmFri.mjs → agents--6bzGtES.mjs} +156 -80
- package/.output/server/chunks/build/agents--6bzGtES.mjs.map +1 -0
- package/.output/server/chunks/build/{channel-templates-CpWxVk5A.mjs → channel-templates-C1Bb-U75.mjs} +19 -20
- package/.output/server/chunks/build/channel-templates-C1Bb-U75.mjs.map +1 -0
- package/.output/server/chunks/build/daq-DxaXk_8N.mjs +1 -1
- package/.output/server/chunks/build/dcw-B0dtiHFG.mjs +1 -1
- package/.output/server/chunks/build/{logs-Crf6f1IC.mjs → logs-UWXeNKy_.mjs} +34 -34
- package/.output/server/chunks/build/{logs-Crf6f1IC.mjs.map → logs-UWXeNKy_.mjs.map} +1 -1
- package/.output/server/chunks/build/{pages-BmRUbGUi.mjs → pages-Aexs6LjD.mjs} +27 -15
- package/.output/server/chunks/build/pages-Aexs6LjD.mjs.map +1 -0
- package/.output/server/chunks/build/{teams-CNflUtPh.mjs → teams-DD8ZXTKL.mjs} +29 -32
- package/.output/server/chunks/build/teams-DD8ZXTKL.mjs.map +1 -0
- package/.output/server/chunks/build/{tokens-BKLzcdMg.mjs → tokens-Dxnuw96_.mjs} +14 -14
- package/.output/server/chunks/build/{tokens-BKLzcdMg.mjs.map → tokens-Dxnuw96_.mjs.map} +1 -1
- package/.output/server/chunks/build/useDaqStream-BkmyQT06.mjs +1 -1
- package/.output/server/chunks/build/useDcwStream-DWVoU96J.mjs +1 -1
- package/.output/server/chunks/build/useWorkshopApi-D2gq06N2.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/channel-templates/_id/instantiate.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/channels.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/harnesses/_id/providers.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/harnesses.get.mjs +14 -2
- package/.output/server/chunks/routes/api/workshop/harnesses.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/hitl/pending.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/hitl/respond.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/index.put.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/plugins/_name/disable.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/plugins/_name/enable.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.delete.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.put.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/workspaces/_id/channel-templates/_tplId_.post.mjs +1 -1
- package/.output/server/chunks/routes/renderer.mjs +2 -2
- package/.output/server/chunks/virtual/entry.mjs +25 -13
- package/.output/server/chunks/virtual/precomputed.mjs +1 -1
- package/.output/server/package.json +1 -1
- package/app/composables/workshop/useWorkshopApi.ts +11 -1
- package/app/pages/index.vue +72 -0
- package/app/pages/logs/index.vue +3 -1
- package/app/pages/tokens.vue +3 -1
- package/app/pages/workshop/agents.vue +71 -6
- package/app/pages/workshop/channel-templates.vue +2 -1
- package/app/pages/workshop/teams.vue +24 -2
- package/app/plugins/http.ts +4 -3
- package/i18n/locales/en.ts +8 -1
- package/i18n/locales/zh-CN.ts +8 -1
- package/package.json +148 -148
- package/scripts/_dbg-harness-e2e.mjs +92 -0
- package/scripts/_dbg-harness-ui-dbg.py +49 -0
- package/scripts/_dbg-harness-ui-e2e.py +87 -0
- package/scripts/_dbg-login-e2e.mjs +26 -0
- package/server/api/workshop/harnesses.get.ts +15 -2
- package/server/data/dcw-lines.json +14 -0
- package/server/data/dcw-rollback.json +6 -6
- package/server/data/dcws.json +198 -198
- package/server/repositories/user.repository.ts +4 -3
- package/server/schemas/user.schema.ts +2 -2
- package/server/services/workshop/agents/adapters/line-spawn.ts +3 -2
- package/server/services/workshop/agents/harness-availability.ts +127 -0
- package/server/services/workshop/agents/registry.ts +21 -0
- package/server/services/workshop/runtime/manager.ts +16 -1
- package/.output/public/_nuxt/CvYfzHYz.js +0 -1
- package/.output/public/_nuxt/D2JyEL28.js +0 -14
- package/.output/public/_nuxt/D8xY-Yjc.js +0 -19
- package/.output/public/_nuxt/QoCzfpo2.js +0 -1
- package/.output/public/_nuxt/agents.CEbox5-D.css +0 -1
- package/.output/public/_nuxt/builds/meta/54e22640-4fd0-4fee-9077-31d4d4bb7969.json +0 -1
- package/.output/public/_nuxt/channel-templates.De5ZvrGz.css +0 -1
- package/.output/public/_nuxt/eUGyJdiT.js +0 -1
- package/.output/public/_nuxt/hb_ceMJP.js +0 -1
- package/.output/public/_nuxt/logs.BS1sb41K.css +0 -1
- package/.output/public/_nuxt/pages.BkE-AfIb.css +0 -1
- package/.output/public/_nuxt/teams.CUMnbQGf.css +0 -1
- package/.output/public/_nuxt/tokens.rW1xHxRW.css +0 -1
- package/.output/server/chunks/build/agents-B10wmFri.mjs.map +0 -1
- package/.output/server/chunks/build/channel-templates-CpWxVk5A.mjs.map +0 -1
- package/.output/server/chunks/build/pages-BmRUbGUi.mjs.map +0 -1
- package/.output/server/chunks/build/teams-CNflUtPh.mjs.map +0 -1
|
@@ -319,10 +319,11 @@ export const userRepository = {
|
|
|
319
319
|
return new Map(rows.map(r => [r.lineId, r.mode]))
|
|
320
320
|
},
|
|
321
321
|
|
|
322
|
-
/**
|
|
323
|
-
|
|
322
|
+
/** 内部:取密码哈希(仅认证路径使用,不参与领域对象外泄)。
|
|
323
|
+
* 登录账号:邮箱或用户名均可(用户名注册时唯一;参数绑定防注入) */
|
|
324
|
+
getPasswordHash(account: string): { id: string, hash: string } | null {
|
|
324
325
|
const d = getDb()
|
|
325
|
-
const row = d.prepare('SELECT id, password_hash AS hash FROM users WHERE LOWER(email) = LOWER(?)').get(
|
|
326
|
+
const row = d.prepare('SELECT id, password_hash AS hash FROM users WHERE LOWER(email) = LOWER(?) OR LOWER(name) = LOWER(?)').get(account, account) as { id: string, hash: string } | undefined
|
|
326
327
|
return row ?? null
|
|
327
328
|
},
|
|
328
329
|
|
|
@@ -42,9 +42,9 @@ export const userRegisterSchema = z.object({
|
|
|
42
42
|
password: passwordSchema,
|
|
43
43
|
})
|
|
44
44
|
|
|
45
|
-
/** 登录请求体(email
|
|
45
|
+
/** 登录请求体(account = 邮箱或用户名均可;字段名保留 email 兼容既有客户端) */
|
|
46
46
|
export const userLoginSchema = z.object({
|
|
47
|
-
email: z.string().trim().
|
|
47
|
+
email: z.string().trim().min(1, '请输入邮箱或用户名').max(128, '过长'),
|
|
48
48
|
password: z.string().min(1, '密码不能为空').max(128, '密码过长'),
|
|
49
49
|
})
|
|
50
50
|
|
|
@@ -24,8 +24,9 @@ export function assertPlainExecutable(command: string): string {
|
|
|
24
24
|
return command
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
/** PATH + PATHEXT 解析裸命令名 → 绝对路径(win32;非 Windows 返回 null)。带扩展名优先,裸名(sh 脚本)
|
|
28
|
-
|
|
27
|
+
/** PATH + PATHEXT 解析裸命令名 → 绝对路径(win32;非 Windows 返回 null)。带扩展名优先,裸名(sh 脚本)最后。
|
|
28
|
+
* 导出:harness 可用性探测(harness-availability.ts)复用同一解析规则,保证「探测可用」与「真实拉起」同源。 */
|
|
29
|
+
export function resolveOnPath(command: string): string | null {
|
|
29
30
|
if (process.platform !== 'win32') return null
|
|
30
31
|
const isFile = (p: string): boolean => {
|
|
31
32
|
try {
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HarnessAvailability —— 执行引擎环境可用性探测(单一事实源)。
|
|
3
|
+
*
|
|
4
|
+
* 两个消费面共用本模块:
|
|
5
|
+
* - 前端选择面:GET /api/workshop/harnesses 的 available 字段(下拉禁用未安装项/仪表盘面板);
|
|
6
|
+
* - 执行前强校验:manager 的模板增改/克隆入 channel/实例改引擎/任务直发/lead 派发
|
|
7
|
+
* 全部经 assertHarnessUsable,不可用直接 409,任务不再进入「起跑后失败」路径。
|
|
8
|
+
*
|
|
9
|
+
* 探测规则(与真实拉起同源,见 line-spawn.resolveExecutable):
|
|
10
|
+
* - 进程内引擎(mock/claude SDK):无外部 CLI,恒可用;
|
|
11
|
+
* - 进程型引擎(omp/opencode/codex/dsh):经 HarnessDef.probe 解析命令
|
|
12
|
+
* (实例 config.command 覆盖 → 运行时设置缺省),再按 PATH/PATHEXT(unix 加 X_OK)
|
|
13
|
+
* 探测磁盘上的真实可执行文件;找不到 = 未安装。
|
|
14
|
+
* 探测结果按「引擎+命令」缓存 30s(fs 探测廉价但热路径避抖);refresh=1 强制重探。
|
|
15
|
+
*/
|
|
16
|
+
import { accessSync, constants, existsSync, statSync } from 'node:fs'
|
|
17
|
+
import { join } from 'node:path'
|
|
18
|
+
import { AppError } from '../../../utils/errors'
|
|
19
|
+
import { resolveOnPath } from './adapters/line-spawn'
|
|
20
|
+
import { HARNESS_REGISTRY, knownHarnesses } from './registry'
|
|
21
|
+
|
|
22
|
+
export interface HarnessAvailability {
|
|
23
|
+
id: string
|
|
24
|
+
available: boolean
|
|
25
|
+
/** 进程内引擎(无外部 CLI,恒可用) */
|
|
26
|
+
inprocess: boolean
|
|
27
|
+
/** 进程型引擎:声明将拉起的命令(config 覆盖/设置缺省后) */
|
|
28
|
+
command: string | null
|
|
29
|
+
/** PATH 解析出的可执行文件绝对路径(未找到为 null) */
|
|
30
|
+
resolvedPath: string | null
|
|
31
|
+
/** 不可用原因(人话,可直接展示) */
|
|
32
|
+
error: string | null
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const isPlainFile = (p: string): boolean => {
|
|
36
|
+
try {
|
|
37
|
+
return existsSync(p) && statSync(p).isFile()
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return false
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const isExecutableFile = (p: string): boolean => {
|
|
45
|
+
try {
|
|
46
|
+
accessSync(p, constants.X_OK)
|
|
47
|
+
return statSync(p).isFile()
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return false
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** PATH 探测可执行文件;探测永不抛(坏 PATH 环境按未安装处理) */
|
|
55
|
+
function probeExecutable(command: string): string | null {
|
|
56
|
+
if (!command) return null
|
|
57
|
+
// 显式路径(含目录分隔符或盘符)不再走 PATH
|
|
58
|
+
if (command.includes('/') || command.includes('\\') || /^[A-Za-z]:/.test(command)) {
|
|
59
|
+
return isPlainFile(command) ? command : null
|
|
60
|
+
}
|
|
61
|
+
if (process.platform === 'win32') return resolveOnPath(command)
|
|
62
|
+
for (const dir of (process.env.PATH ?? '').split(':').filter(Boolean)) {
|
|
63
|
+
const full = join(dir, command)
|
|
64
|
+
if (isExecutableFile(full)) return full
|
|
65
|
+
}
|
|
66
|
+
return null
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const PROBE_TTL_MS = 30_000
|
|
70
|
+
/** 缓存键 = 引擎+命令(命令不同探测结果不同,不能按引擎 id 独占) */
|
|
71
|
+
const probeCache = new Map<string, { at: number, resolvedPath: string | null }>()
|
|
72
|
+
|
|
73
|
+
export function checkHarnessAvailability(
|
|
74
|
+
harnessId: string,
|
|
75
|
+
config?: Record<string, unknown>,
|
|
76
|
+
opts?: { refresh?: boolean },
|
|
77
|
+
): HarnessAvailability {
|
|
78
|
+
const def = HARNESS_REGISTRY[harnessId]
|
|
79
|
+
if (!def) {
|
|
80
|
+
return {
|
|
81
|
+
id: harnessId,
|
|
82
|
+
available: false,
|
|
83
|
+
inprocess: false,
|
|
84
|
+
command: null,
|
|
85
|
+
resolvedPath: null,
|
|
86
|
+
error: `未知 harness: ${harnessId}(可选 ${knownHarnesses().join('/')})`,
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const probe = def.probe
|
|
90
|
+
if (!probe || probe.inprocess || !probe.command) {
|
|
91
|
+
return { id: harnessId, available: true, inprocess: true, command: null, resolvedPath: null, error: null }
|
|
92
|
+
}
|
|
93
|
+
const command = probe.command(config)
|
|
94
|
+
const key = `${harnessId}=${command}`
|
|
95
|
+
const cached = probeCache.get(key)
|
|
96
|
+
let resolvedPath: string | null
|
|
97
|
+
if (!opts?.refresh && cached && Date.now() - cached.at < PROBE_TTL_MS) {
|
|
98
|
+
resolvedPath = cached.resolvedPath
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
resolvedPath = probeExecutable(command)
|
|
102
|
+
probeCache.set(key, { at: Date.now(), resolvedPath })
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
id: harnessId,
|
|
106
|
+
available: resolvedPath !== null,
|
|
107
|
+
inprocess: false,
|
|
108
|
+
command,
|
|
109
|
+
resolvedPath,
|
|
110
|
+
error: resolvedPath ? null : `未找到可执行命令「${command}」:引擎 ${def.label} 未安装或不在 PATH 中`,
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function checkAllHarnessAvailability(opts?: { refresh?: boolean }): HarnessAvailability[] {
|
|
115
|
+
return Object.keys(HARNESS_REGISTRY).map(id => checkHarnessAvailability(id, undefined, opts))
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** 执行前强校验:未知 → 400 UNKNOWN_HARNESS;未安装 → 409 HARNESS_UNAVAILABLE(人话报错,前端可直接展示) */
|
|
119
|
+
export function assertHarnessUsable(harness: string, config?: Record<string, unknown>): void {
|
|
120
|
+
if (!HARNESS_REGISTRY[harness]) {
|
|
121
|
+
throw new AppError(400, 'UNKNOWN_HARNESS', `未知 harness: ${harness}(可选 ${knownHarnesses().join('/')})`)
|
|
122
|
+
}
|
|
123
|
+
const a = checkHarnessAvailability(harness, config)
|
|
124
|
+
if (!a.available) {
|
|
125
|
+
throw new AppError(409, 'HARNESS_UNAVAILABLE', a.error ?? `执行引擎 ${harness} 不可用`)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { AppError } from '../../../utils/errors'
|
|
9
9
|
import type { AgentInfo, AgentInterface } from './agent-interface'
|
|
10
|
+
import { harnessSettings } from '../settings'
|
|
10
11
|
import { MockAgentImpl } from './mock-agent'
|
|
11
12
|
import { OmpRpcAgentImpl } from './omp-agent'
|
|
12
13
|
import { ClaudeSdkAgentImpl } from './claude-agent'
|
|
@@ -36,6 +37,16 @@ export interface HarnessDef {
|
|
|
36
37
|
description: string
|
|
37
38
|
capabilities: HarnessCapabilities
|
|
38
39
|
create(config: Record<string, unknown>, agent: AgentInfo): AgentInterface
|
|
40
|
+
/** 可用性探测面(harness-availability.ts 消费;缺省 = 进程内引擎,恒可用) */
|
|
41
|
+
probe?: HarnessProbe
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** 引擎环境探测声明:进程内引擎无外部依赖;进程型引擎声明将拉起的命令(与真实 spawn 同源) */
|
|
45
|
+
export interface HarnessProbe {
|
|
46
|
+
/** 进程内引擎(mock/claude SDK 骨架):无外部 CLI,恒可用 */
|
|
47
|
+
inprocess?: boolean
|
|
48
|
+
/** 解析将拉起的可执行命令(实例 config.command 覆盖 → 运行时设置缺省 → 内置默认) */
|
|
49
|
+
command?: (config?: Record<string, unknown>) => string
|
|
39
50
|
}
|
|
40
51
|
|
|
41
52
|
const mockCaps: HarnessCapabilities = {
|
|
@@ -57,12 +68,17 @@ const claudeCaps: HarnessCapabilities = {
|
|
|
57
68
|
steer: false, supervise: false, hitl: false, terminal: false, contextStats: false, compact: false,
|
|
58
69
|
}
|
|
59
70
|
|
|
71
|
+
/** 实例 config.command 覆盖提取(与各 impl 的 `config.command ?? 缺省` 同规则;空串回缺省) */
|
|
72
|
+
const cmdFrom = (config: Record<string, unknown> | undefined): string =>
|
|
73
|
+
typeof config?.command === 'string' && config.command.trim() !== '' ? config.command.trim() : ''
|
|
74
|
+
|
|
60
75
|
export const HARNESS_REGISTRY: Record<string, HarnessDef> = {
|
|
61
76
|
mock: {
|
|
62
77
|
id: 'mock',
|
|
63
78
|
label: 'mock(测试)',
|
|
64
79
|
description: '进程内模拟引擎:联调/测试,无 LLM 调用',
|
|
65
80
|
capabilities: mockCaps,
|
|
81
|
+
probe: { inprocess: true },
|
|
66
82
|
create: (config, agent) => new MockAgentImpl({ ...config, agentId: agent.id, name: agent.name, role: agent.role, channelId: agent.channelId, token: agent.token }),
|
|
67
83
|
},
|
|
68
84
|
omp: {
|
|
@@ -70,6 +86,7 @@ export const HARNESS_REGISTRY: Record<string, HarnessDef> = {
|
|
|
70
86
|
label: 'omp(真实 LLM)',
|
|
71
87
|
description: 'omp 子进程(RPC 模式),默认推荐引擎',
|
|
72
88
|
capabilities: ompCaps,
|
|
89
|
+
probe: { command: c => cmdFrom(c) || 'omp' },
|
|
73
90
|
create: (config, agent) => new OmpRpcAgentImpl({ ...config, agentId: agent.id, name: agent.name, role: agent.role, channelId: agent.channelId, token: agent.token }),
|
|
74
91
|
},
|
|
75
92
|
opencode: {
|
|
@@ -77,6 +94,7 @@ export const HARNESS_REGISTRY: Record<string, HarnessDef> = {
|
|
|
77
94
|
label: 'opencode',
|
|
78
95
|
description: 'OpenCode 引擎(serve 进程 + HTTP/SSE),权限审批走 HITL',
|
|
79
96
|
capabilities: opencodeCaps,
|
|
97
|
+
probe: { command: c => cmdFrom(c) || harnessSettings().opencode_command },
|
|
80
98
|
create: (config, agent) => new OpenCodeAgentImpl({ ...config, agentId: agent.id, name: agent.name, role: agent.role, channelId: agent.channelId, token: agent.token }),
|
|
81
99
|
},
|
|
82
100
|
codex: {
|
|
@@ -84,6 +102,7 @@ export const HARNESS_REGISTRY: Record<string, HarnessDef> = {
|
|
|
84
102
|
label: 'codex',
|
|
85
103
|
description: 'OpenAI Codex CLI(app-server JSON-RPC),命令审批走 HITL',
|
|
86
104
|
capabilities: codexCaps,
|
|
105
|
+
probe: { command: c => cmdFrom(c) || harnessSettings().codex_command },
|
|
87
106
|
create: (config, agent) => new CodexAgentImpl({ ...config, agentId: agent.id, name: agent.name, role: agent.role, channelId: agent.channelId, token: agent.token }),
|
|
88
107
|
},
|
|
89
108
|
dsh: {
|
|
@@ -91,6 +110,7 @@ export const HARNESS_REGISTRY: Record<string, HarnessDef> = {
|
|
|
91
110
|
label: 'dsh(DeepSeek)',
|
|
92
111
|
description: 'DeepSeek Harness(ACP 协议);无同轮 steer,审批走 HITL',
|
|
93
112
|
capabilities: dshCaps,
|
|
113
|
+
probe: { command: c => cmdFrom(c) || harnessSettings().dsh_command },
|
|
94
114
|
create: (config, agent) => new DshAgentImpl({ ...config, agentId: agent.id, name: agent.name, role: agent.role, channelId: agent.channelId, token: agent.token }),
|
|
95
115
|
},
|
|
96
116
|
claude: {
|
|
@@ -98,6 +118,7 @@ export const HARNESS_REGISTRY: Record<string, HarnessDef> = {
|
|
|
98
118
|
label: 'claude',
|
|
99
119
|
description: 'Claude Agent SDK(骨架,待接入)',
|
|
100
120
|
capabilities: claudeCaps,
|
|
121
|
+
probe: { inprocess: true },
|
|
101
122
|
create: config => new ClaudeSdkAgentImpl(config),
|
|
102
123
|
},
|
|
103
124
|
}
|
|
@@ -43,6 +43,7 @@ import { createEnvEmbeddingProvider } from './embedding-provider'
|
|
|
43
43
|
import { listHarnessProcesses, listAliveHarnessProcessesByAgent, sweepHarnessProcesses, killHarnessProcess } from '../agents/harness-process'
|
|
44
44
|
import { hasTerminalSession, sweepTerminalSessions } from '../agents/harness-terminal'
|
|
45
45
|
import { knownHarnesses } from '../agents/registry'
|
|
46
|
+
import { assertHarnessUsable } from '../agents/harness-availability'
|
|
46
47
|
import { hostToolsForRole } from '../agents/host-tool-bridge'
|
|
47
48
|
import { TEAM_AGENT_ID, type MemoryRepo } from '../db/memory.repo'
|
|
48
49
|
import type { UserRepo } from '../db/user.repo'
|
|
@@ -1291,6 +1292,8 @@ export class AgentChannelManager {
|
|
|
1291
1292
|
visibility?: string
|
|
1292
1293
|
ownerUserId?: string | null
|
|
1293
1294
|
}): Promise<AgentTemplateDetail> {
|
|
1295
|
+
// 执行前强校验:引擎未安装的模板不允许落库(前端下拉已禁用未安装项,此处兜底)
|
|
1296
|
+
assertHarnessUsable(input.harness, input.config)
|
|
1294
1297
|
const row = this.deps.repos.agents.create({ name: input.name, harness: input.harness, config: input.config, visibility: input.visibility, ownerUserId: input.ownerUserId ?? null })
|
|
1295
1298
|
return this.templateDetailOf(row)
|
|
1296
1299
|
}
|
|
@@ -1324,6 +1327,7 @@ export class AgentChannelManager {
|
|
|
1324
1327
|
async updateAgent(agentId: string, patch: { name?: string, harness?: string, config?: Record<string, unknown>, enabled?: number, visibility?: string }): Promise<AgentTemplateDetail> {
|
|
1325
1328
|
const row = this.deps.repos.agents.findById(agentId)
|
|
1326
1329
|
if (!row) throw new AppError(404, 'NOT_FOUND', `Agent 模板不存在: ${agentId}`)
|
|
1330
|
+
if (patch.harness) assertHarnessUsable(patch.harness, patch.config ?? parseJson<Record<string, unknown>>(row.configJson, {}))
|
|
1327
1331
|
const updated = this.deps.repos.agents.update(agentId, patch)
|
|
1328
1332
|
if (!updated) throw new AppError(404, 'NOT_FOUND', `Agent 模板不存在: ${agentId}`)
|
|
1329
1333
|
return this.templateDetailOf(updated)
|
|
@@ -1367,6 +1371,9 @@ export class AgentChannelManager {
|
|
|
1367
1371
|
}): Promise<AgentInfo> {
|
|
1368
1372
|
const tpl = this.deps.repos.agents.findById(input.agentId)
|
|
1369
1373
|
if (!tpl) throw new AppError(404, 'NOT_FOUND', `Agent 模板不存在: ${input.agentId}`)
|
|
1374
|
+
const instConfig = { ...parseJson<Record<string, unknown>>(tpl.configJson, {}), ...input.configOverride }
|
|
1375
|
+
// 绑定即执行入口:引擎未安装的模板不许入 channel(deploy/加成员在此失败,不产生半成品实例)
|
|
1376
|
+
assertHarnessUsable(tpl.harness, instConfig)
|
|
1370
1377
|
if (input.role === 'lead') {
|
|
1371
1378
|
const channel = this.deps.repos.channels.findById(input.channelId)
|
|
1372
1379
|
if (channel && channel.leadAgentId) {
|
|
@@ -1378,7 +1385,7 @@ export class AgentChannelManager {
|
|
|
1378
1385
|
templateId: tpl.id,
|
|
1379
1386
|
name: tpl.name,
|
|
1380
1387
|
harness: tpl.harness,
|
|
1381
|
-
config:
|
|
1388
|
+
config: instConfig,
|
|
1382
1389
|
role: input.role,
|
|
1383
1390
|
})
|
|
1384
1391
|
if (input.role === 'lead') {
|
|
@@ -1560,6 +1567,7 @@ export class AgentChannelManager {
|
|
|
1560
1567
|
): Promise<AgentInfo> {
|
|
1561
1568
|
const m = this.deps.repos.channelAgents.findById(instanceId)
|
|
1562
1569
|
if (!m) throw new AppError(404, 'NOT_FOUND', `实例不存在: ${instanceId}`)
|
|
1570
|
+
if (patch.harness) assertHarnessUsable(patch.harness, patch.config ?? parseJson<Record<string, unknown>>(m.configJson, {}))
|
|
1563
1571
|
const updated = this.deps.repos.channelAgents.update(instanceId, patch)
|
|
1564
1572
|
if (!updated) throw new AppError(404, 'NOT_FOUND', `实例不存在: ${instanceId}`)
|
|
1565
1573
|
await this.unloadAgent(updated.channelId, instanceId)
|
|
@@ -1624,6 +1632,8 @@ export class AgentChannelManager {
|
|
|
1624
1632
|
if (!KNOWN_HARNESSES.has(harness)) {
|
|
1625
1633
|
throw new AppError(400, 'UNKNOWN_HARNESS', `未知 harness: ${harness}(可选 ${[...KNOWN_HARNESSES].join('/')})`)
|
|
1626
1634
|
}
|
|
1635
|
+
// 先校验引擎可用再落模板,防断言失败遗留孤儿模板行
|
|
1636
|
+
assertHarnessUsable(harness, input.config)
|
|
1627
1637
|
const tpl = this.deps.repos.agents.create({
|
|
1628
1638
|
name,
|
|
1629
1639
|
harness,
|
|
@@ -2056,6 +2066,9 @@ export class AgentChannelManager {
|
|
|
2056
2066
|
if (input.assigneeId && input.assigneeId !== channel.leadAgentId) {
|
|
2057
2067
|
assigneeId = this.resolveMemberRef(input.channelId, input.assigneeId).id
|
|
2058
2068
|
}
|
|
2069
|
+
// 任务执行前强校验:收件引擎不可用直接 409(不留「已创建即失败」的幽灵任务)
|
|
2070
|
+
const assigneeRow = this.deps.repos.channelAgents.findByChannelAgent(input.channelId, assigneeId)
|
|
2071
|
+
if (assigneeRow) assertHarnessUsable(assigneeRow.harness, parseJson<Record<string, unknown>>(assigneeRow.configJson, {}))
|
|
2059
2072
|
const description = input.mode
|
|
2060
2073
|
? encodeTaskMode(input.mode, input.modeConfig ?? {}, input.description ?? '')
|
|
2061
2074
|
: input.description
|
|
@@ -2109,6 +2122,8 @@ export class AgentChannelManager {
|
|
|
2109
2122
|
if (!assignee) {
|
|
2110
2123
|
throw new AppError(403, 'SCOPE_VIOLATION', 'assignee 不在本 channel')
|
|
2111
2124
|
}
|
|
2125
|
+
// 执行前强校验:worker 引擎未安装 → 报错回 lead(可改派),不产生必败子任务
|
|
2126
|
+
assertHarnessUsable(assignee.harness, parseJson<Record<string, unknown>>(assignee.configJson, {}))
|
|
2112
2127
|
let task: WorkspaceTask
|
|
2113
2128
|
if (input.parentTaskId) {
|
|
2114
2129
|
const parent = this.getTaskEngine().get(input.parentTaskId)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{K as e}from"./rqZ-1SoP.js";import{C as t,H as n,Ht as r,St as i,Wt as a,a as o,b as s,et as c,f as l,g as u,h as d,lt as f,m as p,mt as m,nt as h,p as g,t as _,x as v}from"./BSCpFtzG.js";import{d as ee}from"./-6OnbLFb.js";import{n as y}from"./B5S_UyaC.js";import{n as b}from"./BeIfu4N3.js";import{t as te}from"./VLI2Zy4t.js";import{t as ne}from"./DNxVTfhe.js";import{r as x}from"./D-Ca4BGY.js";import{t as re}from"./RzXYI9FM.js";import{t as S}from"./B-xskWta.js";import{t as C}from"./BBwMH_hG.js";import{t as w}from"./DTYBfjRY.js";import{t as T}from"./Cor9siSf.js";import{t as ie}from"./ORt9oDIy2.js";import{t as ae}from"./C9f8QMp12.js";import{t as E}from"./BcrN7QEl2.js";import{t as D}from"./G0qIw7Gq2.js";import{t as O}from"./CNs_Ozdc.js";var oe={class:`page`},se={key:0,class:`auth-gate`},ce={class:`sub`},le={class:`head`},ue={class:`sub`},de={class:`font-medium`},fe={key:1,class:`tok-cell`},pe=[`title`],me={class:`once-banner`},he={class:`raw-row`},ge={class:`raw`},_e=[`title`],ve=[`title`],ye={class:`once-meta`},k=_(t({__name:`tokens`,setup(t){let{t:_}=b();O({layout:`default`});let k=E(),A=m([]),j=m(!1),M=m(!1),N=m(``),P=m(!1),F=m(``),I=m(``),L=m(!1),R=m(``),z=m(``),B=m(!1),V=async()=>{if(k.isLoggedIn){j.value=!0;try{A.value=await k.listTokens()}catch(e){x.error(e instanceof Error?e.message:_(`tokens.k19jvk54013`))}finally{j.value=!1}}};c(()=>k.isLoggedIn,e=>{e?V():A.value=[]},{immediate:!0});let H=async()=>{P.value=!0;try{let e=await k.createToken(N.value);F.value=e.token,I.value=N.value,M.value=!1,N.value=``,await V()}catch(e){x.error(D(e))}finally{P.value=!1}},U=async()=>{if(!z.value.trim()){x.warning(_(`tokens.k169z26g014`));return}B.value=!0;try{await k.renameToken(R.value,z.value),L.value=!1,await V(),x.success(_(`tokens.k3n9aij015`))}catch(e){x.error(D(e))}finally{B.value=!1}},be=e=>{let t=e.id;if(!t)return;let n=t===k.user?.tokenId;(async()=>{try{if(await k.revokeToken(t),x.success(_(`tokens.k3n64kh016`)),n)return;await V()}catch(e){x.error(D(e))}})()},W=m(!1),G=m(!1),xe=l(()=>{let e=F.value;return e?`${e.slice(0,6)}${`•`.repeat(Math.max(12,e.length-10))}${e.slice(-4)}`:``}),Se=()=>{W.value=!W.value},K=async e=>{try{return await navigator.clipboard.writeText(e),!0}catch{try{let t=document.createElement(`textarea`);t.value=e,t.style.position=`fixed`,t.style.opacity=`0`,document.body.appendChild(t),t.select();let n=document.execCommand(`copy`);return document.body.removeChild(t),n}catch{return!1}}},Ce=async()=>{await K(F.value)?(G.value=!0,setTimeout(()=>{G.value=!1},1600)):x.error(_(`tokens.kjtcn2h017`))},q=()=>{F.value=``,W.value=!1,G.value=!1},J=m({}),Y=m(``),X=m(``),Z=e=>e in J.value,we=e=>Z(e.id)?J.value[e.id]:e.preview??`ut-${`•`.repeat(14)}`,Q=async e=>{if(Z(e.id)){Reflect.deleteProperty(J.value,e.id);return}if(!e.hasPlain){x.warning(_(`tokens.kkpgzo8018`));return}Y.value=e.id;try{let t=await k.revealToken(e.id);t&&(J.value[e.id]=t)}catch(e){x.error(e instanceof Error?e.message:_(`tokens.k1gj9ls019`))}finally{Y.value=``}},Te=async e=>{let t=J.value[e.id]??null;if(!t){if(!e.hasPlain){x.warning(_(`tokens.kr5ovor020`));return}Y.value=e.id;try{t=await k.revealToken(e.id)}catch(e){x.error(e instanceof Error?e.message:_(`tokens.kubtby5021`));return}finally{Y.value=``}}t&&await K(t)?(X.value=e.id,setTimeout(()=>{X.value=``},1600)):x.error(_(`tokens.kgkfcr0022`))},$=e=>e?e.replace(`T`,` `).slice(0,19):`-`,Ee=e=>{R.value=e.id??``,z.value=e.label??``,L.value=!0},De=[{title:_(`tokens.k41416002`),key:`label`,dataIndex:`label`},{title:`Token`,key:`token`,width:360},{title:_(`tokens.k1bg95gk023`),key:`createdAt`,dataIndex:`createdAt`,width:170},{title:_(`tokens.k1euotul024`),key:`lastUsedAt`,dataIndex:`lastUsedAt`,width:200},{title:_(`tokens.k40aa6025`),key:`action`,width:170}];return y({title:()=>_(`titles.tokens`)}),(t,c)=>{let l=te,m=T,_=S,y=ie,b=w,x=ne,E=ae,D=re,O=C;return n(),u(`div`,oe,[i(k).isLoggedIn?(n(),u(o,{key:1},[g(`div`,le,[g(`div`,null,[c[6]||=g(`p`,{class:`aw-kicker`},`agentworkshop / api tokens`,-1),c[7]||=g(`h2`,null,`API Token`,-1),g(`p`,ue,a(i(k).user?.name)+` · `+a(t.$t(`tokens.k1upppaw026`)),1)]),v(y,null,{default:h(()=>[i(k).user?.tokenId?(n(),p(_,{key:0,color:`green`},{default:h(()=>[s(a(t.$t(`tokens.khcxmsc006`)),1)]),_:1})):d(``,!0),v(l,{type:`primary`,onClick:c[1]||=e=>M.value=!0},{default:h(()=>[c[8]||=g(`span`,{class:`i-tabler-plus`},null,-1),s(` `+a(t.$t(`chips.issue`)),1)]),_:1})]),_:1})]),v(E,{spinning:i(j)},{default:h(()=>[v(x,{columns:De,"data-source":i(A),pagination:!1,"row-key":`id`},{bodyCell:h(({column:e,record:f})=>[e.key===`label`?(n(),p(y,{key:0},{default:h(()=>[c[9]||=g(`span`,{class:`i-tabler-key text-primary`},null,-1),g(`span`,de,a(f.label||t.$t(`tokens.kj3mklm028`)),1),f.id===i(k).user?.tokenId?(n(),p(_,{key:0,color:`green`},{default:h(()=>[s(a(t.$t(`tokens.k1defr98007`)),1)]),_:1})):d(``,!0)]),_:2},1024)):e.key===`token`?(n(),u(`div`,fe,[g(`code`,{class:r([`tok-val`,{revealed:Z(f.id)}]),title:Z(f.id)?`已显示明文,点击眼睛遮回`:`掩码预览`},a(we(f)),11,pe),f.hasPlain?(n(),p(l,{key:0,type:`text`,size:`small`,class:`tok-op`,loading:i(Y)===f.id,title:Z(f.id)?`遮回`:`查看明文`,onClick:e=>Q(f)},{default:h(()=>[g(`span`,{class:r(Z(f.id)?`i-tabler-eye-off`:`i-tabler-eye`)},null,2)]),_:2},1032,[`loading`,`title`,`onClick`])):d(``,!0),f.hasPlain?(n(),p(l,{key:1,type:`text`,size:`small`,class:r([`tok-op`,{ok:i(X)===f.id}]),title:i(X)===f.id?`已复制`:`复制明文`,onClick:e=>Te(f)},{default:h(()=>[g(`span`,{class:r(i(X)===f.id?`i-tabler-check`:`i-tabler-copy`)},null,2)]),_:2},1032,[`class`,`title`,`onClick`])):d(``,!0)])):e.key===`createdAt`?(n(),u(o,{key:2},[s(a($(f.createdAt)),1)],64)):e.key===`lastUsedAt`?(n(),u(o,{key:3},[s(a($(f.lastUsedAt)),1)],64)):e.key===`action`?(n(),p(y,{key:4},{default:h(()=>[v(l,{type:`link`,size:`small`,onClick:e=>Ee(f)},{default:h(()=>[c[10]||=g(`span`,{class:`i-tabler-edit`},null,-1),s(` `+a(t.$t(`tokens.k3vrpcs009`)),1)]),_:1},8,[`onClick`]),v(b,{title:f.id===i(k).user?.tokenId?`吊销当前会话 token 将退出登录`:`吊销后立即失效`,"ok-text":`吊销`,"cancel-text":`取消`,onConfirm:e=>be(f)},{default:h(()=>[v(l,{type:`text`,size:`small`,class:`tok-revoke`},{default:h(()=>[c[11]||=g(`span`,{class:`i-tabler-trash`},null,-1),s(` `+a(t.$t(`tokens.k3xmrz010`)),1)]),_:1})]),_:1},8,[`title`,`onConfirm`])]),_:2},1024)):d(``,!0)]),_:1},8,[`data-source`])]),_:1},8,[`spinning`]),v(O,{open:i(M),"onUpdate:open":c[3]||=e=>f(M)?M.value=e:null,title:t.$t(`chips.issueTitle`),"confirm-loading":i(P),"ok-text":`创建`,"cancel-text":`取消`,onOk:H},{default:h(()=>[v(D,{value:i(N),"onUpdate:value":c[2]||=e=>f(N)?N.value=e:null,placeholder:t.$t(`tokens.k1hlqknl001`),onKeydown:e(H,[`enter`])},null,8,[`value`,`placeholder`])]),_:1},8,[`open`,`title`,`confirm-loading`]),v(O,{open:i(F)!==``,title:`Token 已创建`,footer:null,"mask-closable":!1,onCancel:q,onAfterClose:q},{default:h(()=>[g(`div`,me,[c[12]||=g(`span`,{class:`i-tabler-circle-check`},null,-1),g(`span`,null,a(t.$t(`tokens.k12149oy011`)),1)]),g(`div`,he,[g(`code`,ge,a(i(W)?i(F):i(xe)),1),g(`button`,{class:`raw-op`,title:i(W)?`隐藏明文`:`显示明文`,onClick:Se},[g(`span`,{class:r(i(W)?`i-tabler-eye-off`:`i-tabler-eye`)},null,2)],8,_e),g(`button`,{class:r([`raw-op`,{ok:i(G)}]),title:i(G)?`已复制`:`复制`,onClick:Ce},[g(`span`,{class:r(i(G)?`i-tabler-check`:`i-tabler-copy`)},null,2)],10,ve)]),g(`div`,ye,[g(`span`,null,a(t.$t(`tokens.k3p0p44027`))+a(i(I)||t.$t(`tokens.kj3mklm028`)),1),c[13]||=g(`span`,null,`用法:Authorization: Bearer <token>`,-1)]),v(l,{type:`primary`,block:``,onClick:q},{default:h(()=>[s(a(t.$t(`tokens.k1s5f5zd012`)),1)]),_:1})]),_:1},8,[`open`]),v(O,{open:i(L),"onUpdate:open":c[5]||=e=>f(L)?L.value=e:null,title:`重命名 Token`,"confirm-loading":i(B),"ok-text":`保存`,"cancel-text":`取消`,onOk:U},{default:h(()=>[v(D,{value:i(z),"onUpdate:value":c[4]||=e=>f(z)?z.value=e:null,placeholder:t.$t(`tokens.k41416002`),onKeydown:e(U,[`enter`])},null,8,[`value`,`placeholder`])]),_:1},8,[`open`,`confirm-loading`])],64)):(n(),u(`div`,se,[v(m,{class:`auth-card`},{default:h(()=>[g(`h2`,null,a(t.$t(`tokens.k1k6z0r8003`)),1),g(`p`,ce,a(t.$t(`tokens.k1gjnree004`)),1),v(l,{type:`primary`,block:``,onClick:c[0]||=e=>(`navigateTo`in t?t.navigateTo:i(ee))(`/workshop`)},{default:h(()=>[s(a(t.$t(`tokens.k1bhhheq005`)),1)]),_:1})]),_:1})]))])}}}),[[`__scopeId`,`data-v-473f21d7`]]);export{k as default};
|