@goodandready/dsh-kanban 0.2.31 → 0.2.33
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/board-tool.js +15 -0
- package/lib/client.js +378 -12
- package/lib/config.js +10 -0
- package/lib/cost-telemetry.js +146 -0
- package/lib/index.js +68 -3
- package/lib/routes.js +104 -4
- package/lib/store.js +25 -0
- package/lib/verification.js +242 -0
- package/lib/worktree.js +162 -1
- package/package.json +1 -1
package/lib/board-tool.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { verifyAndRecordGate } from './verification.js'
|
|
1
2
|
//
|
|
2
3
|
// JSON Schema:
|
|
3
4
|
//
|
|
@@ -111,6 +112,20 @@ export function boardMoveDefinition({ store, config, eventHub }) {
|
|
|
111
112
|
+ `Pending: ${pendingRequired.map((i) => i.text).join('; ')}. `
|
|
112
113
|
+ 'Use `board_checklist` with verification evidence to complete required items.'
|
|
113
114
|
}
|
|
115
|
+
|
|
116
|
+
// Automated Verification Gate (#372)
|
|
117
|
+
if (task.column !== 'review') {
|
|
118
|
+
const gateResult = await verifyAndRecordGate({
|
|
119
|
+
task,
|
|
120
|
+
config,
|
|
121
|
+
store,
|
|
122
|
+
agents: exec?.agents || exec?.agent?.parent,
|
|
123
|
+
createMessage: exec?.createMessage,
|
|
124
|
+
})
|
|
125
|
+
if (!gateResult.allowed) {
|
|
126
|
+
return `Cannot move card to review: verification command "${gateResult.verification.command}" failed with exit code ${gateResult.verification.exitCode}.\nErrors:\n${gateResult.verification.logSnippet}`
|
|
127
|
+
}
|
|
128
|
+
}
|
|
114
129
|
}
|
|
115
130
|
const move = resolveTransition(task.column, [{ column, source: 'tool', detail: detail ?? '' }])
|
|
116
131
|
if (move === undefined) return `The card is already in ${task.column}.`
|
package/lib/client.js
CHANGED
|
@@ -932,6 +932,13 @@ window.__ModuleLoader__.load({
|
|
|
932
932
|
'field.workspaceClaimBoundaries': 'Workspace claim boundaries',
|
|
933
933
|
'field.autoSyncSessions': 'Auto-sync external chat sessions',
|
|
934
934
|
'hint.autoSyncSessions': 'Automatically create board cards for standard DSH chat conversations.',
|
|
935
|
+
'field.verificationGateEnabled': 'Verification gate',
|
|
936
|
+
'hint.verificationGateEnabled': 'Run automated verification gate (e.g. npm test) before allowing cards to move to Review.',
|
|
937
|
+
'field.verificationCommand': 'Verification command',
|
|
938
|
+
'hint.verificationCommand': 'Default verification command to execute in task worktree before Review (default: npm test).',
|
|
939
|
+
'field.verificationTimeoutMs': 'Verification timeout (ms)',
|
|
940
|
+
'hint.verificationTimeoutMs': 'Timeout in milliseconds for verification gate execution (default: 120000).',
|
|
941
|
+
'panel.verificationFailed': 'Automated verification before Review failed',
|
|
935
942
|
'hint.announceToAgent': 'Injects guidance section into the agent system prompt.',
|
|
936
943
|
'task.runs': 'Execution History',
|
|
937
944
|
'task.noRuns': 'No runs yet',
|
|
@@ -1215,6 +1222,8 @@ window.__ModuleLoader__.load({
|
|
|
1215
1222
|
'diff.truncated': 'Diff truncated (exceeded limit)',
|
|
1216
1223
|
'diff.show': 'Show diff',
|
|
1217
1224
|
'diff.hide': 'Hide diff',
|
|
1225
|
+
'diff.discard': 'Discard',
|
|
1226
|
+
'diff.confirmDiscard': 'Discard changes to {file}?',
|
|
1218
1227
|
'acceptance.acceptAndMerge': 'Accept & Merge (--no-ff)',
|
|
1219
1228
|
'acceptance.merging': 'Merging...',
|
|
1220
1229
|
'acceptance.title': 'Review Acceptance Gate',
|
|
@@ -1257,6 +1266,29 @@ window.__ModuleLoader__.load({
|
|
|
1257
1266
|
'error.crossSite': 'Request rejected as cross-site.',
|
|
1258
1267
|
'error.timeout': 'Request timed out. Please try again.',
|
|
1259
1268
|
'error.unknown': 'Request failed.',
|
|
1269
|
+
'field.costMeterEnabled': 'Cost Meter Telemetry',
|
|
1270
|
+
'hint.costMeterEnabled': 'Enable token and cost telemetry integration via @goodandready/dsh-cost-meter.',
|
|
1271
|
+
'field.taskCostLimit': 'Task Cost Limit (USD)',
|
|
1272
|
+
'hint.taskCostLimit': 'Cost threshold per task in USD; 0 disables threshold warnings.',
|
|
1273
|
+
'card.costLimitExceeded': 'Cost limit exceeded',
|
|
1274
|
+
'card.nudge': 'Nudge',
|
|
1275
|
+
'card.nudgeHint': 'Send live instruction to running agent',
|
|
1276
|
+
'nudge.title': 'Live Steering',
|
|
1277
|
+
'nudge.placeholder': 'Enter immediate instruction or correction for the agent...',
|
|
1278
|
+
'nudge.send': 'Send',
|
|
1279
|
+
'nudge.sending': 'Sending...',
|
|
1280
|
+
'nudge.success': 'Instruction delivered to agent',
|
|
1281
|
+
'nudge.failed': 'Failed to deliver instruction',
|
|
1282
|
+
'costMeter.notice': 'Cost and token tracking requires @goodandready/dsh-cost-meter to be installed and active in DSH profile.',
|
|
1283
|
+
'costMeter.active': 'Plugin @goodandready/dsh-cost-meter is active',
|
|
1284
|
+
'costMeter.inactive': 'Plugin @goodandready/dsh-cost-meter not detected',
|
|
1285
|
+
'telemetry.title': 'Resource Telemetry',
|
|
1286
|
+
'telemetry.tokens': 'Tokens',
|
|
1287
|
+
'telemetry.input': 'Input',
|
|
1288
|
+
'telemetry.output': 'Output',
|
|
1289
|
+
'telemetry.cache': 'Cached',
|
|
1290
|
+
'telemetry.cost': 'Estimated Cost',
|
|
1291
|
+
'telemetry.limit': 'Budget Limit',
|
|
1260
1292
|
}
|
|
1261
1293
|
|
|
1262
1294
|
const zh = {
|
|
@@ -1324,6 +1356,13 @@ window.__ModuleLoader__.load({
|
|
|
1324
1356
|
'field.workspaceClaimBoundaries': '工作区认领边界',
|
|
1325
1357
|
'field.autoSyncSessions': '自动同步外部聊天会话',
|
|
1326
1358
|
'hint.autoSyncSessions': '自动将 DSH 的标准聊天会话同步为看板任务卡片。',
|
|
1359
|
+
'field.verificationGateEnabled': '验证网关',
|
|
1360
|
+
'hint.verificationGateEnabled': '在允许卡片移至评审前运行自动验证 (如 npm test)。',
|
|
1361
|
+
'field.verificationCommand': '验证命令',
|
|
1362
|
+
'hint.verificationCommand': '在工作区运行的默认验证命令 (默认: npm test)。',
|
|
1363
|
+
'field.verificationTimeoutMs': '验证超时 (毫秒)',
|
|
1364
|
+
'hint.verificationTimeoutMs': '验证执行的超时时间 (毫秒,默认 120000)。',
|
|
1365
|
+
'panel.verificationFailed': '移至评审前的自动验证失败',
|
|
1327
1366
|
'hint.announceToAgent': '向 Agent 系统提示词注入看板与进度协作说明。',
|
|
1328
1367
|
'task.runs': '执行历史',
|
|
1329
1368
|
'task.noRuns': '暂无执行记录',
|
|
@@ -1607,6 +1646,8 @@ window.__ModuleLoader__.load({
|
|
|
1607
1646
|
'diff.truncated': 'Diff 超限已截断',
|
|
1608
1647
|
'diff.show': '查看 diff',
|
|
1609
1648
|
'diff.hide': '收起 diff',
|
|
1649
|
+
'diff.discard': '放弃修改',
|
|
1650
|
+
'diff.confirmDiscard': '确认放弃对 {file} 的修改吗?',
|
|
1610
1651
|
'acceptance.acceptAndMerge': '验收并合并 (--no-ff)',
|
|
1611
1652
|
'acceptance.merging': '合并中...',
|
|
1612
1653
|
'acceptance.title': '评审验收网关',
|
|
@@ -1649,6 +1690,29 @@ window.__ModuleLoader__.load({
|
|
|
1649
1690
|
'error.crossSite': '请求被判定为跨站而拒绝。',
|
|
1650
1691
|
'error.timeout': '请求超时,请重试。',
|
|
1651
1692
|
'error.unknown': '请求失败。',
|
|
1693
|
+
'field.costMeterEnabled': '成本计量遥测',
|
|
1694
|
+
'hint.costMeterEnabled': '通过 @goodandready/dsh-cost-meter 启用 Token 和成本遥测集成。',
|
|
1695
|
+
'field.taskCostLimit': '任务成本限额 (USD)',
|
|
1696
|
+
'hint.taskCostLimit': '每个任务的成本阈值(美元);0 表示禁用超额警告。',
|
|
1697
|
+
'card.costLimitExceeded': '超出成本限额',
|
|
1698
|
+
'card.nudge': '引导',
|
|
1699
|
+
'card.nudgeHint': '向正在运行的 Agent 发送实时引导指令',
|
|
1700
|
+
'nudge.title': '实时引导',
|
|
1701
|
+
'nudge.placeholder': '输入对 Agent 的即时指令或修正建议...',
|
|
1702
|
+
'nudge.send': '发送',
|
|
1703
|
+
'nudge.sending': '正在发送...',
|
|
1704
|
+
'nudge.success': '指令已发送给 Agent',
|
|
1705
|
+
'nudge.failed': '发送指令失败',
|
|
1706
|
+
'costMeter.notice': 'Token 及成本追踪功能需要 DSH Profile 中安装并激活 @goodandready/dsh-cost-meter 插件。',
|
|
1707
|
+
'costMeter.active': '已激活 @goodandready/dsh-cost-meter 插件',
|
|
1708
|
+
'costMeter.inactive': '未检测到 @goodandready/dsh-cost-meter 插件',
|
|
1709
|
+
'telemetry.title': '资源遥测',
|
|
1710
|
+
'telemetry.tokens': 'Token 统计',
|
|
1711
|
+
'telemetry.input': '输入',
|
|
1712
|
+
'telemetry.output': '输出',
|
|
1713
|
+
'telemetry.cache': '缓存命中',
|
|
1714
|
+
'telemetry.cost': '预估成本',
|
|
1715
|
+
'telemetry.limit': '预算限额',
|
|
1652
1716
|
}
|
|
1653
1717
|
|
|
1654
1718
|
function fallbackT(key, vars) {
|
|
@@ -1694,6 +1758,11 @@ window.__ModuleLoader__.load({
|
|
|
1694
1758
|
{ field: 'maxConcurrentSessions', kind: 'number', group: 'limits' },
|
|
1695
1759
|
{ field: 'workspaceClaimBoundaries', kind: 'bool', group: 'general' },
|
|
1696
1760
|
{ field: 'autoSyncSessions', kind: 'bool', group: 'sync' },
|
|
1761
|
+
{ field: 'verificationGateEnabled', kind: 'bool', group: 'general' },
|
|
1762
|
+
{ field: 'verificationCommand', kind: 'text', group: 'general' },
|
|
1763
|
+
{ field: 'verificationTimeoutMs', kind: 'number', group: 'general' },
|
|
1764
|
+
{ field: 'costMeterEnabled', kind: 'bool', group: 'sync' },
|
|
1765
|
+
{ field: 'taskCostLimit', kind: 'number', group: 'limits' },
|
|
1697
1766
|
]
|
|
1698
1767
|
|
|
1699
1768
|
function hintKey(spec) {
|
|
@@ -1993,6 +2062,29 @@ window.__ModuleLoader__.load({
|
|
|
1993
2062
|
else if (!writable) note = h('p', { className: 'dkb-note', role: 'status' }, t('settings.readOnly'))
|
|
1994
2063
|
else if (saved) note = h('p', { className: 'dkb-note', role: 'status' }, t('settings.saved'))
|
|
1995
2064
|
else note = h('p', { className: 'dkb-note' })
|
|
2065
|
+
const costMeterDetected = Boolean(ctx?.costMeter || (ctx?.registry && Array.from(ctx.registry.keys()).some((k) => k === '@goodandready/dsh-cost-meter' || k?.name === '@goodandready/dsh-cost-meter')))
|
|
2066
|
+
rows.push(h('div', {
|
|
2067
|
+
className: 'dkb-section-card',
|
|
2068
|
+
key: 'cost-meter-notice',
|
|
2069
|
+
style: {
|
|
2070
|
+
borderLeft: '3px solid var(--dsw-alias-state-business-primary, #0969da)',
|
|
2071
|
+
background: 'var(--dsw-alias-bg-layer-1, #f6f8fa)',
|
|
2072
|
+
padding: '12px 14px',
|
|
2073
|
+
marginBottom: '12px',
|
|
2074
|
+
},
|
|
2075
|
+
},
|
|
2076
|
+
h('div', {
|
|
2077
|
+
style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '6px' },
|
|
2078
|
+
},
|
|
2079
|
+
h('span', { style: { fontWeight: '600', fontSize: '13px' } }, '🪙 ' + t('telemetry.title')),
|
|
2080
|
+
h('span', {
|
|
2081
|
+
className: 'dkb-section-badge' + (costMeterDetected ? ' dkb-section-badge-ok' : ''),
|
|
2082
|
+
}, costMeterDetected ? t('costMeter.active') : t('costMeter.inactive')),
|
|
2083
|
+
),
|
|
2084
|
+
h('p', { className: 'dkb-hint', style: { margin: 0, fontSize: '12px', lineHeight: '1.4' } },
|
|
2085
|
+
t('costMeter.notice'),
|
|
2086
|
+
),
|
|
2087
|
+
))
|
|
1996
2088
|
rows.push(h(PluginUpdaterSection, { t, key: 'updater' }))
|
|
1997
2089
|
rows.push(h('div', { className: 'dkb-foot', key: 'foot' },
|
|
1998
2090
|
note,
|
|
@@ -2068,6 +2160,25 @@ window.__ModuleLoader__.load({
|
|
|
2068
2160
|
Array.isArray(task.checklist) && task.checklist.length > 0
|
|
2069
2161
|
? h('span', { className: 'dkb-tag', title: 'DoD Checklist' }, `☑ ${task.checklist.filter((i) => i.completed).length}/${task.checklist.length}`)
|
|
2070
2162
|
: null,
|
|
2163
|
+
task.report?.verification?.status === 'passed'
|
|
2164
|
+
? h('span', { className: 'dkb-tag', style: { background: 'var(--dsw-alias-state-success-secondary, #dafbe1)', color: 'var(--dsw-alias-state-success-primary, #1a7f37)', borderColor: 'var(--dsw-alias-state-success-secondary, #4ac26b)' }, title: task.report.verification.command || 'Tests passed' }, '✓ PASS')
|
|
2165
|
+
: (task.report?.verification?.status === 'failed'
|
|
2166
|
+
? h('span', { className: 'dkb-tag', style: { background: 'var(--dsw-alias-state-error-secondary, #ffebe9)', color: 'var(--dsw-alias-state-error-primary, #cf222e)', borderColor: 'var(--dsw-alias-state-error-secondary, #ffc1c0)' }, title: task.report.verification.command || 'Tests failed' }, '✗ FAIL')
|
|
2167
|
+
: null),
|
|
2168
|
+
task.costMetrics && task.costMetrics.totalTokens > 0
|
|
2169
|
+
? h('span', {
|
|
2170
|
+
className: 'dkb-tag',
|
|
2171
|
+
style: { background: 'var(--dsw-alias-state-business-tertiary, #ddf4ff)', color: 'var(--dsw-alias-state-business-primary, #0969da)', borderColor: 'var(--dsw-alias-state-business-tertiary, #54aeff)' },
|
|
2172
|
+
title: 'Tokens: ' + task.costMetrics.totalTokens.toLocaleString()
|
|
2173
|
+
}, '🪙 ' + (task.costMetrics.totalTokens / 1000).toFixed(1) + 'k' + (task.costMetrics.costFormatted ? ' · ' + task.costMetrics.costFormatted : ''))
|
|
2174
|
+
: null,
|
|
2175
|
+
task.costLimitExceeded
|
|
2176
|
+
? h('span', {
|
|
2177
|
+
className: 'dkb-tag',
|
|
2178
|
+
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)' },
|
|
2179
|
+
title: 'Budget limit exceeded'
|
|
2180
|
+
}, '⚠️ ' + t('card.costLimitExceeded'))
|
|
2181
|
+
: null,
|
|
2071
2182
|
task.report && task.report.summary
|
|
2072
2183
|
? h('span', { className: 'dkb-tag', title: 'Execution Report' }, '📄 report')
|
|
2073
2184
|
: null,
|
|
@@ -2078,6 +2189,16 @@ window.__ModuleLoader__.load({
|
|
|
2078
2189
|
className: 'dkb-tag', key: l, style: tagStyle(task, l),
|
|
2079
2190
|
}, l))),
|
|
2080
2191
|
h('div', { className: 'dkb-taskTitle' }, task.title),
|
|
2192
|
+
task.state === 'running' && task.liveStep
|
|
2193
|
+
? h('div', {
|
|
2194
|
+
className: 'dkb-liveStep',
|
|
2195
|
+
style: { fontSize: '11px', color: 'var(--dsw-alias-state-business-primary, #0969da)', display: 'flex', alignItems: 'center', gap: '4px', margin: '3px 0' },
|
|
2196
|
+
title: task.liveStep
|
|
2197
|
+
},
|
|
2198
|
+
h('span', { style: { display: 'inline-block' } }, '⟳'),
|
|
2199
|
+
h('span', { style: { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, task.liveStep)
|
|
2200
|
+
)
|
|
2201
|
+
: null,
|
|
2081
2202
|
task.subtasksCount && task.subtasksCount.total > 0
|
|
2082
2203
|
? h('div', { className: 'dkb-progressBar', title: `${task.subtasksCount.completed}/${task.subtasksCount.total}` },
|
|
2083
2204
|
h('div', {
|
|
@@ -2134,6 +2255,26 @@ window.__ModuleLoader__.load({
|
|
|
2134
2255
|
onClick: (e) => { e.stopPropagation(); props.onOpenChat(task) },
|
|
2135
2256
|
}, t('card.toChat'))
|
|
2136
2257
|
: null,
|
|
2258
|
+
(task.column === 'in-progress' || task.col === 'in-progress') && task.sessionId && (task.state === 'running' || task.state === 'idle')
|
|
2259
|
+
? h('button', {
|
|
2260
|
+
type: 'button',
|
|
2261
|
+
className: 'dkb-nudgeBtn',
|
|
2262
|
+
title: t('card.nudgeHint'),
|
|
2263
|
+
style: {
|
|
2264
|
+
fontSize: '11px',
|
|
2265
|
+
padding: '1px 6px',
|
|
2266
|
+
borderRadius: '4px',
|
|
2267
|
+
cursor: 'pointer',
|
|
2268
|
+
border: '1px solid var(--dsw-alias-state-business-tertiary, #54aeff)',
|
|
2269
|
+
background: 'var(--dsw-alias-state-business-tertiary, #ddf4ff)',
|
|
2270
|
+
color: 'var(--dsw-alias-state-business-primary, #0969da)',
|
|
2271
|
+
},
|
|
2272
|
+
onClick: (e) => {
|
|
2273
|
+
e.stopPropagation();
|
|
2274
|
+
if (props.onNudge) props.onNudge(task);
|
|
2275
|
+
}
|
|
2276
|
+
}, '💬 ' + t('card.nudge'))
|
|
2277
|
+
: null,
|
|
2137
2278
|
touched
|
|
2138
2279
|
? h('span', {
|
|
2139
2280
|
className: task.stale ? 'dkb-countOver' : '',
|
|
@@ -2291,6 +2432,32 @@ window.__ModuleLoader__.load({
|
|
|
2291
2432
|
const [agentPreset, setAgentPreset] = React.useState('')
|
|
2292
2433
|
const [permission, setPermission] = React.useState('')
|
|
2293
2434
|
const dragged = React.useRef(null)
|
|
2435
|
+
const [nudgeTask, setNudgeTask] = React.useState(null)
|
|
2436
|
+
const [nudgePrompt, setNudgePrompt] = React.useState('')
|
|
2437
|
+
const [nudgeSending, setNudgeSending] = React.useState(false)
|
|
2438
|
+
const [nudgeSuccess, setNudgeSuccess] = React.useState(false)
|
|
2439
|
+
|
|
2440
|
+
const handleNudgeSubmit = React.useCallback(async () => {
|
|
2441
|
+
if (!nudgeTask || !nudgePrompt.trim() || nudgeSending) return
|
|
2442
|
+
setNudgeSending(true)
|
|
2443
|
+
try {
|
|
2444
|
+
await api('/task/' + encodeURIComponent(nudgeTask.id) + '/nudge', {
|
|
2445
|
+
method: 'POST',
|
|
2446
|
+
body: JSON.stringify({ prompt: nudgePrompt.trim() }),
|
|
2447
|
+
})
|
|
2448
|
+
setNudgeSuccess(true)
|
|
2449
|
+
setTimeout(() => {
|
|
2450
|
+
setNudgeTask(null)
|
|
2451
|
+
setNudgePrompt('')
|
|
2452
|
+
setNudgeSuccess(false)
|
|
2453
|
+
}, 1200)
|
|
2454
|
+
load(board)
|
|
2455
|
+
} catch (e) {
|
|
2456
|
+
setError(t(e.key || e.error || 'nudge.failed'))
|
|
2457
|
+
} finally {
|
|
2458
|
+
setNudgeSending(false)
|
|
2459
|
+
}
|
|
2460
|
+
}, [nudgeTask, nudgePrompt, nudgeSending, load, board, t])
|
|
2294
2461
|
|
|
2295
2462
|
const load = React.useCallback(async (nextBoard) => {
|
|
2296
2463
|
try {
|
|
@@ -2842,6 +3009,7 @@ window.__ModuleLoader__.load({
|
|
|
2842
3009
|
if (manyGroups && groups[col.id + '/' + (task.repo || '')] === true) return
|
|
2843
3010
|
rows.push(h(TaskCard, {
|
|
2844
3011
|
key: task.id, task, t, now: state.now,
|
|
3012
|
+
onNudge: (t) => { setNudgeTask(t); setNudgePrompt(''); setNudgeSuccess(false) },
|
|
2845
3013
|
columns: (state.columns || []).map((c) => c.id),
|
|
2846
3014
|
menuOpen: cardMenu,
|
|
2847
3015
|
onMenu: setCardMenu,
|
|
@@ -2998,6 +3166,29 @@ window.__ModuleLoader__.load({
|
|
|
2998
3166
|
t('panel.worktree', { path: openTask.worktree })
|
|
2999
3167
|
+ (openTask.branch ? ' (' + openTask.branch + ')' : ''))
|
|
3000
3168
|
: null,
|
|
3169
|
+
openTask.report?.verification?.status === 'failed'
|
|
3170
|
+
? h('div', {
|
|
3171
|
+
style: {
|
|
3172
|
+
margin: '8px 0', padding: '10px 12px',
|
|
3173
|
+
background: 'var(--dsw-alias-state-error-secondary, #ffebe9)',
|
|
3174
|
+
border: '1px solid var(--dsw-alias-state-error-primary, #cf222e)',
|
|
3175
|
+
borderRadius: 8, fontSize: 12, display: 'flex', flexDirection: 'column', gap: 6,
|
|
3176
|
+
},
|
|
3177
|
+
},
|
|
3178
|
+
h('div', { style: { fontWeight: 'bold', color: 'var(--dsw-alias-state-error-primary, #cf222e)' } },
|
|
3179
|
+
'✗ ' + (t('panel.verificationFailed') || 'Automated verification before Review failed') + (openTask.report.verification.command ? ' (' + openTask.report.verification.command + ')' : '')
|
|
3180
|
+
),
|
|
3181
|
+
openTask.report.verification.log
|
|
3182
|
+
? h('pre', {
|
|
3183
|
+
style: {
|
|
3184
|
+
margin: 0, padding: 6, maxHeight: 150, overflowY: 'auto',
|
|
3185
|
+
background: 'var(--dsw-alias-bg-layer-2)',
|
|
3186
|
+
fontSize: 11, fontFamily: 'monospace', borderRadius: 4, whiteSpace: 'pre-wrap',
|
|
3187
|
+
},
|
|
3188
|
+
}, openTask.report.verification.log)
|
|
3189
|
+
: null
|
|
3190
|
+
)
|
|
3191
|
+
: null,
|
|
3001
3192
|
openTask.permission && !openTask.permissionConfirmedAt
|
|
3002
3193
|
? h('div', {
|
|
3003
3194
|
className: 'dkb-warnBox',
|
|
@@ -3279,6 +3470,59 @@ window.__ModuleLoader__.load({
|
|
|
3279
3470
|
},
|
|
3280
3471
|
}, t('panel.openChat'))
|
|
3281
3472
|
: null,
|
|
3473
|
+
openTask.sessionId && (openTask.column === 'in-progress' || openTask.col === 'in-progress')
|
|
3474
|
+
? h('button', {
|
|
3475
|
+
type: 'button', className: 'dkb-discard', style: { alignSelf: 'flex-start' },
|
|
3476
|
+
onClick: () => { setNudgeTask(openTask); setNudgePrompt(''); setNudgeSuccess(false) },
|
|
3477
|
+
}, '💬 ' + t('card.nudge'))
|
|
3478
|
+
: null,
|
|
3479
|
+
openTask.costMetrics && openTask.costMetrics.totalTokens > 0
|
|
3480
|
+
? h('div', {
|
|
3481
|
+
className: 'dkb-section-card',
|
|
3482
|
+
style: {
|
|
3483
|
+
margin: '8px 0', padding: 12, border: '1px solid var(--dsw-alias-border-l2)',
|
|
3484
|
+
borderRadius: 8, display: 'flex', flexDirection: 'column', gap: 6,
|
|
3485
|
+
},
|
|
3486
|
+
},
|
|
3487
|
+
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' } },
|
|
3488
|
+
h('div', { className: 'dkb-group', style: { margin: 0, fontWeight: 600 } },
|
|
3489
|
+
'🪙 ' + t('telemetry.title')
|
|
3490
|
+
),
|
|
3491
|
+
h('span', {
|
|
3492
|
+
className: 'dkb-tag',
|
|
3493
|
+
style: { background: 'var(--dsw-alias-state-business-tertiary, #ddf4ff)', color: 'var(--dsw-alias-state-business-primary, #0969da)', borderColor: 'var(--dsw-alias-state-business-tertiary, #54aeff)' }
|
|
3494
|
+
}, openTask.costMetrics.costFormatted || '$0.00'),
|
|
3495
|
+
),
|
|
3496
|
+
h('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 8, fontSize: '11px', textAlign: 'center' } },
|
|
3497
|
+
h('div', { style: { background: 'var(--dsw-alias-bg-layer-1)', padding: '6px', borderRadius: '4px' } },
|
|
3498
|
+
h('div', { style: { opacity: 0.7 } }, t('telemetry.tokens')),
|
|
3499
|
+
h('div', { style: { fontWeight: 600 } }, openTask.costMetrics.totalTokens.toLocaleString()),
|
|
3500
|
+
),
|
|
3501
|
+
h('div', { style: { background: 'var(--dsw-alias-bg-layer-1)', padding: '6px', borderRadius: '4px' } },
|
|
3502
|
+
h('div', { style: { opacity: 0.7 } }, t('telemetry.input')),
|
|
3503
|
+
h('div', { style: { fontWeight: 600 } }, (openTask.costMetrics.uncachedInputTokens || 0).toLocaleString()),
|
|
3504
|
+
),
|
|
3505
|
+
h('div', { style: { background: 'var(--dsw-alias-bg-layer-1)', padding: '6px', borderRadius: '4px' } },
|
|
3506
|
+
h('div', { style: { opacity: 0.7 } }, t('telemetry.output')),
|
|
3507
|
+
h('div', { style: { fontWeight: 600 } }, (openTask.costMetrics.outputTokens || 0).toLocaleString()),
|
|
3508
|
+
),
|
|
3509
|
+
h('div', { style: { background: 'var(--dsw-alias-bg-layer-1)', padding: '6px', borderRadius: '4px' } },
|
|
3510
|
+
h('div', { style: { opacity: 0.7 } }, t('telemetry.cache')),
|
|
3511
|
+
h('div', { style: { fontWeight: 600 } }, (openTask.costMetrics.cacheReadTokens || 0).toLocaleString()),
|
|
3512
|
+
),
|
|
3513
|
+
),
|
|
3514
|
+
openTask.costLimitExceeded
|
|
3515
|
+
? h('div', {
|
|
3516
|
+
style: {
|
|
3517
|
+
fontSize: '11px',
|
|
3518
|
+
color: 'var(--dsw-alias-state-warn-primary, #9a6700)',
|
|
3519
|
+
background: 'var(--dsw-alias-state-warn-secondary, #fff8c5)',
|
|
3520
|
+
padding: '4px 8px', borderRadius: '4px', marginTop: '4px'
|
|
3521
|
+
}
|
|
3522
|
+
}, '⚠️ ' + t('card.costLimitExceeded'))
|
|
3523
|
+
: null
|
|
3524
|
+
)
|
|
3525
|
+
: null,
|
|
3282
3526
|
log.length
|
|
3283
3527
|
? h('div', null,
|
|
3284
3528
|
h('div', { className: 'dkb-group' }, t('panel.log')),
|
|
@@ -3357,17 +3601,74 @@ window.__ModuleLoader__.load({
|
|
|
3357
3601
|
: null,
|
|
3358
3602
|
diffLoading
|
|
3359
3603
|
? h('div', { style: { fontSize: 12, opacity: 0.7 } }, 'Loading diff...')
|
|
3360
|
-
:
|
|
3361
|
-
|
|
3362
|
-
style: {
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3604
|
+
: h('div', { style: { display: 'flex', flexDirection: 'column', gap: 6 } },
|
|
3605
|
+
diffData?.files && diffData.files.length > 0
|
|
3606
|
+
? h('div', { style: { display: 'flex', flexDirection: 'column', gap: 4, margin: '2px 0' } },
|
|
3607
|
+
diffData.files.map((f) => h('div', {
|
|
3608
|
+
key: f.path,
|
|
3609
|
+
style: {
|
|
3610
|
+
display: 'flex', alignItems: 'center', gap: 6, fontSize: 11,
|
|
3611
|
+
padding: '3px 6px', background: 'var(--dsw-alias-bg-layer-2)',
|
|
3612
|
+
borderRadius: 4, border: '1px solid var(--dsw-alias-border-l1)',
|
|
3613
|
+
},
|
|
3614
|
+
},
|
|
3615
|
+
h('span', {
|
|
3616
|
+
style: {
|
|
3617
|
+
fontWeight: 'bold', fontSize: 10, padding: '1px 4px', borderRadius: 3,
|
|
3618
|
+
background: f.status === 'added' ? 'var(--dsw-alias-state-success-secondary, #dafbe1)'
|
|
3619
|
+
: (f.status === 'deleted' ? 'var(--dsw-alias-state-error-secondary, #ffebe9)'
|
|
3620
|
+
: 'var(--dsw-alias-state-warn-secondary, #fff8c5)'),
|
|
3621
|
+
color: f.status === 'added' ? 'var(--dsw-alias-state-success-primary, #1a7f37)'
|
|
3622
|
+
: (f.status === 'deleted' ? 'var(--dsw-alias-state-error-primary, #cf222e)'
|
|
3623
|
+
: 'var(--dsw-alias-state-warn-primary, #9a6700)'),
|
|
3624
|
+
},
|
|
3625
|
+
}, f.status === 'added' ? 'A' : (f.status === 'deleted' ? 'D' : (f.status === 'untracked' ? '?' : 'M'))),
|
|
3626
|
+
h('span', { style: { flex: 1, fontFamily: 'monospace', overflow: 'hidden', textOverflow: 'ellipsis' } }, f.path),
|
|
3627
|
+
(f.insertions || f.deletions)
|
|
3628
|
+
? h('span', { style: { fontSize: 10, fontFamily: 'monospace', opacity: 0.85 } },
|
|
3629
|
+
f.insertions ? h('span', { style: { color: 'var(--dsw-alias-state-success-primary, #1a7f37)', marginRight: 4 } }, `+${f.insertions}`) : null,
|
|
3630
|
+
f.deletions ? h('span', { style: { color: 'var(--dsw-alias-state-error-primary, #cf222e)' } }, `-${f.deletions}`) : null,
|
|
3631
|
+
)
|
|
3632
|
+
: null,
|
|
3633
|
+
h('button', {
|
|
3634
|
+
type: 'button',
|
|
3635
|
+
className: 'dkb-discard',
|
|
3636
|
+
style: { padding: '1px 6px', fontSize: 10, cursor: 'pointer' },
|
|
3637
|
+
title: t('diff.discard') || 'Discard',
|
|
3638
|
+
onClick: async (e) => {
|
|
3639
|
+
e.stopPropagation()
|
|
3640
|
+
const confirmMsg = t('diff.confirmDiscard', { file: f.path }) || `Discard changes to ${f.path}?`
|
|
3641
|
+
if (window.confirm(confirmMsg)) {
|
|
3642
|
+
try {
|
|
3643
|
+
await api('/task/' + encodeURIComponent(openTask.id) + '/discard', {
|
|
3644
|
+
method: 'POST',
|
|
3645
|
+
body: JSON.stringify({ file: f.path }),
|
|
3646
|
+
})
|
|
3647
|
+
await loadDiffAndCommits()
|
|
3648
|
+
await load(board)
|
|
3649
|
+
} catch (err) {
|
|
3650
|
+
setError(t(err.key || err.error || 'error.unknown'))
|
|
3651
|
+
}
|
|
3652
|
+
}
|
|
3653
|
+
},
|
|
3654
|
+
}, t('diff.discard') || 'Discard')
|
|
3655
|
+
))
|
|
3656
|
+
)
|
|
3657
|
+
: null,
|
|
3658
|
+
diffData?.diff
|
|
3659
|
+
? h('pre', {
|
|
3660
|
+
style: {
|
|
3661
|
+
margin: 0, padding: 8, maxHeight: 280, overflowY: 'auto',
|
|
3662
|
+
background: 'var(--dsw-alias-bg-layer-2)',
|
|
3663
|
+
color: 'var(--dsw-alias-label-primary)',
|
|
3664
|
+
fontSize: 11, fontFamily: 'monospace', borderRadius: 4, whiteSpace: 'pre-wrap',
|
|
3665
|
+
border: '1px solid var(--dsw-alias-border-l1)',
|
|
3666
|
+
},
|
|
3667
|
+
}, diffData.diff)
|
|
3668
|
+
: (!diffData?.files || diffData.files.length === 0
|
|
3669
|
+
? h('div', { style: { fontSize: 12, opacity: 0.7 } }, t('diff.empty'))
|
|
3670
|
+
: null)
|
|
3671
|
+
)
|
|
3371
3672
|
) : null
|
|
3372
3673
|
)
|
|
3373
3674
|
: null,
|
|
@@ -4266,11 +4567,76 @@ window.__ModuleLoader__.load({
|
|
|
4266
4567
|
)
|
|
4267
4568
|
: null
|
|
4268
4569
|
|
|
4570
|
+
const nudgeDialogEl = nudgeTask
|
|
4571
|
+
? h('div', {
|
|
4572
|
+
className: 'dkb-modal',
|
|
4573
|
+
onClick: () => { setNudgeTask(null); setNudgePrompt(''); setNudgeSuccess(false) },
|
|
4574
|
+
},
|
|
4575
|
+
h('div', {
|
|
4576
|
+
className: 'dkb-dialog',
|
|
4577
|
+
style: { maxWidth: '440px', width: '90%', padding: '16px' },
|
|
4578
|
+
onClick: (e) => e.stopPropagation(),
|
|
4579
|
+
},
|
|
4580
|
+
h('div', { className: 'dkb-dialogHead', style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '12px' } },
|
|
4581
|
+
h('h3', { style: { margin: 0, fontSize: '15px' } }, t('nudge.title') + ': ' + (nudgeTask.title || '')),
|
|
4582
|
+
h('button', {
|
|
4583
|
+
type: 'button',
|
|
4584
|
+
className: 'dkb-discard',
|
|
4585
|
+
style: { border: 'none', background: 'transparent', cursor: 'pointer', fontSize: '16px', padding: '0 4px' },
|
|
4586
|
+
onClick: () => { setNudgeTask(null); setNudgePrompt(''); setNudgeSuccess(false) },
|
|
4587
|
+
}, '✕'),
|
|
4588
|
+
),
|
|
4589
|
+
nudgeSuccess
|
|
4590
|
+
? h('div', {
|
|
4591
|
+
style: {
|
|
4592
|
+
padding: '12px',
|
|
4593
|
+
borderRadius: '6px',
|
|
4594
|
+
background: 'var(--dsw-alias-state-success-secondary, #dafbe1)',
|
|
4595
|
+
color: 'var(--dsw-alias-state-success-primary, #1a7f37)',
|
|
4596
|
+
marginBottom: '12px',
|
|
4597
|
+
textAlign: 'center',
|
|
4598
|
+
fontWeight: '600',
|
|
4599
|
+
},
|
|
4600
|
+
}, '✓ ' + t('nudge.success'))
|
|
4601
|
+
: h('div', null,
|
|
4602
|
+
h('textarea', {
|
|
4603
|
+
className: 'dkb-area',
|
|
4604
|
+
style: { width: '100%', minHeight: '90px', marginBottom: '12px', boxSizing: 'border-box' },
|
|
4605
|
+
placeholder: t('nudge.placeholder'),
|
|
4606
|
+
value: nudgePrompt,
|
|
4607
|
+
autoFocus: true,
|
|
4608
|
+
onChange: (e) => setNudgePrompt(e.target.value),
|
|
4609
|
+
onKeyDown: (e) => {
|
|
4610
|
+
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
|
|
4611
|
+
e.preventDefault();
|
|
4612
|
+
handleNudgeSubmit();
|
|
4613
|
+
}
|
|
4614
|
+
},
|
|
4615
|
+
}),
|
|
4616
|
+
h('div', { style: { display: 'flex', justifyContent: 'flex-end', gap: '8px' } },
|
|
4617
|
+
h('button', {
|
|
4618
|
+
type: 'button',
|
|
4619
|
+
className: 'dkb-discard',
|
|
4620
|
+
disabled: nudgeSending,
|
|
4621
|
+
onClick: () => { setNudgeTask(null); setNudgePrompt(''); setNudgeSuccess(false) },
|
|
4622
|
+
}, t('panel.close')),
|
|
4623
|
+
h('button', {
|
|
4624
|
+
type: 'button',
|
|
4625
|
+
className: 'dkb-save',
|
|
4626
|
+
disabled: !nudgePrompt.trim() || nudgeSending,
|
|
4627
|
+
onClick: handleNudgeSubmit,
|
|
4628
|
+
}, nudgeSending ? t('nudge.sending') : t('nudge.send')),
|
|
4629
|
+
),
|
|
4630
|
+
),
|
|
4631
|
+
),
|
|
4632
|
+
)
|
|
4633
|
+
: null;
|
|
4634
|
+
|
|
4269
4635
|
return h('div', { className: 'dkb-screen' },
|
|
4270
4636
|
style, bar,
|
|
4271
4637
|
error ? h('p', { className: 'dkb-failed' }, error) : null,
|
|
4272
4638
|
metrics ? metricsEl : archive ? archiveEl : columns,
|
|
4273
|
-
panel, dialogEl, moveDialogEl, batchEl, queueEl,
|
|
4639
|
+
panel, dialogEl, moveDialogEl, batchEl, queueEl, nudgeDialogEl,
|
|
4274
4640
|
)
|
|
4275
4641
|
}
|
|
4276
4642
|
|
package/lib/config.js
CHANGED
|
@@ -76,6 +76,11 @@ export const CONFIG_DEFAULTS = {
|
|
|
76
76
|
maxConcurrentSessions: 3,
|
|
77
77
|
workspaceClaimBoundaries: true,
|
|
78
78
|
autoSyncSessions: false,
|
|
79
|
+
verificationGateEnabled: true,
|
|
80
|
+
verificationCommand: 'npm test',
|
|
81
|
+
verificationTimeoutMs: 120000,
|
|
82
|
+
costMeterEnabled: true,
|
|
83
|
+
taskCostLimit: 0,
|
|
79
84
|
}
|
|
80
85
|
|
|
81
86
|
/** Field descriptions reused in schema and settings card. */
|
|
@@ -111,6 +116,11 @@ export const CONFIG_HINTS = {
|
|
|
111
116
|
maxConcurrentSessions: 'Maximum number of simultaneous active agent sessions on the board.',
|
|
112
117
|
workspaceClaimBoundaries: 'Enforce workspace ownership boundaries to prevent cross-workspace task execution.',
|
|
113
118
|
autoSyncSessions: 'Automatically sync and attach live background agent sessions to cards.',
|
|
119
|
+
verificationGateEnabled: 'Run automated verification gate (e.g. npm test) before allowing cards to move to Review.',
|
|
120
|
+
verificationCommand: 'Default verification command to execute in task worktree before Review (default: npm test).',
|
|
121
|
+
verificationTimeoutMs: 'Timeout in milliseconds for verification gate execution (default: 120000).',
|
|
122
|
+
costMeterEnabled: 'Enable token and cost telemetry integration via @goodandready/dsh-cost-meter.',
|
|
123
|
+
taskCostLimit: 'Cost threshold per task in USD; 0 disables threshold warnings.',
|
|
114
124
|
}
|
|
115
125
|
|
|
116
126
|
/** Setting key for column WIP limit; not all columns have limits. */
|