agentworkshop 0.7.65 → 0.7.66
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/{DAfWhONg.js → 1NcBJMyL.js} +3 -3
- package/.output/public/_nuxt/{CKE_kTL92.js → Bb4REys92.js} +1 -1
- package/.output/public/_nuxt/{CVUMEGa1.js → Bnf04goH.js} +1 -1
- package/.output/public/_nuxt/{Cuw2--X2.js → CJPIo2E7.js} +1 -1
- package/.output/public/_nuxt/{CkYk8bj02.js → Cfy5Jn8v2.js} +1 -1
- package/.output/public/_nuxt/{DSWuWYx_2.js → DBERjwK92.js} +1 -1
- package/.output/public/_nuxt/DG0esyZR.js +1 -0
- package/.output/public/_nuxt/{B5RD2ZST.js → DPP0W5Ni.js} +1 -1
- package/.output/public/_nuxt/{DqT2_Jtw2.js → DxJFBokW2.js} +1 -1
- package/.output/public/_nuxt/{Cnf1PFhB2.js → GKiBVVYe2.js} +1 -1
- package/.output/public/_nuxt/{Dc5NeCDq.js → H13l21fR.js} +1 -1
- package/.output/public/_nuxt/{Cdw4juXw.js → XErFZOU9.js} +1 -1
- package/.output/public/_nuxt/builds/latest.json +1 -1
- package/.output/public/_nuxt/builds/meta/8bf65244-eb00-45c7-be23-9dcd7b917ef3.json +1 -0
- package/.output/public/_nuxt/{D-z6N1Cz.js → kYyvM13M.js} +1 -1
- package/.output/public/_nuxt/{B0w8DZ4C.js → t54AYjsC.js} +1 -1
- package/.output/server/chunks/_/nitro.mjs +1331 -1015
- package/.output/server/chunks/_/nitro.mjs.map +1 -1
- package/.output/server/chunks/build/useWorkshopWs-CM-Ywk4h.mjs.map +1 -1
- package/.output/server/chunks/routes/renderer.mjs +1 -1
- package/.output/server/chunks/virtual/precomputed.mjs +1 -1
- package/.output/server/package.json +1 -1
- package/README-zh.md +8 -6
- package/README.md +7 -5
- package/app/stores/workshop/notifications.ts +22 -1
- package/config.yml +1 -1
- package/docs/cli.en.md +2 -2
- package/docs/cli.md +2 -2
- package/docs/plugins.en.md +1 -1
- package/docs/plugins.md +1 -1
- package/docs/sdk.en.md +1 -1
- package/docs/sdk.md +1 -1
- package/package.json +1 -1
- package/server/plugins-builtin/diag-bridge/client.mjs +230 -132
- package/server/plugins-builtin/diag-bridge/i18n.json +45 -15
- package/server/plugins-builtin/rag-bridge/client.mjs +182 -108
- package/server/plugins-builtin/rag-bridge/i18n.json +84 -60
- package/server/services/workshop/agents/host-tool-bridge/dispatch.ts +5 -0
- package/server/services/workshop/agents/industrial/aml-dataset-tools.ts +14 -0
- package/server/services/workshop/agents/industrial/dcw-judge-tools.ts +7 -2
- package/server/services/workshop/agents/industrial/line-tools.ts +16 -3
- package/server/services/workshop/agents/industrial/optimization-tools.ts +10 -0
- package/server/services/workshop/agents/industrial/tool-verify.ts +373 -0
- package/server/services/workshop/agents/industrial/twin-tools.ts +5 -0
- package/server/services/workshop/runtime/manager/channel-template-constants.ts +20 -0
- package/server/services/workshop/runtime/manager/channel-templates.ts +3 -12
- package/.output/public/_nuxt/DIDbAMv5.js +0 -1
- package/.output/public/_nuxt/builds/meta/874575e6-2606-4f09-a59b-724158b3aba2.json +0 -1
|
@@ -1,108 +1,182 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* rag-bridge 客户端面板 —— 经 ctx.ui.registerPanel 注入 'plugins.page' 插槽。
|
|
3
|
-
* 自包含 ESM(零导入);ctx 由宿主 loader 注入(fetch 带鉴权/t 命名空间翻译)。
|
|
4
|
-
* 健康徽标(后端/Web
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
.aw-rb
|
|
13
|
-
.aw-rb-
|
|
14
|
-
.aw-rb-
|
|
15
|
-
.aw-rb-dot
|
|
16
|
-
.aw-rb-dot-
|
|
17
|
-
.aw-rb-
|
|
18
|
-
.aw-rb-
|
|
19
|
-
.aw-rb-btn
|
|
20
|
-
.aw-rb-
|
|
21
|
-
.aw-rb-
|
|
22
|
-
.aw-rb-
|
|
23
|
-
.aw-rb-
|
|
24
|
-
.aw-rb-hit
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
el
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* rag-bridge 客户端面板 —— 经 ctx.ui.registerPanel 注入 'plugins.page' 插槽。
|
|
3
|
+
* 自包含 ESM(零导入);ctx 由宿主 loader 注入(fetch 带鉴权/t 命名空间翻译)。
|
|
4
|
+
* 健康徽标(后端/Web/知识库,30s 自动刷新) + 「检测连接」显式检测(调 /health,
|
|
5
|
+
* 含用户配置 token 的实测三态,结构化渲染) + 快捷检索框;返回清理函数供卸载回收。
|
|
6
|
+
*/
|
|
7
|
+
export function setup(ctx) {
|
|
8
|
+
// 面板样式(自包含注入,幂等;不依赖宿主页面样式表)
|
|
9
|
+
const styleId = 'aw-rb-styles'
|
|
10
|
+
if (!document.getElementById(styleId)) {
|
|
11
|
+
document.head.append(ctx.el('style', { id: styleId }, [`
|
|
12
|
+
.aw-rb { display:flex; flex-direction:column; gap:8px; font-size:12px; }
|
|
13
|
+
.aw-rb-row { display:flex; gap:8px; align-items:center; flex-wrap:wrap; }
|
|
14
|
+
.aw-rb-cell { display:inline-flex; align-items:center; gap:4px; }
|
|
15
|
+
.aw-rb-dot { width:8px; height:8px; border-radius:50%; display:inline-block; }
|
|
16
|
+
.aw-rb-dot-ok { background:#35e0a0; }
|
|
17
|
+
.aw-rb-dot-bad { background:#e05a5a; }
|
|
18
|
+
.aw-rb-dot-na { background:#9aa4ad; }
|
|
19
|
+
.aw-rb-btn { padding:5px 13px; border:1px solid color-mix(in srgb, currentColor 28%, transparent); border-radius:7px; background:transparent; color:inherit; cursor:pointer; }
|
|
20
|
+
.aw-rb-btn:disabled { opacity:.5; cursor:default; }
|
|
21
|
+
.aw-rb-input { flex:1; min-width:200px; padding:5px 9px; border:1px solid color-mix(in srgb, currentColor 24%, transparent); border-radius:7px; background:transparent; color:inherit; }
|
|
22
|
+
.aw-rb-results { display:flex; flex-direction:column; gap:6px; }
|
|
23
|
+
.aw-rb-empty { opacity:.6; }
|
|
24
|
+
.aw-rb-hit { padding:6px 8px; border:1px solid color-mix(in srgb, currentColor 12%, transparent); border-radius:7px; }
|
|
25
|
+
.aw-rb-hit-head { font-size:11px; opacity:.85; }
|
|
26
|
+
.aw-rb-hit-brief { opacity:.7; margin-top:2px; }
|
|
27
|
+
.aw-rb-check { padding:8px 10px; border:1px dashed color-mix(in srgb, currentColor 20%, transparent); border-radius:8px; display:flex; flex-direction:column; gap:5px; }
|
|
28
|
+
.aw-rb-check-rows { display:flex; flex-direction:column; gap:3px; }
|
|
29
|
+
.aw-rb-check-row { display:flex; gap:6px; align-items:baseline; flex-wrap:wrap; }
|
|
30
|
+
.aw-rb-check-k { opacity:.65; min-width:86px; }
|
|
31
|
+
.aw-rb-check-err { color:#e05a5a; }
|
|
32
|
+
.aw-rb-check-ok { color:#35e0a0; }
|
|
33
|
+
`]))
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
ctx.ui.registerPanel({
|
|
37
|
+
slot: 'plugins.page',
|
|
38
|
+
name: 'kb-panel',
|
|
39
|
+
titleKey: 'panel.title',
|
|
40
|
+
order: 10,
|
|
41
|
+
mount(el) {
|
|
42
|
+
const t = k => ctx.t(`panel.${k}`)
|
|
43
|
+
const timer = setInterval(refresh, 30_000)
|
|
44
|
+
|
|
45
|
+
function dot(ok, na = false) {
|
|
46
|
+
return ctx.el('span', {
|
|
47
|
+
class: `aw-rb-dot ${na ? 'aw-rb-dot-na' : ok ? 'aw-rb-dot-ok' : 'aw-rb-dot-bad'}`,
|
|
48
|
+
title: na ? t('tokenUnknown') : ok ? t('ok') : t('bad'),
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const backendCell = ctx.el('span', { class: 'aw-rb-cell' }, [dot(false), ` ${t('backend')}`])
|
|
53
|
+
const webCell = ctx.el('span', { class: 'aw-rb-cell' }, [dot(false), ` ${t('web')}`])
|
|
54
|
+
const kbCell = ctx.el('span', { class: 'aw-rb-cell aw-mono' }, [dot(false), ` ${t('kb')}: -`])
|
|
55
|
+
const counterCell = ctx.el('span', { class: 'aw-rb-cell faint' }, [
|
|
56
|
+
`${t('counters')}: ${t('store')} 0 · ${t('index')} 0`,
|
|
57
|
+
])
|
|
58
|
+
const statusRow = ctx.el('div', { class: 'aw-rb-row' }, [backendCell, webCell, kbCell, counterCell])
|
|
59
|
+
|
|
60
|
+
// ---- 连接检测:显式动作,调 /health(宿主后端会拿用户配置的 token 实测受保护端点) ----
|
|
61
|
+
const checkBtn = ctx.el('button', { class: 'aw-rb-btn' }, [t('checkBtn')])
|
|
62
|
+
const checkRows = ctx.el('div', { class: 'aw-rb-check-rows' })
|
|
63
|
+
const checkBox = ctx.el('div', { class: 'aw-rb-check' }, [checkRows])
|
|
64
|
+
checkBtn.addEventListener('click', () => void runCheck())
|
|
65
|
+
|
|
66
|
+
const input = ctx.el('input', {
|
|
67
|
+
class: 'aw-rb-input',
|
|
68
|
+
placeholder: t('searchPlaceholder'),
|
|
69
|
+
})
|
|
70
|
+
input.addEventListener('keydown', (e) => {
|
|
71
|
+
if (e.key === 'Enter') void runSearch()
|
|
72
|
+
})
|
|
73
|
+
const btn = ctx.el('button', { class: 'aw-rb-btn' }, [t('searchBtn')])
|
|
74
|
+
btn.addEventListener('click', () => void runSearch())
|
|
75
|
+
const results = ctx.el('div', { class: 'aw-rb-results' })
|
|
76
|
+
|
|
77
|
+
el.append(
|
|
78
|
+
ctx.el('div', { class: 'aw-rb' }, [
|
|
79
|
+
statusRow,
|
|
80
|
+
ctx.el('div', { class: 'aw-rb-row' }, [checkBtn]),
|
|
81
|
+
checkBox,
|
|
82
|
+
ctx.el('div', { class: 'aw-rb-row' }, [input, btn]),
|
|
83
|
+
results,
|
|
84
|
+
]),
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
/** 检测明细行:label + 状态点(true 绿/false 红/null 灰) + 值片段 */
|
|
88
|
+
function checkRow(label, okState, value) {
|
|
89
|
+
const val = ctx.el('span')
|
|
90
|
+
for (const seg of (Array.isArray(value) ? value : [{ text: String(value) }])) {
|
|
91
|
+
val.append(ctx.el('span', { class: seg.cls ?? '' }, [String(seg.text)]))
|
|
92
|
+
}
|
|
93
|
+
return ctx.el('div', { class: 'aw-rb-check-row' }, [
|
|
94
|
+
ctx.el('span', { class: 'aw-rb-check-k' }, [label]),
|
|
95
|
+
dot(okState === true, okState == null),
|
|
96
|
+
val,
|
|
97
|
+
])
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function runCheck() {
|
|
101
|
+
checkBtn.disabled = true
|
|
102
|
+
checkBtn.textContent = t('checking')
|
|
103
|
+
const t0 = Date.now()
|
|
104
|
+
const h = await ctx.fetch('/api/plugins/rag-bridge/health').catch(() => null)
|
|
105
|
+
const ms = Date.now() - t0
|
|
106
|
+
const rows = []
|
|
107
|
+
if (!h) {
|
|
108
|
+
rows.push(checkRow(t('checkBtn'), false, [{ text: t('checkFail'), cls: 'aw-rb-check-err' }]))
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
const beOk = h?.backend?.ok === true
|
|
112
|
+
rows.push(checkRow(t('backend'), beOk, beOk
|
|
113
|
+
? [{ text: `${h.backend.url} · ${h.backend.status ?? 'ok'}` }]
|
|
114
|
+
: [{ text: `${h?.backend?.url ?? '-'} · ${h?.backend?.error ?? h?.backend?.status ?? t('bad')}`, cls: 'aw-rb-check-err' }]))
|
|
115
|
+
const webOk = h?.web?.ok === true
|
|
116
|
+
rows.push(checkRow(t('web'), webOk, webOk
|
|
117
|
+
? [{ text: `${h.web.url} · ${t('webDocs')} ${h.web.knowledgeBases ?? '-'}` }]
|
|
118
|
+
: [{ text: `${h?.web?.url ?? '-'} · ${h?.web?.error ?? t('bad')}`, cls: 'aw-rb-check-err' }]))
|
|
119
|
+
const acc = h?.token?.accepted
|
|
120
|
+
const tokenTxt = !h?.token?.configured
|
|
121
|
+
? t('tokenNone')
|
|
122
|
+
: acc === true ? t('tokenAccepted') : acc === false ? t('tokenRejected') : t('tokenUnknown')
|
|
123
|
+
rows.push(checkRow(t('tokenCfg'), h?.token?.configured ? acc : null,
|
|
124
|
+
[{ text: tokenTxt, cls: acc === false ? 'aw-rb-check-err' : '' },
|
|
125
|
+
...(h?.token?.hint ? [{ text: ` · ${h.token.hint}`, cls: 'aw-rb-check-err' }] : []),
|
|
126
|
+
...(h?.auth ? [{ text: ` · ${h.auth === 'bearer' ? t('bearer') : t('anonymous')}` }] : [])]))
|
|
127
|
+
rows.push(checkRow(t('kb'), Boolean(h?.kb?.id),
|
|
128
|
+
[{ text: `${h?.kb?.name ?? '-'}${h?.kb?.id ? ` · ${String(h.kb.id).slice(0, 8)}` : ''}` }]))
|
|
129
|
+
const allOk = beOk && webOk && acc !== false
|
|
130
|
+
rows.push(ctx.el('div', { class: 'aw-rb-check-row' }, [
|
|
131
|
+
ctx.el('span', { class: allOk ? 'aw-rb-check-ok' : 'aw-rb-check-err' },
|
|
132
|
+
[allOk ? `✔ ${t('allOk')}` : `✖ ${t('allBad')}`]),
|
|
133
|
+
ctx.el('span', { class: 'faint' }, [` · ${t('latency')} ${ms}ms`]),
|
|
134
|
+
]))
|
|
135
|
+
}
|
|
136
|
+
checkRows.replaceChildren(...rows)
|
|
137
|
+
checkBtn.disabled = false
|
|
138
|
+
checkBtn.textContent = t('checkBtn')
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function refresh() {
|
|
142
|
+
const h = await ctx.fetch('/api/plugins/rag-bridge/health').catch(() => null)
|
|
143
|
+
backendCell.replaceChildren(dot(Boolean(h?.backend?.ok)), ` ${t('backend')}`)
|
|
144
|
+
webCell.replaceChildren(dot(Boolean(h?.web?.ok)), ` ${t('web')}`)
|
|
145
|
+
kbCell.replaceChildren(dot(Boolean(h?.kb?.id)), ` ${t('kb')}: ${h?.kb?.id ? String(h.kb.id).slice(0, 8) : '-'}(${h?.auth === 'bearer' ? t('bearer') : t('anonymous')})`)
|
|
146
|
+
counterCell.replaceChildren(`${t('counters')}: ${t('store')} ${h?.counters?.store ?? 0} · ${t('index')} ${h?.counters?.index ?? 0}`)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function runSearch() {
|
|
150
|
+
const q = String(input.value ?? '').trim()
|
|
151
|
+
if (!q) return
|
|
152
|
+
btn.disabled = true
|
|
153
|
+
btn.textContent = t('searching')
|
|
154
|
+
results.replaceChildren()
|
|
155
|
+
const r = await ctx.fetch(`/api/plugins/rag-bridge/search?q=${encodeURIComponent(q)}&top_k=5`).catch(() => null)
|
|
156
|
+
btn.disabled = false
|
|
157
|
+
btn.textContent = t('searchBtn')
|
|
158
|
+
const items = Array.isArray(r?.results) ? r.results.slice(0, 5) : []
|
|
159
|
+
if (!items.length) {
|
|
160
|
+
results.append(ctx.el('div', { class: 'aw-rb-empty' }, [t('empty')]))
|
|
161
|
+
return
|
|
162
|
+
}
|
|
163
|
+
for (const [i, h] of items.entries()) {
|
|
164
|
+
const brief = String(h?.content ?? '').replace(/\s+/g, ' ').trim().slice(0, 140)
|
|
165
|
+
results.append(ctx.el('div', { class: 'aw-rb-hit' }, [
|
|
166
|
+
ctx.el('div', { class: 'aw-rb-hit-head aw-mono' }, [
|
|
167
|
+
`${i + 1}. [${Number(h?.score ?? 0).toFixed(3)}] ${h?.doc_path ?? '-'}`,
|
|
168
|
+
]),
|
|
169
|
+
ctx.el('div', { class: 'aw-rb-hit-brief' }, [brief]),
|
|
170
|
+
]))
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
void refresh()
|
|
175
|
+
const offLocale = ctx.hooks.on('i18n:changed', () => void refresh())
|
|
176
|
+
return () => {
|
|
177
|
+
clearInterval(timer)
|
|
178
|
+
offLocale()
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
})
|
|
182
|
+
}
|
|
@@ -1,60 +1,84 @@
|
|
|
1
|
-
{
|
|
2
|
-
"zh-CN": {
|
|
3
|
-
"settings": {
|
|
4
|
-
"base_url": "rag-knowledge 后端地址",
|
|
5
|
-
"web_url": "rag-knowledge Web 地址",
|
|
6
|
-
"token": "rag-knowledge API Token"
|
|
7
|
-
},
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
"
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
1
|
+
{
|
|
2
|
+
"zh-CN": {
|
|
3
|
+
"settings": {
|
|
4
|
+
"base_url": "rag-knowledge 后端地址",
|
|
5
|
+
"web_url": "rag-knowledge Web 地址",
|
|
6
|
+
"token": "rag-knowledge API Token"
|
|
7
|
+
},
|
|
8
|
+
"panel": {
|
|
9
|
+
"title": "工业知识库(rag-knowledge)",
|
|
10
|
+
"backend": "后端",
|
|
11
|
+
"web": "Web",
|
|
12
|
+
"kb": "知识库",
|
|
13
|
+
"counters": "累计",
|
|
14
|
+
"store": "经验沉淀",
|
|
15
|
+
"index": "文档入库",
|
|
16
|
+
"searchPlaceholder": "检索工业知识(诊断报告/经验/规范)…",
|
|
17
|
+
"searchBtn": "检索",
|
|
18
|
+
"searching": "检索中…",
|
|
19
|
+
"empty": "未检索到相关内容",
|
|
20
|
+
"refresh": "刷新",
|
|
21
|
+
"ok": "正常",
|
|
22
|
+
"bad": "不可达",
|
|
23
|
+
"anonymous": "匿名",
|
|
24
|
+
"bearer": "鉴权",
|
|
25
|
+
"checkBtn": "检测连接",
|
|
26
|
+
"checking": "检测中…",
|
|
27
|
+
"checkFail": "检测请求失败(插件路由不可达)",
|
|
28
|
+
"tokenCfg": "API Token",
|
|
29
|
+
"tokenNone": "未配置(匿名)",
|
|
30
|
+
"tokenAccepted": "已配置并被服务接受",
|
|
31
|
+
"tokenRejected": "已配置但被拒绝(401/403)",
|
|
32
|
+
"tokenUnknown": "未知(网络不可达)",
|
|
33
|
+
"webDocs": "知识库数",
|
|
34
|
+
"allOk": "连接正常:API 可达,token 有效",
|
|
35
|
+
"allBad": "连接异常:按上行明细排查",
|
|
36
|
+
"latency": "耗时"
|
|
37
|
+
},
|
|
38
|
+
"group": {
|
|
39
|
+
"conn": "rag-knowledge 连接",
|
|
40
|
+
"auth": "rag-knowledge 鉴权"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"en": {
|
|
44
|
+
"settings": {
|
|
45
|
+
"base_url": "rag-knowledge backend URL",
|
|
46
|
+
"web_url": "rag-knowledge Web URL",
|
|
47
|
+
"token": "rag-knowledge API token"
|
|
48
|
+
},
|
|
49
|
+
"panel": {
|
|
50
|
+
"title": "Industrial knowledge base (rag-knowledge)",
|
|
51
|
+
"backend": "Backend",
|
|
52
|
+
"web": "Web",
|
|
53
|
+
"kb": "Knowledge base",
|
|
54
|
+
"counters": "Total",
|
|
55
|
+
"store": "Experiences",
|
|
56
|
+
"index": "Documents",
|
|
57
|
+
"searchPlaceholder": "Search industrial knowledge (reports/lessons/specs)…",
|
|
58
|
+
"searchBtn": "Search",
|
|
59
|
+
"searching": "Searching…",
|
|
60
|
+
"empty": "No results",
|
|
61
|
+
"refresh": "Refresh",
|
|
62
|
+
"ok": "Up",
|
|
63
|
+
"bad": "Down",
|
|
64
|
+
"anonymous": "Anonymous",
|
|
65
|
+
"bearer": "Auth",
|
|
66
|
+
"checkBtn": "Test connection",
|
|
67
|
+
"checking": "Checking…",
|
|
68
|
+
"checkFail": "Check request failed (plugin route unreachable)",
|
|
69
|
+
"tokenCfg": "API Token",
|
|
70
|
+
"tokenNone": "Not set (anonymous)",
|
|
71
|
+
"tokenAccepted": "Configured and accepted",
|
|
72
|
+
"tokenRejected": "Configured but rejected (401/403)",
|
|
73
|
+
"tokenUnknown": "Unknown (unreachable)",
|
|
74
|
+
"webDocs": "KBs",
|
|
75
|
+
"allOk": "Connection OK: API reachable, token valid",
|
|
76
|
+
"allBad": "Connection problem: see details above",
|
|
77
|
+
"latency": "Latency"
|
|
78
|
+
},
|
|
79
|
+
"group": {
|
|
80
|
+
"conn": "rag-knowledge connection",
|
|
81
|
+
"auth": "rag-knowledge auth"
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -18,6 +18,7 @@ import { listPluginTools, pluginOfTool } from '../plugin-tools'
|
|
|
18
18
|
import type { HostToolBridgeContext, HostToolCall, HostToolResult } from './types'
|
|
19
19
|
import { INDUSTRIAL_TOOL_NAMES, dispatchIndustrialTool } from './tools/industrial'
|
|
20
20
|
import { guardIndustrialToolGrant } from '../industrial/grant-guard'
|
|
21
|
+
import { verifyIndustrialToolCall } from '../industrial/tool-verify'
|
|
21
22
|
import { handleCompleteTask, handleReportProgress } from './tools/progress'
|
|
22
23
|
import { handleCancelTask, handleDispatchTask, handleGetMyTaskQueue, handleGetTaskDetails, handleListChannelTasks, handleReassignTask, handleRefuseTask, handleSubmitTask, handleUpdateTask } from './tools/tasks'
|
|
23
24
|
import { handleGrantNodes, handleRevokeNodes } from './tools/team-delegation'
|
|
@@ -129,6 +130,10 @@ async function dispatchHostToolInner(ctx: HostToolBridgeContext, req: HostToolCa
|
|
|
129
130
|
// 权限模型 v3:运行时 grant 复核(收权即失活;绑定面是静态授权,撤销后此处拦截)
|
|
130
131
|
const denied = await guardIndustrialToolGrant(identity, req.toolName, args)
|
|
131
132
|
if (denied) return denied
|
|
133
|
+
// 统一预检:参数形状 + 引用归属(node_id/line_id/recipe_id/dataset_id… 必须落在
|
|
134
|
+
// 调用者的绑定面内;越界拒绝并把「你的绑定面 + 下一步」返回给 Agent)
|
|
135
|
+
const verifyDenied = verifyIndustrialToolCall(identity.agentId, req.toolName, args)
|
|
136
|
+
if (verifyDenied) return verifyDenied
|
|
132
137
|
return await dispatchIndustrialTool(identity.agentId, req.toolName, args, identity.channelId)
|
|
133
138
|
}
|
|
134
139
|
|
|
@@ -180,6 +180,20 @@ export async function toolAmlDatasetBuild(agentId: string, args: {
|
|
|
180
180
|
return amlErr(`节点 ${amlNodeName(n.nodeId)}(${n.nodeId})归属产线 ${nodeLine || '未分配'},与 line_id=${lineId} 不一致;数据集节点必须全部归属同一条产线(绑定只缩不放)。`)
|
|
181
181
|
}
|
|
182
182
|
}
|
|
183
|
+
// 2026-10-17 补强:隔离三元组的存在性与同线一致性(此前 product/recipe/run_ids 无校验,
|
|
184
|
+
// 可用他线配方/批次 id 构造数据集 —— 谱系污染训练数据)
|
|
185
|
+
const recipe = getDcwRecipeRepo().byId(recipeId)
|
|
186
|
+
if (!recipe) return amlErr(`配方 ${recipeId} 不存在。line_context 可查看当前批次的三个 id。`)
|
|
187
|
+
if (recipe.lineId !== lineId) return amlErr(`配方「${recipe.name}」归属产线 ${recipe.lineId || '未分配'},与 line_id=${lineId} 不一致(隔离三元组必须同线)。`)
|
|
188
|
+
const product = getDcwProductRepo().byId(productId)
|
|
189
|
+
if (!product) return amlErr(`产品 ${productId} 不存在。line_context 可查看当前批次的三个 id。`)
|
|
190
|
+
if (product.lineId && product.lineId !== lineId) return amlErr(`产品「${product.name}」归属产线 ${product.lineId},与 line_id=${lineId} 不一致(隔离三元组必须同线)。`)
|
|
191
|
+
const runIdsArg = Array.isArray(args.run_ids) ? args.run_ids.map(r => String(r)).filter(Boolean) : []
|
|
192
|
+
for (const rid of runIdsArg) {
|
|
193
|
+
const run = getDcwRecipeRepo().listRuns().find(x => x.id === rid)
|
|
194
|
+
if (!run) return amlErr(`批次 ${rid} 不存在(仅可引用已落库批次;line_context 可查当前批次 id)。`)
|
|
195
|
+
if (run.lineId !== lineId) return amlErr(`批次 ${rid} 归属产线 ${run.lineId},与 line_id=${lineId} 不一致(隔离三元组必须同线)。`)
|
|
196
|
+
}
|
|
183
197
|
const purpose = args.purpose === 'quality_predict' ? 'quality_predict' : 'mpc_surrogate'
|
|
184
198
|
try {
|
|
185
199
|
const spec = parseDatasetSpec({
|
|
@@ -141,8 +141,13 @@ export async function toolDcwJournal(agentId: string, args: { node_id?: string,
|
|
|
141
141
|
const limit = Math.min(Number(args.limit) || 20, 100)
|
|
142
142
|
const repo = getAgentNodeBindingRepo()
|
|
143
143
|
const bound = new Set(repo.byAgent(agentId).map(b => b.nodeId))
|
|
144
|
-
if (nodeId && !bound.has(nodeId))
|
|
145
|
-
|
|
144
|
+
if (nodeId && !bound.has(nodeId)) {
|
|
145
|
+
const mine = [...bound]
|
|
146
|
+
return {
|
|
147
|
+
text: `无权查询节点 ${nodeId} 的账本(仅可查你绑定范围内的对象:${mine.length ? mine.join(', ') : '(当前无绑定,请先绑定节点/配方,或改传 recipe_id 查配方面记录)'})。`,
|
|
148
|
+
isError: true,
|
|
149
|
+
}
|
|
150
|
+
}
|
|
146
151
|
const records = rb.records({ nodeId: nodeId || undefined, recipeId: recipeId || undefined, limit })
|
|
147
152
|
if (records.length === 0)
|
|
148
153
|
return { text: '窗口内无优化记录(该节点/配方尚无 Agent 调控历史)。' }
|
|
@@ -14,6 +14,7 @@ import { agentBadgeLabel } from '../agent-badge'
|
|
|
14
14
|
import { getDcwController } from '../../dcw/dcw-controller'
|
|
15
15
|
import { getDcwLineRepo } from '../../dcw/dcw-line.repo'
|
|
16
16
|
import { recordOps } from '../../ops/ops'
|
|
17
|
+
import { resolveRefFaces } from './tool-verify'
|
|
17
18
|
|
|
18
19
|
export interface LineStartInput { recipe_id?: string, reason?: string }
|
|
19
20
|
export interface LineStopInput { reason?: string }
|
|
@@ -133,13 +134,25 @@ export async function toolLineStop(agentId: string, args: LineStopInput, channel
|
|
|
133
134
|
|
|
134
135
|
/** 工具:line_status —— 本频道绑定产线的运行状态(只读)。 */
|
|
135
136
|
export async function toolLineStatus(agentId: string, args: LineStatusInput, channelId?: string): Promise<{ text: string, isError?: boolean }> {
|
|
136
|
-
void agentId
|
|
137
137
|
const ctrl = getDcwController()
|
|
138
138
|
const manager = getWorkshopManager()
|
|
139
139
|
const ch = channelId ? manager.deps.repos.channels.findById(channelId) : undefined
|
|
140
|
+
const faces = resolveRefFaces(agentId)
|
|
141
|
+
const readable = [...faces.readableLines]
|
|
140
142
|
const wantLine = String(args.line_id ?? '').trim() || String(ch?.lineId ?? '').trim()
|
|
141
|
-
|
|
142
|
-
|
|
143
|
+
// 2026-10-17 补强:产线参数必须落在可读面内(此前无 channelId 时任意 line_id 可查、缺省列全平台产线)
|
|
144
|
+
if (wantLine && !faces.readableLines.has(wantLine)) {
|
|
145
|
+
return {
|
|
146
|
+
text: `无权查看产线 ${wantLine}(你的可读产线:${readable.join(', ') || '(无)'}${faces.channelLineId ? `;频道绑线「${faces.channelLineId}」为只读来源` : ''})。如需访问,请让用户授予对应产线权限(权限管理),或把当前频道绑定到该产线;也可以改用上述可读产线重试。`,
|
|
147
|
+
isError: true,
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (!wantLine && readable.length === 0) {
|
|
151
|
+
return { text: '你当前没有可读产线(未绑定任何节点/配方,频道也未绑定产线)。请先让用户或 lead 完成绑定后重试。', isError: true }
|
|
152
|
+
}
|
|
153
|
+
const scopeLines = wantLine ? [wantLine] : readable
|
|
154
|
+
const lines = ctrl.listLines().filter(l => scopeLines.includes(l.id))
|
|
155
|
+
const states = ctrl.allLineStates().filter(s => scopeLines.includes(s.lineId))
|
|
143
156
|
if (lines.length === 0) return { text: `产线不存在或不在你的可见范围: ${wantLine || '(未指定)'}`, isError: true }
|
|
144
157
|
const rows = lines.map((l) => {
|
|
145
158
|
const s = states.find(x => x.lineId === l.id)
|
|
@@ -17,6 +17,7 @@ import { getDaqController } from '../../daq/daq-controller'
|
|
|
17
17
|
import { getDcwController } from '../../dcw/dcw-controller'
|
|
18
18
|
import type { HostToolResult } from '../host-tool-bridge/types'
|
|
19
19
|
import { dcwControlGovernedInternal } from './dcw-tools'
|
|
20
|
+
import { resolveRefFaces } from './tool-verify'
|
|
20
21
|
|
|
21
22
|
const sleep = (ms: number) => new Promise(r => setTimeout(r, ms))
|
|
22
23
|
|
|
@@ -81,6 +82,15 @@ export async function toolOptimizationExplore(agentId: string, args: {
|
|
|
81
82
|
const daqBindings = repo.byAgent(agentId).filter(b => b.kind === 'daq')
|
|
82
83
|
const targetNodeId = String(args.target_node_id ?? '').trim() || daqBindings[0]?.nodeId || ''
|
|
83
84
|
if (!targetNodeId) return { text: '未指定目标观测节点且当前无数采绑定;请传 target_node_id 或先绑定数采节点。', isError: true }
|
|
85
|
+
// 2026-10-17 补强:目标观测节点必须在数采可见面内(绑定数采 ∪ 频道绑线只读,与 daq_query 同源;
|
|
86
|
+
// 此前任意 daq 节点 id 都能被读取均值并写入探索记录 —— 跨线数据会进训练回流)
|
|
87
|
+
const faces = resolveRefFaces(agentId)
|
|
88
|
+
if (!faces.daqBoundNodes.has(targetNodeId) && !faces.channelLineNodes.has(targetNodeId)) {
|
|
89
|
+
return {
|
|
90
|
+
text: `目标观测节点 ${targetNodeId} 不在你的数采可见范围(绑定数采节点:${[...faces.daqBoundNodes].join(', ') || '(无)'}${faces.channelLineNodes.size > 0 ? `;频道绑线只读节点:${[...faces.channelLineNodes].join(', ')}` : ''})。请让用户或 lead 绑定该数采节点,或改用上述节点重试。`,
|
|
91
|
+
isError: true,
|
|
92
|
+
}
|
|
93
|
+
}
|
|
84
94
|
|
|
85
95
|
const targetBefore = await readTargetMean(targetNodeId)
|
|
86
96
|
|