agentworkshop 0.7.48 → 0.7.49
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/builds/latest.json +1 -1
- package/.output/public/_nuxt/builds/meta/0530cee1-fff5-4025-a6f4-5708b6534608.json +1 -0
- package/.output/server/chunks/_/nitro.mjs +957 -955
- package/.output/server/package.json +1 -1
- package/README-zh.md +813 -811
- package/README.md +823 -821
- package/config.yml +176 -176
- package/docs/cli.en.md +445 -445
- package/docs/cli.md +410 -410
- package/docs/full-test-plan.md +191 -191
- package/docs/plugins.en.md +567 -567
- package/docs/plugins.md +560 -560
- package/docs/sdk.en.md +922 -922
- package/docs/sdk.md +861 -861
- package/package.json +172 -172
- package/scripts/_aw0746-stage5.mjs +159 -144
- package/scripts/_aw0746-stage7-twin.mjs +301 -267
- package/scripts/_aw0748-failclosed-probe.mjs +117 -90
- package/scripts/_aw0748-fresh-channel.mjs +21 -0
- package/scripts/_aw0748-mock-closedloop.mjs +89 -0
- package/scripts/_aw0748-wake-channel.mjs +46 -0
- package/scripts/_aw0749-readme-rows.mjs +23 -0
- package/scripts/_probe-lease.cjs +13 -0
- package/scripts/_probe-task-queue.cjs +9 -0
- package/server/services/workshop/aml/twin/provider-contracts.ts +34 -0
- package/server/services/workshop/aml/twin/provider-health.ts +8 -0
- package/server/services/workshop/aml/twin/provider-loader.ts +67 -0
- package/server/services/workshop/aml/twin/provider-registry.ts +50 -0
- package/server/services/workshop/runtime/manager/state.ts +21 -4
- package/.output/public/_nuxt/builds/meta/6f524f3b-53e1-4df9-84e6-84d8058b9f16.json +0 -1
|
@@ -1,144 +1,159 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 0.7.46/0.7.47 打包系统验收 · 阶段 5:真实场景 prompt 闭环控制优化(hybrid twin channel)
|
|
3
|
-
* - 用 PLC 模拟注塑产线的**真实场景事实**(克重窗口/守卫/受控量/活动批次)下发 goal 任务
|
|
4
|
-
* - 断言:真实 omp 团队走完 twin_scene_read → twin_snapshot_create → mpc_optimize → twin_gate_evaluate
|
|
5
|
-
* - 断言:recommendation-only 生效 —— 全程**零真实 DCW 写入**
|
|
6
|
-
* 用法:AW_BASE=http://127.0.0.1:3001 [AW_HOME=<隔离实例目录>] node scripts/_aw0746-stage5.mjs
|
|
7
|
-
*/
|
|
8
|
-
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs'
|
|
9
|
-
import { join } from 'node:path'
|
|
10
|
-
|
|
11
|
-
const BASE = process.env.AW_BASE ?? 'http://127.0.0.1:3001'
|
|
12
|
-
const HOME = process.env.AW_HOME ?? `${process.cwd()}/.aw0746-home`
|
|
13
|
-
const ADMIN = { email: process.env.AW_ADMIN_EMAIL ?? 'admin@awshop.local', password: process.env.AW_ADMIN_PASSWORD ?? 'Awshop@123!' }
|
|
14
|
-
|
|
15
|
-
let pass = 0
|
|
16
|
-
const fails = []
|
|
17
|
-
const check = (name, ok, detail = '') => {
|
|
18
|
-
console.log(` ${ok ? 'PASS' : 'FAIL'} ${name}${detail ? ` — ${detail}` : ''}`)
|
|
19
|
-
if (ok) pass += 1
|
|
20
|
-
else fails.push(name)
|
|
21
|
-
}
|
|
22
|
-
const sleep = ms => new Promise(r => setTimeout(r, ms))
|
|
23
|
-
const j = async (method, path, body, token) => {
|
|
24
|
-
const r = await fetch(BASE + path, {
|
|
25
|
-
method,
|
|
26
|
-
headers: { 'content-type': 'application/json', ...(token ? { authorization: `Bearer ${token}` } : {}) },
|
|
27
|
-
body: body === undefined ? undefined : JSON.stringify(body),
|
|
28
|
-
signal: AbortSignal.timeout(180_000),
|
|
29
|
-
})
|
|
30
|
-
return { status: r.status, ...(await r.json().catch(() => ({}))) }
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const token = (await j('POST', '/api/users/login', { email: ADMIN.email, password: ADMIN.password })).data?.token
|
|
34
|
-
const chans = (await j('GET', '/api/workshop/channels', undefined, token)).data ?? []
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
const
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const
|
|
141
|
-
check('
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
1
|
+
/**
|
|
2
|
+
* 0.7.46/0.7.47 打包系统验收 · 阶段 5:真实场景 prompt 闭环控制优化(hybrid twin channel)
|
|
3
|
+
* - 用 PLC 模拟注塑产线的**真实场景事实**(克重窗口/守卫/受控量/活动批次)下发 goal 任务
|
|
4
|
+
* - 断言:真实 omp 团队走完 twin_scene_read → twin_snapshot_create → mpc_optimize → twin_gate_evaluate
|
|
5
|
+
* - 断言:recommendation-only 生效 —— 全程**零真实 DCW 写入**
|
|
6
|
+
* 用法:AW_BASE=http://127.0.0.1:3001 [AW_HOME=<隔离实例目录>] node scripts/_aw0746-stage5.mjs
|
|
7
|
+
*/
|
|
8
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs'
|
|
9
|
+
import { join } from 'node:path'
|
|
10
|
+
|
|
11
|
+
const BASE = process.env.AW_BASE ?? 'http://127.0.0.1:3001'
|
|
12
|
+
const HOME = process.env.AW_HOME ?? `${process.cwd()}/.aw0746-home`
|
|
13
|
+
const ADMIN = { email: process.env.AW_ADMIN_EMAIL ?? 'admin@awshop.local', password: process.env.AW_ADMIN_PASSWORD ?? 'Awshop@123!' }
|
|
14
|
+
|
|
15
|
+
let pass = 0
|
|
16
|
+
const fails = []
|
|
17
|
+
const check = (name, ok, detail = '') => {
|
|
18
|
+
console.log(` ${ok ? 'PASS' : 'FAIL'} ${name}${detail ? ` — ${detail}` : ''}`)
|
|
19
|
+
if (ok) pass += 1
|
|
20
|
+
else fails.push(name)
|
|
21
|
+
}
|
|
22
|
+
const sleep = ms => new Promise(r => setTimeout(r, ms))
|
|
23
|
+
const j = async (method, path, body, token) => {
|
|
24
|
+
const r = await fetch(BASE + path, {
|
|
25
|
+
method,
|
|
26
|
+
headers: { 'content-type': 'application/json', ...(token ? { authorization: `Bearer ${token}` } : {}) },
|
|
27
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
28
|
+
signal: AbortSignal.timeout(180_000),
|
|
29
|
+
})
|
|
30
|
+
return { status: r.status, ...(await r.json().catch(() => ({}))) }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const token = (await j('POST', '/api/users/login', { email: ADMIN.email, password: ADMIN.password })).data?.token
|
|
34
|
+
const chans = (await j('GET', '/api/workshop/channels', undefined, token)).data ?? []
|
|
35
|
+
// AW_CHANNEL_ID 可指定专用 Channel:FIFO 根队列是串行的,上一个根任务 FAILED 后
|
|
36
|
+
// 该 Channel 可能长时间不再派发新根任务(实测 55017fd8 悬挂 15 分钟),
|
|
37
|
+
// 验收用新实例化 Channel 可避免被历史队列拖住。
|
|
38
|
+
const twin = process.env.AW_CHANNEL_ID
|
|
39
|
+
? chans.find(c => c.id === process.env.AW_CHANNEL_ID)
|
|
40
|
+
: chans.find(c => /闭环优化-hybridtwin/.test(c.name))
|
|
41
|
+
check('找到闭环优化 Channel(hybrid twin 模板实例)', Boolean(twin), `${twin?.name} ${String(twin?.id).slice(0, 8)}`)
|
|
42
|
+
const channelId = twin.id
|
|
43
|
+
const members = (await j('GET', `/api/workshop/channels/${channelId}/agents`, undefined, token)).data ?? []
|
|
44
|
+
const lead = members.find(m => m.role === 'lead')
|
|
45
|
+
|
|
46
|
+
const dcw = (await j('GET', '/api/workshop/dcw', undefined, token)).data
|
|
47
|
+
const line = (dcw.lines ?? []).find(l => /injection-aw0746/i.test(String(l.name)))
|
|
48
|
+
const daq = (await j('GET', '/api/workshop/daq', undefined, token)).data
|
|
49
|
+
const nodes = (daq.nodes ?? []).filter(n => n.lineId === line.id)
|
|
50
|
+
const byName = re => nodes.find(n => re.test(n.name))
|
|
51
|
+
// auto_daq 走 Agent 真实绑定:给 Channel 全体成员补绑定(模板实例化不自带)
|
|
52
|
+
const requiredIds = [byName(/part-weight/), byName(/flash-rate/), byName(/sink-mark/), byName(/melt-temp-pv/), byName(/melt-pressure-pv/)].filter(Boolean).map(n => n.id)
|
|
53
|
+
let bindCount = 0
|
|
54
|
+
for (const m of members) {
|
|
55
|
+
for (const nodeId of requiredIds) {
|
|
56
|
+
const r = await j('POST', '/api/workshop/agent-tools/bindings', { agentId: m.id, nodeId, kind: 'daq', mode: 'auto' }, token)
|
|
57
|
+
if (r.code === 0) bindCount += 1
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
console.log(` · 场景绑定注入:${bindCount} 条(成员 ${members.length} × 节点 ${requiredIds.length})`)
|
|
61
|
+
const controls = (dcw.nodes ?? []).filter(n => n.lineId === line.id && /hold-pressure|hold-time|mold-temp/.test(n.name))
|
|
62
|
+
const writesBefore = Number(dcw.controller?.writesTotal ?? 0)
|
|
63
|
+
|
|
64
|
+
// 真实场景 prompt(取自 PLC 模拟器注塑场景的工程事实)
|
|
65
|
+
const SCENARIO = [
|
|
66
|
+
'场景:PLC 模拟器注塑成型产线(真实协议栈,非抽象任务)。',
|
|
67
|
+
`产线 ${line.name}(${line.id}),活动批次配方三件套由 line_context 读取,不要臆造 id。`,
|
|
68
|
+
'质量目标:制品克重 part-weight 命中 32.5 ± 0.35 g;飞边 flash-rate ≤ 0.4%;缩痕 sink-mark ≤ 1.5%;',
|
|
69
|
+
'守卫:熔体温度 melt-temp-pv 235~262℃;注射压力 inj-pressure-pv 40~70 bar。',
|
|
70
|
+
`受控量(不可越界,单变量小步):${controls.map(c => `${c.name}(${c.id},${c.min}~${c.max}${c.unit})`).join('; ')}。`,
|
|
71
|
+
`观测节点:${[byName(/part-weight/), byName(/flash-rate/), byName(/sink-mark/), byName(/melt-temp/), byName(/inj-pressure/)].filter(Boolean).map(n => `${n.name}(${n.id})`).join('; ')}。`,
|
|
72
|
+
].join('\n')
|
|
73
|
+
|
|
74
|
+
const TASK = [
|
|
75
|
+
'目标:完成一次 recommendation-only 的混合孪生 MPC 闭环优化推演,并给出可审计的推荐结论。',
|
|
76
|
+
'硬性要求(按序执行,每步都有据可查):',
|
|
77
|
+
'1) twin_scene_read 读取场景契约;若场景未就绪,先用 scene/objective 参数把当前产线与活动批次登记进去(允许使用工具自带参数)。',
|
|
78
|
+
'2) twin_snapshot_create 生成工况快照(必须带最新 DAQ 值)。',
|
|
79
|
+
'3) twin_trial_run 跑至少 1 个候选的虚拟试验(硬约束拒绝不安全候选;candidateExecuted 必须为 false)。',
|
|
80
|
+
'4) mpc_optimize 在受控量小步范围内搜索推荐设定;',
|
|
81
|
+
'5) twin_gate_evaluate 做门禁与收益评估 —— 若门禁未通过,报告 safe_small_step 结论(这是合法结果,不要伪造通过)。',
|
|
82
|
+
'禁止事项:任何成员不得调用 dcw_control / param_control 写真实设定(本次为 recommendation-only,写控需另行授权)。',
|
|
83
|
+
'交付物(中文,≤ 12 行):① 场景与批次三元组;② 快照关键值;③ 推荐设定(或 safe_small_step 原因);④ 门禁/证书结论;⑤ 一句话说明"未写真实 PLC"这一事实。',
|
|
84
|
+
].join('\n')
|
|
85
|
+
|
|
86
|
+
const task = await j('POST', `/api/workshop/channels/${channelId}/tasks`, {
|
|
87
|
+
title: `闭环优化:注塑克重窗口 recommendation-only ${Date.now().toString(36).slice(-4)}`,
|
|
88
|
+
description: `${SCENARIO}\n\n${TASK}`,
|
|
89
|
+
assigneeId: lead?.id,
|
|
90
|
+
}, token)
|
|
91
|
+
const taskId = task.data?.id
|
|
92
|
+
const taskStartMs = Date.now() - 60_000
|
|
93
|
+
check('闭环优化任务下发到 lead(真实 omp 团队)', Boolean(taskId), `task=${String(taskId).slice(0, 8)} ${task.message ?? ''}`)
|
|
94
|
+
|
|
95
|
+
console.log(' … 等待真实 omp 团队执行(最长 20 分钟)…')
|
|
96
|
+
let final = null
|
|
97
|
+
const deadline = Date.now() + 20 * 60_000
|
|
98
|
+
while (Date.now() < deadline) {
|
|
99
|
+
await sleep(15_000)
|
|
100
|
+
const ts = (await j('GET', `/api/workshop/channels/${channelId}/tasks`, undefined, token)).data ?? []
|
|
101
|
+
const t = ts.find(x => x.id === taskId)
|
|
102
|
+
process.stdout.write(`\r state=${t?.state ?? '?'} progress=${t?.progress ?? 0}% children=${ts.filter(x => x.parentId === taskId).length} `)
|
|
103
|
+
if (t && ['COMPLETED', 'FAILED', 'CANCELED'].includes(t.state)) {
|
|
104
|
+
final = t
|
|
105
|
+
break
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
console.log('')
|
|
109
|
+
check('任务到达终态', Boolean(final), `state=${final?.state ?? 'timeout'}`)
|
|
110
|
+
check('任务 COMPLETED(闭环推演完成)', final?.state === 'COMPLETED', `state=${final?.state}`)
|
|
111
|
+
|
|
112
|
+
const arts = (final?.artifacts ?? []).map(a => (a.parts ?? []).map(p => p.text ?? '').join('\n')).join('\n---\n')
|
|
113
|
+
console.log('\n交付物:\n' + (arts || '(空)').slice(0, 1400))
|
|
114
|
+
|
|
115
|
+
// 工具证据必须落在**磁盘工件**上,而不是频道事件里 grep 工具名:
|
|
116
|
+
// ① 事件流不记 Agent 的工具调用(实测命中为空,旧断言属于误判);
|
|
117
|
+
// ② 交付物里的 snapshot_id/trial_id 可能是模型幻觉,只有工件文件才是硬证据。
|
|
118
|
+
const twinsRoot = `${HOME}/aml/twins`
|
|
119
|
+
const since = taskStartMs
|
|
120
|
+
const newer = (dir) => {
|
|
121
|
+
const p = join(twinsRoot, dir)
|
|
122
|
+
if (!existsSync(p)) return []
|
|
123
|
+
return readdirSync(p, { withFileTypes: true })
|
|
124
|
+
.filter(e => e.isDirectory() && statSync(join(p, e.name)).mtimeMs >= since)
|
|
125
|
+
.map(e => join(p, e.name))
|
|
126
|
+
}
|
|
127
|
+
const snapDirs = newer('snapshots')
|
|
128
|
+
const trialDirs = newer('trials')
|
|
129
|
+
const mpcDirs = newer('mpc')
|
|
130
|
+
check('孪生/MPC 工具链被真实调用(磁盘工件留痕)', snapDirs.length >= 1 && trialDirs.length >= 1, `快照=${snapDirs.length} 试验=${trialDirs.length} MPC=${mpcDirs.length}(${twinsRoot})`)
|
|
131
|
+
|
|
132
|
+
const trialArtifacts = trialDirs.map((d) => {
|
|
133
|
+
try {
|
|
134
|
+
return JSON.parse(readFileSync(join(d, 'trials.json'), 'utf8'))
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return null
|
|
138
|
+
}
|
|
139
|
+
}).filter(Boolean)
|
|
140
|
+
const trialsReal = trialArtifacts.map(a => a.trial).filter(Boolean)
|
|
141
|
+
check('虚拟试验恒为 candidateExecuted=false(recommendation-only)', trialsReal.length >= 1 && trialsReal.every(t => t.candidateExecuted === false), `试验数=${trialsReal.length} candidateExecuted=${[...new Set(trialsReal.map(t => t.candidateExecuted))].join(',')}`)
|
|
142
|
+
const snapIds = snapDirs.map((d) => {
|
|
143
|
+
try {
|
|
144
|
+
return JSON.parse(readFileSync(join(d, 'snapshots.json'), 'utf8'))
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return null
|
|
148
|
+
}
|
|
149
|
+
}).filter(Boolean)
|
|
150
|
+
check('TwinSnapshot 为真实数据(fresh=true 且水位>0)', snapIds.length >= 1 && snapIds.every(s2 => s2.dataQuality?.fresh === true && s2.daqWatermark > 0), snapIds.map(s2 => `${s2.snapshotId} fresh=${s2.dataQuality?.fresh}`).join('; '))
|
|
151
|
+
const certs = trialArtifacts.filter(a => a.certificate).length
|
|
152
|
+
console.log(` · 工件明细:快照 ${snapIds.length} 个 / 试验 ${trialsReal.length} 个 / 推荐证书 ${certs} 张`)
|
|
153
|
+
|
|
154
|
+
const dcwAfter = (await j('GET', '/api/workshop/dcw', undefined, token)).data
|
|
155
|
+
const writesAfter = Number(dcwAfter.controller?.writesTotal ?? 0)
|
|
156
|
+
check('全程零真实 DCW 写入(recommendation-only 生效)', writesAfter === writesBefore, `writes ${writesBefore} → ${writesAfter}`)
|
|
157
|
+
|
|
158
|
+
console.log(`\n★ 阶段 5:${pass} 通过 / ${fails.length} 失败${fails.length ? ` (${fails.join('; ')})` : ''}`)
|
|
159
|
+
console.log(JSON.stringify({ channelId, taskId, state: final?.state }, null, 1))
|