@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 +30 -34
- package/lib/config.js +6 -0
- package/lib/index.js +110 -1255
- package/lib/routes/board.js +282 -0
- package/lib/routes/security.js +274 -0
- package/lib/routes/task-actions.js +345 -0
- package/lib/routes/task-worktree.js +107 -0
- package/lib/routes/task.js +211 -0
- package/lib/routes.js +71 -1209
- package/lib/server/route-handlers.js +367 -0
- package/lib/server/services.js +225 -0
- package/lib/server/task-handler.js +530 -0
- package/lib/server/timers.js +204 -0
- package/lib/store.js +9 -2
- package/lib/watchdog.js +169 -0
- package/package.json +1 -1
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 {
|
|
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 (_) {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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. */
|