@goodandready/dsh-kanban 0.2.34 → 0.2.35

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
@@ -1,11 +1,3 @@
1
- // Browser half of dsh-kanban: board screen and settings card.
2
- //
3
- // The identifier below must match `name` in package.json and `name:` in
4
- // cordis.patch.yml. A mismatch produces NO log error: the loader silently fails
5
- // to resolve the package, the server half works, and the UI never appears.
6
- //
7
- // This module is loaded by ModuleLoader and cannot import repository files;
8
- // pure helpers live here and are exported as `helpers` for node:vm testing.
9
1
  window.__ModuleLoader__.load({
10
2
  id: '@goodandready/dsh-kanban',
11
3
  factory: (require) => {
@@ -16,7 +8,6 @@ window.__ModuleLoader__.load({
16
8
  const ReactDOM = require('react-dom/client')
17
9
 
18
10
  const NS = 'dsh-kanban'
19
- // Plugins page row seat (DSH 0.1.6-alpha.2): key = '<package name>#<row id>'.
20
11
  const PKG = '@goodandready/dsh-kanban'
21
12
  const ROW_ID = 'dsh-kanban'
22
13
  const ROW_CONFIG_KEY = PKG + '#' + ROW_ID
@@ -77,7 +68,7 @@ window.__ModuleLoader__.load({
77
68
  function rememberLaunch(board, choice) {
78
69
  try {
79
70
  window.localStorage.setItem(RECALL_KEY + (board || 'main'), JSON.stringify(choice))
80
- } catch { /* storage unavailable */ }
71
+ } catch { }
81
72
  }
82
73
 
83
74
  function stillOffered(value, known, fallback) {
@@ -548,7 +539,7 @@ window.__ModuleLoader__.load({
548
539
  try {
549
540
  const s = (ctx?.get && ctx.get('lanSettings')) || ctx?.configForms
550
541
  s?.describe?.()?.load?.()
551
- } catch (_) { /* clipboard read best-effort */ }
542
+ } catch (_) { }
552
543
  }, 1000)
553
544
  return () => clearTimer(timer)
554
545
  }
@@ -997,6 +988,13 @@ window.__ModuleLoader__.load({
997
988
  'hint.remoteServerUrl': 'Remote DSH URL (http://192.168.1.111:3080) for unified board state.',
998
989
  'field.remoteServerToken': 'Server auth token',
999
990
  'hint.remoteServerToken': 'Optional authentication token for remote DSH server.',
991
+ 'field.watchdogEnabled': 'Watchdog & circuit breaker',
992
+ 'hint.watchdogEnabled': 'Pause stalled tasks and prevent runaway token consumption.',
993
+ 'field.watchdogStallTimeoutMin': 'Watchdog stall timeout (min)',
994
+ 'hint.watchdogStallTimeoutMin': 'Minutes of inactivity before active task is marked stalled.',
995
+ 'field.watchdogMaxConsecutiveErrors': 'Watchdog max errors',
996
+ 'hint.watchdogMaxConsecutiveErrors': 'Consecutive tool execution errors before circuit breaker trips.',
997
+ 'watchdog.stalled': 'Stalled',
1000
998
  'remote.testBtn': 'Test Connection',
1001
999
  'remote.testing': 'Testing connection...',
1002
1000
  'remote.ok': 'Connected to server 111',
@@ -1447,6 +1445,13 @@ window.__ModuleLoader__.load({
1447
1445
  'hint.remoteServerUrl': '远程 DSH 服务器地址 (http://192.168.1.111:3080),用于共享看板状态。',
1448
1446
  'field.remoteServerToken': '服务器认证令牌',
1449
1447
  'hint.remoteServerToken': '远程 DSH 服务器的可选认证令牌。',
1448
+ 'field.watchdogEnabled': '看门狗与熔断器',
1449
+ 'hint.watchdogEnabled': '暂停卡住的任务并防止额度失控消耗。',
1450
+ 'field.watchdogStallTimeoutMin': '看门狗超时时间 (分钟)',
1451
+ 'hint.watchdogStallTimeoutMin': '在任务被标记为停滞之前的非活跃分钟数。',
1452
+ 'field.watchdogMaxConsecutiveErrors': '看门狗最大连续错误数',
1453
+ 'hint.watchdogMaxConsecutiveErrors': '熔断器跳闸前的连续工具执行错误次数。',
1454
+ 'watchdog.stalled': '已停滞',
1450
1455
  'remote.testBtn': '测试连接',
1451
1456
  'remote.testing': '正在测试连接...',
1452
1457
  'remote.ok': '已连接到 111 服务器',
@@ -1512,6 +1517,9 @@ window.__ModuleLoader__.load({
1512
1517
  { field: 'remoteRoot', kind: 'text', group: 'remote' },
1513
1518
  { field: 'remoteServerUrl', kind: 'text', group: 'remote' },
1514
1519
  { field: 'remoteServerToken', kind: 'text', group: 'remote' },
1520
+ { field: 'watchdogEnabled', kind: 'bool', group: 'general' },
1521
+ { field: 'watchdogStallTimeoutMin', kind: 'number', group: 'limits' },
1522
+ { field: 'watchdogMaxConsecutiveErrors', kind: 'number', group: 'limits' },
1515
1523
  ]
1516
1524
 
1517
1525
  function hintKey(spec) {
@@ -1706,8 +1714,6 @@ window.__ModuleLoader__.load({
1706
1714
  const ctx = props && props.ctx
1707
1715
  const t = (props && props.t) || fallbackT
1708
1716
  ensureCss()
1709
-
1710
- // Row seat (plugins.row.config): summary is a one-liner
1711
1717
  if (props && props.view === 'summary') {
1712
1718
  return h('span', { className: 'dkb-sub' }, t('settings.subtitle') || t('subtitle') || 'Kanban board for sessions and tasks')
1713
1719
  }
@@ -1930,6 +1936,7 @@ window.__ModuleLoader__.load({
1930
1936
  return h('div', {
1931
1937
  className: 'dkb-taskCard' + (urgent ? ' dkb-urgent' : '')
1932
1938
  + (overdue ? ' dkb-overdue' : '')
1939
+ + (task.stalled ? ' dkb-stalled' : '')
1933
1940
  + (props.sameSession ? ' dkb-sameSession' : ''),
1934
1941
  onMouseEnter: () => { if (props.onHover) props.onHover(task.sessionId || '') },
1935
1942
  onMouseLeave: () => { if (props.onHover) props.onHover('') },
@@ -1938,7 +1945,7 @@ window.__ModuleLoader__.load({
1938
1945
  tabIndex: 0,
1939
1946
  draggable: true,
1940
1947
  onDragStart: (e) => {
1941
- try { e.dataTransfer.setData('text/plain', task.id) } catch { /* dataTransfer unsupported */ }
1948
+ try { e.dataTransfer.setData('text/plain', task.id) } catch { }
1942
1949
  props.onDragStart(task)
1943
1950
  },
1944
1951
  onClick: () => props.onOpen(task),
@@ -1954,6 +1961,7 @@ window.__ModuleLoader__.load({
1954
1961
  }),
1955
1962
  waiting ? h('span', { className: 'dkb-waitDot' }) : null,
1956
1963
  waiting ? h('span', { className: 'dkb-waiting' }, t('board.waiting')) : null,
1964
+ task.stalled ? h('span', { className: 'dkb-tag', style: { background: 'var(--dsw-alias-state-warn-secondary, #fff8c5)', color: 'var(--dsw-alias-state-warn-primary, #9a6700)', borderColor: 'var(--dsw-alias-state-warn-secondary, #d4a72c)' }, title: task.stalledReason || 'Stalled' }, '⚠️ ' + (t('watchdog.stalled') || 'Stalled')) : null,
1957
1965
  taskRef(task, t),
1958
1966
  task.isEpic ? h('span', { className: 'dkb-tag dkb-epicTag', title: t('epic.badge') }, '👑 ' + t('epic.badge')) : null,
1959
1967
  task.parentId ? h('span', { className: 'dkb-tag', style: { opacity: 0.75 } }, '↳') : null,
@@ -2293,7 +2301,7 @@ window.__ModuleLoader__.load({
2293
2301
  es.addEventListener('task:change', scheduleReload)
2294
2302
  es.onerror = () => {
2295
2303
  if (es) {
2296
- try { es.close() } catch { /* eventsource close best-effort */ }
2304
+ try { es.close() } catch { }
2297
2305
  es = null
2298
2306
  }
2299
2307
  if (!reconnectTimer) {
@@ -2303,7 +2311,7 @@ window.__ModuleLoader__.load({
2303
2311
  }, 5000)
2304
2312
  }
2305
2313
  }
2306
- } catch { /* eventsource setup best-effort */ }
2314
+ } catch { }
2307
2315
  }
2308
2316
 
2309
2317
  connect()
@@ -2311,7 +2319,7 @@ window.__ModuleLoader__.load({
2311
2319
  return () => {
2312
2320
  if (reconnectTimer) clearTimeout(reconnectTimer)
2313
2321
  if (es) {
2314
- try { es.close() } catch { /* eventsource close best-effort */ }
2322
+ try { es.close() } catch { }
2315
2323
  es = null
2316
2324
  }
2317
2325
  }
@@ -2946,7 +2954,7 @@ window.__ModuleLoader__.load({
2946
2954
  method: 'POST',
2947
2955
  body: JSON.stringify({ reasoningEffort: val }),
2948
2956
  })
2949
- } catch { /* update reasoning effort best-effort */ }
2957
+ } catch { }
2950
2958
  },
2951
2959
  },
2952
2960
  h('option', { value: '' }, t('reasoning.default')),
@@ -3897,7 +3905,7 @@ window.__ModuleLoader__.load({
3897
3905
  }, t('panel.cleanupWorktree'))
3898
3906
  : null,
3899
3907
  ),
3900
- (openTask.worktree || openTask.rejectReason || (Array.isArray(openTask.runs) && openTask.runs.length > 0)) ? h('button', {
3908
+ (openTask.worktree || openTask.rejectReason || openTask.stalled || (Array.isArray(openTask.runs) && openTask.runs.length > 0)) ? h('button', {
3901
3909
  type: 'button',
3902
3910
  className: 'dkb-secondary',
3903
3911
  style: { marginRight: '8px', border: '1px solid var(--dsw-alias-border-l2)' },
@@ -4488,7 +4496,7 @@ window.__ModuleLoader__.load({
4488
4496
  })
4489
4497
  setTask(out.task)
4490
4498
  setOpen(false)
4491
- } catch { /* board handles error; keep header intact */ }
4499
+ } catch { }
4492
4500
  }, [task])
4493
4501
 
4494
4502
  if (!task) return null
@@ -4531,7 +4539,7 @@ window.__ModuleLoader__.load({
4531
4539
  setTask(out.task)
4532
4540
  setNote('')
4533
4541
  setOpen(false)
4534
- } catch { /* board handles error; keep header intact */ } finally {
4542
+ } catch { } finally {
4535
4543
  setSaving(false)
4536
4544
  }
4537
4545
  },
@@ -4728,7 +4736,7 @@ window.__ModuleLoader__.load({
4728
4736
  if (registeredInSlot || (res !== null && res !== false && res !== undefined)) {
4729
4737
  return entry.name
4730
4738
  }
4731
- } catch { /* slot missing in build — try next */ }
4739
+ } catch { }
4732
4740
  }
4733
4741
  return undefined
4734
4742
  }
@@ -4741,11 +4749,6 @@ window.__ModuleLoader__.load({
4741
4749
  refreshMirrorUntilVisible(ctx)
4742
4750
  const SettingsErrorBoundary = createErrorBoundary()
4743
4751
  const SafeSettingsCard = (props) => h(SettingsErrorBoundary, null, h(KanbanSettingsCard, props))
4744
- // List seat (plugins.item): the seat the Plugins page renders as the plugin's own
4745
- // page with its configuration. Registered separately from the fallback chain
4746
- // below — the seat is additive, not an either/or candidate. The label is a static
4747
- // string on purpose: it is resolved while the page renders, and a locale lookup
4748
- // there would take the whole client batch down with it.
4749
4752
  const trySlot = (name, register) => {
4750
4753
  try {
4751
4754
  if (typeof ctx.slots?.inject === 'function') ctx.slots.inject(name, register)
@@ -4754,9 +4757,6 @@ window.__ModuleLoader__.load({
4754
4757
  console.warn('[dsh-kanban] slot registration failed for ' + name + ':', e && e.message)
4755
4758
  }
4756
4759
  }
4757
-
4758
- // #274: Newest slot first, older slots as fallbacks:
4759
- // 1. plugins.row.config (Row seat on Plugins page, DSH 0.1.6-alpha.2)
4760
4760
  let cardSlot = undefined
4761
4761
  trySlot('plugins.row.config', () => {
4762
4762
  ctx.slots.register(
@@ -4770,8 +4770,6 @@ window.__ModuleLoader__.load({
4770
4770
  )
4771
4771
  if (!cardSlot) cardSlot = 'plugins.row.config'
4772
4772
  })
4773
-
4774
- // 2. plugins.item (Official group on Plugins page)
4775
4773
  trySlot('plugins.item', () => {
4776
4774
  ctx.slots.register(
4777
4775
  {
@@ -4785,8 +4783,6 @@ window.__ModuleLoader__.load({
4785
4783
  SafeSettingsCard,
4786
4784
  )
4787
4785
  })
4788
-
4789
- // 3. settings.plugin.item (legacy fallback for older cores)
4790
4786
  trySlot('settings.plugin.item', () => {
4791
4787
  ctx.slots.register(
4792
4788
  {
package/lib/config.js CHANGED
@@ -91,6 +91,9 @@ export const CONFIG_DEFAULTS = {
91
91
  remoteRoot: '/mnt/external/Project/DEV',
92
92
  remoteServerUrl: 'http://192.168.1.111:3080',
93
93
  remoteServerToken: '',
94
+ watchdogEnabled: true,
95
+ watchdogStallTimeoutMin: 15,
96
+ watchdogMaxConsecutiveErrors: 5,
94
97
  }
95
98
 
96
99
  /** Field descriptions reused in schema and settings card. */
@@ -141,6 +144,9 @@ export const CONFIG_HINTS = {
141
144
  remoteRoot: 'Base directory on remote server containing project repositories (/mnt/external/Project/DEV).',
142
145
  remoteServerUrl: 'DSH server URL on 111 (http://192.168.1.111:3080) for unified board state and sessions.',
143
146
  remoteServerToken: 'Authentication token for remote DSH server if required.',
147
+ watchdogEnabled: 'Enable background watchdog timer and circuit breaker for stalled agent tasks.',
148
+ watchdogStallTimeoutMin: 'Minutes of inactivity before active task is marked stalled by watchdog (default: 15).',
149
+ watchdogMaxConsecutiveErrors: 'Number of consecutive execution errors before tripping circuit breaker (default: 5).',
144
150
  }
145
151
 
146
152
  /** Setting key for column WIP limit; not all columns have limits. */