agentworkshop 0.7.62 → 0.7.64

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 (31) hide show
  1. package/.AgentWorkShop/prompts/host-tools.json +2488 -2526
  2. package/.output/.AgentWorkShop/prompts/host-tools.json +2488 -2526
  3. package/.output/nitro.json +1 -1
  4. package/.output/public/_nuxt/builds/latest.json +1 -1
  5. package/.output/public/_nuxt/builds/meta/1809e625-ff1d-4fd5-9ad2-28cea81f06ab.json +1 -0
  6. package/.output/server/chunks/_/nitro.mjs +924 -1022
  7. package/.output/server/chunks/_/nitro.mjs.map +1 -1
  8. package/.output/server/package.json +1 -1
  9. package/README-zh.md +946 -945
  10. package/README.md +970 -969
  11. package/config.yml +180 -180
  12. package/docs/cli.en.md +447 -447
  13. package/docs/cli.md +412 -412
  14. package/docs/full-test-plan.md +1 -1
  15. package/docs/plugins.en.md +1 -1
  16. package/docs/plugins.md +1 -1
  17. package/docs/sdk.en.md +1 -1
  18. package/docs/sdk.md +1 -1
  19. package/package.json +181 -181
  20. package/scripts/e2e-plc-plugin-closedloop.mjs +1 -1
  21. package/server/plugins-builtin/diag-bridge/host/diagnosis.mjs +13 -3
  22. package/server/plugins-builtin/diag-bridge/host/intents.mjs +62 -0
  23. package/server/plugins-builtin/diag-bridge/host/plugin.mjs +28 -14
  24. package/server/plugins-builtin/diag-bridge/host/sweep.mjs +26 -18
  25. package/server/services/workshop/agents/host-tool-bridge/dispatch.ts +241 -243
  26. package/server/services/workshop/agents/host-tool-bridge/tools/industrial.ts +96 -98
  27. package/server/services/workshop/agents/industrial/dcw-tools.ts +210 -255
  28. package/server/services/workshop/agents/industrial/index.ts +32 -32
  29. package/server/services/workshop/agents/industrial/param-tools.ts +0 -40
  30. package/server/services/workshop/prompts/loader.ts +15 -2
  31. package/.output/public/_nuxt/builds/meta/603a62a6-71d7-4d9c-bb1d-e8264a58798e.json +0 -1
@@ -1,255 +1,210 @@
1
- /**
2
- * 节点清单、数控下发/回读(dcw_control / dcw_read)
3
- * (由 server/services/workshop/agents/industrial-tools.ts 按职责拆出;内容逐行原文搬运)
4
- */
5
- import type { AgentNodeBinding } from '../node-bindings.repo'
6
- import { agentBadgeLabel } from '../agent-badge'
7
- import { findDcwTemplate } from '../../dcw/dcw-templates'
8
- import { getActiveLineRun } from '../../dcw/line-run'
9
- import { getAgentNodeBindingRepo } from '../node-bindings.repo'
10
- import { getDaqNodeRepo } from '../../daq/daq-node.repo'
11
- import { getDcwController } from '../../dcw/dcw-controller'
12
- import { getRecipeRollBackManager } from '../../dcw/recipe-rollback-manager'
13
- import { limitsBreakdownOf } from '../../dcw/param-limits'
14
- import { nodeSemanticCards, recipeOperationFace } from '../industrial-context'
15
- import { writeToleranceOf } from './param-tools'
16
- import { requestManualApproval } from './manual-approval'
17
- import { guardTwinWrite } from '../../aml/twin/write-guard'
18
-
19
- export async function toolMyIndustrialNodes(agentId: string): Promise<{ text: string }> {
20
- const repo = getAgentNodeBindingRepo()
21
- const bindings = repo.byAgent(agentId)
22
- if (bindings.length === 0) {
23
- return { text: '你尚未绑定任何工业对象。请让用户或 lead 为你绑定:数控/数采节点(kind=daq 观察)或配方(kind=recipe,参数写入与下发)。' }
24
- }
25
- const cards = nodeSemanticCards(agentId)
26
- if (cards.stale > 0) repo.removeAgentNodeStale(agentId, bindings.filter((b) => {
27
- const has = b.kind === 'dcw' ? !!getDcwController().byId(b.nodeId) : b.kind === 'daq' ? !!getDaqNodeRepo().byId(b.nodeId) : true
28
- return !has
29
- }).map(b => b.id))
30
- // 配方操作面(v2:绑定配方的 Agent 在此直接看到"怎么做" —— 运行状态/绑定模式/认证/参数对照)
31
- const recipeFace = recipeOperationFace(agentId)
32
- if (!cards.text && !recipeFace) return { text: '绑定的节点均已不存在(可能被删除),请重新绑定。' }
33
- const staleNote = cards.stale > 0
34
- ? `
35
- (另有 ${cards.stale} 条失效绑定已自动清理)`
36
- : ''
37
- // 调控闭环洞察:每个数控节点的 open 优化记录 / lastGood / 最近判定(Agent 驱动的状态面)
38
- const rb = getRecipeRollBackManager()
39
- const insights: string[] = []
40
- for (const b of bindings.filter(x => x.kind === 'dcw')) {
41
- try {
42
- const ins = rb.nodeInsight(b.nodeId)
43
- const parts: string[] = []
44
- if (ins.openRecord)
45
- parts.push(`进行中优化 ${ins.openRecord.id}: ${ins.openRecord.params[0]?.from ?? '?'}→${ins.openRecord.params[0]?.to}(setAt ${ins.openRecord.setAt.slice(11, 19)},policy=${ins.openRecord.policy})`)
46
- if (ins.lastGood != null)
47
- parts.push(`上次良好值 ${ins.lastGood}`)
48
- for (const j of ins.recentJudges.slice(0, 1))
49
- parts.push(`最近判定 ${j.verdict}(${j.by}):${j.reason.slice(0, 60)}`)
50
- if (parts.length > 0)
51
- insights.push(`- ${b.nodeId}: ${parts.join(';')}`)
52
- }
53
- catch { /* 节点刚被删等情况忽略 */ }
54
- }
55
- const insightBlock = insights.length > 0 ? `\n\n调控闭环状态:\n${insights.join('\n')}` : ''
56
- return {
57
- text: `${cards.text}${staleNote}${insightBlock}${recipeFace ? `\n\n${recipeFace}` : ''}
58
-
59
- ---
60
- 通用规则:
61
- 1. 一个 Agent 可绑定多个节点与多个配方;先读本清单理解每个节点/配方的物理意义、下发链路与操作守则,再动手。
62
- 2. 参数写入与下发一律走你绑定的配方(权限模型 v2,直接写节点已禁用):
63
- recipe_update(recipe_id, params[{node_id,value}], reason) 保存新版本 → recipe_apply(整批下发到运行中批次)
64
- 或 recipe_trial(候选整批试验,不写版本);劣化用 recipe_rollback(dispatch=true) 统一回退。
65
- 设定值受四层限界联锁(节点安全量程 ∩ 工艺参数基准限界 ∩ 活动产品限界 ∩ 活动配方工艺窗口)逐层收窄,
66
- 越界一律拒绝 —— 用户与 Agent 一体约束,无旁路;底层数控节点(PLC/MES)由配置层承接,你只面对工程量。
67
- 配方必须正在执行(运行门),未运行时任何操作被拒。
68
- 3. 数据获取 daq_query(不传 node_id = 全部数采节点),支持按产线/产品/配方/时间检索;解读数据时结合语义卡的判读方法;
69
- MES 数据用 mes_catalog / mes_fetch(检测向量/图像帧/表格/事件全格式;统计摘要回包,原文落盘)。
70
- 需要完整波形(非统计摘要)做深度分析时用 daq_export:自选节点全量原始时序导出为 CSV + manifest.json
71
- (节点映射/语义/产线-配方上下文);把 export_id 交 diag_run 发起深度根因诊断,或把目录绝对路径交给其他 worker 离线分析。
72
- 4. 改动设定后等待工艺响应(热惯性/传动惯量)再评估,避免连续大幅调整。
73
- 5. 调控闭环:每次下发自动开一条优化记录(open);观察数采后用 dcw_judge 落判定(keep/rollback/uncertain);
74
- 判 rollback 后用 recipe_rollback(dispatch=true, reason) 统一回退;dcw_journal 可查节点参数变更史。未判定前再下发,旧记录会被标记 superseded。
75
- 若节点被他人的 open 记录阻塞(对方已消失),超时(30 分钟)后你可接管:dcw_judge 会带接管标记入册。
76
- 6. 自查面:line_context 看你控制的产线/产品/配方全景(动手前必读,确认归属);
77
- ops_log 查你负责产线的运维日志(谁/何时做了什么,来源区分 Agent/用户/系统;
78
- 参数 line_id/node_id/kind/actor_kind/minutes/limit/mine);recipe_log 查配方下发与回退事件;
79
- recipe_versions 查配方参数版本史(谁改的/为什么/参数 diff)。
80
- 7. 配方操控闭环(在线优化框架):验证过参数更优 → recipe_update 保存进配方(记录你的名字与原因,
81
- 生成新版本);发现优化有问题 → recipe_rollback 回退到稳定版本(version)或已知良好批次
82
- (to_last_good=true,dispatch=true 整批恢复运行中产线)。`,
83
- }
84
- }
85
-
86
- /** 工具:dcw_control —— 数控下发(鉴权 → 停线守卫 → 手动审批 → 安全联锁 → 回读语义结果)。
87
- * 调控闭环:下发自动开优化记录;args.hypothesis 声明本次假设(入册),args.task_id 关联任务。 */
88
- export async function toolDcwControl(agentId: string, args: { node_id?: string, value?: number | string, hypothesis?: string, task_id?: string }, channelId?: string): Promise<{ text: string, isError?: boolean }> {
89
- // 权限模型 v2:Agent 不直接操作数控节点 —— 参数写入与下发全部收敛到绑定的 recipe
90
- // (defense in depth:工具面已摘除定义,handler 侧仍拒绝,防直连 invoke 绕行)
91
- void agentId
92
- void args
93
- void channelId
94
- return {
95
- text: '直接节点写控已禁用(权限模型 v2):Agent 不再直接操作数控节点。请改用绑定的配方完成同样动作 —— recipe_update(参数写入,生成新版本)/ recipe_apply(整批下发)/ recipe_trial(候选整批试验,不写版本)/ recipe_rollback(回退);均为带理由与 HITL 的配方面操作,治理联锁(量程/步长/限速)照常生效。',
96
- isError: true,
97
- }
98
- }
99
-
100
- /**
101
- * v2 前的直写实现(工具面已摘除,但**治理链完整**:四层限界/HITL/60s 联锁/journal/open 记录)。
102
- * 仍允许**平台内部治理工具**复用:optimization_explore(工艺优化 Channel 的闭环探索,
103
- * profile 门控)的写入委托它执行 —— Agent 可调用面依然收敛在配方面,此处非 Agent 入口。
104
- */
105
- 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 }> {
106
- return _toolDcwControlDisabled(agentId, args, channelId)
107
- }
108
-
109
- /** v2 前的直写实现(已停用;保留供回溯/内部审计阅读,不再被任何入口调用) */
110
- async function _toolDcwControlDisabled(agentId: string, args: { node_id?: string, value?: number | string, hypothesis?: string, task_id?: string }, channelId?: string): Promise<{ text: string, isError?: boolean }> {
111
- const twinGuard = guardTwinWrite(agentId, channelId)
112
- if (!twinGuard.allowed) return { text: `${twinGuard.code}: ${twinGuard.message}`, isError: true }
113
- const nodeId = String(args.node_id ?? '').trim()
114
- const value = Number(args.value)
115
- const repo = getAgentNodeBindingRepo()
116
- const binding: AgentNodeBinding | undefined = nodeId ? repo.find(agentId, nodeId, 'dcw') : undefined
117
- // 权限模型 v2:配方绑定(kind=recipe)覆盖其参数节点 —— 该节点在绑定配方参数内即算授权
118
- let recipeAuthorized = false
119
- if (!binding && nodeId) {
120
- const recipes = repo.byAgent(agentId).filter(b => b.kind === 'recipe')
121
- recipeAuthorized = recipes.some((b) => {
122
- const r = getDcwController().listRecipes().find(x => x.id === b.nodeId)
123
- return r?.params.some(p => p.nodeId === nodeId) ?? false
124
- })
125
- }
126
- if (!binding && !recipeAuthorized) {
127
- const mine = repo.byAgent(agentId).filter(b => b.kind === 'dcw')
128
- return {
129
- text: mine.length
130
- ? `无权操作节点 ${nodeId || '(空)'}。你有权控制的数控节点:${mine.map(b => b.nodeId).join(', ')}(可用 my_industrial_nodes 查看物理含义)。`
131
- : '你尚未绑定任何数控节点,也无含该参数的配方绑定,无权下发控制指令(权限模型 v2:持有含该参数的配方绑定即可)。',
132
- isError: true,
133
- }
134
- }
135
- if (!Number.isFinite(value)) {
136
- return { text: '设定值 value 必须为数字。', isError: true }
137
- }
138
- const node = getDcwController().byId(nodeId)
139
- if (!node) {
140
- // 节点已被删除 → 绑定失效自清理,提示重新绑定(仅 dcw 直绑;配方路径无绑定可清)
141
- if (binding) repo.removeAgentNode(agentId, nodeId, 'dcw')
142
- return { text: `数控节点 ${nodeId} 已不存在(可能被删除),原绑定已自动清理,请重新绑定。`, isError: true }
143
- }
144
- // 停用/解绑守卫:停用节点拒绝下发(与手动/配方同源语义,给出恢复路径)
145
- if (!node.enabled) {
146
- return { text: `节点「${node.name}」已停用(控制已暂停),无法下发。请先让用户在产线/数采界面恢复该节点的控制,或改派其他节点。`, isError: true }
147
- }
148
- // 停线守卫:产线未开跑时手动写允许(调试),但提示当前无配方窗口约束
149
- const tpl = findDcwTemplate(node.templateKey)
150
-
151
- // 手动确认模式:与 param_control 同源审批面(manual-approval),批准附言回进回执
152
- let manualFeedback = ''
153
- if (binding?.mode === 'manual') {
154
- const bd = limitsBreakdownOf(node)
155
- const ap = await requestManualApproval({
156
- agentId,
157
- nodeId,
158
- detail: `${node.name}(${tpl?.ch ?? node.templateKey})设定 ${value}${node.unit},有效写入区间 ${bd.effective.min}~${bd.effective.max}${node.unit}(${bd.layers.map(l => l.label).join(' ∩ ')})`,
159
- })
160
- if (!ap.ok) return { text: ap.text, isError: ap.isError }
161
- manualFeedback = ap.comment ?? ''
162
- }
163
-
164
- try {
165
- const meta = {
166
- source: 'agent' as const,
167
- actor: agentId,
168
- // 人话操作者:「Channel名/成员名」——/logs 来源=Agent 与用户/系统操作可区分追溯
169
- actorName: agentBadgeLabel(agentId),
170
- taskId: args.task_id ? String(args.task_id) : undefined,
171
- hypothesis: args.hypothesis ? String(args.hypothesis) : '',
172
- }
173
- const outcome = await getDcwController().write(nodeId, value, null, meta)
174
- const run = node.lineId ? getActiveLineRun(node.lineId) : null
175
- const winTxt = run
176
- ? `当前活动配方「${run.recipeName}」`
177
- : '当前无活动配方(全局量程约束)'
178
- if (outcome.ok) {
179
- // 调控闭环回包:记录 id + 上一稳定锚 + 策略提示(安全网信息,Agent 据此规划判定)
180
- const rb = getRecipeRollBackManager()
181
- const stable = rb.journal({ nodeId, limit: 10 }).find(a => a.prevValue != null && a.prevValue !== a.newValue)
182
- const policyHint = binding?.mode === 'manual'
183
- ? '本节点为手动确认模式:判定回退将推请用户确认'
184
- : '本节点为自动模式:越配方监控窗将触发系统自动回退'
185
- const loopTxt = [
186
- outcome.recordId ? `优化记录 ${outcome.recordId} 已开窗(观察数采后 dcw_judge 落判定)` : null,
187
- stable ? `上一稳定锚:${stable.prevValue}${node.unit}(可 dcw_rollback 回退)` : null,
188
- policyHint,
189
- ].filter(Boolean).join(';')
190
- // ACK 鉴定如实表述(2026-10-08):ok 只是命令受理 —— 回读证实与仅链路受理必须区分,
191
- // 不许对无回读驱动(mqtt/http-no-echo)虚报"回读一致"。
192
- const ackTxt = outcome.ack === 'readback-verified'
193
- ? `回读 ${outcome.readback != null ? `${outcome.readback}${node.unit}` : '-'}一致 [设备证实✓]`
194
- : outcome.ack === 'transport-ack'
195
- ? '设备侧未回读证实,仅链路受理 [未证实⚠,建议 daq_query 复测]'
196
- : '无确认通道 [未证实⚠]'
197
- return {
198
- text: `下发受理:${node.name}(${tpl?.ch ?? node.templateKey})设定 ${value}${node.unit} → PLC 原始值 ${outcome.raw ?? '-'};${ackTxt}。${winTxt}。${outcome.message}\n[调控闭环] ${loopTxt}${manualFeedback ? `\n[人工反馈] ${manualFeedback}` : ''}`,
199
- }
200
- }
201
- // 失败文案带自我纠正线索:当前保持原值 + 建议动作(缩小步进/稍后重试)
202
- const keptHint = `当前设定值保持 ${node.value ?? '原值'}${node.unit} 未被改动`
203
- const retryHint = /忙|busy/i.test(outcome.message) ? '链路忙属瞬时状态,可稍后重试' : '可缩小步进幅度后重试'
204
- return { text: `下发失败:${outcome.message}(节点 ${node.name},物理量 ${tpl?.ch ?? node.templateKey},安全量程 ${node.min}~${node.max}${node.unit};${keptHint};${retryHint})`, isError: true }
205
- }
206
- catch (err) {
207
- const msg = err instanceof Error ? err.message : String(err)
208
- const retryHint = /忙|busy/i.test(msg) ? '链路忙属瞬时状态,可稍后重试' : '设定值必须落在安全量程与活动配方工艺窗口内'
209
- return { text: `下发被拒绝:${msg}(节点 ${node.name},物理量 ${tpl?.ch ?? node.templateKey};${retryHint})`, isError: true }
210
- }
211
- }
212
-
213
- /** 工具:dcw_read —— 读取数控节点的 PLC 当前值(读写集成的读半边;被动观测免审批)。
214
- * 返回 PLC 实时读数(标定解码后的物理量)与当前设定值对照,供下发前取证/下发后复核。 */
215
- export async function toolDcwRead(agentId: string, args: { node_id?: string }): Promise<{ text: string, isError?: boolean }> {
216
- const nodeId = String(args.node_id ?? '').trim()
217
- const repo = getAgentNodeBindingRepo()
218
- const binding: AgentNodeBinding | undefined = nodeId ? repo.find(agentId, nodeId, 'dcw') : undefined
219
- if (!binding) {
220
- const mine = repo.byAgent(agentId).filter(b => b.kind === 'dcw')
221
- return {
222
- text: mine.length
223
- ? `无权读取节点 ${nodeId || '(空)'}。你有权读取的数控节点:${mine.map(b => b.nodeId).join(', ')}。`
224
- : '你尚未绑定任何数控节点,无权读取控制通道数据。请在数字孪生界面绑定数控节点。',
225
- isError: true,
226
- }
227
- }
228
- const node = getDcwController().byId(nodeId)
229
- if (!node) {
230
- repo.removeAgentNode(agentId, nodeId, 'dcw')
231
- return { text: `数控节点 ${nodeId} 已不存在(可能被删除),原绑定已自动清理,请重新绑定。`, isError: true }
232
- }
233
- const tpl = findDcwTemplate(node.templateKey)
234
- try {
235
- const read = await getDcwController().readNow(nodeId)
236
- if (!read.ok && read.value == null && !node.readValue) {
237
- return { text: `读取失败:${read.message}(节点 ${node.name},驱动 ${node.driver} 可能不支持读取;可改用 daq_query 查关联数采通道)`, isError: true }
238
- }
239
- const setTxt = node.value != null ? `${node.value}${node.unit}` : '从未下发'
240
- const readTxt = read.value != null
241
- ? `${Number(read.value.toFixed(node.decimals))}${node.unit}`
242
- : (node.readValue != null ? `${node.readValue}${node.unit}(最近一次)` : '无读数')
243
- const devTxt = read.value != null && node.value != null
244
- ? (Math.abs(read.value - node.value) < writeToleranceOf(node)
245
- ? '读数与设定一致(设定已生效)'
246
- : '读数与设定存在偏差(可能工艺在响应或被本地修改)')
247
- : '设定与读数暂不可对照'
248
- return {
249
- text: `读取成功:${node.name}(${tpl?.ch ?? node.templateKey})\n PLC 读数(ACT): ${readTxt} @ ${read.at.slice(11, 19)}\n 当前设定(SET): ${setTxt}\n 对照: ${devTxt}${read.raw != null ? `\n 原始值(raw): ${Number(read.raw.toFixed(4))}` : ''}`,
250
- }
251
- }
252
- catch (err) {
253
- return { text: `读取被拒绝:${err instanceof Error ? err.message : String(err)}(节点 ${node.name})`, isError: true }
254
- }
255
- }
1
+ /**
2
+ * 节点清单、数控下发/回读(dcw_control / dcw_read)
3
+ * (由 server/services/workshop/agents/industrial-tools.ts 按职责拆出;内容逐行原文搬运)
4
+ */
5
+ import type { AgentNodeBinding } from '../node-bindings.repo'
6
+ import { agentBadgeLabel } from '../agent-badge'
7
+ import { findDcwTemplate } from '../../dcw/dcw-templates'
8
+ import { getActiveLineRun } from '../../dcw/line-run'
9
+ import { getAgentNodeBindingRepo } from '../node-bindings.repo'
10
+ import { getDaqNodeRepo } from '../../daq/daq-node.repo'
11
+ import { getDcwController } from '../../dcw/dcw-controller'
12
+ import { getRecipeRollBackManager } from '../../dcw/recipe-rollback-manager'
13
+ import { limitsBreakdownOf } from '../../dcw/param-limits'
14
+ import { nodeSemanticCards, recipeOperationFace } from '../industrial-context'
15
+ import { requestManualApproval } from './manual-approval'
16
+ import { guardTwinWrite } from '../../aml/twin/write-guard'
17
+
18
+ export async function toolMyIndustrialNodes(agentId: string): Promise<{ text: string }> {
19
+ const repo = getAgentNodeBindingRepo()
20
+ const bindings = repo.byAgent(agentId)
21
+ if (bindings.length === 0) {
22
+ return { text: '你尚未绑定任何工业对象。请让用户或 lead 为你绑定:数控/数采节点(kind=daq 观察)或配方(kind=recipe,参数写入与下发)。' }
23
+ }
24
+ const cards = nodeSemanticCards(agentId)
25
+ if (cards.stale > 0) repo.removeAgentNodeStale(agentId, bindings.filter((b) => {
26
+ const has = b.kind === 'dcw' ? !!getDcwController().byId(b.nodeId) : b.kind === 'daq' ? !!getDaqNodeRepo().byId(b.nodeId) : true
27
+ return !has
28
+ }).map(b => b.id))
29
+ // 配方操作面(v2:绑定配方的 Agent 在此直接看到"怎么做" —— 运行状态/绑定模式/认证/参数对照)
30
+ const recipeFace = recipeOperationFace(agentId)
31
+ if (!cards.text && !recipeFace) return { text: '绑定的节点均已不存在(可能被删除),请重新绑定。' }
32
+ const staleNote = cards.stale > 0
33
+ ? `
34
+ (另有 ${cards.stale} 条失效绑定已自动清理)`
35
+ : ''
36
+ // 调控闭环洞察:每个数控节点的 open 优化记录 / lastGood / 最近判定(Agent 驱动的状态面)
37
+ const rb = getRecipeRollBackManager()
38
+ const insights: string[] = []
39
+ for (const b of bindings.filter(x => x.kind === 'dcw')) {
40
+ try {
41
+ const ins = rb.nodeInsight(b.nodeId)
42
+ const parts: string[] = []
43
+ if (ins.openRecord)
44
+ parts.push(`进行中优化 ${ins.openRecord.id}: ${ins.openRecord.params[0]?.from ?? '?'}→${ins.openRecord.params[0]?.to}(setAt ${ins.openRecord.setAt.slice(11, 19)},policy=${ins.openRecord.policy})`)
45
+ if (ins.lastGood != null)
46
+ parts.push(`上次良好值 ${ins.lastGood}`)
47
+ for (const j of ins.recentJudges.slice(0, 1))
48
+ parts.push(`最近判定 ${j.verdict}(${j.by}):${j.reason.slice(0, 60)}`)
49
+ if (parts.length > 0)
50
+ insights.push(`- ${b.nodeId}: ${parts.join(';')}`)
51
+ }
52
+ catch { /* 节点刚被删等情况忽略 */ }
53
+ }
54
+ const insightBlock = insights.length > 0 ? `\n\n调控闭环状态:\n${insights.join('\n')}` : ''
55
+ return {
56
+ text: `${cards.text}${staleNote}${insightBlock}${recipeFace ? `\n\n${recipeFace}` : ''}
57
+
58
+ ---
59
+ 通用规则:
60
+ 1. 一个 Agent 可绑定多个节点与多个配方;先读本清单理解每个节点/配方的物理意义、下发链路与操作守则,再动手。
61
+ 2. 参数写入与下发一律走你绑定的配方(权限模型 v2,直接写节点已禁用):
62
+ recipe_update(recipe_id, params[{node_id,value}], reason) 保存新版本 → recipe_apply(整批下发到运行中批次)
63
+ 或 recipe_trial(候选整批试验,不写版本);劣化用 recipe_rollback(dispatch=true) 统一回退。
64
+ 设定值受四层限界联锁(节点安全量程 ∩ 工艺参数基准限界 ∩ 活动产品限界 ∩ 活动配方工艺窗口)逐层收窄,
65
+ 越界一律拒绝 —— 用户与 Agent 一体约束,无旁路;底层数控节点(PLC/MES)由配置层承接,你只面对工程量。
66
+ 配方必须正在执行(运行门),未运行时任何操作被拒。
67
+ 3. 数据获取 daq_query(不传 node_id = 全部数采节点),支持按产线/产品/配方/时间检索;解读数据时结合语义卡的判读方法;
68
+ MES 数据用 mes_catalog / mes_fetch(检测向量/图像帧/表格/事件全格式;统计摘要回包,原文落盘)。
69
+ 需要完整波形(非统计摘要)做深度分析时用 daq_export:自选节点全量原始时序导出为 CSV + manifest.json
70
+ (节点映射/语义/产线-配方上下文);把 export_id 交 diag_run 发起深度根因诊断,或把目录绝对路径交给其他 worker 离线分析。
71
+ 4. 改动设定后等待工艺响应(热惯性/传动惯量)再评估,避免连续大幅调整。
72
+ 5. 调控闭环:每次下发自动开一条优化记录(open);观察数采后用 dcw_judge 落判定(keep/rollback/uncertain);
73
+ 判 rollback 后用 recipe_rollback(dispatch=true, reason) 统一回退;dcw_journal 可查节点参数变更史。未判定前再下发,旧记录会被标记 superseded。
74
+ 若节点被他人的 open 记录阻塞(对方已消失),超时(30 分钟)后你可接管:dcw_judge 会带接管标记入册。
75
+ 6. 自查面:line_context 看你控制的产线/产品/配方全景(动手前必读,确认归属);
76
+ ops_log 查你负责产线的运维日志(谁/何时做了什么,来源区分 Agent/用户/系统;
77
+ 参数 line_id/node_id/kind/actor_kind/minutes/limit/mine);recipe_log 查配方下发与回退事件;
78
+ recipe_versions 查配方参数版本史(谁改的/为什么/参数 diff)。
79
+ 7. 配方操控闭环(在线优化框架):验证过参数更优 → recipe_update 保存进配方(记录你的名字与原因,
80
+ 生成新版本);发现优化有问题 → recipe_rollback 回退到稳定版本(version)或已知良好批次
81
+ (to_last_good=true,dispatch=true 整批恢复运行中产线)。`,
82
+ }
83
+ }
84
+
85
+ /** 工具:dcw_control —— 数控下发(鉴权 → 停线守卫 → 手动审批 → 安全联锁 → 回读语义结果)。
86
+ * 调控闭环:下发自动开优化记录;args.hypothesis 声明本次假设(入册),args.task_id 关联任务。 */
87
+ export async function toolDcwControl(agentId: string, args: { node_id?: string, value?: number | string, hypothesis?: string, task_id?: string }, channelId?: string): Promise<{ text: string, isError?: boolean }> {
88
+ // 权限模型 v2:Agent 不直接操作数控节点 —— 参数写入与下发全部收敛到绑定的 recipe
89
+ // (defense in depth:工具面已摘除定义,handler 侧仍拒绝,防直连 invoke 绕行)
90
+ void agentId
91
+ void args
92
+ void channelId
93
+ return {
94
+ text: '直接节点写控已禁用(权限模型 v2):Agent 不再直接操作数控节点。请改用绑定的配方完成同样动作 —— recipe_update(参数写入,生成新版本)/ recipe_apply(整批下发)/ recipe_trial(候选整批试验,不写版本)/ recipe_rollback(回退);均为带理由与 HITL 的配方面操作,治理联锁(量程/步长/限速)照常生效。',
95
+ isError: true,
96
+ }
97
+ }
98
+
99
+ /**
100
+ * v2 前的直写实现(工具面已摘除,但**治理链完整**:四层限界/HITL/60s 联锁/journal/open 记录)。
101
+ * 仍允许**平台内部治理工具**复用:optimization_explore(工艺优化 Channel 的闭环探索,
102
+ * profile 门控)的写入委托它执行 —— Agent 可调用面依然收敛在配方面,此处非 Agent 入口。
103
+ */
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)
106
+ }
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 }> {
110
+ const twinGuard = guardTwinWrite(agentId, channelId)
111
+ if (!twinGuard.allowed) return { text: `${twinGuard.code}: ${twinGuard.message}`, isError: true }
112
+ const nodeId = String(args.node_id ?? '').trim()
113
+ const value = Number(args.value)
114
+ const repo = getAgentNodeBindingRepo()
115
+ const binding: AgentNodeBinding | undefined = nodeId ? repo.find(agentId, nodeId, 'dcw') : undefined
116
+ // 权限模型 v2:配方绑定(kind=recipe)覆盖其参数节点 —— 该节点在绑定配方参数内即算授权
117
+ let recipeAuthorized = false
118
+ if (!binding && nodeId) {
119
+ const recipes = repo.byAgent(agentId).filter(b => b.kind === 'recipe')
120
+ recipeAuthorized = recipes.some((b) => {
121
+ const r = getDcwController().listRecipes().find(x => x.id === b.nodeId)
122
+ return r?.params.some(p => p.nodeId === nodeId) ?? false
123
+ })
124
+ }
125
+ if (!binding && !recipeAuthorized) {
126
+ const mine = repo.byAgent(agentId).filter(b => b.kind === 'dcw')
127
+ return {
128
+ text: mine.length
129
+ ? `无权操作节点 ${nodeId || '(空)'}。你有权控制的数控节点:${mine.map(b => b.nodeId).join(', ')}(可用 my_industrial_nodes 查看物理含义)。`
130
+ : '你尚未绑定任何数控节点,也无含该参数的配方绑定,无权下发控制指令(权限模型 v2:持有含该参数的配方绑定即可)。',
131
+ isError: true,
132
+ }
133
+ }
134
+ if (!Number.isFinite(value)) {
135
+ return { text: '设定值 value 必须为数字。', isError: true }
136
+ }
137
+ const node = getDcwController().byId(nodeId)
138
+ if (!node) {
139
+ // 节点已被删除 → 绑定失效自清理,提示重新绑定(仅 dcw 直绑;配方路径无绑定可清)
140
+ if (binding) repo.removeAgentNode(agentId, nodeId, 'dcw')
141
+ return { text: `数控节点 ${nodeId} 已不存在(可能被删除),原绑定已自动清理,请重新绑定。`, isError: true }
142
+ }
143
+ // 停用/解绑守卫:停用节点拒绝下发(与手动/配方同源语义,给出恢复路径)
144
+ if (!node.enabled) {
145
+ return { text: `节点「${node.name}」已停用(控制已暂停),无法下发。请先让用户在产线/数采界面恢复该节点的控制,或改派其他节点。`, isError: true }
146
+ }
147
+ // 停线守卫:产线未开跑时手动写允许(调试),但提示当前无配方窗口约束
148
+ const tpl = findDcwTemplate(node.templateKey)
149
+
150
+ // 手动确认模式:与 param_control 同源审批面(manual-approval),批准附言回进回执
151
+ let manualFeedback = ''
152
+ if (binding?.mode === 'manual') {
153
+ const bd = limitsBreakdownOf(node)
154
+ const ap = await requestManualApproval({
155
+ agentId,
156
+ nodeId,
157
+ detail: `${node.name}(${tpl?.ch ?? node.templateKey})设定 ${value}${node.unit},有效写入区间 ${bd.effective.min}~${bd.effective.max}${node.unit}(${bd.layers.map(l => l.label).join(' ∩ ')})`,
158
+ })
159
+ if (!ap.ok) return { text: ap.text, isError: ap.isError }
160
+ manualFeedback = ap.comment ?? ''
161
+ }
162
+
163
+ try {
164
+ const meta = {
165
+ source: 'agent' as const,
166
+ actor: agentId,
167
+ // 人话操作者:「Channel名/成员名」——/logs 来源=Agent 与用户/系统操作可区分追溯
168
+ actorName: agentBadgeLabel(agentId),
169
+ taskId: args.task_id ? String(args.task_id) : undefined,
170
+ hypothesis: args.hypothesis ? String(args.hypothesis) : '',
171
+ }
172
+ const outcome = await getDcwController().write(nodeId, value, null, meta)
173
+ const run = node.lineId ? getActiveLineRun(node.lineId) : null
174
+ const winTxt = run
175
+ ? `当前活动配方「${run.recipeName}」`
176
+ : '当前无活动配方(全局量程约束)'
177
+ if (outcome.ok) {
178
+ // 调控闭环回包:记录 id + 上一稳定锚 + 策略提示(安全网信息,Agent 据此规划判定)
179
+ const rb = getRecipeRollBackManager()
180
+ const stable = rb.journal({ nodeId, limit: 10 }).find(a => a.prevValue != null && a.prevValue !== a.newValue)
181
+ const policyHint = binding?.mode === 'manual'
182
+ ? '本节点为手动确认模式:判定回退将推请用户确认'
183
+ : '本节点为自动模式:越配方监控窗将触发系统自动回退'
184
+ const loopTxt = [
185
+ outcome.recordId ? `优化记录 ${outcome.recordId} 已开窗(观察数采后 dcw_judge 落判定)` : null,
186
+ stable ? `上一稳定锚:${stable.prevValue}${node.unit}(可 dcw_rollback 回退)` : null,
187
+ policyHint,
188
+ ].filter(Boolean).join(';')
189
+ // ACK 鉴定如实表述(2026-10-08):ok 只是命令受理 —— 回读证实与仅链路受理必须区分,
190
+ // 不许对无回读驱动(mqtt/http-no-echo)虚报"回读一致"。
191
+ const ackTxt = outcome.ack === 'readback-verified'
192
+ ? `回读 ${outcome.readback != null ? `${outcome.readback}${node.unit}` : '-'}一致 [设备证实✓]`
193
+ : outcome.ack === 'transport-ack'
194
+ ? '设备侧未回读证实,仅链路受理 [未证实⚠,建议 daq_query 复测]'
195
+ : '无确认通道 [未证实⚠]'
196
+ return {
197
+ text: `下发受理:${node.name}(${tpl?.ch ?? node.templateKey})设定 ${value}${node.unit} → PLC 原始值 ${outcome.raw ?? '-'};${ackTxt}。${winTxt}。${outcome.message}\n[调控闭环] ${loopTxt}${manualFeedback ? `\n[人工反馈] ${manualFeedback}` : ''}`,
198
+ }
199
+ }
200
+ // 失败文案带自我纠正线索:当前保持原值 + 建议动作(缩小步进/稍后重试)
201
+ const keptHint = `当前设定值保持 ${node.value ?? '原值'}${node.unit} 未被改动`
202
+ const retryHint = /忙|busy/i.test(outcome.message) ? '链路忙属瞬时状态,可稍后重试' : '可缩小步进幅度后重试'
203
+ return { text: `下发失败:${outcome.message}(节点 ${node.name},物理量 ${tpl?.ch ?? node.templateKey},安全量程 ${node.min}~${node.max}${node.unit};${keptHint};${retryHint})`, isError: true }
204
+ }
205
+ catch (err) {
206
+ const msg = err instanceof Error ? err.message : String(err)
207
+ const retryHint = /忙|busy/i.test(msg) ? '链路忙属瞬时状态,可稍后重试' : '设定值必须落在安全量程与活动配方工艺窗口内'
208
+ return { text: `下发被拒绝:${msg}(节点 ${node.name},物理量 ${tpl?.ch ?? node.templateKey};${retryHint})`, isError: true }
209
+ }
210
+ }
@@ -1,32 +1,32 @@
1
- /**
2
- * 工业工具族(daq_query / dcw_* / param_* / ops_* / recipe_* / aml_*)
3
- *
4
- * 由原单文件按职责拆分而来;此处保持**原文件的公开 API 与 import 路径不变**
5
- * (目录 index 解析),外部调用方无需改动。
6
- *
7
- * 模块划分:
8
- * dcw-tools.ts 节点清单、数控下发/回读(dcw_control / dcw_read)
9
- * param-tools.ts 工艺参数读写(param_control / param_read)与量程/容差辅助
10
- * daq-tools.ts 数采查询与帧检索(daq_query / daq_frames)
11
- * dcw-judge-tools.ts 判定与回滚(dcw_judge / dcw_rollback / dcw_journal)与绑定鉴权
12
- * ops-tools.ts 运维日志与配方变更史(ops_log / recipe_* / line_context)
13
- * aml-dataset-tools.ts AML 数据集与节点目录(aml_node_catalog / aml_dataset_*)
14
- * aml-job-tools.ts AML 作业与模型(aml_job_* / aml_leaderboard / aml_model_*)
15
- * mes-tools.ts MES 数据集面(mes_catalog / mes_fetch / mes_dataset_read / mes_datasets)
16
- */
17
- export { toolMyIndustrialNodes, toolDcwControl, toolDcwRead } from './dcw-tools'
18
- export { toolParamControl, toolParamRead } from './param-tools'
19
- export { toolDaqQuery, toolDaqFrames } from './daq-tools'
20
- export { toolDaqExport } from './daq-export'
21
- export { toolDcwJudge, toolDcwRollback, toolDcwJournal } from './dcw-judge-tools'
22
- export { toolOpsLog, toolRecipeLog, toolLineContext, toolRecipeVersions, toolRecipeUpdate, toolRecipeRollback, toolRecipeTrial, toolRecipeApply } from './ops-tools'
23
- export { toolMesCatalog, toolMesFetch, toolMesDatasetRead, toolMesDatasets } from './mes-tools'
24
- export { toolAmlNodeCatalog, toolAmlDatasetBuild, toolAmlDatasetStats } from './aml-dataset-tools'
25
- export { toolAmlJobSubmit, toolAmlJobStatus, toolAmlJobLogs, toolAmlJobCancel, toolAmlLeaderboard, toolAmlModelFind, toolAmlModelPromote, toolAmlModelReference, toolAmlTrainingPlanList, toolAmlTrainingPlanTrain, toolAmlTrainingPlanCreate } from './aml-job-tools'
26
-
27
- export { toolOptimizationExplore } from './optimization-tools'
28
- export { toolTwinProviderCatalog, toolTwinSceneDiscover, toolTwinSceneCompile, toolTwinSceneFreeze, toolTwinSceneRead, toolTwinPhysicsSpecDraft, toolTwinPhysicsSpecValidate, toolTwinPhysicsSpecCompile, toolTwinSnapshotCreate, toolTwinTrialRun, toolMpcOptimize, toolTwinGateEvaluate, toolTwinCalibrationRequest, toolTwinBayesOptimize } from './twin-tools'
29
- export { toolAmlActivity } from './aml-activity'
30
- export { toolRecipePropose } from './recipe-propose-tools'
31
- export { toolLineStart, toolLineStop, toolLineStatus } from './line-tools'
32
- export { toolAnalysisSave, toolAnalysisQuery } from './analysis-tools'
1
+ /**
2
+ * 工业工具族(daq_query / dcw_* / param_* / ops_* / recipe_* / aml_*)
3
+ *
4
+ * 由原单文件按职责拆分而来;此处保持**原文件的公开 API 与 import 路径不变**
5
+ * (目录 index 解析),外部调用方无需改动。
6
+ *
7
+ * 模块划分:
8
+ * dcw-tools.ts 节点清单、数控下发/回读(dcw_control / dcw_read)
9
+ * param-tools.ts 工艺参数读写(param_control / param_read)与量程/容差辅助
10
+ * daq-tools.ts 数采查询与帧检索(daq_query / daq_frames)
11
+ * dcw-judge-tools.ts 判定与回滚(dcw_judge / dcw_rollback / dcw_journal)与绑定鉴权
12
+ * ops-tools.ts 运维日志与配方变更史(ops_log / recipe_* / line_context)
13
+ * aml-dataset-tools.ts AML 数据集与节点目录(aml_node_catalog / aml_dataset_*)
14
+ * aml-job-tools.ts AML 作业与模型(aml_job_* / aml_leaderboard / aml_model_*)
15
+ * mes-tools.ts MES 数据集面(mes_catalog / mes_fetch / mes_dataset_read / mes_datasets)
16
+ */
17
+ export { toolMyIndustrialNodes, toolDcwControl } from './dcw-tools'
18
+ export { toolParamControl } from './param-tools'
19
+ export { toolDaqQuery, toolDaqFrames } from './daq-tools'
20
+ export { toolDaqExport } from './daq-export'
21
+ export { toolDcwJudge, toolDcwRollback, toolDcwJournal } from './dcw-judge-tools'
22
+ export { toolOpsLog, toolRecipeLog, toolLineContext, toolRecipeVersions, toolRecipeUpdate, toolRecipeRollback, toolRecipeTrial, toolRecipeApply } from './ops-tools'
23
+ export { toolMesCatalog, toolMesFetch, toolMesDatasetRead, toolMesDatasets } from './mes-tools'
24
+ export { toolAmlNodeCatalog, toolAmlDatasetBuild, toolAmlDatasetStats } from './aml-dataset-tools'
25
+ export { toolAmlJobSubmit, toolAmlJobStatus, toolAmlJobLogs, toolAmlJobCancel, toolAmlLeaderboard, toolAmlModelFind, toolAmlModelPromote, toolAmlModelReference, toolAmlTrainingPlanList, toolAmlTrainingPlanTrain, toolAmlTrainingPlanCreate } from './aml-job-tools'
26
+
27
+ export { toolOptimizationExplore } from './optimization-tools'
28
+ export { toolTwinProviderCatalog, toolTwinSceneDiscover, toolTwinSceneCompile, toolTwinSceneFreeze, toolTwinSceneRead, toolTwinPhysicsSpecDraft, toolTwinPhysicsSpecValidate, toolTwinPhysicsSpecCompile, toolTwinSnapshotCreate, toolTwinTrialRun, toolMpcOptimize, toolTwinGateEvaluate, toolTwinCalibrationRequest, toolTwinBayesOptimize } from './twin-tools'
29
+ export { toolAmlActivity } from './aml-activity'
30
+ export { toolRecipePropose } from './recipe-propose-tools'
31
+ export { toolLineStart, toolLineStop, toolLineStatus } from './line-tools'
32
+ export { toolAnalysisSave, toolAnalysisQuery } from './analysis-tools'
@@ -150,43 +150,3 @@ async function _toolParamControlDisabled(agentId: string, args: {
150
150
  return { text: `下发被拒绝:${msg}(工艺参数 ${param.key};${paramLimitsText(param)})`, isError: true }
151
151
  }
152
152
  }
153
-
154
- /** 工具:param_read —— 按工艺参数读取 PLC 当前值(被动观测免审批) */
155
- export async function toolParamRead(agentId: string, args: {
156
- param?: string
157
- line_id?: string
158
- }): Promise<{ text: string, isError?: boolean }> {
159
- const resolved = resolveParamRef(String(args.param ?? ''), args.line_id ? String(args.line_id) : undefined)
160
- if (!resolved.ok) return resolved
161
- const param = resolved.param
162
- const repo = getAgentNodeBindingRepo()
163
- if (!repo.find(agentId, param.nodeId, 'dcw')) {
164
- const mine = repo.byAgent(agentId).filter(b => b.kind === 'dcw')
165
- return {
166
- text: mine.length
167
- ? `无权读取工艺参数「${param.key}」(执行节点 ${param.nodeId} 未绑定给你)。你有权读取的数控节点:${mine.map(b => b.nodeId).join(', ')}。`
168
- : '你尚未绑定任何数控节点,无权读取控制通道数据。请在数字孪生界面绑定数控节点。',
169
- isError: true,
170
- }
171
- }
172
- const node = getDcwController().byId(param.nodeId)
173
- if (!node) {
174
- repo.removeAgentNode(agentId, param.nodeId, 'dcw')
175
- return { text: `工艺参数「${param.key}」的执行节点已不存在(可能被删除),原绑定已自动清理。`, isError: true }
176
- }
177
- try {
178
- const read = await getDcwController().readParam(param.id)
179
- if (!read.ok && read.value == null && !node.readValue) {
180
- return { text: `读取失败:${read.message}(执行节点「${node.name}」驱动 ${node.driver} 可能不支持读取;可改用 daq_query 查关联数采通道)`, isError: true }
181
- }
182
- const readTxt = read.value != null
183
- ? `${Number(read.value.toFixed(param.decimals))}${param.unit} @ ${read.at.slice(11, 19)}`
184
- : (node.readValue != null ? `${node.readValue}${param.unit}(最近一次)` : '无读数')
185
- return {
186
- text: `读取成功:工艺参数「${param.key}」(${param.name})\n PLC 读数(ACT): ${readTxt}\n 当前设定(SET): ${node.value != null ? `${node.value}${param.unit}` : '从未下发'}\n ${paramLimitsText(param)}`,
187
- }
188
- }
189
- catch (err) {
190
- return { text: `读取被拒绝:${err instanceof Error ? err.message : String(err)}(工艺参数 ${param.key})`, isError: true }
191
- }
192
- }