dsh-mesh-ui 0.0.7 → 0.0.8

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/lib/client.js CHANGED
@@ -42,7 +42,7 @@ window.__ModuleLoader__.load({
42
42
  var type = deviceType(name)
43
43
  return type ? label + ' · ' + type : label
44
44
  }
45
- // 同一台设备可能有多个 peer 记录(不同 token/入口:phone-node / phone-node-lan / phone-node-exec)。
45
+ // 同一台设备可能有多个 peer 记录(不同 token/入口:peer-node / peer-node-lan / peer-node-exec)。
46
46
  // AI 网络拓扑按「设备」展示:去掉后缀归并,取可达且延迟最优的那条。
47
47
  var PEER_SUFFIX = /-(lan|wan|exec|exe|import|local|ctl)$/i
48
48
  function peerDeviceKey(name) {
@@ -96,6 +96,7 @@ function nodeLabel(name) { return NODE_LABELS[String(name || '').toLowerCase()]
96
96
  return keys
97
97
  }
98
98
  // 2026-11-10:用户手工调整后认可的默认布局(世界坐标,10000×6000 画布内)
99
+ // 设备按设备名(home/peer-node/company/peer-node/peer-node)映射,各机通用;服务器两张卡单独一张表
99
100
  var DEFAULT_DEVICE_POS = {}
100
101
  var DEFAULT_INFRA_POS = {
101
102
  aliyun: [4887.59, 2856.78],
@@ -811,6 +812,7 @@ var DOWN_COLOR = "rgba(209,75,65,.7)"
811
812
  // 各机 DSH 安装形态(实测/档案)
812
813
  var DEVICE_DSH = {}
813
814
  function dshTypesFor(devKey) { return DEVICE_DSH[peerDeviceKey(devKey)] || [] }
815
+ // LAN peer 判定:-lan 后缀 / 内网 IP / kind=lan(实际数据里 peer-node-lan 的 kind 是 frp)
814
816
  function isLanPeer(peer) {
815
817
  var t = (peer && peer.tunnel) || {}
816
818
  var name = String((peer && peer.name) || '')
@@ -2019,6 +2021,7 @@ body[data-ds-dark-theme] .dm-dg-fs-item.active { background:rgba(88,166,255,.28)
2019
2021
  loading ? h('span', { className: 'dm-dg-loading' }, h('span', { className: 'dm-dg-spin' }), '刷新中') : '刷新')
2020
2022
  ),
2021
2023
  notice ? h('div', { className: 'dm-dg-notice ' + notice.type }, notice.text) : null,
2024
+ selected && selected.staleActive ? h('div', { className: 'dm-dg-notice warn' }, '该任务已超过 ' + Math.round(selected.staleAfterSec / 60) + ' 分钟没有更新,可能已失联;可点「刷新状态」再查对端,或重新发起委托。') : null,
2022
2025
  h('div', { className: 'dm-dg-body', style: { '--dm-dg-list-w': listWidth + 'px' } },
2023
2026
  h('section', { className: 'dm-dg-pane' },
2024
2027
  h('div', { className: 'dm-dg-pane-head' }, '委托列表', h('span', { className: 'dm-dg-sub' }, '(' + visibleTasks.length + (visibleTasks.length !== tasks.length ? '/' + tasks.length : '') + ')')),
@@ -2040,7 +2043,7 @@ body[data-ds-dark-theme] .dm-dg-fs-item.active { background:rgba(88,166,255,.28)
2040
2043
  key: task.taskId,
2041
2044
  className: 'dm-dg-row ' + tone + (task.taskId === selectedId ? ' active' : ''),
2042
2045
  onClick: function () { if (task.taskId !== selectedId) selectTask(task) },
2043
- title: task.goal || ''
2046
+ title: task.staleActive ? '超过 ' + Math.round(task.staleAfterSec / 60) + ' 分钟无更新(疑似失联,可点刷新状态)' : (task.goal || '')
2044
2047
  },
2045
2048
  h('div', { className: 'dm-dg-row-top' },
2046
2049
  task.activeFresh ? h(LoadingSpinner, { size: 12 }) : null,
@@ -2050,7 +2053,7 @@ body[data-ds-dark-theme] .dm-dg-fs-item.active { background:rgba(88,166,255,.28)
2050
2053
  h('span', { className: 'dm-dg-arrow' }, '→'),
2051
2054
  h('span', { className: 'dm-dg-to' }, (task.nodeLabel || task.node || '?') + (task.instance ? ' · ' + task.instance : ''))
2052
2055
  ),
2053
- dgChip(task.state),
2056
+ dgChip(task.displayState || task.state),
2054
2057
  h('span', { className: 'dm-dg-row-time' }, (task.isActive ? '更新 ' : '完成 ') + dgClock(task.displayAt || task.updatedAt))
2055
2058
  ),
2056
2059
  h('div', { className: 'dm-dg-goal' }, task.goal || '(无目标)'),
@@ -2084,7 +2087,7 @@ body[data-ds-dark-theme] .dm-dg-fs-item.active { background:rgba(88,166,255,.28)
2084
2087
  h('span', { className: 'dm-dg-k' }, '状态'),
2085
2088
  h('span', { className: 'dm-dg-v' },
2086
2089
  selected.activeFresh ? h(LoadingSpinner, { size: 13 }) : null,
2087
- dgChip(selected.state)
2090
+ dgChip(selected.displayState || selected.state)
2088
2091
  )
2089
2092
  ),
2090
2093
  kv('委托方', (selected.fromLabel || selected.from || '—') + ((selected.fromIsLocal || (localNode && selected.from === localNode)) ? '(本机)' : '')),
@@ -26,53 +26,76 @@ function readJson(file, fallback = null) {
26
26
  }
27
27
  }
28
28
 
29
- /** 委托列表(本地台账,按更新时间倒序)。 */
29
+ /** 委托列表(本地台账,按更新时间倒序)。
30
+ * - 过滤 selftest 污染记录(selftest 曾把测试任务写进真实台账)
31
+ * - 活跃任务超过阈值未更新 → displayState=stalled(UI 不再显示无限 loading)
32
+ */
30
33
  export function listDelegations(limit = 50) {
31
34
  const data = readJson(join(meshHome(), 'delegations.json'), { tasks: {} })
32
35
  const localNode = readJson(join(meshHome(), 'config.json'), {})?.node?.name || ''
33
- const tasks = Object.values(data?.tasks || {}).map((task) => {
34
- const start = Date.parse(task.createdAt || '')
35
- const end = Date.parse(task.updatedAt || '') || Date.now()
36
- const from = task.from || ''
37
- return {
38
- taskId: task.taskId || '',
39
- conversationId: task.conversationId || '',
40
- sessionId: task.sessionId || '',
41
- from,
42
- fromLabel: nodeLabel(from),
43
- fromIsLocal: Boolean(from) && from === localNode,
44
- fromInstance: task.fromInstance || '',
45
- node: task.node || '',
46
- nodeLabel: nodeLabel(task.node),
47
- toIsLocal: Boolean(task.node) && task.node === localNode,
48
- instance: task.instance || '',
49
- workspace: task.workspace || '',
50
- dedicated: Boolean(task.dedicated),
51
- state: task.state || 'unknown',
52
- startedAt: task.startedAt || '',
53
- finishedAt: task.finishedAt || '',
54
- isActive: ['working', 'submitted', 'accepted', 'question', 'provisioning', 'running'].indexOf(task.state || '') >= 0,
55
- activeFresh: ['working', 'submitted', 'accepted', 'question', 'provisioning', 'running'].indexOf(task.state || '') >= 0 && (Date.now() - Date.parse(task.updatedAt || 0) < 12 * 3600 * 1000),
56
- displayAt: task.finishedAt || task.updatedAt || '',
57
- policy: task.requestedPolicy || task.policy || '',
58
- goal: String(task.goal || ''),
59
- createdAt: task.createdAt || '',
60
- updatedAt: task.updatedAt || '',
61
- durationSec: (() => {
62
- if (!Number.isFinite(start)) return null
63
- const value = Math.max(0, Math.round((end - start) / 1000))
64
- return value > 12 * 3600 ? null : value // 台账 updatedAt 会被轮询刷新,超长值不可信
65
- })(),
66
- stalledReason: task.stalledReason || '',
67
- lastSummary: task.summary || ''
68
- }
69
- })
36
+ const ACTIVE_STATES = ['working', 'submitted', 'accepted', 'question', 'provisioning', 'running']
37
+ // 超过这些时间没有 updatedAt 更新,就认为“疑似失联”(不直接改文件,UI 只展示)
38
+ const STALE_AFTER_MS = {
39
+ submitted: 15 * 60 * 1000,
40
+ accepted: 15 * 60 * 1000,
41
+ provisioning: 15 * 60 * 1000,
42
+ working: 60 * 60 * 1000,
43
+ running: 60 * 60 * 1000,
44
+ question: 6 * 60 * 60 * 1000
45
+ }
46
+ const nowMs = Date.now()
47
+ const tasks = Object.values(data?.tasks || {})
48
+ .filter((task) => task && task.from !== 'selftest')
49
+ .map((task) => {
50
+ const start = Date.parse(task.createdAt || '')
51
+ const end = Date.parse(task.updatedAt || '') || nowMs
52
+ const from = task.from || ''
53
+ const state = task.state || 'unknown'
54
+ const isActive = ACTIVE_STATES.indexOf(state) >= 0
55
+ const updatedMs = Date.parse(task.updatedAt || task.createdAt || '')
56
+ const ageMs = Number.isFinite(updatedMs) ? nowMs - updatedMs : Infinity
57
+ const staleAfter = STALE_AFTER_MS[state] ?? 60 * 60 * 1000
58
+ const staleActive = isActive && ageMs > staleAfter
59
+ return {
60
+ taskId: task.taskId || '',
61
+ conversationId: task.conversationId || '',
62
+ sessionId: task.sessionId || '',
63
+ from,
64
+ fromLabel: nodeLabel(from),
65
+ fromIsLocal: Boolean(from) && from === localNode,
66
+ fromInstance: task.fromInstance || '',
67
+ node: task.node || '',
68
+ nodeLabel: nodeLabel(task.node),
69
+ toIsLocal: Boolean(task.node) && task.node === localNode,
70
+ instance: task.instance || '',
71
+ workspace: task.workspace || '',
72
+ dedicated: Boolean(task.dedicated),
73
+ state,
74
+ displayState: staleActive ? 'stalled' : state,
75
+ isActive,
76
+ staleActive,
77
+ staleAfterSec: Math.round(staleAfter / 1000),
78
+ activeFresh: isActive && !staleActive,
79
+ displayAt: task.finishedAt || task.updatedAt || '',
80
+ policy: task.requestedPolicy || task.policy || '',
81
+ goal: String(task.goal || ''),
82
+ createdAt: task.createdAt || '',
83
+ updatedAt: task.updatedAt || '',
84
+ durationSec: (() => {
85
+ if (!Number.isFinite(start)) return null
86
+ const value = Math.max(0, Math.round((end - start) / 1000))
87
+ return value > 12 * 3600 ? null : value // 台账 updatedAt 会被轮询刷新,超长值不可信
88
+ })(),
89
+ stalledReason: task.stalledReason || '',
90
+ lastSummary: task.summary || ''
91
+ }
92
+ })
70
93
  tasks.sort((a, b) => {
71
94
  if (a.activeFresh !== b.activeFresh) return a.activeFresh ? -1 : 1
72
95
  return String(b.displayAt || b.updatedAt || '').localeCompare(String(a.displayAt || a.updatedAt || ''))
73
96
  })
74
97
  const byState = {}
75
- for (const task of tasks) byState[task.state] = (byState[task.state] || 0) + 1
98
+ for (const task of tasks) byState[task.displayState] = (byState[task.displayState] || 0) + 1
76
99
  return { total: tasks.length, byState, localNode, tasks: tasks.slice(0, Math.max(1, Math.min(limit, 200))) }
77
100
  }
78
101
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-mesh-ui",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "description": "DSH settings-page panel for dsh-mesh: AI network topology, peer health, and delegation panel. Read-only; data is collected from the local mesh CLI.",
5
5
  "icon": "icon.svg",
6
6
  "type": "module",