agentworkshop 0.7.14 → 0.7.16
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 +819 -755
- package/.output/.AgentWorkShop/prompts/host-tools.json +819 -755
- package/.output/nitro.json +1 -1
- package/.output/public/_nuxt/builds/latest.json +1 -1
- package/.output/public/_nuxt/builds/meta/cf0a8831-4514-493d-ad5a-4e371a089ef9.json +1 -0
- package/.output/server/chunks/_/nitro.mjs +885 -725
- package/.output/server/chunks/_/nitro.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/index.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/index.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/index3.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/index3.post.mjs.map +1 -1
- package/.output/server/package.json +1 -1
- package/package.json +1 -1
- package/scripts/_dbg-harness-ui-e2e.py +23 -13
- package/scripts/_dbg-opslog-agent-e2e.mjs +191 -0
- package/scripts/aw-expose.mjs +159 -0
- package/server/api/workshop/daq/index.post.ts +1 -1
- package/server/api/workshop/dcw/lines/index.post.ts +1 -1
- package/server/data/daqs.json +471 -61
- package/server/data/dcw-lines.json +49 -0
- package/server/data/dcw-products.json +49 -0
- package/server/data/dcw-recipes.json +64 -0
- package/server/data/dcw-rollback.json +239 -6
- package/server/data/dcw-runs.json +162 -142
- package/server/data/dcw-writes.json +156 -0
- package/server/data/dcws.json +363 -217
- package/server/data/line-runs.json +41 -11
- package/server/data/scene-layouts.json +11 -0
- package/server/plugins/workshop.ts +9 -0
- package/server/services/workshop/agents/agent-badge.ts +38 -0
- package/server/services/workshop/agents/host-tool-bridge.ts +622 -612
- package/server/services/workshop/agents/industrial-tools.ts +139 -4
- package/server/services/workshop/daq/drivers.ts +25 -5
- package/server/services/workshop/dcw/recipe-rollback-manager.ts +8 -6
- package/.output/public/_nuxt/builds/meta/23c30854-f9e1-4651-bc3a-b695ef5faca4.json +0 -1
|
@@ -11,6 +11,7 @@ import type { AgentNodeBinding } from './node-bindings.repo'
|
|
|
11
11
|
import { getAgentNodeBindingRepo } from './node-bindings.repo'
|
|
12
12
|
import { getToolApprovals } from './tool-approvals'
|
|
13
13
|
import { nodeSemanticCards } from './industrial-context'
|
|
14
|
+
import { agentBadgeLabel } from './agent-badge'
|
|
14
15
|
import { getDcwController } from '../dcw/dcw-controller'
|
|
15
16
|
import { getActiveLineRun } from '../dcw/line-run'
|
|
16
17
|
import { findDcwTemplate } from '../dcw/dcw-templates'
|
|
@@ -18,6 +19,7 @@ import { daqRuntimeSettings } from '../settings'
|
|
|
18
19
|
import { getDaqNodeRepo } from '../daq/daq-node.repo'
|
|
19
20
|
import { findDaqTemplate } from '../daq/daq-templates'
|
|
20
21
|
import { getRecipeRollBackManager } from '../dcw/recipe-rollback-manager'
|
|
22
|
+
import { getOps } from '../ops/ops'
|
|
21
23
|
|
|
22
24
|
export async function toolMyIndustrialNodes(agentId: string): Promise<{ text: string }> {
|
|
23
25
|
const repo = getAgentNodeBindingRepo()
|
|
@@ -65,7 +67,11 @@ export async function toolMyIndustrialNodes(agentId: string): Promise<{ text: st
|
|
|
65
67
|
4. 改动设定后等待工艺响应(热惯性/传动惯量)再评估,避免连续大幅调整。
|
|
66
68
|
5. 调控闭环:每次下发自动开一条优化记录(open);观察数采后用 dcw_judge 落判定(keep/rollback/uncertain);
|
|
67
69
|
判 rollback 后用 dcw_rollback 执行回退;dcw_journal 可查节点参数变更史。未判定前再下发,旧记录会被标记 superseded。
|
|
68
|
-
若节点被他人的 open 记录阻塞(对方已消失),超时(30 分钟)后你可接管:dcw_judge
|
|
70
|
+
若节点被他人的 open 记录阻塞(对方已消失),超时(30 分钟)后你可接管:dcw_judge 会带接管标记入册。
|
|
71
|
+
6. 自查面:ops_log 查你负责产线的运维日志(谁/何时做了什么,来源区分 Agent/用户/系统;
|
|
72
|
+
参数 line_id/node_id/kind/actor_kind/minutes/limit/mine);recipe_log 查配方下发与回退历史
|
|
73
|
+
(recipe.apply/优化开窗/判定/回退,参数 line_id/recipe_id/minutes/limit)。
|
|
74
|
+
作业前后各查一次,即可完整知道自己与团队对当前节点做过哪些操作、Recipe 有过哪些变更。`,
|
|
69
75
|
}
|
|
70
76
|
}
|
|
71
77
|
|
|
@@ -123,6 +129,8 @@ export async function toolDcwControl(agentId: string, args: { node_id?: string,
|
|
|
123
129
|
const meta = {
|
|
124
130
|
source: 'agent' as const,
|
|
125
131
|
actor: agentId,
|
|
132
|
+
// 人话操作者:「Channel名/成员名」——/logs 来源=Agent 与用户/系统操作可区分追溯
|
|
133
|
+
actorName: agentBadgeLabel(agentId),
|
|
126
134
|
taskId: args.task_id ? String(args.task_id) : undefined,
|
|
127
135
|
hypothesis: args.hypothesis ? String(args.hypothesis) : '',
|
|
128
136
|
}
|
|
@@ -355,7 +363,7 @@ export async function toolDcwJudge(agentId: string, args: { record_id?: string,
|
|
|
355
363
|
return { text: `记录 ${recordId} 不是你发起的优化(发起者:${record.agentId ?? '用户'}),Agent 仅可判定自己的记录;他人记录请请用户在界面判定。`, isError: true }
|
|
356
364
|
try {
|
|
357
365
|
const finalReason = takeover ? `[接管孤儿记录,原属主 ${record.agentId ?? '?'} 超时未判定] ${reason}` : reason
|
|
358
|
-
const updated = rb.judge(recordId, verdict, finalReason, 'agent', agentId, { takeover })
|
|
366
|
+
const updated = rb.judge(recordId, verdict, finalReason, 'agent', agentId, { takeover, actorName: agentBadgeLabel(agentId) })
|
|
359
367
|
const next = verdict === 'rollback'
|
|
360
368
|
? '判定已入册;请立即用 dcw_rollback(record_id) 执行回退(判定不自动改 PLC)。'
|
|
361
369
|
: verdict === 'keep'
|
|
@@ -385,10 +393,10 @@ export async function toolDcwRollback(agentId: string, args: { record_id?: strin
|
|
|
385
393
|
const takeover = record.agentId !== agentId && rb.isStale(record)
|
|
386
394
|
if (record.agentId !== agentId && !takeover)
|
|
387
395
|
return { text: `记录 ${recordId} 不是你发起的优化(发起者:${record.agentId ?? '用户'}),回退他人记录请请用户在数采中心/产线详情执行;若原属主已消失(超时未判定),可先 dcw_judge 接管后再回退。`, isError: true }
|
|
388
|
-
const fresh = await rb.rollbackRecord(recordId, agentId, 'agent')
|
|
396
|
+
const fresh = await rb.rollbackRecord(recordId, agentId, 'agent', undefined, { actorName: agentBadgeLabel(agentId) })
|
|
389
397
|
return { text: `回退已执行:记录 ${recordId} 标记 rolled-back;下发恢复值 ${fresh?.params[0]?.to}${'(以回读为准)'};新回退记录 ${fresh?.id} 已入册。请 daq_query 复测确认恢复。` }
|
|
390
398
|
}
|
|
391
|
-
const fresh = await rb.rollbackNode(nodeId, agentId, 'agent', to)
|
|
399
|
+
const fresh = await rb.rollbackNode(nodeId, agentId, 'agent', to, { actorName: agentBadgeLabel(agentId) })
|
|
392
400
|
return { text: `节点单步回退已执行:恢复到最近稳定锚值;新回退记录 ${fresh?.id} 已入册。请 daq_query 复测确认恢复。` }
|
|
393
401
|
}
|
|
394
402
|
catch (err) {
|
|
@@ -481,3 +489,130 @@ export async function toolDaqFrames(agentId: string, args: {
|
|
|
481
489
|
}
|
|
482
490
|
return { text: `数采帧查询结果(${targets.length} 个节点):\n\n${sections.join('\n\n')}\n\n向量=多点工程量轮廓(完整点列前端可查);图像=像素在对象存储,指标供判读(brightness 过低=曝光不足/遮挡)。` }
|
|
483
491
|
}
|
|
492
|
+
|
|
493
|
+
// ================================================================
|
|
494
|
+
// 运维日志 / Recipe 变更史查询(Agent 自查面:负责产线 scoped)
|
|
495
|
+
// ================================================================
|
|
496
|
+
|
|
497
|
+
/** Agent 负责范围 = 绑定节点的全集(节点 id 集 + 这些节点所属产线集);无绑定 = 无范围 */
|
|
498
|
+
function agentOpsScope(agentId: string): { lineIds: string[], nodeIds: Set<string> } | null {
|
|
499
|
+
const bindings = getAgentNodeBindingRepo().byAgent(agentId)
|
|
500
|
+
if (bindings.length === 0) return null
|
|
501
|
+
const lineIds = new Set<string>()
|
|
502
|
+
const nodeIds = new Set<string>()
|
|
503
|
+
for (const b of bindings) {
|
|
504
|
+
nodeIds.add(b.nodeId)
|
|
505
|
+
try {
|
|
506
|
+
const n = b.kind === 'dcw' ? getDcwController().byId(b.nodeId) : getDaqNodeRepo().byId(b.nodeId)
|
|
507
|
+
if (n?.lineId) lineIds.add(n.lineId)
|
|
508
|
+
}
|
|
509
|
+
catch { /* 节点刚被删等情况忽略 */ }
|
|
510
|
+
}
|
|
511
|
+
return { lineIds: [...lineIds], nodeIds }
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const OPS_ACTOR_LABEL: Record<string, string> = { agent: 'Agent', user: '用户', system: '系统' }
|
|
515
|
+
|
|
516
|
+
function fmtAuditAt(iso: string): string {
|
|
517
|
+
return iso.length >= 19 ? iso.slice(5, 19).replace('T', ' ') : iso
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/** 工具:ops_log —— 负责产线的运维/审计日志查询(全部下发/判定/回退/配方/产线事件)。
|
|
521
|
+
* 权限:仅自己绑定节点所覆盖的产线;node_id 须为绑定节点。来源(actor_kind)区分 Agent/用户/系统。 */
|
|
522
|
+
export async function toolOpsLog(agentId: string, args: {
|
|
523
|
+
line_id?: string
|
|
524
|
+
node_id?: string
|
|
525
|
+
kind?: string
|
|
526
|
+
actor_kind?: string
|
|
527
|
+
minutes?: number | string
|
|
528
|
+
limit?: number | string
|
|
529
|
+
mine?: boolean | string
|
|
530
|
+
}): Promise<{ text: string, isError?: boolean }> {
|
|
531
|
+
const audit = getOps()?.audit
|
|
532
|
+
if (!audit) return { text: '审计仓储未装配(服务未就绪),请稍后重试。', isError: true }
|
|
533
|
+
const scope = agentOpsScope(agentId)
|
|
534
|
+
if (!scope) return { text: '你尚未绑定任何工业节点,无日志可查(日志权限跟随节点绑定)。', isError: true }
|
|
535
|
+
const nodeId = String(args.node_id ?? '').trim()
|
|
536
|
+
const lineId = String(args.line_id ?? '').trim()
|
|
537
|
+
if (nodeId && !scope.nodeIds.has(nodeId))
|
|
538
|
+
return { text: `无权查询节点 ${nodeId} 的日志(仅可查自己绑定的节点;用 my_industrial_nodes 查看绑定)。`, isError: true }
|
|
539
|
+
if (lineId && !scope.lineIds.includes(lineId))
|
|
540
|
+
return { text: `无权查询产线 ${lineId} 的日志(你的负责产线:${scope.lineIds.join(', ') || '(绑定节点均未分配产线)'})。`, isError: true }
|
|
541
|
+
const kind = String(args.kind ?? '').trim() || undefined
|
|
542
|
+
const actorKind = String(args.actor_kind ?? '').trim() || undefined
|
|
543
|
+
const mine = args.mine === true || args.mine === 'true'
|
|
544
|
+
const minutes = Number(args.minutes) || 1440
|
|
545
|
+
const limit = Math.min(Number(args.limit) || 20, 100)
|
|
546
|
+
const from = new Date(Date.now() - minutes * 60_000).toISOString()
|
|
547
|
+
|
|
548
|
+
const lines = lineId ? [lineId] : scope.lineIds
|
|
549
|
+
const byId = new Map<string, Record<string, unknown>>()
|
|
550
|
+
for (const lid of lines) {
|
|
551
|
+
for (const r of audit.query({ lineId: lid, kind, actorKind, actor: mine ? agentId : undefined, from, limit }))
|
|
552
|
+
byId.set(String(r.id), r)
|
|
553
|
+
}
|
|
554
|
+
let rows = [...byId.values()]
|
|
555
|
+
if (nodeId) rows = rows.filter(r => r.targetId === nodeId)
|
|
556
|
+
rows.sort((a, b) => String(b.at).localeCompare(String(a.at)))
|
|
557
|
+
rows = rows.slice(0, limit)
|
|
558
|
+
if (rows.length === 0) return { text: `窗口(近 ${minutes} 分钟)内无匹配日志。可调大 minutes,或放宽 kind/actor_kind/node_id 过滤。` }
|
|
559
|
+
|
|
560
|
+
const body = rows.map((r) => {
|
|
561
|
+
const src = OPS_ACTOR_LABEL[String(r.actorKind)] ?? String(r.actorKind)
|
|
562
|
+
return `- [${fmtAuditAt(String(r.at))}] 来源=${src} 操作者=${String(r.actorName) || String(r.actor)} | ${String(r.action)} | ${String(r.summary)}`
|
|
563
|
+
})
|
|
564
|
+
const scopeNote = lineId ? `产线 ${lineId}` : `负责产线 ${scope.lineIds.length} 条`
|
|
565
|
+
return {
|
|
566
|
+
text: `运维日志(${scopeNote},近 ${minutes} 分钟,${rows.length} 条,新→旧):\n${body.join('\n')}\n\n说明:来源=Agent 的操作者格式为「Channel名/成员名」;需要节点级参数值变更史用 dcw_journal,配方下发/回退专门视图用 recipe_log。`,
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/** 工具:recipe_log —— 负责产线的配方下发与回退历史(recipe.apply + 优化开窗/判定/回退)。
|
|
571
|
+
* Agent 据此知道 Recipe 层面发生过哪些下发变更、谁做的、是否已回退。 */
|
|
572
|
+
export async function toolRecipeLog(agentId: string, args: {
|
|
573
|
+
line_id?: string
|
|
574
|
+
recipe_id?: string
|
|
575
|
+
minutes?: number | string
|
|
576
|
+
limit?: number | string
|
|
577
|
+
}): Promise<{ text: string, isError?: boolean }> {
|
|
578
|
+
const audit = getOps()?.audit
|
|
579
|
+
if (!audit) return { text: '审计仓储未装配(服务未就绪),请稍后重试。', isError: true }
|
|
580
|
+
const scope = agentOpsScope(agentId)
|
|
581
|
+
if (!scope) return { text: '你尚未绑定任何工业节点,无 Recipe 历史可查(权限跟随节点绑定)。', isError: true }
|
|
582
|
+
const lineId = String(args.line_id ?? '').trim()
|
|
583
|
+
if (lineId && !scope.lineIds.includes(lineId))
|
|
584
|
+
return { text: `无权查询产线 ${lineId} 的 Recipe 历史(你的负责产线:${scope.lineIds.join(', ') || '(无)'})。`, isError: true }
|
|
585
|
+
const recipeId = String(args.recipe_id ?? '').trim() || undefined
|
|
586
|
+
const minutes = Number(args.minutes) || 1440
|
|
587
|
+
const limit = Math.min(Number(args.limit) || 20, 100)
|
|
588
|
+
const from = new Date(Date.now() - minutes * 60_000).toISOString()
|
|
589
|
+
|
|
590
|
+
const lines = lineId ? [lineId] : scope.lineIds
|
|
591
|
+
const byId = new Map<string, Record<string, unknown>>()
|
|
592
|
+
for (const lid of lines) {
|
|
593
|
+
for (const kind of ['recipe', 'rollback']) {
|
|
594
|
+
for (const r of audit.query({ lineId: lid, kind, recipeId, from, limit }))
|
|
595
|
+
byId.set(String(r.id), r)
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
const rows = [...byId.values()].sort((a, b) => String(b.at).localeCompare(String(a.at))).slice(0, limit)
|
|
599
|
+
if (rows.length === 0) return { text: `窗口(近 ${minutes} 分钟)内无配方下发/回退记录。可调大 minutes 或换 line_id。` }
|
|
600
|
+
|
|
601
|
+
const body = rows.map((r) => {
|
|
602
|
+
const src = OPS_ACTOR_LABEL[String(r.actorKind)] ?? String(r.actorKind)
|
|
603
|
+
const action = String(r.action)
|
|
604
|
+
const tag = action === 'recipe.apply'
|
|
605
|
+
? '配方下发'
|
|
606
|
+
: action === 'optimization.open'
|
|
607
|
+
? '优化开窗'
|
|
608
|
+
: action === 'optimization.judge'
|
|
609
|
+
? '优化判定'
|
|
610
|
+
: action === 'optimization.rollback'
|
|
611
|
+
? '回退执行'
|
|
612
|
+
: action
|
|
613
|
+
return `- [${fmtAuditAt(String(r.at))}] ${tag} 来源=${src} 操作者=${String(r.actorName) || String(r.actor)} | ${String(r.summary)}${r.recipeId ? `(配方 ${String(r.recipeId).slice(0, 8)})` : ''}`
|
|
614
|
+
})
|
|
615
|
+
return {
|
|
616
|
+
text: `Recipe 变更史(${lineId ? `产线 ${lineId}` : `负责产线 ${scope.lineIds.length} 条`},近 ${minutes} 分钟,${rows.length} 条,新→旧):\n${body.join('\n')}\n\n说明:配方下发=整批参数写命令;优化开窗=单次设定变更(含 Agent 假设);回退执行=已恢复基线值。节点级参数值逐笔历史用 dcw_journal(node_id)。`,
|
|
617
|
+
}
|
|
618
|
+
}
|
|
@@ -231,15 +231,35 @@ export function evictModbusConn(cfg: Record<string, unknown>, transport: ModbusT
|
|
|
231
231
|
/**
|
|
232
232
|
* 连接级排队执行:操作串行入队,完成后链路归还。
|
|
233
233
|
* 数控写入等当前采样读完成再执行(不再 409 快速失败);数采采样忙时仍跳帧让路防堆积。
|
|
234
|
+
* per-request 3s 硬超时:半开连接(对端断电/SYN 黑洞)时底层请求永不 settle,
|
|
235
|
+
* 超时后作废连接并重置 tail 链,防止单条死连接堵死该 host:port 的全部后续请求。
|
|
234
236
|
*/
|
|
235
237
|
export function withModbusConn<R>(conn: ModbusConn, fn: () => Promise<R>): Promise<R> {
|
|
236
238
|
conn.pending++
|
|
237
239
|
const run = conn.tail.then(() => fn(), () => fn())
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
240
|
+
let timer: NodeJS.Timeout | undefined
|
|
241
|
+
let dead = false
|
|
242
|
+
const guarded = Promise.race([
|
|
243
|
+
run,
|
|
244
|
+
new Promise<never>((_, rej) => {
|
|
245
|
+
timer = setTimeout(() => {
|
|
246
|
+
dead = true
|
|
247
|
+
try {
|
|
248
|
+
void conn.client.close()
|
|
249
|
+
}
|
|
250
|
+
catch { /* 已断 */ }
|
|
251
|
+
rej(new Error('Modbus 响应超时(3s)——连接可能半开,已作废重连'))
|
|
252
|
+
}, 3000)
|
|
253
|
+
}),
|
|
254
|
+
] as Promise<R>)
|
|
255
|
+
void guarded.catch(() => { /* 超时分支先 reject,防 unhandled */ })
|
|
256
|
+
conn.tail = guarded.then(
|
|
257
|
+
() => { clearTimeout(timer); conn.pending-- },
|
|
258
|
+
() => { clearTimeout(timer); conn.pending-- },
|
|
259
|
+
).then(() => {
|
|
260
|
+
if (dead) conn.tail = Promise.resolve()
|
|
261
|
+
})
|
|
262
|
+
return guarded
|
|
243
263
|
}
|
|
244
264
|
|
|
245
265
|
export function modbusKey(cfg: Record<string, unknown>, transport: ModbusTransport = 'tcp'): string {
|
|
@@ -200,7 +200,7 @@ export class RecipeRollBackManager {
|
|
|
200
200
|
return record.status === 'open' && Date.now() - Date.parse(record.setAt) > OPEN_RECORD_STALE_MS()
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
-
judge(recordId: string, verdict: OptimizationVerdict, reason: string, by: 'agent' | 'system' | 'user', actor: string, opts?: { takeover?: boolean }): OptimizationRecord {
|
|
203
|
+
judge(recordId: string, verdict: OptimizationVerdict, reason: string, by: 'agent' | 'system' | 'user', actor: string, opts?: { takeover?: boolean, actorName?: string }): OptimizationRecord {
|
|
204
204
|
const record = this.repo.byId(recordId)
|
|
205
205
|
if (!record)
|
|
206
206
|
throw new AppError(404, ErrorCodes.NOT_FOUND, `优化记录不存在: ${recordId}`)
|
|
@@ -218,7 +218,7 @@ export class RecipeRollBackManager {
|
|
|
218
218
|
this.emit('judged', record)
|
|
219
219
|
recordOps({
|
|
220
220
|
actor,
|
|
221
|
-
actorName: actor,
|
|
221
|
+
actorName: opts?.actorName ?? actor,
|
|
222
222
|
actorKind: by,
|
|
223
223
|
action: 'optimization.judge',
|
|
224
224
|
kind: 'rollback',
|
|
@@ -237,7 +237,7 @@ export class RecipeRollBackManager {
|
|
|
237
237
|
// ================================================================
|
|
238
238
|
|
|
239
239
|
/** 回退一条优化记录:目标 = 该记录的 from 值(设定前基线) */
|
|
240
|
-
async rollbackRecord(recordId: string, actor: string, by: 'agent' | 'user' | 'system', approvalId?: string): Promise<OptimizationRecord> {
|
|
240
|
+
async rollbackRecord(recordId: string, actor: string, by: 'agent' | 'user' | 'system', approvalId?: string, opts?: { actorName?: string }): Promise<OptimizationRecord> {
|
|
241
241
|
const record = this.repo.byId(recordId)
|
|
242
242
|
if (!record)
|
|
243
243
|
throw new AppError(404, ErrorCodes.NOT_FOUND, `优化记录不存在: ${recordId}`)
|
|
@@ -246,11 +246,11 @@ export class RecipeRollBackManager {
|
|
|
246
246
|
throw new AppError(409, ErrorCodes.CONFLICT, `记录 ${recordId} 无基线值(首写无 prevValue),无法回退`)
|
|
247
247
|
if (by === 'agent')
|
|
248
248
|
this.checkRollbackAllowed(record.nodeId)
|
|
249
|
-
return this.executeRollbackWrite(record, from, actor, approvalId, undefined, by)
|
|
249
|
+
return this.executeRollbackWrite(record, from, actor, approvalId, undefined, by, opts?.actorName)
|
|
250
250
|
}
|
|
251
251
|
|
|
252
252
|
/** 节点级单步回退:目标 = 最近稳定锚的 prevValue(撤销栈栈顶) */
|
|
253
|
-
async rollbackNode(nodeId: string, actor: string, by: 'agent' | 'user' | 'system', toAnchorId?: string): Promise<OptimizationRecord> {
|
|
253
|
+
async rollbackNode(nodeId: string, actor: string, by: 'agent' | 'user' | 'system', toAnchorId?: string, opts?: { actorName?: string }): Promise<OptimizationRecord> {
|
|
254
254
|
const node = getDcwNodeRepo().byId(nodeId)
|
|
255
255
|
if (!node)
|
|
256
256
|
throw new AppError(404, ErrorCodes.NOT_FOUND, `控制节点不存在: ${nodeId}`)
|
|
@@ -274,6 +274,7 @@ export class RecipeRollBackManager {
|
|
|
274
274
|
undefined,
|
|
275
275
|
toAnchorId,
|
|
276
276
|
by,
|
|
277
|
+
opts?.actorName,
|
|
277
278
|
)
|
|
278
279
|
}
|
|
279
280
|
|
|
@@ -336,6 +337,7 @@ export class RecipeRollBackManager {
|
|
|
336
337
|
approvalId?: string,
|
|
337
338
|
toAnchorId?: string,
|
|
338
339
|
by: 'agent' | 'user' | 'system' = 'system',
|
|
340
|
+
actorName?: string,
|
|
339
341
|
): Promise<OptimizationRecord> {
|
|
340
342
|
// 先关原记录(防回退写自身的 afterWrite 把它当 open supersede)
|
|
341
343
|
const orig = this.repo.byId(record.id)
|
|
@@ -368,7 +370,7 @@ export class RecipeRollBackManager {
|
|
|
368
370
|
this.emit('rolled-back', orig)
|
|
369
371
|
recordOps({
|
|
370
372
|
actor,
|
|
371
|
-
actorName: actor,
|
|
373
|
+
actorName: actorName ?? actor,
|
|
372
374
|
actorKind: by === 'agent' ? 'agent' : by === 'user' ? 'user' : 'system',
|
|
373
375
|
action: 'optimization.rollback',
|
|
374
376
|
kind: 'rollback',
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"id":"23c30854-f9e1-4651-bc3a-b695ef5faca4","timestamp":1788693267652,"prerendered":[]}
|