agentworkshop 0.3.0 → 0.4.1

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.
@@ -0,0 +1,32 @@
1
+ /**
2
+ * 文档站截图走查 — vitepress preview(127.0.0.1:4477)+ Edge + puppeteer-core。
3
+ * 用法:node scripts/_dbg-docs-site-shot.mjs [输出目录]
4
+ */
5
+ import puppeteer from 'puppeteer-core'
6
+ import fs from 'node:fs'
7
+
8
+ const EDGE = 'C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe'
9
+ const BASE = 'http://127.0.0.1:4477/AgentWorkShop'
10
+ const OUT = process.argv[2] ?? 'gui-test-screenshots/docs-site-v5'
11
+
12
+ const main = async () => {
13
+ fs.mkdirSync(OUT, { recursive: true })
14
+ const browser = await puppeteer.launch({ executablePath: EDGE, headless: 'new' })
15
+ const shoot = async (name, path, { width = 1440, fullPage = true, height = 900 } = {}) => {
16
+ const page = await browser.newPage()
17
+ await page.setViewport({ width, height, deviceScaleFactor: 1 })
18
+ await page.goto(`${BASE}${path}`, { waitUntil: 'networkidle0', timeout: 30000 })
19
+ await new Promise(r => setTimeout(r, 600))
20
+ await page.screenshot({ path: `${OUT}/${name}.png`, fullPage })
21
+ console.log('shot', name)
22
+ await page.close()
23
+ }
24
+ await shoot('home-top', '/', { fullPage: false })
25
+ await shoot('home', '/', {})
26
+ await shoot('guide-getting-started', '/guide/getting-started', {})
27
+ await shoot('guide-top', '/guide/getting-started', { fullPage: false })
28
+ await shoot('license', '/guide/license', {})
29
+ await browser.close()
30
+ }
31
+
32
+ main().catch(e => { console.error(e); process.exit(1) })
@@ -7,7 +7,7 @@
7
7
  // 原则:幂等(已存在绝不覆盖)、零依赖、任何失败只告警不阻断安装。
8
8
  // ============================================================
9
9
  import { randomBytes } from 'node:crypto'
10
- import { copyFileSync, existsSync, mkdirSync, writeFileSync } from 'node:fs'
10
+ import { copyFileSync, cpSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs'
11
11
  import { homedir } from 'node:os'
12
12
  import { dirname, join, resolve } from 'node:path'
13
13
  import { fileURLToPath } from 'node:url'
@@ -121,6 +121,31 @@ export function runBootstrap({ quiet = false, env = process.env } = {}) {
121
121
  seeds.push('plugins/README.md')
122
122
  }
123
123
 
124
+ // 8. 官方示例插件随包分发(sdk/examples/*):只复制目标缺失的目录,绝不覆盖用户改动;
125
+ // 新种子的示例默认**停用**(写入 plugins-state.json),经 aw plugin enable 开启
126
+ const examplesSrc = join(packageRoot, 'sdk', 'examples')
127
+ if (existsSync(examplesSrc)) {
128
+ const pluginsDir2 = join(home, 'plugins')
129
+ const stateFile = join(home, 'plugins-state.json')
130
+ let state = { version: 1, updatedAt: new Date().toISOString(), disabled: [] }
131
+ try {
132
+ state = JSON.parse(readFileSync(stateFile, 'utf8'))
133
+ }
134
+ catch { /* 首次无状态文件 */ }
135
+ state.disabled ??= []
136
+ for (const example of readdirSync(examplesSrc)) {
137
+ const dest = join(pluginsDir2, example)
138
+ if (existsSync(join(dest, 'index.mjs'))) continue // 已存在(或用户改过)——不动
139
+ cpSync(join(examplesSrc, example), dest, { recursive: true })
140
+ if (!state.disabled.includes(example)) state.disabled.push(example) // 默认停用
141
+ seeds.push(`plugins/${example}(默认停用)`)
142
+ }
143
+ state.updatedAt = new Date().toISOString()
144
+ const stateTmp = `${stateFile}.${process.pid}.tmp`
145
+ writeFileSync(stateTmp, `${JSON.stringify(state, null, 2)}\n`, 'utf8')
146
+ renameSync(stateTmp, stateFile)
147
+ }
148
+
124
149
  log(`[aw-home] ${home} ${created.length ? `(新建 ${created.length} 项)` : '(已就绪)'}`)
125
150
  if (seeds.length) log(`[aw-home] 种子文件: ${seeds.join(', ')}`)
126
151
  return { home, created, seeds }
package/sdk/client.mjs CHANGED
@@ -47,10 +47,14 @@ export function createClientContext({ name, eventBridge, baseUrl = '' }) {
47
47
  disposables.push(off)
48
48
  return off
49
49
  },
50
- /** 同源平台 API(JSON;自动解 {data} 信封;非 2xx 抛错) */
50
+ /** 同源平台 API(JSON;自动解 {data} 信封;非 2xx 抛错;自动携带 cookie token) */
51
51
  fetch: async (path, opt = {}) => {
52
+ const headers = { accept: 'application/json', ...(opt.body !== undefined ? { 'content-type': 'application/json' } : {}), ...(opt.headers ?? {}) }
53
+ // 自动注入 cookie 里的平台 token(Authorization: Bearer)——与全站 $http 拦截器同源
54
+ const mCookie = document.cookie.match(/(?:^|;\s*)token=([^;]+)/)
55
+ if (mCookie) headers.authorization = `Bearer ${decodeURIComponent(mCookie[1])}`
52
56
  const res = await fetch(`${baseUrl}${path}`, {
53
- headers: { accept: 'application/json', ...(opt.body !== undefined ? { 'content-type': 'application/json' } : {}), ...(opt.headers ?? {}) },
57
+ headers,
54
58
  method: opt.method ?? (opt.body !== undefined ? 'POST' : 'GET'),
55
59
  body: opt.body !== undefined ? JSON.stringify(opt.body) : undefined,
56
60
  })
@@ -0,0 +1,31 @@
1
+ // line-sentinel — 客户端徽标(实时显示采样计数与告警态)
2
+ export function setup(ctx) {
3
+ const badge = ctx.el('div', {
4
+ id: 'line-sentinel-badge',
5
+ style: 'display:flex;align-items:center;gap:8px;padding:8px 12px;'
6
+ + 'border:1px solid rgba(53,224,160,.5);border-radius:10px;'
7
+ + 'background:rgba(6,18,14,.85);color:#35e0a0;'
8
+ + 'font:600 12px/1 ui-monospace,monospace;letter-spacing:.4px;'
9
+ + 'box-shadow:0 4px 18px rgba(0,0,0,.35);cursor:default',
10
+ }, ['🛡 line-sentinel · 待机'])
11
+
12
+ ctx.root().append(badge)
13
+
14
+ let n = 0
15
+ let alarms = 0
16
+
17
+ ctx.on('daq:sample', () => {
18
+ n++
19
+ badge.textContent = `🛡 line-sentinel · ${n} 样本 · ${alarms} 告警`
20
+ })
21
+
22
+ // 服务端告警状态变化经 event 桥可见(ops.log 或轮询 stats;此处演示事件订阅)
23
+ ctx.on('event:line.start', () => {
24
+ badge.style.borderColor = '#35e0a0'
25
+ })
26
+ ctx.on('event:line.stop', () => {
27
+ badge.style.borderColor = 'rgba(53,224,160,.35)'
28
+ })
29
+
30
+ ctx.log.info('哨兵徽标已挂载(右下角)')
31
+ }
@@ -0,0 +1,102 @@
1
+ /**
2
+ * line-sentinel — 产线哨兵(真实场景插件)
3
+ * ------------------------------------------------------------
4
+ * 职责:持续监视运行中产线的数采样本,越过告警阈值即记录并在 API 暴露;
5
+ * 心跳定时器自证存活;监听产线启停与配置变更;展示 SDK 全部能力面。
6
+ *
7
+ * 使用 SDK 面:ctx.hooks · ctx.api(平台客户端) · ctx.timer(自动回收)
8
+ * · ctx.onDispose · ctx.kv · ctx.route · ctx.config.onChange
9
+ * · ctx.events · ctx.logger
10
+ */
11
+ export default {
12
+ name: 'line-sentinel',
13
+ version: '1.0.0',
14
+ description: '产线哨兵:数采越限告警 + 心跳 + 产线生命周期跟踪',
15
+ client: './client.mjs',
16
+
17
+ async setup(ctx) {
18
+ ctx.logger.info(`装载完成(scope=${ctx.scope}, sdk=${ctx.sdkVersion})`)
19
+
20
+ // 平台服务消费:启动时经 SDK API 客户端读取产线清单(自环调用)
21
+ try {
22
+ const lines = await ctx.api.lines.list()
23
+ ctx.kv.set('linesTotal', Array.isArray(lines) ? lines.length : 0)
24
+ ctx.logger.info(`平台产线清单: ${ctx.kv.get('linesTotal')} 条`)
25
+ }
26
+ catch (err) {
27
+ ctx.logger.warn('产线清单读取失败(服务启动中,跳过):', err?.message)
28
+ }
29
+
30
+ // 配置变更订阅(aw config set / 设置页写入 → runtime-settings.json 变化)
31
+ ctx.config.onChange(() => {
32
+ const theme = ctx.config.get('theme.primaryColor')
33
+ ctx.logger.info(`配置已变更,当前主题色: ${theme}`)
34
+ ctx.kv.set('lastConfigAt', new Date().toISOString())
35
+ })
36
+
37
+ // 产线生命周期跟踪(事件消费增强:运行中产线计数)
38
+ ctx.hooks.on('line:start', (p) => {
39
+ ctx.kv.set('running', true)
40
+ ctx.kv.bump('runningCount')
41
+ ctx.kv.set('lastRun', { lineId: p?.lineId, at: new Date().toISOString() })
42
+ ctx.logger.info(`▶ 产线开跑 ${p?.lineId}`)
43
+ })
44
+ ctx.hooks.on('line:stop', () => {
45
+ ctx.kv.set('running', false)
46
+ ctx.logger.info('■ 产线停止')
47
+ })
48
+
49
+ // 核心:数采样本越限告警(阈值可经插件 KV 配置,默认 180)
50
+ ctx.hooks.on('daq:sample', (s) => {
51
+ ctx.kv.bump('samples')
52
+ const threshold = Number(ctx.kv.get('threshold')) || 180
53
+ if (typeof s?.value === 'number' && s.value > threshold) {
54
+ const key = `alarm:${s.nodeId}`
55
+ const prev = ctx.kv.get(key) ?? { count: 0 }
56
+ ctx.kv.set(key, { count: (prev.count ?? 0) + 1, value: s.value, at: s.at })
57
+ if ((prev.count ?? 0) === 0) ctx.logger.warn(`⚠ 越限告警 ${s.nodeId}: ${s.value} > ${threshold}`)
58
+ }
59
+ })
60
+
61
+ // scene 实时事件订阅(糖衣)
62
+ ctx.events.on('daq.node.changed', (p) => {
63
+ ctx.logger.debug?.('节点变更', p?.op ?? '')
64
+ })
65
+
66
+ // 心跳定时器(服务关闭自动回收):活性自证经免鉴权 manifest ping 验证 REST 自环通道
67
+ ctx.timer.setInterval(() => {
68
+ ctx.kv.set('heartbeat', new Date().toISOString())
69
+ ctx.api.plugins.manifest()
70
+ .then(() => ctx.kv.set('apiOk', true))
71
+ .catch(() => ctx.kv.set('apiOk', false))
72
+ }, 5000)
73
+
74
+ // 清理登记(关停时宿主逐个调用)
75
+ ctx.onDispose(() => ctx.logger.info('哨兵清理:告警状态已随 KV 落盘'))
76
+
77
+ // 插件 API:综合报告
78
+ ctx.route('GET', '/report', () => {
79
+ const alarms = Object.entries(ctx.kv.all())
80
+ .filter(([k]) => k.startsWith('alarm:'))
81
+ .map(([k, v]) => ({ nodeId: k.slice(6), ...v }))
82
+ return {
83
+ plugin: ctx.name,
84
+ version: ctx.version,
85
+ running: ctx.kv.get('running') ?? false,
86
+ runningCount: ctx.kv.get('runningCount') ?? 0,
87
+ heartbeat: ctx.kv.get('heartbeat'),
88
+ apiChannel: ctx.kv.get('apiOk') === true ? 'ok' : ctx.kv.get('apiOk') === false ? 'down' : 'pending',
89
+ samplesWatched: ctx.kv.get('samples') ?? 0,
90
+ threshold: Number(ctx.kv.get('threshold')) || 180,
91
+ alarms,
92
+ }
93
+ })
94
+
95
+ ctx.route('POST', '/threshold', (event) => {
96
+ // 宿主 catchall 已预读 body 挂在 event.awBody
97
+ const v = Number(event.awBody?.threshold) || 180
98
+ ctx.kv.set('threshold', v)
99
+ return { ok: true, threshold: v }
100
+ })
101
+ },
102
+ }
@@ -0,0 +1,23 @@
1
+ // ops-notifier — 客户端通知(最近一次写控 toast)
2
+ export function setup(ctx) {
3
+ const toast = ctx.el('div', {
4
+ style: 'display:none;padding:10px 14px;border:1px solid rgba(244,197,66,.6);'
5
+ + 'border-radius:10px;background:rgba(20,16,4,.88);color:#f4c542;'
6
+ + 'font:600 12px/1.5 ui-monospace,monospace;box-shadow:0 4px 18px rgba(0,0,0,.35)',
7
+ })
8
+ ctx.root().prepend(toast)
9
+
10
+ let timer = null
11
+ ctx.on('dcw:write', (w) => {
12
+ toast.textContent = w.ok
13
+ ? `✔ 写入 ${w.name} → ${w.eng}${w.source ? `(${w.source})` : ''}`
14
+ : `✖ 写入失败 ${w.name}`
15
+ toast.style.display = 'block'
16
+ clearTimeout(timer)
17
+ timer = setTimeout(() => {
18
+ toast.style.display = 'none'
19
+ }, 4000)
20
+ })
21
+
22
+ ctx.log.info('写控通知已就绪')
23
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * ops-notifier — 写控与产线事件回声(SDK 示例插件)
3
+ * 滚动记录最近写控/启停事件(内存 + KV 防抖落盘),提供 /recent 查询 API;
4
+ * 客户端以 toast 展示最近一次写控。展示 dcw:write / line:* 钩子与事件消费。
5
+ */
6
+ const ROLL = 30
7
+
8
+ export default {
9
+ name: 'ops-notifier',
10
+ version: '1.0.0',
11
+ description: '写控与产线事件回声:滚动记录最近事件 + 查询 API + 客户端通知',
12
+ client: './client.mjs',
13
+
14
+ async setup(ctx) {
15
+ ctx.logger.info('已装载 —— 监听 dcw:write 与产线启停')
16
+
17
+ const push = (entry) => {
18
+ const log = ctx.kv.get('recent') ?? []
19
+ log.unshift(entry)
20
+ ctx.kv.set('recent', log.slice(0, ROLL))
21
+ }
22
+
23
+ ctx.hooks.on('dcw:write', (w) => {
24
+ push({ kind: 'write', nodeId: w.nodeId, name: w.name, eng: w.eng, ok: w.ok, source: w.source, at: w.at })
25
+ })
26
+ ctx.hooks.on('line:start', p => push({ kind: 'line:start', lineId: p?.lineId, at: new Date().toISOString() }))
27
+ ctx.hooks.on('line:stop', p => push({ kind: 'line:stop', lineId: p?.lineId, at: new Date().toISOString() }))
28
+
29
+ ctx.route('GET', '/recent', () => ({
30
+ plugin: ctx.name,
31
+ recent: ctx.kv.get('recent') ?? [],
32
+ }))
33
+ },
34
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * POST /api/workshop/plugins/:name/disable —— 停用插件(admin;热重载生效)。
3
+ */
4
+ import { defineEventHandler, createError } from 'h3'
5
+ import { resolveUser, requireAdmin } from '@/server/api/workshop/caller'
6
+ import { pluginManifest, reloadPluginHost, setPluginEnabled } from '@/server/services/workshop/plugins/host.mjs'
7
+
8
+ export default defineEventHandler(async (event) => {
9
+ const user = resolveUser(event)
10
+ requireAdmin(event)
11
+ const name = String(event.context.params?.name ?? '')
12
+ if (!pluginManifest().some(p => p.name === name)) {
13
+ throw createError({ statusCode: 404, statusMessage: `plugin not found: ${name}` })
14
+ }
15
+ setPluginEnabled(name, false)
16
+ await reloadPluginHost()
17
+ const p = pluginManifest().find(x => x.name === name)
18
+ return { ok: true, enabled: p?.enabled ?? false, by: user.name }
19
+ })
@@ -0,0 +1,19 @@
1
+ /**
2
+ * POST /api/workshop/plugins/:name/enable —— 启用插件(admin;热重载生效)。
3
+ */
4
+ import { defineEventHandler, createError } from 'h3'
5
+ import { resolveUser, requireAdmin } from '@/server/api/workshop/caller'
6
+ import { pluginManifest, reloadPluginHost, setPluginEnabled } from '@/server/services/workshop/plugins/host.mjs'
7
+
8
+ export default defineEventHandler(async (event) => {
9
+ const user = resolveUser(event)
10
+ requireAdmin(event)
11
+ const name = String(event.context.params?.name ?? '')
12
+ if (!pluginManifest().some(p => p.name === name)) {
13
+ throw createError({ statusCode: 404, statusMessage: `plugin not found: ${name}` })
14
+ }
15
+ setPluginEnabled(name, true)
16
+ await reloadPluginHost()
17
+ const p = pluginManifest().find(x => x.name === name)
18
+ return { ok: true, enabled: p?.enabled ?? true, by: user.name }
19
+ })
@@ -0,0 +1,14 @@
1
+ /**
2
+ * GET /api/workshop/plugins/:name —— 单插件详情(鉴权)。
3
+ */
4
+ import { defineEventHandler, createError } from 'h3'
5
+ import { resolveUser } from '@/server/api/workshop/caller'
6
+ import { pluginManifest } from '@/server/services/workshop/plugins/host.mjs'
7
+
8
+ export default defineEventHandler((event) => {
9
+ resolveUser(event)
10
+ const name = String(event.context.params?.name ?? '')
11
+ const p = pluginManifest().find(x => x.name === name)
12
+ if (!p) throw createError({ statusCode: 404, statusMessage: `plugin not found: ${name}` })
13
+ return p
14
+ })
@@ -0,0 +1,17 @@
1
+ /**
2
+ * GET /api/workshop/plugins —— 插件管理清单(鉴权;含启停状态/路由/装载失败)。
3
+ * 网页「插件管理」页数据源。
4
+ */
5
+ import { defineEventHandler } from 'h3'
6
+ import { resolveUser } from '@/server/api/workshop/caller'
7
+ import { getPluginHost, pluginManifest } from '@/server/services/workshop/plugins/host.mjs'
8
+
9
+ export default defineEventHandler((event) => {
10
+ resolveUser(event)
11
+ const host = getPluginHost()
12
+ return {
13
+ plugins: pluginManifest(),
14
+ failures: host?.failures ?? [],
15
+ initedAt: host?.initedAt ?? null,
16
+ }
17
+ })
@@ -64,9 +64,9 @@
64
64
  "posX": 1695,
65
65
  "posZ": 1135,
66
66
  "lineId": "ln-af002514",
67
- "value": 167.8,
67
+ "value": 168.1,
68
68
  "state": "ok",
69
- "lastAt": "2026-09-02T04:20:59.479Z",
69
+ "lastAt": "2026-09-02T05:37:50.665Z",
70
70
  "createdAt": "2026-08-27T12:39:08.110Z"
71
71
  },
72
72
  {
@@ -308,9 +308,9 @@
308
308
  "deviceBindingId": null,
309
309
  "driverConfig": {},
310
310
  "lineId": "ln-af002514",
311
- "value": 315,
311
+ "value": 320,
312
312
  "state": "ok",
313
- "lastAt": "2026-09-02T04:20:59.479Z",
313
+ "lastAt": "2026-09-02T05:37:50.665Z",
314
314
  "createdAt": "2026-08-28T09:37:38.518Z"
315
315
  },
316
316
  {
@@ -330,9 +330,9 @@
330
330
  "deviceBindingId": null,
331
331
  "driverConfig": {},
332
332
  "lineId": "ln-af002514",
333
- "value": 167.4,
333
+ "value": 169.8,
334
334
  "state": "ok",
335
- "lastAt": "2026-09-02T04:20:59.479Z",
335
+ "lastAt": "2026-09-02T05:37:50.665Z",
336
336
  "createdAt": "2026-08-29T03:30:57.463Z"
337
337
  },
338
338
  {
@@ -1154,9 +1154,9 @@
1154
1154
  "deviceBindingId": null,
1155
1155
  "driverConfig": {},
1156
1156
  "lineId": "ln-29c185e7",
1157
- "value": 170,
1157
+ "value": 168.8,
1158
1158
  "state": "ok",
1159
- "lastAt": "2026-09-02T04:20:59.479Z",
1159
+ "lastAt": "2026-09-02T05:37:50.665Z",
1160
1160
  "createdAt": "2026-08-31T16:20:13.689Z"
1161
1161
  },
1162
1162
  {
@@ -1176,9 +1176,9 @@
1176
1176
  "deviceBindingId": "dev-mti1f14p-3uyp2",
1177
1177
  "driverConfig": {},
1178
1178
  "lineId": "ln-29c185e7",
1179
- "value": 0.79,
1179
+ "value": 0.81,
1180
1180
  "state": "ok",
1181
- "lastAt": "2026-09-02T04:20:59.479Z",
1181
+ "lastAt": "2026-09-02T05:37:50.665Z",
1182
1182
  "createdAt": "2026-08-31T16:20:13.693Z"
1183
1183
  },
1184
1184
  {
@@ -1244,9 +1244,9 @@
1244
1244
  "deviceBindingId": null,
1245
1245
  "driverConfig": {},
1246
1246
  "lineId": "ln-c377a6bf",
1247
- "value": 169.5,
1247
+ "value": 169.8,
1248
1248
  "state": "alarm",
1249
- "lastAt": "2026-09-02T04:20:59.479Z",
1249
+ "lastAt": "2026-09-02T05:37:50.665Z",
1250
1250
  "createdAt": "2026-09-01T03:23:18.165Z"
1251
1251
  },
1252
1252
  {
@@ -1504,9 +1504,9 @@
1504
1504
  "jsonPath": "data.value"
1505
1505
  },
1506
1506
  "lineId": "ln-9224d3d7",
1507
- "value": 42.3,
1507
+ "value": 42.2,
1508
1508
  "state": "alarm",
1509
- "lastAt": "2026-09-02T04:20:59.479Z",
1509
+ "lastAt": "2026-09-02T05:37:50.665Z",
1510
1510
  "createdAt": "2026-09-01T05:59:24.282Z"
1511
1511
  },
1512
1512
  {
@@ -1584,9 +1584,9 @@
1584
1584
  "jsonPath": "data.value"
1585
1585
  },
1586
1586
  "lineId": "ln-386c53ed",
1587
- "value": 42.8,
1587
+ "value": 42.3,
1588
1588
  "state": "alarm",
1589
- "lastAt": "2026-09-02T04:20:59.479Z",
1589
+ "lastAt": "2026-09-02T05:37:50.665Z",
1590
1590
  "createdAt": "2026-09-01T06:05:53.225Z"
1591
1591
  },
1592
1592
  {
@@ -1664,9 +1664,9 @@
1664
1664
  "jsonPath": "data.value"
1665
1665
  },
1666
1666
  "lineId": "ln-5957f6db",
1667
- "value": 42.6,
1667
+ "value": 42.5,
1668
1668
  "state": "alarm",
1669
- "lastAt": "2026-09-02T04:20:59.479Z",
1669
+ "lastAt": "2026-09-02T05:37:50.665Z",
1670
1670
  "createdAt": "2026-09-01T06:09:02.277Z"
1671
1671
  },
1672
1672
  {
@@ -1746,7 +1746,7 @@
1746
1746
  "lineId": "ln-9ec42f08",
1747
1747
  "value": 42.1,
1748
1748
  "state": "alarm",
1749
- "lastAt": "2026-09-02T04:20:59.479Z",
1749
+ "lastAt": "2026-09-02T05:37:50.665Z",
1750
1750
  "createdAt": "2026-09-01T06:13:44.498Z"
1751
1751
  },
1752
1752
  {
@@ -1824,9 +1824,9 @@
1824
1824
  "jsonPath": "data.value"
1825
1825
  },
1826
1826
  "lineId": "ln-69e37924",
1827
- "value": 42.6,
1827
+ "value": 42.3,
1828
1828
  "state": "alarm",
1829
- "lastAt": "2026-09-02T04:20:59.479Z",
1829
+ "lastAt": "2026-09-02T05:37:50.665Z",
1830
1830
  "createdAt": "2026-09-01T06:16:47.597Z"
1831
1831
  },
1832
1832
  {
@@ -1904,9 +1904,9 @@
1904
1904
  "jsonPath": "data.value"
1905
1905
  },
1906
1906
  "lineId": "ln-c269e692",
1907
- "value": 42.3,
1907
+ "value": 42.5,
1908
1908
  "state": "alarm",
1909
- "lastAt": "2026-09-02T04:20:59.479Z",
1909
+ "lastAt": "2026-09-02T05:37:50.665Z",
1910
1910
  "createdAt": "2026-09-01T06:56:06.588Z"
1911
1911
  },
1912
1912
  {
@@ -1984,9 +1984,9 @@
1984
1984
  "jsonPath": "data.value"
1985
1985
  },
1986
1986
  "lineId": "ln-229d1637",
1987
- "value": 42.2,
1987
+ "value": 42.7,
1988
1988
  "state": "alarm",
1989
- "lastAt": "2026-09-02T04:20:59.479Z",
1989
+ "lastAt": "2026-09-02T05:37:50.665Z",
1990
1990
  "createdAt": "2026-09-01T06:57:29.458Z"
1991
1991
  },
1992
1992
  {
@@ -2064,9 +2064,9 @@
2064
2064
  "jsonPath": "data.value"
2065
2065
  },
2066
2066
  "lineId": "ln-9546f91f",
2067
- "value": 42.7,
2067
+ "value": 42.1,
2068
2068
  "state": "alarm",
2069
- "lastAt": "2026-09-02T04:20:59.479Z",
2069
+ "lastAt": "2026-09-02T05:37:50.665Z",
2070
2070
  "createdAt": "2026-09-01T07:00:25.267Z"
2071
2071
  },
2072
2072
  {
@@ -2146,7 +2146,7 @@
2146
2146
  "lineId": "ln-087aad5b",
2147
2147
  "value": 42.3,
2148
2148
  "state": "alarm",
2149
- "lastAt": "2026-09-02T04:20:59.479Z",
2149
+ "lastAt": "2026-09-02T05:37:50.665Z",
2150
2150
  "createdAt": "2026-09-01T07:09:07.476Z"
2151
2151
  },
2152
2152
  {
@@ -2252,9 +2252,9 @@
2252
2252
  "jsonPath": "data.value"
2253
2253
  },
2254
2254
  "lineId": "ln-97d44f8a",
2255
- "value": 42.4,
2255
+ "value": 42.5,
2256
2256
  "state": "alarm",
2257
- "lastAt": "2026-09-02T04:20:59.479Z",
2257
+ "lastAt": "2026-09-02T05:37:50.665Z",
2258
2258
  "createdAt": "2026-09-01T07:13:45.651Z"
2259
2259
  },
2260
2260
  {
@@ -2332,9 +2332,9 @@
2332
2332
  "jsonPath": "data.value"
2333
2333
  },
2334
2334
  "lineId": "ln-a9551770",
2335
- "value": 42,
2335
+ "value": 42.9,
2336
2336
  "state": "alarm",
2337
- "lastAt": "2026-09-02T04:20:59.479Z",
2337
+ "lastAt": "2026-09-02T05:37:50.665Z",
2338
2338
  "createdAt": "2026-09-01T07:15:40.282Z"
2339
2339
  },
2340
2340
  {
@@ -2598,9 +2598,9 @@
2598
2598
  "jsonPath": "data.value"
2599
2599
  },
2600
2600
  "lineId": "ln-844ad82f",
2601
- "value": 42.3,
2601
+ "value": 42.1,
2602
2602
  "state": "alarm",
2603
- "lastAt": "2026-09-02T04:20:59.479Z",
2603
+ "lastAt": "2026-09-02T05:37:50.665Z",
2604
2604
  "createdAt": "2026-09-01T09:06:55.906Z"
2605
2605
  },
2606
2606
  {
@@ -2848,9 +2848,9 @@
2848
2848
  "deviceBindingId": null,
2849
2849
  "driverConfig": {},
2850
2850
  "lineId": "ln-0eccf6ba",
2851
- "value": 169.3,
2851
+ "value": 170.7,
2852
2852
  "state": "ok",
2853
- "lastAt": "2026-09-02T04:20:59.479Z",
2853
+ "lastAt": "2026-09-02T05:37:50.665Z",
2854
2854
  "createdAt": "2026-09-02T04:09:11.452Z"
2855
2855
  },
2856
2856
  {
@@ -2870,9 +2870,9 @@
2870
2870
  "deviceBindingId": null,
2871
2871
  "driverConfig": {},
2872
2872
  "lineId": "ln-e984246e",
2873
- "value": 166.2,
2873
+ "value": 167.6,
2874
2874
  "state": "ok",
2875
- "lastAt": "2026-09-02T04:20:59.479Z",
2875
+ "lastAt": "2026-09-02T05:37:50.665Z",
2876
2876
  "createdAt": "2026-09-02T04:11:26.541Z"
2877
2877
  },
2878
2878
  {
@@ -2892,9 +2892,9 @@
2892
2892
  "deviceBindingId": null,
2893
2893
  "driverConfig": {},
2894
2894
  "lineId": "ln-1fe0b5bc",
2895
- "value": 168.8,
2895
+ "value": 167.3,
2896
2896
  "state": "ok",
2897
- "lastAt": "2026-09-02T04:20:59.479Z",
2897
+ "lastAt": "2026-09-02T05:37:50.665Z",
2898
2898
  "createdAt": "2026-09-02T04:13:17.861Z"
2899
2899
  },
2900
2900
  {
@@ -2914,9 +2914,9 @@
2914
2914
  "deviceBindingId": null,
2915
2915
  "driverConfig": {},
2916
2916
  "lineId": "ln-d826473e",
2917
- "value": 169,
2917
+ "value": 166.5,
2918
2918
  "state": "ok",
2919
- "lastAt": "2026-09-02T04:20:59.479Z",
2919
+ "lastAt": "2026-09-02T05:37:50.665Z",
2920
2920
  "createdAt": "2026-09-02T04:16:59.844Z"
2921
2921
  },
2922
2922
  {
@@ -2936,9 +2936,9 @@
2936
2936
  "deviceBindingId": null,
2937
2937
  "driverConfig": {},
2938
2938
  "lineId": "ln-156abb71",
2939
- "value": 166.6,
2940
- "state": "offline",
2941
- "lastAt": "2026-09-02T04:20:59.479Z",
2939
+ "value": 165.3,
2940
+ "state": "ok",
2941
+ "lastAt": "2026-09-02T05:37:50.665Z",
2942
2942
  "createdAt": "2026-09-02T04:18:29.303Z"
2943
2943
  },
2944
2944
  {
@@ -2958,9 +2958,31 @@
2958
2958
  "deviceBindingId": null,
2959
2959
  "driverConfig": {},
2960
2960
  "lineId": "ln-bac43927",
2961
+ "value": 168.9,
2962
+ "state": "offline",
2963
+ "lastAt": "2026-09-02T05:37:50.665Z",
2964
+ "createdAt": "2026-09-02T04:20:59.637Z"
2965
+ },
2966
+ {
2967
+ "id": "dn-16eb10bb",
2968
+ "templateRef": "temp-tc",
2969
+ "name": "传感器",
2970
+ "driver": "mock",
2971
+ "enabled": true,
2972
+ "intervalMs": 1000,
2973
+ "publishIntervalMs": null,
2974
+ "unit": "℃",
2975
+ "decimals": 1,
2976
+ "min": 150,
2977
+ "max": 185,
2978
+ "warnLow": 152.8,
2979
+ "warnHigh": 182.2,
2980
+ "deviceBindingId": null,
2981
+ "driverConfig": {},
2982
+ "lineId": "ln-f223008a",
2961
2983
  "value": null,
2962
2984
  "state": "offline",
2963
2985
  "lastAt": null,
2964
- "createdAt": "2026-09-02T04:20:59.637Z"
2986
+ "createdAt": "2026-09-02T05:37:50.702Z"
2965
2987
  }
2966
2988
  ]