@goodandready/dsh-kanban 0.2.7 → 0.2.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/README.md +14 -0
- package/README.zh.md +11 -0
- package/lib/board-tool.js +1 -1
- package/lib/client.js +163 -30
- package/lib/commands.js +6 -11
- package/lib/config.js +31 -29
- package/lib/events.js +2 -2
- package/lib/filters.js +2 -2
- package/lib/index.js +62 -51
- package/lib/power.js +1 -1
- package/lib/progress-dump.js +3 -3
- package/lib/routes.js +48 -6
- package/lib/store.js +19 -7
- package/lib/sync.js +2 -2
- package/lib/task-service.js +56 -0
- package/lib/updater.js +338 -0
- package/lib/worktree.js +1 -1
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -296,3 +296,17 @@ deregistration.
|
|
|
296
296
|
## 📄 License
|
|
297
297
|
|
|
298
298
|
MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
|
|
299
|
+
|
|
300
|
+
## Public composition service
|
|
301
|
+
|
|
302
|
+
The server half exposes an optional dshKanban composition service. Its
|
|
303
|
+
createTask({ title, body, board, column, labels, owner, repo, issueNumber,
|
|
304
|
+
issueUrl, externalRef }) method validates the target board and column, stores
|
|
305
|
+
the task through the existing SQLite-backed path, and returns { ok, task,
|
|
306
|
+
taskId, alreadyExists }. A repeated externalRef returns the original card,
|
|
307
|
+
including after a restart.
|
|
308
|
+
|
|
309
|
+
This service only creates a card. It does not launch an agent, create a branch,
|
|
310
|
+
create a worktree, or accept credentials from a consumer. The canonical
|
|
311
|
+
consumer contract for task provisioning is
|
|
312
|
+
dsh-drives.task-provision.v1.
|
package/README.zh.md
CHANGED
|
@@ -181,3 +181,14 @@ npm test
|
|
|
181
181
|
## 📄 开源许可证
|
|
182
182
|
|
|
183
183
|
MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
|
|
184
|
+
|
|
185
|
+
## 公共组合服务
|
|
186
|
+
|
|
187
|
+
服务器端暴露可选的 dshKanban 组合服务。其
|
|
188
|
+
createTask({ title, body, board, column, labels, owner, repo, issueNumber,
|
|
189
|
+
issueUrl, externalRef }) 方法校验目标看板和列,通过现有的 SQLite 存储路径
|
|
190
|
+
保存任务,并返回 { ok, task, taskId, alreadyExists }。重复的 externalRef
|
|
191
|
+
会返回原有卡片,重启后仍然有效。
|
|
192
|
+
|
|
193
|
+
该服务只创建卡片,不启动代理、不创建分支或 worktree,也不接受调用方传入的
|
|
194
|
+
凭据。任务供应的规范消费者契约是 dsh-drives.task-provision.v1。
|
package/lib/board-tool.js
CHANGED
|
@@ -426,7 +426,7 @@ export function boardDecomposeDefinition({ store, gitea }) {
|
|
|
426
426
|
issueNumber = giteaIssue.number
|
|
427
427
|
issueUrl = giteaIssue.html_url || null
|
|
428
428
|
}
|
|
429
|
-
} catch {}
|
|
429
|
+
} catch { /* gitea issue creation best-effort */ }
|
|
430
430
|
}
|
|
431
431
|
|
|
432
432
|
const sub = store.createTask({
|
package/lib/client.js
CHANGED
|
@@ -115,7 +115,7 @@ window.__ModuleLoader__.load({
|
|
|
115
115
|
function rememberLaunch(board, choice) {
|
|
116
116
|
try {
|
|
117
117
|
window.localStorage.setItem(RECALL_KEY + (board || 'main'), JSON.stringify(choice))
|
|
118
|
-
} catch { /*
|
|
118
|
+
} catch { /* storage unavailable */ }
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
/**
|
|
@@ -707,7 +707,7 @@ window.__ModuleLoader__.load({
|
|
|
707
707
|
try {
|
|
708
708
|
const s = (ctx?.get && ctx.get('lanSettings')) || ctx?.settingsScope
|
|
709
709
|
s?.describe?.()?.load?.()
|
|
710
|
-
} catch (_) {}
|
|
710
|
+
} catch (_) { /* clipboard read best-effort */ }
|
|
711
711
|
}, 1000)
|
|
712
712
|
return () => clearTimer(timer)
|
|
713
713
|
}
|
|
@@ -721,7 +721,7 @@ window.__ModuleLoader__.load({
|
|
|
721
721
|
'.dkb-headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}' +
|
|
722
722
|
'.dkb-title{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}' +
|
|
723
723
|
'.dkb-sub{color:var(--dsw-alias-label-secondary);font-size:13px}' +
|
|
724
|
-
'.dkb-chevron{color:var(--dsw-alias-label-secondary);flex:none;transition:transform .16s}' +
|
|
724
|
+
'.dkb-chevron{color:var(--dsw-alias-label-secondary);display:inline-flex;align-items:center;justify-content:center;flex:none;transition:transform .16s}' +
|
|
725
725
|
'.dkb-chevronOpen{transform:rotate(180deg)}' +
|
|
726
726
|
'.dkb-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}' +
|
|
727
727
|
'.dkb-group{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;padding:14px 0 2px}' +
|
|
@@ -791,7 +791,7 @@ window.__ModuleLoader__.load({
|
|
|
791
791
|
'.dkb-cols{overscroll-behavior-inline:contain;scrollbar-color:var(--dsw-alias-border-l3) var(--dsw-alias-interactive-bg-hover);scrollbar-width:thin;flex:1;gap:12px;min-height:0;padding-bottom:6px;display:flex;overflow:auto hidden}' +
|
|
792
792
|
'.dkb-cols::-webkit-scrollbar{height:10px}' +
|
|
793
793
|
'.dkb-cols::-webkit-scrollbar-track{background:var(--dsw-alias-interactive-bg-hover);border-radius:999px}' +
|
|
794
|
-
'.dkb-cols::-webkit-scrollbar-thumb{background:var(--dsw-alias-border-l3);background-clip:content-box;border:2px solid
|
|
794
|
+
'.dkb-cols::-webkit-scrollbar-thumb{background:var(--dsw-alias-border-l3);background-clip:content-box;border:2px solid transparent;border-radius:999px}' +
|
|
795
795
|
'.dkb-col{flex:1 1 220px;min-width:220px;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:12px;flex-direction:column;min-height:0;display:flex;overflow:hidden}' +
|
|
796
796
|
'.dkb-colOver{border-color:var(--dsw-alias-border-l3)}' +
|
|
797
797
|
'.dkb-colHead{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;flex:none;align-items:center;gap:6px;padding:10px 12px;display:flex}' +
|
|
@@ -813,7 +813,7 @@ window.__ModuleLoader__.load({
|
|
|
813
813
|
'.dkb-count[data-col=review]{background:var(--dsw-alias-state-business-primary)}' +
|
|
814
814
|
'.dkb-count[data-col=deploy]{background:var(--dsw-alias-state-business-primary)}' +
|
|
815
815
|
'.dkb-count[data-col=done]{background:var(--dsw-alias-state-success-primary)}' +
|
|
816
|
-
'.dkb-countOver{background:var(--dsw-alias-state-error-primary);color
|
|
816
|
+
'.dkb-countOver{background:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-label-inverse,#fff)}' +
|
|
817
817
|
'.dkb-overdue{border-color:var(--dsw-alias-state-error-primary)}' +
|
|
818
818
|
'.dkb-dueOver{color:var(--dsw-alias-state-error-primary);font-weight:600}' +
|
|
819
819
|
'.dkb-list{flex-direction:column;flex:1;gap:8px;min-height:0;padding:2px 8px 10px;display:flex;overflow-y:auto}' +
|
|
@@ -858,7 +858,7 @@ window.__ModuleLoader__.load({
|
|
|
858
858
|
'.dkb-waiting{color:var(--dsw-alias-state-warn-primary);font-weight:600}' +
|
|
859
859
|
'.dkb-waitDot{border-radius:50%;flex:none;width:8px;height:8px;background:var(--dsw-alias-state-warn-primary);display:inline-block}' +
|
|
860
860
|
'.dkb-tag{white-space:nowrap;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-interactive-bg-hover);border:1px solid transparent;border-radius:999px;padding:1px 7px;font-size:11px;line-height:1.5;font-weight:500}' +
|
|
861
|
-
'.dkb-modal{position:absolute;inset:0;background:rgba(0,0,0,.4);display:flex;align-items:center;justify-content:center;padding:24px 24px 84px;box-sizing:border-box;z-index:70}' +
|
|
861
|
+
'.dkb-modal{position:absolute;inset:0;background:var(--dsw-alias-mask-bg,rgba(0,0,0,.4));display:flex;align-items:center;justify-content:center;padding:24px 24px 84px;box-sizing:border-box;z-index:70}' +
|
|
862
862
|
'.dkb-panel{width:min(720px,100%);max-height:100%;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:12px;padding:16px;box-sizing:border-box;overflow-y:auto;display:flex;flex-direction:column;gap:12px}' +
|
|
863
863
|
'@media (min-width: 960px){.dkb-panelWide{width:min(1100px,96%)}.dkb-drawerGrid{display:grid;grid-template-columns:300px 1fr;gap:20px;align-items:start}}' +
|
|
864
864
|
'.dkb-drawerSidebar{display:flex;flex-direction:column;gap:12px;border-right:1px solid var(--dsw-alias-border-l2);padding-right:16px}' +
|
|
@@ -866,7 +866,7 @@ window.__ModuleLoader__.load({
|
|
|
866
866
|
'.dkb-panelBody{white-space:pre-wrap;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:1.5;margin:0}' +
|
|
867
867
|
'.dkb-log{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:4px}' +
|
|
868
868
|
'.dkb-logRow{color:var(--dsw-alias-label-secondary);font-size:12px}' +
|
|
869
|
-
'.dkb-dialog{position:absolute;inset:0;background:rgba(0,0,0,.4);display:flex;align-items:center;justify-content:center;z-index:80}' +
|
|
869
|
+
'.dkb-dialog{position:absolute;inset:0;background:var(--dsw-alias-mask-bg,rgba(0,0,0,.4));display:flex;align-items:center;justify-content:center;z-index:80}' +
|
|
870
870
|
'.dkb-dialogBox{width:min(560px,94%);max-height:86%;overflow-y:auto;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:12px;padding:16px;display:flex;flex-direction:column;gap:10px}' +
|
|
871
871
|
'.dkb-issue{appearance:none;font:inherit;text-align:left;cursor:pointer;background:0 0;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:8px 10px;color:var(--dsw-alias-label-primary);font-size:13px}' +
|
|
872
872
|
'.dkb-issueUsed{opacity:.5}' +
|
|
@@ -880,15 +880,26 @@ window.__ModuleLoader__.load({
|
|
|
880
880
|
'.dkb-quickFilterBar{display:flex;gap:6px;align-items:center;flex-wrap:wrap;padding:4px 0}' +
|
|
881
881
|
'.dkb-filterChip{appearance:none;font:inherit;cursor:pointer;background:var(--dsw-alias-interactive-bg-hover);border:1px solid var(--dsw-alias-border-l2);border-radius:999px;padding:3px 10px;font-size:12px;color:var(--dsw-alias-label-secondary);transition:all .15s}' +
|
|
882
882
|
'.dkb-filterChip:hover{background:var(--dsw-alias-interactive-bg-active);color:var(--dsw-alias-label-primary)}' +
|
|
883
|
-
'.dkb-filterChipActive{background:var(--dsw-alias-state-business-primary);color
|
|
883
|
+
'.dkb-filterChipActive{background:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-label-inverse,#fff);border-color:transparent;font-weight:600}' +
|
|
884
884
|
'.dkb-epicTag{background:var(--dsw-alias-state-info-bg,#ddf4ff);color:var(--dsw-alias-state-info-primary,#0969da);font-weight:600}' +
|
|
885
885
|
'.dkb-progressBar{height:4px;background:var(--dsw-alias-border-l2);border-radius:2px;overflow:hidden;margin:4px 0}' +
|
|
886
886
|
'.dkb-progressFill{height:100%;background:var(--dsw-alias-state-success-primary,#16a34a);transition:width .2s}' +
|
|
887
|
-
'.dkb-consolePre{background
|
|
887
|
+
'.dkb-consolePre{background:var(--dsw-alias-bg-layer-1,#0d1117);color:var(--dsw-alias-state-info-primary,#58a6ff);border:1px solid var(--dsw-alias-border-l2);padding:12px;border-radius:8px;font-family:monospace;font-size:11px;max-height:260px;overflow-y:auto;white-space:pre-wrap}'
|
|
888
888
|
|
|
889
889
|
|
|
890
890
|
|
|
891
891
|
const en = {
|
|
892
|
+
'updater.title': 'Plugin Updates',
|
|
893
|
+
'updater.check': 'Check updates',
|
|
894
|
+
'updater.checking': 'Checking…',
|
|
895
|
+
'updater.current': 'Current version',
|
|
896
|
+
'updater.latest': 'Latest version',
|
|
897
|
+
'updater.upToDate': 'Up to date',
|
|
898
|
+
'updater.updateNow': 'Update to v{version}',
|
|
899
|
+
'updater.updating': 'Updating…',
|
|
900
|
+
'updater.success': 'Plugin updated successfully! Please restart DSH service.',
|
|
901
|
+
'updater.failed': 'Update check failed: ',
|
|
902
|
+
|
|
892
903
|
'title': 'Kanban',
|
|
893
904
|
'subtitle': 'Task board: Gitea issues and a session per task',
|
|
894
905
|
'section.label': 'Kanban',
|
|
@@ -1256,6 +1267,17 @@ window.__ModuleLoader__.load({
|
|
|
1256
1267
|
}
|
|
1257
1268
|
|
|
1258
1269
|
const zh = {
|
|
1270
|
+
'updater.title': '插件更新',
|
|
1271
|
+
'updater.check': '检查更新',
|
|
1272
|
+
'updater.checking': '正在检查…',
|
|
1273
|
+
'updater.current': '当前版本',
|
|
1274
|
+
'updater.latest': '最新版本',
|
|
1275
|
+
'updater.upToDate': '已是最新',
|
|
1276
|
+
'updater.updateNow': '一键更新至 v{version}',
|
|
1277
|
+
'updater.updating': '正在更新…',
|
|
1278
|
+
'updater.success': '插件更新成功!请重启 DSH 服务以生效。',
|
|
1279
|
+
'updater.failed': '更新检查失败:',
|
|
1280
|
+
|
|
1259
1281
|
'title': '看板',
|
|
1260
1282
|
'subtitle': '任务看板:Gitea issue,每个任务一个会话',
|
|
1261
1283
|
'section.label': '看板',
|
|
@@ -1671,16 +1693,126 @@ window.__ModuleLoader__.load({
|
|
|
1671
1693
|
return 'hint.' + spec.field
|
|
1672
1694
|
}
|
|
1673
1695
|
|
|
1674
|
-
|
|
1696
|
+
let ChevronIcon = null
|
|
1697
|
+
try {
|
|
1698
|
+
const primitives = typeof require === 'function' ? require('@deepseek-ai/dsh-client-ui-primitives') : null
|
|
1699
|
+
ChevronIcon = primitives && (primitives.IconChevronDownOutline14 || primitives.IconChevronDown)
|
|
1700
|
+
} catch (_) {
|
|
1701
|
+
ChevronIcon = null
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
function FallbackChevron(props) {
|
|
1675
1705
|
return h('svg', {
|
|
1676
|
-
|
|
1677
|
-
|
|
1706
|
+
width: 14,
|
|
1707
|
+
height: 14,
|
|
1708
|
+
viewBox: '0 0 14 14',
|
|
1709
|
+
fill: 'none',
|
|
1710
|
+
'aria-hidden': 'true',
|
|
1678
1711
|
}, h('path', {
|
|
1679
|
-
d: '
|
|
1680
|
-
|
|
1712
|
+
d: 'M3.5 5.25L7 8.75L10.5 5.25',
|
|
1713
|
+
stroke: 'currentColor',
|
|
1714
|
+
strokeWidth: '1.5',
|
|
1715
|
+
strokeLinecap: 'round',
|
|
1716
|
+
strokeLinejoin: 'round',
|
|
1681
1717
|
}))
|
|
1682
1718
|
}
|
|
1683
1719
|
|
|
1720
|
+
function Chevron(props) {
|
|
1721
|
+
const Icon = ChevronIcon || FallbackChevron
|
|
1722
|
+
return h('span', {
|
|
1723
|
+
className: 'dkb-chevron' + (props.open ? ' dkb-chevronOpen' : ''),
|
|
1724
|
+
'aria-hidden': 'true',
|
|
1725
|
+
}, h(Icon))
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
|
|
1729
|
+
function PluginUpdaterSection(props) {
|
|
1730
|
+
const t = props.t || fallbackT
|
|
1731
|
+
const [status, setStatus] = React.useState(null)
|
|
1732
|
+
const [loading, setLoading] = React.useState(false)
|
|
1733
|
+
const [updating, setUpdating] = React.useState(false)
|
|
1734
|
+
const [msg, setMsg] = React.useState(null)
|
|
1735
|
+
|
|
1736
|
+
const checkUpdate = React.useCallback(async () => {
|
|
1737
|
+
setLoading(true)
|
|
1738
|
+
setMsg(null)
|
|
1739
|
+
try {
|
|
1740
|
+
const res = await fetch('/api/dsh-kanban/update', {
|
|
1741
|
+
headers: { accept: 'application/json' },
|
|
1742
|
+
cache: 'no-store',
|
|
1743
|
+
})
|
|
1744
|
+
if (!res.ok) throw new Error('HTTP ' + res.status)
|
|
1745
|
+
const data = await res.json()
|
|
1746
|
+
setStatus(data)
|
|
1747
|
+
} catch (e) {
|
|
1748
|
+
setMsg({ ok: false, text: (t('updater.failed') || 'Update check failed: ') + (e?.message || String(e)) })
|
|
1749
|
+
} finally {
|
|
1750
|
+
setLoading(false)
|
|
1751
|
+
}
|
|
1752
|
+
}, [t])
|
|
1753
|
+
|
|
1754
|
+
const onUpdateNow = async () => {
|
|
1755
|
+
setUpdating(true)
|
|
1756
|
+
setMsg(null)
|
|
1757
|
+
try {
|
|
1758
|
+
const res = await fetch('/api/dsh-kanban/update', {
|
|
1759
|
+
method: 'POST',
|
|
1760
|
+
headers: {
|
|
1761
|
+
'x-dsh-plugin-update': '1',
|
|
1762
|
+
'content-type': 'application/json',
|
|
1763
|
+
},
|
|
1764
|
+
})
|
|
1765
|
+
const data = await res.json()
|
|
1766
|
+
if (!res.ok) throw new Error(data?.error || 'HTTP ' + res.status)
|
|
1767
|
+
setStatus(data)
|
|
1768
|
+
setMsg({ ok: true, text: t('updater.success') || 'Plugin updated successfully! Please restart DSH service.' })
|
|
1769
|
+
} catch (e) {
|
|
1770
|
+
setMsg({ ok: false, text: e?.message || String(e) })
|
|
1771
|
+
} finally {
|
|
1772
|
+
setUpdating(false)
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
React.useEffect(() => {
|
|
1777
|
+
checkUpdate()
|
|
1778
|
+
}, [checkUpdate])
|
|
1779
|
+
|
|
1780
|
+
const currentVer = status?.currentVersion || '0.2.7'
|
|
1781
|
+
const latestVer = status?.latestVersion
|
|
1782
|
+
const updateAvailable = Boolean(status?.updateAvailable && latestVer && latestVer !== currentVer)
|
|
1783
|
+
|
|
1784
|
+
return h('div', { className: 'dkb-section-card', key: 'g-updater' },
|
|
1785
|
+
h('div', { className: 'dkb-section-title' },
|
|
1786
|
+
h('span', null, '🔄 ' + t('updater.title')),
|
|
1787
|
+
h('span', { className: 'dkb-section-badge' + (!updateAvailable ? ' dkb-section-badge-ok' : '') },
|
|
1788
|
+
updateAvailable ? 'v' + latestVer + ' available' : 'v' + currentVer + ' (' + t('updater.upToDate') + ')'
|
|
1789
|
+
),
|
|
1790
|
+
),
|
|
1791
|
+
h('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexWrap: 'wrap', gap: '8px', marginTop: '6px' } },
|
|
1792
|
+
h('span', { style: { fontSize: '12px', color: 'var(--dsw-alias-label-secondary)' } },
|
|
1793
|
+
updateAvailable ? (t('updater.latest') + ': v' + latestVer) : (t('updater.current') + ': v' + currentVer)
|
|
1794
|
+
),
|
|
1795
|
+
h('div', { style: { display: 'flex', gap: '8px' } },
|
|
1796
|
+
updateAvailable ? h('button', {
|
|
1797
|
+
type: 'button',
|
|
1798
|
+
className: 'dkb-save',
|
|
1799
|
+
style: { padding: '4px 10px', fontSize: '12px' },
|
|
1800
|
+
onClick: onUpdateNow,
|
|
1801
|
+
disabled: updating || loading,
|
|
1802
|
+
}, updating ? (t('updater.updating') || 'Updating…') : (t('updater.updateNow') || 'Update now').replace('{version}', latestVer)) : null,
|
|
1803
|
+
h('button', {
|
|
1804
|
+
type: 'button',
|
|
1805
|
+
className: 'dkb-discard',
|
|
1806
|
+
style: { padding: '4px 10px', fontSize: '12px' },
|
|
1807
|
+
onClick: checkUpdate,
|
|
1808
|
+
disabled: loading || updating,
|
|
1809
|
+
}, loading ? (t('updater.checking') || 'Checking…') : (t('updater.check') || 'Check updates')),
|
|
1810
|
+
),
|
|
1811
|
+
),
|
|
1812
|
+
msg ? h('p', { className: msg.ok ? 'dkb-note' : 'dkb-failed', style: { margin: '4px 0 0 0', fontSize: '12px' } }, msg.text) : null,
|
|
1813
|
+
)
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1684
1816
|
function KanbanSettingsCard(props) {
|
|
1685
1817
|
const ctx = props.ctx
|
|
1686
1818
|
const t = props.t || fallbackT
|
|
@@ -1835,6 +1967,7 @@ window.__ModuleLoader__.load({
|
|
|
1835
1967
|
else if (!writable) note = h('p', { className: 'dkb-note', role: 'status' }, t('settings.readOnly'))
|
|
1836
1968
|
else if (saved) note = h('p', { className: 'dkb-note', role: 'status' }, t('settings.saved'))
|
|
1837
1969
|
else note = h('p', { className: 'dkb-note' })
|
|
1970
|
+
rows.push(h(PluginUpdaterSection, { t, key: 'updater' }))
|
|
1838
1971
|
rows.push(h('div', { className: 'dkb-foot', key: 'foot' },
|
|
1839
1972
|
note,
|
|
1840
1973
|
h('button', { type: 'button', className: 'dkb-discard', disabled: !dirty || saving, onClick: discard }, t('discard')),
|
|
@@ -1879,7 +2012,7 @@ window.__ModuleLoader__.load({
|
|
|
1879
2012
|
tabIndex: 0,
|
|
1880
2013
|
draggable: true,
|
|
1881
2014
|
onDragStart: (e) => {
|
|
1882
|
-
try { e.dataTransfer.setData('text/plain', task.id) } catch { /*
|
|
2015
|
+
try { e.dataTransfer.setData('text/plain', task.id) } catch { /* dataTransfer unsupported */ }
|
|
1883
2016
|
props.onDragStart(task)
|
|
1884
2017
|
},
|
|
1885
2018
|
onClick: () => props.onOpen(task),
|
|
@@ -2139,7 +2272,7 @@ window.__ModuleLoader__.load({
|
|
|
2139
2272
|
es.addEventListener('task:change', scheduleReload)
|
|
2140
2273
|
es.onerror = () => {
|
|
2141
2274
|
if (es) {
|
|
2142
|
-
try { es.close() } catch {}
|
|
2275
|
+
try { es.close() } catch { /* eventsource close best-effort */ }
|
|
2143
2276
|
es = null
|
|
2144
2277
|
}
|
|
2145
2278
|
if (!reconnectTimer) {
|
|
@@ -2149,7 +2282,7 @@ window.__ModuleLoader__.load({
|
|
|
2149
2282
|
}, 5000)
|
|
2150
2283
|
}
|
|
2151
2284
|
}
|
|
2152
|
-
} catch {}
|
|
2285
|
+
} catch { /* eventsource setup best-effort */ }
|
|
2153
2286
|
}
|
|
2154
2287
|
|
|
2155
2288
|
connect()
|
|
@@ -2157,7 +2290,7 @@ window.__ModuleLoader__.load({
|
|
|
2157
2290
|
return () => {
|
|
2158
2291
|
if (reconnectTimer) clearTimeout(reconnectTimer)
|
|
2159
2292
|
if (es) {
|
|
2160
|
-
try { es.close() } catch {}
|
|
2293
|
+
try { es.close() } catch { /* eventsource close best-effort */ }
|
|
2161
2294
|
es = null
|
|
2162
2295
|
}
|
|
2163
2296
|
}
|
|
@@ -2779,7 +2912,7 @@ window.__ModuleLoader__.load({
|
|
|
2779
2912
|
method: 'POST',
|
|
2780
2913
|
body: JSON.stringify({ reasoningEffort: val }),
|
|
2781
2914
|
})
|
|
2782
|
-
} catch {}
|
|
2915
|
+
} catch { /* update reasoning effort best-effort */ }
|
|
2783
2916
|
},
|
|
2784
2917
|
},
|
|
2785
2918
|
h('option', { value: '' }, t('reasoning.default')),
|
|
@@ -3179,8 +3312,8 @@ window.__ModuleLoader__.load({
|
|
|
3179
3312
|
? h('pre', {
|
|
3180
3313
|
style: {
|
|
3181
3314
|
margin: 0, padding: 8, maxHeight: 280, overflowY: 'auto',
|
|
3182
|
-
background: 'var(--dsw-alias-bg-layer-2
|
|
3183
|
-
color: 'var(--dsw-alias-label-primary
|
|
3315
|
+
background: 'var(--dsw-alias-bg-layer-2)',
|
|
3316
|
+
color: 'var(--dsw-alias-label-primary)',
|
|
3184
3317
|
fontSize: 11, fontFamily: 'monospace', borderRadius: 4, whiteSpace: 'pre-wrap',
|
|
3185
3318
|
border: '1px solid var(--dsw-alias-border-l1)',
|
|
3186
3319
|
},
|
|
@@ -3204,7 +3337,7 @@ window.__ModuleLoader__.load({
|
|
|
3204
3337
|
? h('button', {
|
|
3205
3338
|
type: 'button', className: 'dkb-save',
|
|
3206
3339
|
disabled: merging,
|
|
3207
|
-
style: { background: 'var(--dsw-alias-
|
|
3340
|
+
style: { background: 'var(--dsw-alias-state-success-primary, #16a34a)', color: 'var(--dsw-alias-label-inverse, #fff)' },
|
|
3208
3341
|
onClick: async () => {
|
|
3209
3342
|
try {
|
|
3210
3343
|
setMerging(true)
|
|
@@ -3234,7 +3367,7 @@ window.__ModuleLoader__.load({
|
|
|
3234
3367
|
? h('button', {
|
|
3235
3368
|
type: 'button', className: 'dkb-save',
|
|
3236
3369
|
disabled: prCreating,
|
|
3237
|
-
style: { background: 'var(--dsw-alias-state-info-primary, #0969da)', color: '#fff' },
|
|
3370
|
+
style: { background: 'var(--dsw-alias-state-info-primary, #0969da)', color: 'var(--dsw-alias-label-inverse, #fff)' },
|
|
3238
3371
|
onClick: async () => {
|
|
3239
3372
|
setPrCreating(true)
|
|
3240
3373
|
try {
|
|
@@ -3252,7 +3385,7 @@ window.__ModuleLoader__.load({
|
|
|
3252
3385
|
createdPr
|
|
3253
3386
|
? h('a', {
|
|
3254
3387
|
className: 'dkb-save',
|
|
3255
|
-
style: { background: 'var(--dsw-alias-state-success-primary, #16a34a)', color: '#fff', textDecoration: 'none', display: 'inline-flex', alignItems: 'center' },
|
|
3388
|
+
style: { background: 'var(--dsw-alias-state-success-primary, #16a34a)', color: 'var(--dsw-alias-label-inverse, #fff)', textDecoration: 'none', display: 'inline-flex', alignItems: 'center' },
|
|
3256
3389
|
href: createdPr.html_url,
|
|
3257
3390
|
target: '_blank', rel: 'noopener noreferrer',
|
|
3258
3391
|
}, '↗ ' + t('pr.view') + ` #${createdPr.number}`)
|
|
@@ -3600,7 +3733,7 @@ window.__ModuleLoader__.load({
|
|
|
3600
3733
|
(openTask.worktree || openTask.rejectReason || (Array.isArray(openTask.runs) && openTask.runs.length > 0)) ? h('button', {
|
|
3601
3734
|
type: 'button',
|
|
3602
3735
|
className: 'dkb-secondary',
|
|
3603
|
-
style: { marginRight: '8px', border: '1px solid var(--dsw-alias-border-
|
|
3736
|
+
style: { marginRight: '8px', border: '1px solid var(--dsw-alias-border-l2)' },
|
|
3604
3737
|
disabled: starting || model === '',
|
|
3605
3738
|
onClick: async () => {
|
|
3606
3739
|
setStarting(true)
|
|
@@ -4082,7 +4215,7 @@ window.__ModuleLoader__.load({
|
|
|
4082
4215
|
})
|
|
4083
4216
|
setTask(out.task)
|
|
4084
4217
|
setOpen(false)
|
|
4085
|
-
} catch { /*
|
|
4218
|
+
} catch { /* board handles error; keep header intact */ }
|
|
4086
4219
|
}, [task])
|
|
4087
4220
|
|
|
4088
4221
|
if (!task) return null
|
|
@@ -4125,7 +4258,7 @@ window.__ModuleLoader__.load({
|
|
|
4125
4258
|
setTask(out.task)
|
|
4126
4259
|
setNote('')
|
|
4127
4260
|
setOpen(false)
|
|
4128
|
-
} catch { /*
|
|
4261
|
+
} catch { /* board handles error; keep header intact */ } finally {
|
|
4129
4262
|
setSaving(false)
|
|
4130
4263
|
}
|
|
4131
4264
|
},
|
|
@@ -4356,7 +4489,7 @@ window.__ModuleLoader__.load({
|
|
|
4356
4489
|
if (registeredInSlot || (res !== null && res !== false && res !== undefined)) {
|
|
4357
4490
|
return entry.name
|
|
4358
4491
|
}
|
|
4359
|
-
} catch { /*
|
|
4492
|
+
} catch { /* slot missing in build — try next */ }
|
|
4360
4493
|
}
|
|
4361
4494
|
return undefined
|
|
4362
4495
|
}
|
|
@@ -4364,7 +4497,7 @@ window.__ModuleLoader__.load({
|
|
|
4364
4497
|
helpers.createToggle = createToggle
|
|
4365
4498
|
|
|
4366
4499
|
function apply(ctx) {
|
|
4367
|
-
ctx.effect(() => ctx.locale.register(NS, { en, zh }), 'dsh-kanban:
|
|
4500
|
+
ctx.effect(() => ctx.locale.register(NS, { en, zh }), 'dsh-kanban: locales')
|
|
4368
4501
|
|
|
4369
4502
|
refreshMirrorUntilVisible(ctx)
|
|
4370
4503
|
const SettingsErrorBoundary = createErrorBoundary()
|
|
@@ -4377,7 +4510,7 @@ window.__ModuleLoader__.load({
|
|
|
4377
4510
|
ctx.effect(() => {
|
|
4378
4511
|
const off = [mountSidebarEntry(toggle, fallbackT('section.label')), mountBoard(ctx, toggle)]
|
|
4379
4512
|
return () => { for (const dispose of off) dispose() }
|
|
4380
|
-
}, 'dsh-kanban:
|
|
4513
|
+
}, 'dsh-kanban: shell board')
|
|
4381
4514
|
|
|
4382
4515
|
const chipSlot = registerFirst(ctx, [
|
|
4383
4516
|
{ name: 'conversation.session.header.utilities', id: '@goodandready/dsh-kanban.chip', order: 26, locale: NS, inject: () => ({ ctx, toggle }) },
|
package/lib/commands.js
CHANGED
|
@@ -23,28 +23,23 @@ const TABLE = {
|
|
|
23
23
|
project: {
|
|
24
24
|
'backlog': { stops: true },
|
|
25
25
|
'in-progress': {
|
|
26
|
-
instruction: '
|
|
26
|
+
instruction: 'Start or continue implementing this task following standard workflow.',
|
|
27
27
|
},
|
|
28
28
|
'review': {
|
|
29
|
-
instruction: '
|
|
30
|
-
+ 'и запроси проверку.',
|
|
29
|
+
instruction: 'Prepare work for review: mark pull request ready for review and request code review.',
|
|
31
30
|
},
|
|
32
31
|
'deploy': {
|
|
33
|
-
|
|
34
|
-
// прямо, иначе агент будет спрашивать разрешение, которое уже получил.
|
|
35
|
-
instruction: 'Влей pull request и выкати. Перенос карточки в Deploy — это и есть '
|
|
36
|
-
+ 'явное «ок» человека на deploy, отдельно спрашивать не нужно.',
|
|
32
|
+
instruction: 'Merge pull request and deploy. Moving card to Deploy serves as explicit OK for deploy; do not ask separately.',
|
|
37
33
|
},
|
|
38
34
|
'cleanup': {
|
|
39
|
-
instruction: '
|
|
40
|
-
+ 'запиши сделанное в issue.',
|
|
35
|
+
instruction: 'Clean up after task: delete branch in Gitea, worktree, and local branch; record summary in issue.',
|
|
41
36
|
},
|
|
42
37
|
'done': { humanOnly: true },
|
|
43
38
|
},
|
|
44
39
|
simple: {
|
|
45
40
|
'backlog': { stops: true },
|
|
46
|
-
'in-progress': { instruction: '
|
|
47
|
-
'review': { instruction: '
|
|
41
|
+
'in-progress': { instruction: 'Start or continue work on this task.' },
|
|
42
|
+
'review': { instruction: 'Present result and wait for human response.' },
|
|
48
43
|
'done': { humanOnly: true },
|
|
49
44
|
},
|
|
50
45
|
}
|
package/lib/config.js
CHANGED
|
@@ -30,7 +30,7 @@ export const COLUMN_ORDER = ['backlog', 'in-progress', 'review', 'deploy', 'clea
|
|
|
30
30
|
export const BOARD_KINDS = ['project', 'simple']
|
|
31
31
|
|
|
32
32
|
/** Вид доски по умолчанию: неизвестный вид откатывается сюда, а не в пустоту. */
|
|
33
|
-
|
|
33
|
+
const DEFAULT_KIND = 'project'
|
|
34
34
|
|
|
35
35
|
const COLUMNS_BY_KIND = {
|
|
36
36
|
project: COLUMN_ORDER,
|
|
@@ -49,8 +49,8 @@ export function normalizeKind(kind) {
|
|
|
49
49
|
|
|
50
50
|
/** Заводимые при первом запуске доски. Их ровно две, и обе нужны сразу. */
|
|
51
51
|
export const DEFAULT_BOARDS = [
|
|
52
|
-
{ id: 'main', title: '
|
|
53
|
-
{ id: 'simple', title: '
|
|
52
|
+
{ id: 'main', title: 'Project Board', kind: 'project' },
|
|
53
|
+
{ id: 'simple', title: 'Simple Board', kind: 'simple' },
|
|
54
54
|
]
|
|
55
55
|
|
|
56
56
|
/** Значения по умолчанию. Из них же собирается схема в `lib/index.js`. */
|
|
@@ -59,7 +59,7 @@ export const CONFIG_DEFAULTS = {
|
|
|
59
59
|
giteaTokenRef: 'GITEA_TOKEN',
|
|
60
60
|
defaultProjectRoot: '',
|
|
61
61
|
startPrompt: '',
|
|
62
|
-
replyInstruction: '
|
|
62
|
+
replyInstruction: '',
|
|
63
63
|
wipInProgress: 3,
|
|
64
64
|
wipReview: 0,
|
|
65
65
|
syncIntervalSec: 120,
|
|
@@ -86,31 +86,33 @@ export const CONFIG_DEFAULTS = {
|
|
|
86
86
|
|
|
87
87
|
/** Пояснения к полям; повторно используются схемой и карточкой настроек. */
|
|
88
88
|
export const CONFIG_HINTS = {
|
|
89
|
-
giteaUrl: '
|
|
90
|
-
giteaTokenRef: '
|
|
91
|
-
defaultProjectRoot: '
|
|
92
|
-
startPrompt: '
|
|
93
|
-
replyInstruction: '
|
|
94
|
-
wipInProgress: '
|
|
95
|
-
wipReview: '
|
|
96
|
-
syncIntervalSec: '
|
|
97
|
-
staleAfterMin: '
|
|
98
|
-
giteaOwner: '
|
|
99
|
-
columnNames: '
|
|
100
|
-
watchRepos: '
|
|
101
|
-
archiveAfterDays: '
|
|
102
|
-
boardToolEnabled: '
|
|
103
|
-
webhookSecretRef: '
|
|
104
|
-
pushToGitea: '
|
|
105
|
-
worktreeIsolation: '
|
|
106
|
-
worktreeRoot: '
|
|
107
|
-
cronEnabled: '
|
|
108
|
-
preventIdleSleep: '
|
|
109
|
-
progressDumpEnabled: '
|
|
110
|
-
permissionGateEnabled: '
|
|
111
|
-
sessionDefaultPermission: '
|
|
112
|
-
announceToAgent: '
|
|
113
|
-
|
|
89
|
+
giteaUrl: 'Gitea or Forgejo instance URL, e.g. https://gitea.example.com. Empty disables import.',
|
|
90
|
+
giteaTokenRef: 'Name of DSH credential holding the access token. Do not enter raw token.',
|
|
91
|
+
defaultProjectRoot: 'Directory containing project repositories. Empty uses harness working directory.',
|
|
92
|
+
startPrompt: 'Template for initial message to agent session. Empty uses built-in default.',
|
|
93
|
+
replyInstruction: 'Instruction appended to initial message (e.g. language/format constraints). Empty adds nothing.',
|
|
94
|
+
wipInProgress: 'Maximum task cards allowed in "In Progress" column. 0 means unlimited.',
|
|
95
|
+
wipReview: 'Maximum task cards allowed in "Review" column. 0 means unlimited.',
|
|
96
|
+
syncIntervalSec: 'Interval in seconds for polling Gitea synchronization. 0 disables polling.',
|
|
97
|
+
staleAfterMin: 'Minutes of inactivity before active task session is flagged stale. 0 disables.',
|
|
98
|
+
giteaOwner: 'Default Gitea organization/user to track. Auto-detected if single organization.',
|
|
99
|
+
columnNames: 'Custom column title mappings as "id=Name" pairs comma-separated.',
|
|
100
|
+
watchRepos: 'Filter sync to specific repository names comma-separated. Empty watches all repos.',
|
|
101
|
+
archiveAfterDays: 'Days completed tasks remain before automatic archiving. 0 disables.',
|
|
102
|
+
boardToolEnabled: 'Allow autonomous agent to advance cards via board_move tool.',
|
|
103
|
+
webhookSecretRef: 'Name of DSH credential holding Gitea webhook secret.',
|
|
104
|
+
pushToGitea: 'Automatically close corresponding Gitea issue when card moves to Done.',
|
|
105
|
+
worktreeIsolation: 'Isolate task sessions in dedicated git worktrees to prevent workspace collisions.',
|
|
106
|
+
worktreeRoot: 'Custom root directory for task worktrees. Empty defaults to $DSH_HOME/worktrees.',
|
|
107
|
+
cronEnabled: 'Enable background scheduled task execution via cron expressions.',
|
|
108
|
+
preventIdleSleep: 'Prevent host sleep while task agent sessions are running.',
|
|
109
|
+
progressDumpEnabled: 'Enable PROGRESSDUMP state checkpoints between agent sessions.',
|
|
110
|
+
permissionGateEnabled: 'Require human confirmation before launching elevated permission tasks.',
|
|
111
|
+
sessionDefaultPermission: 'Default permission tier for task sessions (read-only, workspace-write, full).',
|
|
112
|
+
announceToAgent: 'Post stage change announcement messages directly into task agent session.',
|
|
113
|
+
maxConcurrentSessions: 'Maximum number of simultaneous active agent sessions on the board.',
|
|
114
|
+
workspaceClaimBoundaries: 'Enforce workspace ownership boundaries to prevent cross-workspace task execution.',
|
|
115
|
+
autoSyncSessions: 'Automatically sync and attach live background agent sessions to cards.',
|
|
114
116
|
}
|
|
115
117
|
|
|
116
118
|
/** Поле настроек с пределом числа карточек; задан не у всех колонок. */
|
package/lib/events.js
CHANGED
|
@@ -42,7 +42,7 @@ export class KanbanEventHub {
|
|
|
42
42
|
|
|
43
43
|
const cleanup = () => {
|
|
44
44
|
this.clients.delete(res)
|
|
45
|
-
try { res.end() } catch {}
|
|
45
|
+
try { res.end() } catch { /* client already disconnected */ }
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
if (typeof req.on === 'function') req.on('close', cleanup)
|
|
@@ -82,7 +82,7 @@ export class KanbanEventHub {
|
|
|
82
82
|
try {
|
|
83
83
|
client.write('event: close\ndata: {}\n\n')
|
|
84
84
|
client.end()
|
|
85
|
-
} catch {}
|
|
85
|
+
} catch { /* client already closed */ }
|
|
86
86
|
}
|
|
87
87
|
this.clients.clear()
|
|
88
88
|
}
|
package/lib/filters.js
CHANGED
|
@@ -41,7 +41,7 @@ export const NOBODY = '—'
|
|
|
41
41
|
export const NO_MILESTONE = '—'
|
|
42
42
|
|
|
43
43
|
/** Порядок приоритетов в отборе: срочное сверху. */
|
|
44
|
-
|
|
44
|
+
const PRIORITY_ORDER = ['critical', 'high', 'medium', 'low']
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
47
|
* Порядок известных пространств.
|
|
@@ -279,7 +279,7 @@ export function liveRank(task) {
|
|
|
279
279
|
}
|
|
280
280
|
|
|
281
281
|
/** Вес приоритета при подъёме: срочное впереди тихого. */
|
|
282
|
-
|
|
282
|
+
function priorityWeight(priority) {
|
|
283
283
|
const at = PRIORITY_ORDER.indexOf(priority)
|
|
284
284
|
return at === -1 ? PRIORITY_ORDER.length : at
|
|
285
285
|
}
|