agentworkshop 0.7.16 → 0.7.18
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/.AgentWorkShop/prompts/host-tools.json +105 -0
- package/.output/.AgentWorkShop/prompts/host-tools.json +105 -0
- package/.output/nitro.json +1 -1
- package/.output/public/_nuxt/{DMdVWIGz.js → B-E8fXAO.js} +1 -1
- package/.output/public/_nuxt/{DyisHHzD.js → BA3XzwiD.js} +1 -1
- package/.output/public/_nuxt/{CD1SlCoI.js → BOJaHO5V.js} +4 -4
- package/.output/public/_nuxt/{6vYobj4F.js → BWC4HQHQ.js} +1 -1
- package/.output/public/_nuxt/B_C3GLsD.js +1 -0
- package/.output/public/_nuxt/{kkEthL84.js → Ba7YofCb.js} +1 -1
- package/.output/public/_nuxt/{DGVAuScD.js → C_2bTK0C.js} +1 -1
- package/.output/public/_nuxt/{B53Mp7lD.js → CgT5wMfz.js} +1 -1
- package/.output/public/_nuxt/{DT-Wg8r1.js → D4CLp1hc.js} +1 -1
- package/.output/public/_nuxt/{C6m-kenn.js → DAR70Lf5.js} +1 -1
- package/.output/public/_nuxt/{B3Psw_sL.js → DBXhDLgl.js} +1 -1
- package/.output/public/_nuxt/{Cj6139IM.js → DsOkkMX3.js} +1 -1
- package/.output/public/_nuxt/{DaE5saZg.js → QT54MQV9.js} +1 -1
- package/.output/public/_nuxt/{Bmpvki7M.js → UtviM1bg.js} +1 -1
- package/.output/public/_nuxt/_id_.IbmpkhH4.css +1 -0
- package/.output/public/_nuxt/builds/latest.json +1 -1
- package/.output/public/_nuxt/builds/meta/253c093e-3336-4451-9f11-08061bf22f01.json +1 -0
- package/.output/public/_nuxt/{entry.BNApuuEW.css → entry.DRUzDwmX.css} +1 -1
- package/.output/server/chunks/_/nitro.mjs +1130 -734
- package/.output/server/chunks/build/{TownView-CunmEMzS.mjs → TownView-ql2eHZOg.mjs} +4 -4
- package/.output/server/chunks/build/{TownView-CunmEMzS.mjs.map → TownView-ql2eHZOg.mjs.map} +1 -1
- package/.output/server/chunks/build/{_id_-D265Wh_k.mjs → _id_-Bs-euJPu.mjs} +215 -120
- package/.output/server/chunks/build/_id_-Bs-euJPu.mjs.map +1 -0
- package/.output/server/chunks/build/{_id_-CVnMdSeG.mjs → _id_-CwR5oLu9.mjs} +4 -4
- package/.output/server/chunks/build/{_id_-CVnMdSeG.mjs.map → _id_-CwR5oLu9.mjs.map} +1 -1
- package/.output/server/chunks/build/{_wsId_-eNJmBp05.mjs → _wsId_-B3aB9bIp.mjs} +5 -5
- package/.output/server/chunks/build/{_wsId_-eNJmBp05.mjs.map → _wsId_-B3aB9bIp.mjs.map} +1 -1
- package/.output/server/chunks/build/{daq-DxaXk_8N.mjs → daq-epWL_sC7.mjs} +5 -5
- package/.output/server/chunks/build/{daq-DxaXk_8N.mjs.map → daq-epWL_sC7.mjs.map} +1 -1
- package/.output/server/chunks/build/{dcw-B0dtiHFG.mjs → dcw-CBPaFj2f.mjs} +2 -2
- package/.output/server/chunks/build/{dcw-B0dtiHFG.mjs.map → dcw-CBPaFj2f.mjs.map} +1 -1
- package/.output/server/chunks/build/{logs-UWXeNKy_.mjs → logs-Bz1p6x_5.mjs} +3 -3
- package/.output/server/chunks/build/{logs-UWXeNKy_.mjs.map → logs-Bz1p6x_5.mjs.map} +1 -1
- package/.output/server/chunks/build/{pages-Aexs6LjD.mjs → pages-Dq7_H4FK.mjs} +3 -3
- package/.output/server/chunks/build/{pages-Aexs6LjD.mjs.map → pages-Dq7_H4FK.mjs.map} +1 -1
- package/.output/server/chunks/build/{town-BbaR-8io.mjs → town-CstiKxf_.mjs} +5 -5
- package/.output/server/chunks/build/{town-BbaR-8io.mjs.map → town-CstiKxf_.mjs.map} +1 -1
- package/.output/server/chunks/build/{useDaqStream-BkmyQT06.mjs → useDaqStream-BOOZ-p-l.mjs} +2 -2
- package/.output/server/chunks/build/{useDaqStream-BkmyQT06.mjs.map → useDaqStream-BOOZ-p-l.mjs.map} +1 -1
- package/.output/server/chunks/build/{useDcwStream-DWVoU96J.mjs → useDcwStream-DyB2ufQI.mjs} +15 -1
- package/.output/server/chunks/build/{useDcwStream-DWVoU96J.mjs.map → useDcwStream-DyB2ufQI.mjs.map} +1 -1
- package/.output/server/chunks/build/{useDeviceTwins-34WGk9aN.mjs → useDeviceTwins-ZbT86L9O.mjs} +2 -2
- package/.output/server/chunks/build/{useDeviceTwins-34WGk9aN.mjs.map → useDeviceTwins-ZbT86L9O.mjs.map} +1 -1
- package/.output/server/chunks/build/{useOpsLog-C9zTzYof.mjs → useOpsLog-8HCYFb6n.mjs} +2 -2
- package/.output/server/chunks/build/{useOpsLog-C9zTzYof.mjs.map → useOpsLog-8HCYFb6n.mjs.map} +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/revert.post.mjs +42 -0
- package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/revert.post.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/versions.get.mjs +29 -0
- package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/versions.get.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/dcw/recipes/{_id_.patch.mjs → index.patch.mjs} +11 -6
- package/.output/server/chunks/routes/api/workshop/dcw/recipes/index.patch.mjs.map +1 -0
- package/.output/server/chunks/routes/renderer.mjs +1 -1
- package/.output/server/chunks/virtual/entry.mjs +42 -8
- package/.output/server/chunks/virtual/precomputed.mjs +1 -1
- package/.output/server/package.json +1 -1
- package/app/composables/workshop/useDcwStream.ts +12 -0
- package/app/pages/dcw/[id].vue +219 -5
- package/i18n/locales/en.ts +17 -0
- package/i18n/locales/zh-CN.ts +17 -0
- package/package.json +1 -1
- package/scripts/_dbg-recipe-e2e.mjs +176 -0
- package/scripts/_dbg-recipe-stale-e2e.mjs +135 -0
- package/server/api/workshop/dcw/recipes/{[id].patch.ts → [id]/index.patch.ts} +9 -3
- package/server/api/workshop/dcw/recipes/[id]/revert.post.ts +28 -0
- package/server/api/workshop/dcw/recipes/[id]/versions.get.ts +14 -0
- package/server/data/daqs.json +203 -66
- package/server/data/dcw-lines.json +7 -28
- package/server/data/dcw-products.json +14 -7
- package/server/data/dcw-recipes.json +28 -14
- package/server/data/dcw-rollback.json +108 -9
- package/server/data/dcw-runs.json +430 -230
- package/server/data/dcw-writes.json +48 -0
- package/server/data/dcws.json +464 -372
- package/server/data/line-runs.json +23 -13
- package/server/services/workshop/agents/host-tool-bridge.ts +21 -1
- package/server/services/workshop/agents/industrial-tools.ts +256 -4
- package/server/services/workshop/daq/drivers.ts +16 -3
- package/server/services/workshop/dcw/dcw-controller.ts +61 -3
- package/server/services/workshop/dcw/dcw-recipe.repo.ts +96 -3
- package/server/services/workshop/dcw/drivers.ts +2 -2
- package/shared/dcw-protocol.ts +14 -2
- package/.output/public/_nuxt/Bc3PiGGr.js +0 -1
- package/.output/public/_nuxt/_id_.DTvjd6VN.css +0 -1
- package/.output/public/_nuxt/builds/meta/cf0a8831-4514-493d-ad5a-4e371a089ef9.json +0 -1
- package/.output/server/chunks/build/_id_-D265Wh_k.mjs.map +0 -1
- package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id_.patch.mjs.map +0 -1
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 配方失效节点(停用/解绑/删除)一致性 E2E(隔离实例 :3021)
|
|
3
|
+
*
|
|
4
|
+
* 验证:
|
|
5
|
+
* 1) 配方一键下发:A 正常下发;B(停用)/C(解绑)/D(删除)三类失效参数逐一跳过并记明原因
|
|
6
|
+
* 2) 手动 REST 写停用节点 → 409(单点门控)
|
|
7
|
+
* 3) Agent dcw_control 停用节点 → 明确「已停用」报错
|
|
8
|
+
* 4) Agent recipe_update:触到停用/解绑节点被拒;正常参数保存时自动剪除基线中的失效参数并告知
|
|
9
|
+
* 5) recipe_rollback 回退到含失效节点的历史版本 → 剪枝成功,版本描述记录剔除
|
|
10
|
+
* 6) line_context 参数行带 [已停用/已取消绑定/已删除] 标记
|
|
11
|
+
* 用法:NO_PROXY='127.0.0.1,localhost' node scripts/_dbg-recipe-stale-e2e.mjs [base]
|
|
12
|
+
*/
|
|
13
|
+
const BASE = process.argv[2] ?? 'http://127.0.0.1:3021'
|
|
14
|
+
const TAG = `st${Math.random().toString(36).slice(2, 6)}`
|
|
15
|
+
let pass = 0
|
|
16
|
+
let fail = 0
|
|
17
|
+
const ok = (cond, name, extra = '') => {
|
|
18
|
+
if (cond) { pass++; console.log(` ✓ ${name}`) }
|
|
19
|
+
else { fail++; console.log(` ✗ ${name} ${extra}`) }
|
|
20
|
+
}
|
|
21
|
+
const api = async (method, path, { body, token } = {}) => {
|
|
22
|
+
const res = await fetch(`${BASE}${path}`, {
|
|
23
|
+
method,
|
|
24
|
+
headers: { 'content-type': 'application/json', ...(token ? { authorization: `Bearer ${token}` } : {}) },
|
|
25
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
26
|
+
signal: AbortSignal.timeout(30_000),
|
|
27
|
+
})
|
|
28
|
+
return { status: res.status, ...(await res.json().catch(() => ({}))) }
|
|
29
|
+
}
|
|
30
|
+
const invoke = (token, agentId, tool, args) => api('POST', '/api/workshop/agent-tools/invoke', { body: { agentId, tool, args }, token })
|
|
31
|
+
|
|
32
|
+
async function main() {
|
|
33
|
+
console.log(`━━━ 配方失效节点一致性 E2E @ ${BASE} (tag=${TAG}) ━━━`)
|
|
34
|
+
const login = await api('POST', '/api/users/login', { body: { email: 'admin', password: 'admin123' } })
|
|
35
|
+
const token = login.data?.token
|
|
36
|
+
ok(Boolean(token), 'admin 登录')
|
|
37
|
+
|
|
38
|
+
// ── 夹具:产线 + 4 节点 + 产品 + 配方(4 参数)──
|
|
39
|
+
const line = (await api('POST', '/api/workshop/dcw/lines', { body: { name: `st-line-${TAG}` }, token })).data?.line
|
|
40
|
+
const mkNode = async (name) => (await api('POST', '/api/workshop/dcw', {
|
|
41
|
+
body: { name: `${name}-${TAG}`, templateRef: 'temp-sp', driver: 'mock', driverConfig: { key: `${name}-${TAG}` }, min: 0, max: 100, unit: '℃', lineId: line.id },
|
|
42
|
+
token,
|
|
43
|
+
})).data?.node
|
|
44
|
+
const na = await mkNode('st-a')
|
|
45
|
+
const nb = await mkNode('st-b')
|
|
46
|
+
const nc = await mkNode('st-c')
|
|
47
|
+
const nd = await mkNode('st-d')
|
|
48
|
+
const product = (await api('POST', '/api/workshop/dcw/products', { body: { name: `st-prod-${TAG}`, lineId: line.id }, token })).data?.product
|
|
49
|
+
const recipe = (await api('POST', '/api/workshop/dcw/recipes', {
|
|
50
|
+
body: { name: `st-recipe-${TAG}`, productId: product.id, params: [
|
|
51
|
+
{ nodeId: na.id, value: 40 },
|
|
52
|
+
{ nodeId: nb.id, value: 41 },
|
|
53
|
+
{ nodeId: nc.id, value: 42 },
|
|
54
|
+
{ nodeId: nd.id, value: 43 },
|
|
55
|
+
] },
|
|
56
|
+
token,
|
|
57
|
+
})).data?.recipe
|
|
58
|
+
ok(Boolean(na?.id && nb?.id && nc?.id && nd?.id && recipe?.id), `夹具:4 节点 + 配方 ${recipe?.id}`)
|
|
59
|
+
|
|
60
|
+
// ── 三态变异:B 停用 / C 解绑 / D 删除 ──
|
|
61
|
+
await api('PATCH', `/api/workshop/dcw/${nb.id}`, { body: { enabled: false }, token })
|
|
62
|
+
await api('PATCH', `/api/workshop/dcw/${nc.id}`, { body: { lineId: '' }, token })
|
|
63
|
+
await api('DELETE', `/api/workshop/dcw/${nd.id}`, { token })
|
|
64
|
+
|
|
65
|
+
// ── 1. 一键下发:失效参数逐一跳过 ──
|
|
66
|
+
const apply = await api('POST', `/api/workshop/dcw/recipes/${recipe.id}/apply`, { body: {}, token })
|
|
67
|
+
ok(apply.status === 200, '一键下发执行')
|
|
68
|
+
const results = apply.data?.run?.results ?? []
|
|
69
|
+
const byNode = Object.fromEntries(results.map(r => [r.nodeId ?? 'deleted', r]))
|
|
70
|
+
ok(byNode[na.id]?.ok === true, 'A 正常节点照常下发')
|
|
71
|
+
ok((byNode[nb.id]?.message ?? '').includes('已停用') && byNode[nb.id]?.ok === false, `B 停用跳过(${byNode[nb.id]?.message ?? '∅'})`)
|
|
72
|
+
ok((byNode[nc.id]?.message ?? '').includes('已取消绑定') && byNode[nc.id]?.ok === false, `C 解绑跳过(${byNode[nc.id]?.message ?? '∅'})`)
|
|
73
|
+
ok(Object.values(byNode).some(r => (r.message ?? '').includes('已删除') && r.ok === false), `D 删除跳过(${(results.find(r => !r.nodeId)?.message) ?? '∅'})`)
|
|
74
|
+
ok((apply.data?.run?.results ?? []).every(r => results.some(x => x === r)), 'run.results 完整保留跳过原因')
|
|
75
|
+
|
|
76
|
+
// ── 2. 手动 REST 写停用节点 → 409 ──
|
|
77
|
+
const manual = await api('POST', `/api/workshop/dcw/${nb.id}/write`, { body: { value: 10 }, token })
|
|
78
|
+
ok(manual.status === 409, `手动写停用节点 409(实际 ${manual.status} ${manual.message ?? ''})`)
|
|
79
|
+
|
|
80
|
+
// ── 3. Agent 组建 + 直调 ──
|
|
81
|
+
const ch = (await api('POST', '/api/workshop/channels', {
|
|
82
|
+
body: { name: `st-${TAG}`, leadAgent: { name: `lead-${TAG}`, harness: 'mock', config: { delayMs: 60 } } },
|
|
83
|
+
token,
|
|
84
|
+
})).data
|
|
85
|
+
const tpl = await api('POST', '/api/workshop/agents', { body: { name: `stw-${TAG}`, harness: 'omp', config: { provider: 'zhipu-coding-plan', model: 'glm-5.3-flash' } }, token })
|
|
86
|
+
const join = await api('POST', `/api/workshop/channels/${ch.channelId}/agents`, { body: { agentId: tpl.data.id, role: 'worker' }, token })
|
|
87
|
+
const instId = join.data?.id
|
|
88
|
+
for (const n of [na, nb, nc]) await api('POST', '/api/workshop/agent-tools/bindings', { body: { agentId: instId, nodeId: n.id, kind: 'dcw', mode: 'auto' }, token })
|
|
89
|
+
ok(Boolean(instId), 'omp worker 建号+绑定')
|
|
90
|
+
|
|
91
|
+
const wB = await invoke(token, instId, 'dcw_control', { node_id: nb.id, value: 10 })
|
|
92
|
+
ok(wB.data?.result?.isError === true && wB.data?.result?.text.includes('已停用'), '[agent] dcw_control 停用节点明确报错', wB.data?.result?.text?.slice(0, 80))
|
|
93
|
+
|
|
94
|
+
// ── 4. recipe_update:触雷拒绝 + 自动剪枝 ──
|
|
95
|
+
const uB = await invoke(token, instId, 'recipe_update', { recipe_id: recipe.id, params: [{ node_id: nb.id, value: 70 }], reason: '触雷测试' })
|
|
96
|
+
ok(uB.data?.result?.isError === true && uB.data?.result?.text.includes('已停用'), '[agent] recipe_update 停用节点被拒', uB.data?.result?.text?.slice(0, 80))
|
|
97
|
+
const uC = await invoke(token, instId, 'recipe_update', { recipe_id: recipe.id, params: [{ node_id: nc.id, value: 70 }], reason: '触雷测试' })
|
|
98
|
+
ok(uC.data?.result?.isError === true && uC.data?.result?.text.includes('已取消绑定'), '[agent] recipe_update 解绑节点被拒', uC.data?.result?.text?.slice(0, 80))
|
|
99
|
+
|
|
100
|
+
const uA = await invoke(token, instId, 'recipe_update', { recipe_id: recipe.id, params: [{ node_id: na.id, value: 77 }], reason: '正常参数保存(应自动剔除 C/D 失效参数)' })
|
|
101
|
+
ok(uA.data?.result?.text?.includes('v2') && uA.data?.result?.text?.includes('剔除'), '[agent] 正常保存 → v2 + 剔除失效参数告知', uA.data?.result?.text?.slice(0, 120))
|
|
102
|
+
const cur = (await api('GET', '/api/workshop/dcw', { token })).data?.recipes?.find(r => r.id === recipe.id)
|
|
103
|
+
ok(cur?.params?.length === 2 && cur?.params?.some(p => p.nodeId === na.id) && cur?.params?.some(p => p.nodeId === nb.id), `基线剪枝后参数=A+B(实际 ${cur?.params?.length} 条)`)
|
|
104
|
+
|
|
105
|
+
// ── 5. 回退到含失效节点的 v1 → 剪枝成功 ──
|
|
106
|
+
const rb = await api('POST', `/api/workshop/dcw/recipes/${recipe.id}/revert`, { body: { version: 1, reason: '回退到含失效节点的初版' }, token })
|
|
107
|
+
ok(rb.status === 200 && rb.data?.recipe?.version === 3, `回退 v1 → v3 成功(实际 v${rb.data?.recipe?.version})`)
|
|
108
|
+
ok((rb.data?.recipe?.description ?? '').includes('剔除') || (rb.data?.recipe?.paramsHistory ?? []).some(h => (h.description ?? '').includes('剔除')), '回退版本描述记录剔除')
|
|
109
|
+
ok((rb.data?.recipe?.params ?? []).every(p => p.nodeId !== nd.id && p.nodeId !== nc.id), '回退后不含已删除/已解绑节点参数')
|
|
110
|
+
|
|
111
|
+
// ── 6. line_context 状态标记 ──
|
|
112
|
+
const lc = await invoke(token, instId, 'line_context', {})
|
|
113
|
+
if (lc.data?.result?.text?.includes('已停用') && lc.data?.result?.text?.includes('参数不下发')) ok(true, '[agent] line_context 标记停用参数')
|
|
114
|
+
else {
|
|
115
|
+
ok(false, '[agent] line_context 标记停用参数')
|
|
116
|
+
console.log(' ── line_context 实际输出 ──')
|
|
117
|
+
console.log(lc.data?.result?.text ?? JSON.stringify(lc))
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ── 清理 ──
|
|
121
|
+
await api('DELETE', `/api/workshop/channels/${ch.channelId}?purge=1`, { token }).catch(() => {})
|
|
122
|
+
await api('DELETE', `/api/workshop/agents/${tpl.data.id}`, { token }).catch(() => {})
|
|
123
|
+
await api('DELETE', `/api/workshop/dcw/recipes/${recipe.id}`, { token }).catch(() => {})
|
|
124
|
+
await api('DELETE', `/api/workshop/dcw/products/${product.id}`, { token }).catch(() => {})
|
|
125
|
+
for (const n of [na, nb, nc]) await api('DELETE', `/api/workshop/dcw/${n.id}`, { token }).catch(() => {})
|
|
126
|
+
await api('DELETE', `/api/workshop/dcw/lines/${line.id}`, { token }).catch(() => {})
|
|
127
|
+
|
|
128
|
+
console.log(`\n━━━ StaleRef E2E: ${pass} passed / ${fail} failed ━━━`)
|
|
129
|
+
process.exit(fail === 0 ? 0 : 1)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
main().catch((err) => {
|
|
133
|
+
console.error('E2E 异常:', err)
|
|
134
|
+
process.exit(1)
|
|
135
|
+
})
|
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* PATCH /api/workshop/dcw/recipes/:id —— 编辑配方。
|
|
3
|
+
* params 变更自动版本化(paramsHistory 入史,归因=当前用户)。
|
|
3
4
|
*/
|
|
4
5
|
import { getRouterParam, readBody } from 'h3'
|
|
5
6
|
import type { RecipeInput } from '#shared/dcw-protocol'
|
|
6
7
|
import { resolveUser } from '@/server/api/workshop/caller'
|
|
7
8
|
import { defineApiHandler } from '@/server/utils/response'
|
|
8
|
-
import {
|
|
9
|
+
import { getDcwController } from '@/server/services/workshop/dcw/dcw-controller'
|
|
9
10
|
|
|
10
11
|
export default defineApiHandler(async (event) => {
|
|
11
|
-
resolveUser(event)
|
|
12
|
+
const user = resolveUser(event)
|
|
12
13
|
const id = getRouterParam(event, 'id') ?? ''
|
|
13
14
|
const body = await readBody<Partial<RecipeInput>>(event) ?? {}
|
|
14
|
-
const recipe =
|
|
15
|
+
const recipe = getDcwController().updateRecipe(id, body, {
|
|
16
|
+
by: 'user',
|
|
17
|
+
actorName: user.name,
|
|
18
|
+
actor: user.id,
|
|
19
|
+
description: '界面编辑配方',
|
|
20
|
+
})
|
|
15
21
|
return { recipe }
|
|
16
22
|
})
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POST /api/workshop/dcw/recipes/:id/revert —— 回退配方参数到指定历史版本/已知良好批次。
|
|
3
|
+
* 生成新版本(非破坏;历史完整保留)。body: { version?: number, toLastGood?: boolean, reason: string }。
|
|
4
|
+
*/
|
|
5
|
+
import { getRouterParam, readBody } from 'h3'
|
|
6
|
+
import { resolveUser } from '@/server/api/workshop/caller'
|
|
7
|
+
import { defineApiHandler } from '@/server/utils/response'
|
|
8
|
+
import { getDcwController } from '@/server/services/workshop/dcw/dcw-controller'
|
|
9
|
+
import { AppError, ErrorCodes } from '@/server/utils/errors'
|
|
10
|
+
|
|
11
|
+
export default defineApiHandler(async (event) => {
|
|
12
|
+
const user = resolveUser(event)
|
|
13
|
+
const id = getRouterParam(event, 'id') ?? ''
|
|
14
|
+
const body = await readBody<{ version?: number, toLastGood?: boolean, reason?: string }> (event) ?? {}
|
|
15
|
+
if (!body.toLastGood && body.version == null) {
|
|
16
|
+
throw new AppError(400, ErrorCodes.VALIDATION_ERROR, '需提供 version(历史版本号)或 toLastGood=true')
|
|
17
|
+
}
|
|
18
|
+
const recipe = getDcwController().revertRecipe(id, {
|
|
19
|
+
version: body.version,
|
|
20
|
+
toLastGood: body.toLastGood === true,
|
|
21
|
+
}, {
|
|
22
|
+
by: 'user',
|
|
23
|
+
actorName: user.name,
|
|
24
|
+
actor: user.id,
|
|
25
|
+
description: body.reason?.trim() || '界面回退',
|
|
26
|
+
})
|
|
27
|
+
return { recipe }
|
|
28
|
+
})
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GET /api/workshop/dcw/recipes/:id/versions —— 配方参数版本历史(旧→新,尾行=当前版)。
|
|
3
|
+
* 每条:version/at/by(来源)/actorName(人话操作者)/description(变更原因)/params。
|
|
4
|
+
*/
|
|
5
|
+
import { getRouterParam } from 'h3'
|
|
6
|
+
import { resolveUser } from '@/server/api/workshop/caller'
|
|
7
|
+
import { defineApiHandler } from '@/server/utils/response'
|
|
8
|
+
import { getDcwController } from '@/server/services/workshop/dcw/dcw-controller'
|
|
9
|
+
|
|
10
|
+
export default defineApiHandler(async (event) => {
|
|
11
|
+
resolveUser(event)
|
|
12
|
+
const id = getRouterParam(event, 'id') ?? ''
|
|
13
|
+
return { versions: getDcwController().recipeVersions(id) }
|
|
14
|
+
})
|