@goodandready/dsh-kanban 0.2.7 → 0.2.9

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 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 #0000;border-radius:999px}' +
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:#fff}' +
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:#fff;border-color:transparent;font-weight:600}' +
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:#0d1117;color:#58a6ff;padding:12px;border-radius:8px;font-family:monospace;font-size:11px;max-height:260px;overflow-y:auto;white-space:pre-wrap;border:1px solid #30363d}'
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
- function Chevron(props) {
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
- className: 'dkb-chevron' + (props.open ? ' dkb-chevronOpen' : ''),
1677
- width: 16, height: 16, viewBox: '0 0 16 16', 'aria-hidden': 'true',
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: 'M4 6l4 4 4-4', fill: 'none', stroke: 'currentColor', strokeWidth: '1.5',
1680
- strokeLinecap: 'round', strokeLinejoin: 'round',
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, #1e1e1e)',
3183
- color: 'var(--dsw-alias-label-primary, #ddd)',
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-bg-success, #16a34a)', color: '#fff' },
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-color-primary, #444)' },
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 { /* причину показывает доска; шапку не ломаем */ } finally {
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: 'Доведи работу до ревью: сними с pull request пометку черновика '
30
- + 'и запроси проверку.',
29
+ instruction: 'Prepare work for review: mark pull request ready for review and request code review.',
31
30
  },
32
31
  'deploy': {
33
- // Воркфлоу требует явного «ок» человека перед deploy. Сказать это надо
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: 'Прибери за задачей: удали ветку в Gitea, worktree и локальную ветку, '
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
  }
@@ -101,7 +96,7 @@ export function dispatchMove({ agents, task, column, kind = 'project', createMes
101
96
  try {
102
97
  agent.cancel({ kind: 'user' })
103
98
  } catch (error) {
104
- logger?.warn?.(`dsh-kanban: ход по задаче ${task.id} не прервался: ${error?.message}`)
99
+ logger?.warn?.(`dsh-kanban: agent turn for task ${task.id} was not interrupted: ${error?.message}`)
105
100
  return { acted: 'idle' }
106
101
  }
107
102
  return { acted: 'stopped' }
@@ -138,7 +133,7 @@ export function stopWork({ agents, task, logger }) {
138
133
  try {
139
134
  agent.cancel({ kind: 'user' })
140
135
  } catch (error) {
141
- logger?.warn?.(`dsh-kanban: ход по задаче ${task.id} не прервался: ${error?.message}`)
136
+ logger?.warn?.(`dsh-kanban: agent turn for task ${task.id} was not interrupted: ${error?.message}`)
142
137
  return { acted: 'idle' }
143
138
  }
144
139
  return { acted: 'stopped' }
@@ -146,9 +141,9 @@ export function stopWork({ agents, task, logger }) {
146
141
 
147
142
  /** Пояснения к остановке в журнале задачи. */
148
143
  export const STOP_DETAIL = {
149
- stopped: 'человек остановил ход агента',
150
- idle: 'агент не работал, останавливать было нечего',
151
- 'no-session': 'у задачи нет сессии',
144
+ stopped: 'user stopped agent turn',
145
+ idle: 'agent was idle, nothing to stop',
146
+ 'no-session': 'task has no session',
152
147
  }
153
148
 
154
149
  /**
@@ -158,10 +153,10 @@ export const STOP_DETAIL = {
158
153
  * значило бы задваивать событие, которое было одним.
159
154
  */
160
155
  export const MOVE_DETAIL = {
161
- stopped: 'перенос прервал идущий ход агента',
162
- sent: 'команда отправлена в чат задачи',
163
- idle: 'агент не работал, останавливать было нечего',
164
- 'no-session': 'у задачи нет сессии, карточка просто передвинута',
165
- human: 'решение человека',
156
+ stopped: 'move interrupted running agent turn',
157
+ sent: 'command sent to task chat',
158
+ idle: 'agent was idle, nothing to stop',
159
+ 'no-session': 'task has no session, card moved',
160
+ human: 'human decision',
166
161
  unknown: '',
167
162
  }
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
- export const DEFAULT_KIND = 'project'
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: 'Проектная доска', kind: 'project' },
53
- { id: 'simple', title: 'Простая доска', kind: 'simple' },
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: 'Адрес инстанса Gitea или Forgejo, например https://gitea.example.com. Пусто — импорт недоступен.',
90
- giteaTokenRef: 'ИМЯ учётной записи DSH, в которой лежит токен. Сам токен сюда не вводить.',
91
- defaultProjectRoot: 'Корень, в котором лежат рабочие копии проектов. Пусто — рабочая папка харнесса.',
92
- startPrompt: 'Шаблон первого сообщения агенту. Пусто — встроенный шаблон.',
93
- replyInstruction: 'Постоянная приписка к первому сообщению — например язык ответа. Пусто — ничего не добавляется.',
94
- wipInProgress: 'Предел числа карточек в колонке «В работе». Ноль — без предела.',
95
- wipReview: 'Предел числа карточек в колонке «Ревью». Ноль — без предела.',
96
- syncIntervalSec: 'Как часто сверяться с Gitea, в секундах. Ноль — не сверяться.',
97
- staleAfterMin: 'После скольких минут молчания задача с сессией помечается тревожно. Ноль — не помечать.',
98
- giteaOwner: 'Организация Gitea, чьи репозитории смотрит доска. Пусто — определяется сама, если организация одна.',
99
- columnNames: 'Свои названия колонок парами «идентификатор=Название» через запятую. Пусто — обычные названия.',
100
- watchRepos: 'Сузить подхват до этих репозиториев, через запятую. Пусто — все репозитории организации, где есть открытые задачи.',
101
- archiveAfterDays: 'Через сколько дней в колонке «Выполнено» карточка уходит в архив. Ноль — не убирать.',
102
- boardToolEnabled: 'Разрешить агенту двигать карточки инструментом board_move. Выключено, пока скилл воркфлоу запрещает CLI трогать канбан.',
103
- webhookSecretRef: 'ИМЯ учётной записи DSH с секретом вебхука Gitea. Пусто — вебхук не принимается, остаётся только опрос.',
104
- pushToGitea: 'Закрывать issue в Gitea, когда карточка уходит в колонку «Выполнено».',
105
- worktreeIsolation: 'Изолировать сессии задач в отдельные git worktrees для предотвращения коллизий.',
106
- worktreeRoot: 'Каталог для воркдеревьев задач. Пусто — $DSH_HOME/worktrees или .worktrees проекта.',
107
- cronEnabled: 'Включить фоновый запуск задач по расписанию cron на стороне хоста.',
108
- preventIdleSleep: 'Запретить переход хоста в спящий режим, пока есть активные сессии задач.',
109
- progressDumpEnabled: 'Включить поддержку фиксации срезов PROGRESSDUMP для передачи эстафеты между сессиями.',
110
- permissionGateEnabled: 'Требовать подтверждения человеком для запуска задач с повышенными правами.',
111
- sessionDefaultPermission: 'Базовый уровень прав без подтверждения. Права выше этого требуют одобрения.',
112
- announceToAgent: 'Добавлять секцию о доске задач в системный промпт агентов.',
113
- autoSyncSessions: 'Автоматически создавать карточки задач для обычных сессий чата DSH.',
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
  }