agentworkshop 0.7.64 → 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.
Files changed (52) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/_nuxt/{DAfWhONg.js → 1NcBJMyL.js} +3 -3
  3. package/.output/public/_nuxt/{CKE_kTL92.js → Bb4REys92.js} +1 -1
  4. package/.output/public/_nuxt/{CVUMEGa1.js → Bnf04goH.js} +1 -1
  5. package/.output/public/_nuxt/{Cuw2--X2.js → CJPIo2E7.js} +1 -1
  6. package/.output/public/_nuxt/{CkYk8bj02.js → Cfy5Jn8v2.js} +1 -1
  7. package/.output/public/_nuxt/{DSWuWYx_2.js → DBERjwK92.js} +1 -1
  8. package/.output/public/_nuxt/DG0esyZR.js +1 -0
  9. package/.output/public/_nuxt/{B5RD2ZST.js → DPP0W5Ni.js} +1 -1
  10. package/.output/public/_nuxt/{DqT2_Jtw2.js → DxJFBokW2.js} +1 -1
  11. package/.output/public/_nuxt/{Cnf1PFhB2.js → GKiBVVYe2.js} +1 -1
  12. package/.output/public/_nuxt/{Dc5NeCDq.js → H13l21fR.js} +1 -1
  13. package/.output/public/_nuxt/{Cdw4juXw.js → XErFZOU9.js} +1 -1
  14. package/.output/public/_nuxt/builds/latest.json +1 -1
  15. package/.output/public/_nuxt/builds/meta/8bf65244-eb00-45c7-be23-9dcd7b917ef3.json +1 -0
  16. package/.output/public/_nuxt/{D-z6N1Cz.js → kYyvM13M.js} +1 -1
  17. package/.output/public/_nuxt/{B0w8DZ4C.js → t54AYjsC.js} +1 -1
  18. package/.output/server/chunks/_/nitro.mjs +1220 -904
  19. package/.output/server/chunks/_/nitro.mjs.map +1 -1
  20. package/.output/server/chunks/build/useWorkshopWs-CM-Ywk4h.mjs.map +1 -1
  21. package/.output/server/chunks/routes/renderer.mjs +1 -1
  22. package/.output/server/chunks/virtual/precomputed.mjs +1 -1
  23. package/.output/server/package.json +1 -1
  24. package/README-zh.md +8 -6
  25. package/README.md +7 -5
  26. package/app/stores/workshop/notifications.ts +22 -1
  27. package/config.yml +1 -1
  28. package/docs/cli.en.md +2 -2
  29. package/docs/cli.md +2 -2
  30. package/docs/full-test-plan.md +1 -1
  31. package/docs/plugins.en.md +1 -1
  32. package/docs/plugins.md +1 -1
  33. package/docs/sdk.en.md +1 -1
  34. package/docs/sdk.md +1 -1
  35. package/package.json +1 -1
  36. package/server/plugins-builtin/diag-bridge/client.mjs +230 -132
  37. package/server/plugins-builtin/diag-bridge/i18n.json +45 -15
  38. package/server/plugins-builtin/rag-bridge/client.mjs +182 -108
  39. package/server/plugins-builtin/rag-bridge/i18n.json +84 -60
  40. package/server/services/workshop/agents/host-tool-bridge/dispatch.ts +5 -0
  41. package/server/services/workshop/agents/industrial/aml-dataset-tools.ts +14 -0
  42. package/server/services/workshop/agents/industrial/dcw-judge-tools.ts +7 -2
  43. package/server/services/workshop/agents/industrial/dcw-tools.ts +5 -3
  44. package/server/services/workshop/agents/industrial/line-tools.ts +16 -3
  45. package/server/services/workshop/agents/industrial/optimization-tools.ts +10 -0
  46. package/server/services/workshop/agents/industrial/param-tools.ts +1 -84
  47. package/server/services/workshop/agents/industrial/tool-verify.ts +373 -0
  48. package/server/services/workshop/agents/industrial/twin-tools.ts +5 -0
  49. package/server/services/workshop/runtime/manager/channel-template-constants.ts +20 -0
  50. package/server/services/workshop/runtime/manager/channel-templates.ts +3 -12
  51. package/.output/public/_nuxt/DIDbAMv5.js +0 -1
  52. package/.output/public/_nuxt/builds/meta/1809e625-ff1d-4fd5-9ad2-28cea81f06ab.json +0 -1
@@ -1,108 +1,182 @@
1
- /**
2
- * rag-bridge 客户端面板 —— 经 ctx.ui.registerPanel 注入 'plugins.page' 插槽。
3
- * 自包含 ESM(零导入);ctx 由宿主 loader 注入(fetch 带鉴权/t 命名空间翻译)。
4
- * 健康徽标(后端/Web/知识库) + 快捷检索框,30s 自动刷新,返回清理函数供卸载回收。
5
- */
6
- export function setup(ctx) {
7
- // 面板样式(自包含注入,幂等;不依赖宿主页面样式表)
8
- const styleId = 'aw-rb-styles'
9
- if (!document.getElementById(styleId)) {
10
- document.head.append(ctx.el('style', { id: styleId }, [`
11
- .aw-rb { display:flex; flex-direction:column; gap:8px; font-size:12px; }
12
- .aw-rb-row { display:flex; gap:8px; align-items:center; flex-wrap:wrap; }
13
- .aw-rb-cell { display:inline-flex; align-items:center; gap:4px; }
14
- .aw-rb-dot { width:8px; height:8px; border-radius:50%; display:inline-block; }
15
- .aw-rb-dot-ok { background:#35e0a0; }
16
- .aw-rb-dot-bad { background:#e05a5a; }
17
- .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; }
18
- .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; }
19
- .aw-rb-btn:disabled { opacity:.5; cursor:default; }
20
- .aw-rb-results { display:flex; flex-direction:column; gap:6px; }
21
- .aw-rb-empty { opacity:.6; }
22
- .aw-rb-hit { padding:6px 8px; border:1px solid color-mix(in srgb, currentColor 12%, transparent); border-radius:7px; }
23
- .aw-rb-hit-head { font-size:11px; opacity:.85; }
24
- .aw-rb-hit-brief { opacity:.7; margin-top:2px; }
25
- `]))
26
- }
27
-
28
- ctx.ui.registerPanel({
29
- slot: 'plugins.page',
30
- name: 'kb-panel',
31
- titleKey: 'panel.title',
32
- order: 10,
33
- mount(el) {
34
- const t = k => ctx.t(`panel.${k}`)
35
- const timer = setInterval(refresh, 30_000)
36
-
37
- function dot(ok) {
38
- return ctx.el('span', {
39
- class: `aw-rb-dot ${ok ? 'aw-rb-dot-ok' : 'aw-rb-dot-bad'}`,
40
- title: ok ? t('ok') : t('bad'),
41
- })
42
- }
43
-
44
- const backendCell = ctx.el('span', { class: 'aw-rb-cell' }, [dot(false), ` ${t('backend')}`])
45
- const webCell = ctx.el('span', { class: 'aw-rb-cell' }, [dot(false), ` ${t('web')}`])
46
- const kbCell = ctx.el('span', { class: 'aw-rb-cell aw-mono' }, [dot(false), ` ${t('kb')}: -`])
47
- const counterCell = ctx.el('span', { class: 'aw-rb-cell faint' }, [
48
- `${t('counters')}: ${t('store')} 0 · ${t('index')} 0`,
49
- ])
50
- const statusRow = ctx.el('div', { class: 'aw-rb-row' }, [backendCell, webCell, kbCell, counterCell])
51
-
52
- const input = ctx.el('input', {
53
- class: 'aw-rb-input',
54
- placeholder: t('searchPlaceholder'),
55
- })
56
- input.addEventListener('keydown', (e) => {
57
- if (e.key === 'Enter') void runSearch()
58
- })
59
- const btn = ctx.el('button', { class: 'aw-rb-btn' }, [t('searchBtn')])
60
- btn.addEventListener('click', () => void runSearch())
61
- const results = ctx.el('div', { class: 'aw-rb-results' })
62
-
63
- el.append(
64
- ctx.el('div', { class: 'aw-rb' }, [
65
- statusRow,
66
- ctx.el('div', { class: 'aw-rb-row' }, [input, btn]),
67
- results,
68
- ]),
69
- )
70
-
71
- async function refresh() {
72
- const h = await ctx.fetch('/api/plugins/rag-bridge/health').catch(() => null)
73
- backendCell.replaceChildren(dot(Boolean(h?.backend?.ok)), ` ${t('backend')}`)
74
- webCell.replaceChildren(dot(Boolean(h?.web?.ok)), ` ${t('web')}`)
75
- 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')})`)
76
- counterCell.replaceChildren(`${t('counters')}: ${t('store')} ${h?.counters?.store ?? 0} · ${t('index')} ${h?.counters?.index ?? 0}`)
77
- }
78
-
79
- async function runSearch() {
80
- const q = String(input.value ?? '').trim()
81
- if (!q) return
82
- btn.disabled = true
83
- btn.textContent = t('searching')
84
- results.replaceChildren()
85
- const r = await ctx.fetch(`/api/plugins/rag-bridge/search?q=${encodeURIComponent(q)}&top_k=5`).catch(() => null)
86
- btn.disabled = false
87
- btn.textContent = t('searchBtn')
88
- const items = Array.isArray(r?.results) ? r.results.slice(0, 5) : []
89
- if (!items.length) {
90
- results.append(ctx.el('div', { class: 'aw-rb-empty' }, [t('empty')]))
91
- return
92
- }
93
- for (const [i, h] of items.entries()) {
94
- const brief = String(h?.content ?? '').replace(/\s+/g, ' ').trim().slice(0, 140)
95
- results.append(ctx.el('div', { class: 'aw-rb-hit' }, [
96
- ctx.el('div', { class: 'aw-rb-hit-head aw-mono' }, [
97
- `${i + 1}. [${Number(h?.score ?? 0).toFixed(3)}] ${h?.doc_path ?? '-'}`,
98
- ]),
99
- ctx.el('div', { class: 'aw-rb-hit-brief' }, [brief]),
100
- ]))
101
- }
102
- }
103
-
104
- void refresh()
105
- return () => clearInterval(timer)
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
- "group": {
9
- "conn": "rag-knowledge 连接",
10
- "auth": "rag-knowledge 鉴权"
11
- },
12
- "panel": {
13
- "title": "工业知识库(rag-knowledge)",
14
- "backend": "后端",
15
- "web": "Web",
16
- "kb": "知识库",
17
- "counters": "累计",
18
- "store": "经验沉淀",
19
- "index": "文档入库",
20
- "searchPlaceholder": "检索工业知识(诊断报告/经验/规范)…",
21
- "searchBtn": "检索",
22
- "searching": "检索中…",
23
- "empty": "未检索到相关内容",
24
- "refresh": "刷新",
25
- "ok": "正常",
26
- "bad": "不可达",
27
- "anonymous": "匿名",
28
- "bearer": "鉴权"
29
- }
30
- },
31
- "en": {
32
- "settings": {
33
- "base_url": "rag-knowledge backend URL",
34
- "web_url": "rag-knowledge Web URL",
35
- "token": "rag-knowledge API token"
36
- },
37
- "group": {
38
- "conn": "rag-knowledge connection",
39
- "auth": "rag-knowledge auth"
40
- },
41
- "panel": {
42
- "title": "Industrial knowledge base (rag-knowledge)",
43
- "backend": "Backend",
44
- "web": "Web",
45
- "kb": "Knowledge base",
46
- "counters": "Total",
47
- "store": "Experiences",
48
- "index": "Documents",
49
- "searchPlaceholder": "Search industrial knowledge (reports/lessons/specs)…",
50
- "searchBtn": "Search",
51
- "searching": "Searching…",
52
- "empty": "No results",
53
- "refresh": "Refresh",
54
- "ok": "Up",
55
- "bad": "Down",
56
- "anonymous": "Anonymous",
57
- "bearer": "Auth"
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
- return { text: `无权查询节点 ${nodeId} 的账本(仅可查自己绑定的节点)。`, isError: true }
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 调控历史)。' }
@@ -102,11 +102,13 @@ export async function toolDcwControl(agentId: string, args: { node_id?: string,
102
102
  * profile 门控)的写入委托它执行 —— Agent 可调用面依然收敛在配方面,此处非 Agent 入口。
103
103
  */
104
104
  export async function dcwControlGovernedInternal(agentId: string, args: { node_id?: string, value?: number | string, hypothesis?: string, task_id?: string }, channelId?: string): Promise<{ text: string, isError?: boolean }> {
105
- return _toolDcwControlDisabled(agentId, args, channelId)
105
+ return dcwControlGovernedLegacy(agentId, args, channelId)
106
106
  }
107
107
 
108
- /** v2 前的直写实现(已停用;保留供回溯/内部审计阅读,不再被任何入口调用) */
109
- async function _toolDcwControlDisabled(agentId: string, args: { node_id?: string, value?: number | string, hypothesis?: string, task_id?: string }, channelId?: string): Promise<{ text: string, isError?: boolean }> {
108
+ /** v2 前的直写实现(治理链完整:四层限界/HITL/60s 联锁/journal/open 记录)。
109
+ * 非任何 Agent 工具入口 —— 仅由 dcwControlGovernedInternal 承载平台内部治理写
110
+ * (optimization_explore 闭环探索的写入委托),Agent 可调用面依然收敛在配方面。 */
111
+ async function dcwControlGovernedLegacy(agentId: string, args: { node_id?: string, value?: number | string, hypothesis?: string, task_id?: string }, channelId?: string): Promise<{ text: string, isError?: boolean }> {
110
112
  const twinGuard = guardTwinWrite(agentId, channelId)
111
113
  if (!twinGuard.allowed) return { text: `${twinGuard.code}: ${twinGuard.message}`, isError: true }
112
114
  const nodeId = String(args.node_id ?? '').trim()
@@ -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
- const lines = ctrl.listLines().filter(l => !wantLine || l.id === wantLine)
142
- const states = ctrl.allLineStates().filter(s => !wantLine || s.lineId === wantLine)
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