agentworkshop 0.7.47 → 0.7.48
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/6f524f3b-53e1-4df9-84e6-84d8058b9f16.json +1 -0
- package/.output/server/chunks/_/nitro.mjs +870 -858
- package/.output/server/package.json +1 -1
- package/README-zh.md +7 -2
- package/README.md +7 -2
- package/config.yml +1 -1
- package/docs/aml.md +1 -0
- package/docs/cli.en.md +2 -2
- package/docs/cli.md +2 -2
- package/docs/full-test-plan.md +1 -1
- package/docs/plugins.en.md +1 -1
- package/docs/plugins.md +1 -1
- package/docs/sdk.en.md +1 -1
- package/docs/sdk.md +1 -1
- package/package.json +1 -1
- package/scripts/_aw0746-stage12.mjs +4 -4
- package/scripts/_aw0746-stage34.mjs +6 -4
- package/scripts/_aw0746-stage5.mjs +50 -12
- package/scripts/_aw0746-stage6-aml.mjs +3 -3
- package/scripts/_aw0746-stage6b-train-sweep.mjs +6 -3
- package/scripts/_aw0746-stage6c-jobsmoke.mjs +69 -0
- package/scripts/_aw0746-stage7-twin.mjs +16 -8
- package/scripts/_aw0747-readme-rows.mjs +32 -0
- package/scripts/_aw0748-failclosed-probe.mjs +90 -0
- package/server/services/workshop/aml/twin/physics-runtime.ts +16 -1
- package/.output/public/_nuxt/builds/meta/1b18aaf2-ccbc-45e0-b9c3-107655b226f9.json +0 -1
- package/scripts/_probe-density.cjs +0 -15
- package/scripts/_probe-gate-model.mjs +0 -34
- package/scripts/_probe-model-eligibility.cjs +0 -13
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 0.7.48 打包系统验收 · 硬约束失败关闭探针(无需 LLM,秒级)
|
|
3
|
+
* 场景:同一份真实快照 + 同一组候选,只改 constraints[].id
|
|
4
|
+
* ① id = weight(可映射)→ 试验正常给出约束结论
|
|
5
|
+
* ② id = part_weight(不可映射)→ 必须**判不通过**并给出"无法映射"明细(v0.7.47 会假通过)
|
|
6
|
+
* 用法:AW_BASE=http://127.0.0.1:3001 [AW_HOME=<home>] node scripts/_aw0748-failclosed-probe.mjs
|
|
7
|
+
*/
|
|
8
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs'
|
|
9
|
+
|
|
10
|
+
const BASE = process.env.AW_BASE ?? 'http://127.0.0.1:3001'
|
|
11
|
+
const HOME = process.env.AW_HOME ?? `${process.cwd()}/.aw0746-home`
|
|
12
|
+
const ADMIN = { email: process.env.AW_ADMIN_EMAIL ?? 'admin@awshop.local', password: process.env.AW_ADMIN_PASSWORD ?? 'Awshop@123!' }
|
|
13
|
+
|
|
14
|
+
let pass = 0
|
|
15
|
+
const fails = []
|
|
16
|
+
const check = (name, ok, detail = '') => {
|
|
17
|
+
console.log(` ${ok ? 'PASS' : 'FAIL'} ${name}${detail ? ` — ${detail}` : ''}`)
|
|
18
|
+
if (ok) pass += 1
|
|
19
|
+
else fails.push(name)
|
|
20
|
+
}
|
|
21
|
+
const j = async (method, path, body, token) => {
|
|
22
|
+
const r = await fetch(BASE + path, {
|
|
23
|
+
method,
|
|
24
|
+
headers: { 'content-type': 'application/json', ...(token ? { authorization: `Bearer ${token}` } : {}) },
|
|
25
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
26
|
+
signal: AbortSignal.timeout(120_000),
|
|
27
|
+
})
|
|
28
|
+
return { status: r.status, ...(await r.json().catch(() => ({}))) }
|
|
29
|
+
}
|
|
30
|
+
const firstJson = (text) => {
|
|
31
|
+
const a = text.indexOf('{')
|
|
32
|
+
const b = text.lastIndexOf('}')
|
|
33
|
+
return a >= 0 && b > a ? JSON.parse(text.slice(a, b + 1)) : null
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const token = (await j('POST', '/api/users/login', ADMIN)).data?.token
|
|
37
|
+
console.log(`\n═══ 0.7.48 硬约束失败关闭探针 @ ${BASE} ═══`)
|
|
38
|
+
|
|
39
|
+
// 复用最近一次真实快照(阶段 7 / 阶段 5 产物)
|
|
40
|
+
const snapRoot = `${HOME}/aml/twins/snapshots`
|
|
41
|
+
const dirs = existsSync(snapRoot)
|
|
42
|
+
? readdirSync(snapRoot).map(d => ({ d, m: statSync(`${snapRoot}/${d}`).mtimeMs })).sort((a, b) => b.m - a.m)
|
|
43
|
+
: []
|
|
44
|
+
check('存在真实 TwinSnapshot 工件(取最新)', dirs.length > 0, dirs[0]?.d ?? '(无)')
|
|
45
|
+
if (!dirs.length) {
|
|
46
|
+
console.log('\n★ 无可复用快照,先跑阶段 7/5 生成')
|
|
47
|
+
process.exit(2)
|
|
48
|
+
}
|
|
49
|
+
const snapshot = JSON.parse(readFileSync(`${snapRoot}/${dirs[0].d}/snapshots.json`, 'utf8'))
|
|
50
|
+
check('快照新鲜且水位>0(可直接跑试验)', snapshot.dataQuality?.fresh === true && Number(snapshot.daqWatermark) > 0, `fresh=${snapshot.dataQuality?.fresh} watermark=${snapshot.daqWatermark}`)
|
|
51
|
+
|
|
52
|
+
const chans = (await j('GET', '/api/workshop/channels', undefined, token)).data ?? []
|
|
53
|
+
const twin = chans.find(c => /闭环优化-hybridtwin/.test(c.name))
|
|
54
|
+
const members = (await j('GET', `/api/workshop/channels/${twin.id}/agents`, undefined, token)).data ?? []
|
|
55
|
+
const lead = members.find(m => m.role === 'lead')
|
|
56
|
+
const invoke = async (tool, args) => (await j('POST', '/api/workshop/agent-tools/invoke', { agentId: lead.id, tool, args }, token)).data?.result ?? {}
|
|
57
|
+
|
|
58
|
+
const baseScene = firstJson(String((await invoke('twin_scene_read', {})).text ?? ''))
|
|
59
|
+
const controls = { hold_pressure: 65, hold_time: 8, melt_temperature_setpoint: 247 }
|
|
60
|
+
const objective = { schemaVersion: 1, createdAt: new Date().toISOString(), createdBy: lead.id, objectiveId: 'weight-quality', targets: { weight: 32.5 }, weights: { weight: 1 }, controlCosts: {}, horizonSteps: 1, trustRegion: {} }
|
|
61
|
+
const trialOf = async (scene) => {
|
|
62
|
+
const res = await invoke('twin_trial_run', { scene_json: scene, snapshot_json: snapshot, baseline_controls: controls, candidate_controls: [{ ...controls, hold_pressure: 67 }], objective })
|
|
63
|
+
const text = String(res.text ?? '')
|
|
64
|
+
const id = (text.match(/trial_id:\s*(\S+)/) ?? [])[1]
|
|
65
|
+
const artifact = id ? JSON.parse(readFileSync(`${HOME}/aml/twins/trials/${id}/trials.json`, 'utf8')) : null
|
|
66
|
+
return { text, trial: artifact?.trial ?? null }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ① 可映射 id:baseline 预测克重 32.5 g 落在 31~34 内 → 应通过
|
|
70
|
+
const mapped = { ...baseScene, lineId: snapshot.lineId, productId: snapshot.productId, recipeId: snapshot.recipeId, constraints: [{ id: 'weight', kind: 'hard_range', min: 31, max: 34 }] }
|
|
71
|
+
const okTrial = await trialOf(mapped)
|
|
72
|
+
const okWeight = okTrial.trial?.constraintResults?.[0]
|
|
73
|
+
check('可映射约束 id(weight)正常评估', Boolean(okTrial.trial) && okWeight?.id === 'weight' && okWeight.passed === true, `weight=${okTrial.trial?.predictedTrajectory?.[0]?.weight?.toFixed?.(3)} detail=${okWeight?.detail}`)
|
|
74
|
+
|
|
75
|
+
// ② 不可映射 id:必须失败关闭(旧版本这里会返回"全轨迹通过")
|
|
76
|
+
const unmapped = { ...mapped, constraints: [{ id: 'part_weight', kind: 'hard_range', min: 31, max: 34 }] }
|
|
77
|
+
const badTrial = await trialOf(unmapped)
|
|
78
|
+
const badCon = badTrial.trial?.constraintResults?.[0]
|
|
79
|
+
const allPassed = (badTrial.trial?.constraintResults ?? []).every(c => c.passed)
|
|
80
|
+
check('不可映射约束 id 判为不通过(失败关闭)', Boolean(badTrial.trial) && badCon?.passed === false && /无法映射/.test(String(badCon?.detail)), `passed=${badCon?.passed} detail=${String(badCon?.detail).slice(0, 80)}`)
|
|
81
|
+
check('候选不再被当作安全(constraints_passed=false)', allPassed === false, `constraints_passed=${allPassed}`)
|
|
82
|
+
check('工具文本同步反映未通过', /constraints_passed:\s*false/.test(badTrial.text), (badTrial.text.match(/constraints_passed:.*/) ?? [''])[0])
|
|
83
|
+
|
|
84
|
+
// ③ 空轨迹(候选为空)同样失败关闭
|
|
85
|
+
const empty = await invoke('twin_trial_run', { scene_json: mapped, snapshot_json: snapshot, baseline_controls: controls, candidate_controls: [], objective })
|
|
86
|
+
const emptyTrialId = (String(empty.text ?? '').match(/trial_id:\s*(\S+)/) ?? [])[1]
|
|
87
|
+
const emptyTrial = emptyTrialId ? JSON.parse(readFileSync(`${HOME}/aml/twins/trials/${emptyTrialId}/trials.json`, 'utf8')).trial : null
|
|
88
|
+
check('空轨迹判为不通过(不再默认通过)', emptyTrial ? emptyTrial.constraintResults.every(c => c.passed === false) : false, `details=${(emptyTrial?.constraintResults ?? []).map(c => c.detail).join(' | ').slice(0, 80)}`)
|
|
89
|
+
|
|
90
|
+
console.log(`\n★ 0.7.48 探针:${pass} 通过 / ${fails.length} 失败${fails.length ? ` (${fails.join('; ')})` : ''}`)
|
|
@@ -94,14 +94,29 @@ export class InjectionGreyboxProvider implements PhysicsModelProvider {
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
evaluateConstraints(scene: SceneContract, trajectory: PhysicsTrajectory): Array<{ id: string, passed: boolean, detail: string, firstViolationStep?: number }> {
|
|
97
|
+
// 失败关闭(fail-closed):约束 id 必须能映射到轨迹观测量/守卫量。
|
|
98
|
+
// 旧行为对映射不到的 id 直接跳过并返回「全轨迹通过」(value == null 分支),
|
|
99
|
+
// 于是 scene_json 里写成 part_weight / 质量窗口 这类 id 时,硬约束门禁形同虚设
|
|
100
|
+
// —— 实测被真实 Agent 团队两次抓出:预测 weight 30.3 g 明显低于窗口下界仍报通过。
|
|
101
|
+
const known = [...new Set([
|
|
102
|
+
...Object.keys(trajectory.steps.at(-1)?.observations ?? {}),
|
|
103
|
+
...Object.keys(trajectory.steps.at(-1)?.guards ?? {}),
|
|
104
|
+
])]
|
|
105
|
+
if (trajectory.steps.length === 0) {
|
|
106
|
+
return scene.constraints.map(constraint => ({ id: constraint.id, passed: false, detail: '空轨迹:未执行任何校验' }))
|
|
107
|
+
}
|
|
97
108
|
return scene.constraints.map((constraint) => {
|
|
109
|
+
let unmapped = true
|
|
98
110
|
for (let i = 0; i < trajectory.steps.length; i++) {
|
|
99
111
|
const obs = trajectory.steps[i]?.observations ?? {}
|
|
100
112
|
const value = obs[constraint.id] ?? trajectory.steps[i]?.guards[constraint.id]
|
|
101
|
-
if (
|
|
113
|
+
if (value == null) continue
|
|
114
|
+
unmapped = false
|
|
115
|
+
if (constraint.kind === 'hard_range' && ((constraint.min != null && value < constraint.min) || (constraint.max != null && value > constraint.max))) {
|
|
102
116
|
return { id: constraint.id, passed: false, detail: `${constraint.id}=${value} 越过 ${constraint.min ?? '-∞'}~${constraint.max ?? '∞'}`, firstViolationStep: i }
|
|
103
117
|
}
|
|
104
118
|
}
|
|
119
|
+
if (unmapped) return { id: constraint.id, passed: false, detail: `约束 ${constraint.id} 无法映射到观测量/守卫量(可用:${known.join(', ') || '无'}),按失败关闭处理` }
|
|
105
120
|
return { id: constraint.id, passed: true, detail: '全轨迹通过' }
|
|
106
121
|
})
|
|
107
122
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"id":"1b18aaf2-ccbc-45e0-b9c3-107655b226f9","timestamp":1790321904277,"prerendered":[]}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/** 临时:探测 daq_samples 采样密度(评估 AML beatMs 可下调空间) */
|
|
2
|
-
const { Client } = require('pg')
|
|
3
|
-
;(async () => {
|
|
4
|
-
const c = new Client({ host: '127.0.0.1', port: 5432, user: 'postgres', password: 'awshop', database: 'awshop' })
|
|
5
|
-
await c.connect()
|
|
6
|
-
const q = (sql) => c.query(sql).then(r => r.rows)
|
|
7
|
-
const rows = await q(`select node_id, count(*)::int n, min(ts) a, max(ts) b,
|
|
8
|
-
round(extract(epoch from (max(ts)-min(ts)))/greatest(count(*)-1,1),2) avg_gap_s
|
|
9
|
-
from daq_samples where ts > now() - interval '45 minutes'
|
|
10
|
-
group by node_id order by n desc limit 12`)
|
|
11
|
-
console.table(rows)
|
|
12
|
-
const t = await q(`select count(*)::int total, count(distinct node_id)::int nodes from daq_samples where ts > now() - interval '45 minutes'`)
|
|
13
|
-
console.log('total', t)
|
|
14
|
-
await c.end()
|
|
15
|
-
})().catch(e => { console.error(e.message); process.exit(1) })
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/** 临时:看 twin_gate_evaluate 的 modelUpdate 尾部 + 最近作业来源 */
|
|
2
|
-
import { DatabaseSync } from 'node:sqlite'
|
|
3
|
-
const BASE = 'http://127.0.0.1:3001'
|
|
4
|
-
const ADMIN = { email: 'admin@awshop.local', password: 'Awshop@123!' }
|
|
5
|
-
|
|
6
|
-
const j = async (method, path, body, token) => {
|
|
7
|
-
const r = await fetch(BASE + path, { method, headers: { 'content-type': 'application/json', ...(token ? { authorization: `Bearer ${token}` } : {}) }, body: body === undefined ? undefined : JSON.stringify(body) })
|
|
8
|
-
return { status: r.status, ...(await r.json().catch(() => ({}))) }
|
|
9
|
-
}
|
|
10
|
-
const token = (await j('POST', '/api/users/login', ADMIN)).data?.token
|
|
11
|
-
const chans = (await j('GET', '/api/workshop/channels', undefined, token)).data ?? []
|
|
12
|
-
const twin = chans.find(c => /闭环优化-hybridtwin/.test(c.name))
|
|
13
|
-
const members = (await j('GET', `/api/workshop/channels/${twin.id}/agents`, undefined, token)).data ?? []
|
|
14
|
-
const lead = members.find(m => m.role === 'lead')
|
|
15
|
-
const models = ((await j('GET', '/api/workshop/aml/models', undefined, token)).data?.models ?? [])
|
|
16
|
-
const prod = models.find(m => m.stage === 'production')
|
|
17
|
-
console.log('production model =', prod?.id, 'ds =', prod?.datasetId)
|
|
18
|
-
|
|
19
|
-
const res = await j('POST', '/api/workshop/agent-tools/invoke', {
|
|
20
|
-
agentId: lead.id,
|
|
21
|
-
tool: 'twin_gate_evaluate',
|
|
22
|
-
args: { rows: 1724, runs: 10, one_step_nrmse: 0.0763, rollout_nrmse: 0.0964, val_test_gap: 0.1072, calibration_rows: 1724, coverage: 1, candidate_trials: [], physics_failure_rate: 0, model_id: prod?.id ?? '' },
|
|
23
|
-
}, token)
|
|
24
|
-
const text = String(res.data?.result?.text ?? '')
|
|
25
|
-
const tail = text.slice(-700)
|
|
26
|
-
console.log('--- gate tail ---')
|
|
27
|
-
console.log(tail)
|
|
28
|
-
console.log('--- parsed keys ---', Object.keys(JSON.parse(text)).join(','))
|
|
29
|
-
|
|
30
|
-
const db = new DatabaseSync('D:/codes/ABO/AgentWorkShop/.aw0746-home/data/workshop.sqlite', { readOnly: true })
|
|
31
|
-
const cols = db.prepare('pragma table_info(aml_jobs)').all().map(c => c.name)
|
|
32
|
-
console.log('\naml_jobs columns:', cols.join(','))
|
|
33
|
-
const pick = ['id', 'status', 'kind', 'created_by', 'change_note', 'created_at'].filter(c => cols.includes(c))
|
|
34
|
-
for (const r of db.prepare(`select ${pick.join(',')} from aml_jobs order by created_at desc limit 6`).all()) console.log(' ', JSON.stringify(r).slice(0, 220))
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/** 临时:检查 AML 模型 metrics_json 里的 twinEligibility 是否回写 */
|
|
2
|
-
const { DatabaseSync } = require('node:sqlite')
|
|
3
|
-
const db = new DatabaseSync('D:/codes/ABO/AgentWorkShop/.aw0746-home/data/workshop.sqlite', { readOnly: true })
|
|
4
|
-
const rows = db.prepare('select id, stage, dataset_id, experiment_id, metrics_json from aml_models order by created_at desc limit 4').all()
|
|
5
|
-
for (const r of rows) {
|
|
6
|
-
let m = {}
|
|
7
|
-
try { m = JSON.parse(r.metrics_json || '{}') } catch { m = { parse: 'fail' } }
|
|
8
|
-
console.log(`${r.id} stage=${r.stage} ds=${r.dataset_id} exp=${r.experiment_id}`)
|
|
9
|
-
console.log(` twinEligibility=${JSON.stringify(m.twinEligibility ?? null)} oneStep=${m.oneStepTest?.nrmse}`)
|
|
10
|
-
}
|
|
11
|
-
const jobs = db.prepare('select id, status, created_by_kind, created_by, substr(change_note,1,60) note, created_at from aml_jobs order by created_at desc limit 6').all()
|
|
12
|
-
console.log('\njobs:')
|
|
13
|
-
for (const j of jobs) console.log(` ${j.id} ${j.status} by=${j.created_by_kind}/${j.created_by} ${j.created_at} ${j.note}`)
|