@alpacachen/dsh-kanban 1.3.7 → 1.4.0
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 +4 -3
- package/README.zh.md +4 -3
- package/index.js +89 -8
- package/lib/client.js +84 -14
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@ You can take over at any time—drag a card, refine a note, reorder a list—and
|
|
|
27
27
|
|
|
28
28
|
| 💬 Plan naturally | 🧭 Stay in control | ⚡ Start instantly |
|
|
29
29
|
| --- | --- | --- |
|
|
30
|
-
| Manage the board with
|
|
30
|
+
| Manage the board with 15 built-in `kanban_*` tools. | Edit, comment, drag, prioritize, label, and filter from the UI. | Install the prebuilt plugin—no frontend setup required. |
|
|
31
31
|
| **🗂️ Keep work separate** | **🌓 Feel at home** | **🤝 Share one source of truth** |
|
|
32
32
|
| Every DSH workspace gets its own board. | Follows your DSH language, theme, and dark mode. | You and the agent always see the same tasks and status. |
|
|
33
33
|
|
|
@@ -67,8 +67,9 @@ The cards appear directly on your board. Continue in conversation, or switch to
|
|
|
67
67
|
|
|
68
68
|
- Create, edit, delete, and drag cards between lists
|
|
69
69
|
- Add notes, labels, and P0 / P1 / P2 priorities
|
|
70
|
+
- Exchange persistent card comments with the agent and see comment counts at a glance
|
|
70
71
|
- Open a card's activity timeline to see when it changed, what changed, and whether you or the agent made the change
|
|
71
|
-
- Track card creation, moves, title and note edits, label changes, and priority changes
|
|
72
|
+
- Track card creation, comments, moves, title and note edits, label changes, and priority changes
|
|
72
73
|
- Send any card to the agent in your current conversation or a new one
|
|
73
74
|
- Create, rename, reorder, and remove workflow lists
|
|
74
75
|
- Create color-coded labels for fast visual scanning
|
|
@@ -85,7 +86,7 @@ The plugin automatically gives the agent these tools in every workspace:
|
|
|
85
86
|
| Area | Tools |
|
|
86
87
|
| --- | --- |
|
|
87
88
|
| **Board** | `kanban_get` |
|
|
88
|
-
| **Cards** | `kanban_get_card`, `kanban_add_card`, `kanban_update_card`, `kanban_delete_card`, `kanban_move_card` |
|
|
89
|
+
| **Cards** | `kanban_get_card`, `kanban_add_comment`, `kanban_add_card`, `kanban_update_card`, `kanban_delete_card`, `kanban_move_card` |
|
|
89
90
|
| **Lists** | `kanban_add_column`, `kanban_rename_column`, `kanban_delete_column`, `kanban_move_column` |
|
|
90
91
|
| **Labels** | `kanban_get_label`, `kanban_add_label`, `kanban_update_label`, `kanban_delete_label` |
|
|
91
92
|
|
package/README.zh.md
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
|
|
28
28
|
| 💬 自然对话 | 🧭 随时掌控 | ⚡ 开箱即用 |
|
|
29
29
|
| --- | --- | --- |
|
|
30
|
-
| 通过
|
|
30
|
+
| 通过 15 个 `kanban_*` 工具直接管理任务。 | 在界面中编辑、评论、拖拽、标记和筛选卡片。 | 浏览器端已预构建,无需配置前端环境。 |
|
|
31
31
|
| **🗂️ 项目隔离** | **🌓 原生体验** | **🤝 实时协作** |
|
|
32
32
|
| 每个 DSH 工作区都有自己的独立看板。 | 自动跟随 DSH 的语言、主题与暗色模式。 | 你做的调整,Agent 立即可见;反之亦然。 |
|
|
33
33
|
|
|
@@ -67,8 +67,9 @@ dsh --profile web --dump-config
|
|
|
67
67
|
|
|
68
68
|
- 创建、编辑、删除卡片,并将卡片拖到其他列表
|
|
69
69
|
- 为卡片补充备注、标签和 P0 / P1 / P2 优先级
|
|
70
|
+
- 与 Agent 在卡片下持续评论,并在看板上快速查看评论数量
|
|
70
71
|
- 打开卡片活动时间线,查看变更时间、具体内容,以及操作来自你还是 Agent
|
|
71
|
-
-
|
|
72
|
+
- 追踪卡片创建、评论、移动、标题与备注编辑、标签变化和优先级变化
|
|
72
73
|
- 从任意卡片发起对话,在当前会话或新会话中让 Agent 接着处理
|
|
73
74
|
- 创建、重命名、排序和删除工作流列表
|
|
74
75
|
- 创建彩色标签,让不同任务一眼可辨
|
|
@@ -85,7 +86,7 @@ dsh --profile web --dump-config
|
|
|
85
86
|
| 范围 | 工具 |
|
|
86
87
|
| --- | --- |
|
|
87
88
|
| **看板** | `kanban_get` |
|
|
88
|
-
| **卡片** | `kanban_get_card`、`kanban_add_card`、`kanban_update_card`、`kanban_delete_card`、`kanban_move_card` |
|
|
89
|
+
| **卡片** | `kanban_get_card`、`kanban_add_comment`、`kanban_add_card`、`kanban_update_card`、`kanban_delete_card`、`kanban_move_card` |
|
|
89
90
|
| **列表** | `kanban_add_column`、`kanban_rename_column`、`kanban_delete_column`、`kanban_move_column` |
|
|
90
91
|
| **标签** | `kanban_get_label`、`kanban_add_label`、`kanban_update_label`、`kanban_delete_label` |
|
|
91
92
|
|
package/index.js
CHANGED
|
@@ -7,14 +7,15 @@
|
|
|
7
7
|
* 职责:
|
|
8
8
|
* - 按工作区(项目)隔离:boards 以 workspaceId 为键,每个工作区一块独立看板
|
|
9
9
|
* - 磁盘持久化:经 ctx.fs 写入 <workspace.path>/.dsh-kanban.json
|
|
10
|
-
* - 模型工具:经 ctx.tools.register 注册
|
|
10
|
+
* - 模型工具:经 ctx.tools.register 注册 15 个 kanban_* 工具
|
|
11
11
|
* - 浏览器数据层:经 ctx.get('webServer') 注册 /api/kanban 前缀路由
|
|
12
12
|
*
|
|
13
13
|
* 数据模型(每工作区,磁盘文件带 schemaVersion):
|
|
14
|
-
* schemaVersion:
|
|
14
|
+
* schemaVersion: 3
|
|
15
15
|
* columns: [{ id, title }]
|
|
16
16
|
* labels: [{ name, color }] —— 标签与颜色绑定,name 为唯一键
|
|
17
|
-
* cards: [{ id, columnId, title, note, label, priority, createdAt, createdBy
|
|
17
|
+
* cards: [{ id, columnId, title, note, label, priority, createdAt, createdBy,
|
|
18
|
+
* comments: [{ id, content, source, createdAt }] }]
|
|
18
19
|
* activities: [{ id, ts, cardId, type, source, field?, from?, to?, meta? }] —— 追加式活动日志
|
|
19
20
|
*
|
|
20
21
|
* 数据安全:
|
|
@@ -44,7 +45,7 @@ export const inject = ['tools']
|
|
|
44
45
|
// 旧文件在首次打开时自动沿迁移链升级,升级前原文件先备份。
|
|
45
46
|
// ---------------------------------------------------------------------------
|
|
46
47
|
|
|
47
|
-
export const SCHEMA_VERSION =
|
|
48
|
+
export const SCHEMA_VERSION = 3
|
|
48
49
|
export const LEGACY_VERSION = 0
|
|
49
50
|
|
|
50
51
|
// 活动日志:追加式只读记录,随看板一起落盘;超过上限丢弃最旧事件,防止日志无界增长。
|
|
@@ -104,6 +105,20 @@ export const MIGRATIONS = {
|
|
|
104
105
|
activities: [],
|
|
105
106
|
}
|
|
106
107
|
},
|
|
108
|
+
2: (data) => {
|
|
109
|
+
// v2 → v3:卡片新增评论数组;历史卡片默认没有评论
|
|
110
|
+
const src = isObj(data) ? data : {}
|
|
111
|
+
const pick = (arr) => (Array.isArray(arr) ? arr : [])
|
|
112
|
+
return {
|
|
113
|
+
schemaVersion: 3,
|
|
114
|
+
columns: pick(src.columns),
|
|
115
|
+
labels: pick(src.labels),
|
|
116
|
+
cards: pick(src.cards)
|
|
117
|
+
.map((c) => (isObj(c) ? { ...c, comments: [] } : null))
|
|
118
|
+
.filter(Boolean),
|
|
119
|
+
activities: pick(src.activities),
|
|
120
|
+
}
|
|
121
|
+
},
|
|
107
122
|
}
|
|
108
123
|
|
|
109
124
|
/**
|
|
@@ -177,8 +192,9 @@ export function validateBoard(data) {
|
|
|
177
192
|
|
|
178
193
|
if (Array.isArray(data.cards)) {
|
|
179
194
|
const seen = new Set()
|
|
195
|
+
const commentIds = new Set()
|
|
180
196
|
for (const c of data.cards) {
|
|
181
|
-
if (!isObj(c) || typeof c.id !== 'string' || !c.id || typeof c.columnId !== 'string' || typeof c.title !== 'string') {
|
|
197
|
+
if (!isObj(c) || typeof c.id !== 'string' || !c.id || typeof c.columnId !== 'string' || typeof c.title !== 'string' || !Array.isArray(c.comments)) {
|
|
182
198
|
errors.push('cards contain an invalid entry')
|
|
183
199
|
break
|
|
184
200
|
}
|
|
@@ -189,6 +205,17 @@ export function validateBoard(data) {
|
|
|
189
205
|
if (!columnIds.has(c.columnId)) errors.push('card references missing column: ' + c.id)
|
|
190
206
|
if (c.label != null && !labelNames.has(c.label)) errors.push('card references missing label: ' + c.id)
|
|
191
207
|
if (c.priority != null && !['high', 'medium', 'low'].includes(c.priority)) errors.push('card has invalid priority: ' + c.id)
|
|
208
|
+
for (const comment of c.comments) {
|
|
209
|
+
if (!isObj(comment) || typeof comment.id !== 'string' || !comment.id || typeof comment.content !== 'string' || !comment.content.trim() || comment.content.length > 2000 || !['human', 'agent'].includes(comment.source) || typeof comment.createdAt !== 'string' || !comment.createdAt) {
|
|
210
|
+
errors.push('card comments contain an invalid entry: ' + c.id)
|
|
211
|
+
break
|
|
212
|
+
}
|
|
213
|
+
if (commentIds.has(comment.id)) {
|
|
214
|
+
errors.push('duplicate comment id: ' + comment.id)
|
|
215
|
+
break
|
|
216
|
+
}
|
|
217
|
+
commentIds.add(comment.id)
|
|
218
|
+
}
|
|
192
219
|
seen.add(c.id)
|
|
193
220
|
}
|
|
194
221
|
}
|
|
@@ -444,7 +471,10 @@ export function apply(ctx) {
|
|
|
444
471
|
}
|
|
445
472
|
}
|
|
446
473
|
for (const col of board.columns) bumpSeq(col.id)
|
|
447
|
-
for (const card of board.cards)
|
|
474
|
+
for (const card of board.cards) {
|
|
475
|
+
bumpSeq(card.id)
|
|
476
|
+
for (const comment of card.comments) bumpSeq(comment.id)
|
|
477
|
+
}
|
|
448
478
|
for (const act of board.activities) bumpSeq(act.id)
|
|
449
479
|
if (board.columns.length === 0) {
|
|
450
480
|
for (const title of DEFAULT_COLUMNS) board.columns.push({ id: nextId('c'), title })
|
|
@@ -515,6 +545,14 @@ export function apply(ctx) {
|
|
|
515
545
|
priority: c.priority ?? null,
|
|
516
546
|
createdAt: typeof c.createdAt === 'string' ? c.createdAt : null,
|
|
517
547
|
createdBy: typeof c.createdBy === 'string' ? c.createdBy : null,
|
|
548
|
+
comments: Array.isArray(c.comments)
|
|
549
|
+
? c.comments.map((comment) => ({
|
|
550
|
+
id: comment.id,
|
|
551
|
+
content: comment.content,
|
|
552
|
+
source: comment.source,
|
|
553
|
+
createdAt: comment.createdAt,
|
|
554
|
+
}))
|
|
555
|
+
: [],
|
|
518
556
|
})),
|
|
519
557
|
activities: Array.isArray(b.activities) ? b.activities.map((a) => ({ ...a })) : [],
|
|
520
558
|
})
|
|
@@ -531,6 +569,7 @@ export function apply(ctx) {
|
|
|
531
569
|
title: c.title,
|
|
532
570
|
label: c.label ?? null,
|
|
533
571
|
priority: c.priority ?? null,
|
|
572
|
+
commentCount: c.comments.length,
|
|
534
573
|
})),
|
|
535
574
|
})
|
|
536
575
|
|
|
@@ -583,6 +622,7 @@ export function apply(ctx) {
|
|
|
583
622
|
priority: normPriority(a.priority),
|
|
584
623
|
createdAt: new Date().toISOString(),
|
|
585
624
|
createdBy: actor,
|
|
625
|
+
comments: [],
|
|
586
626
|
}
|
|
587
627
|
board.cards.push(card)
|
|
588
628
|
record(board, {
|
|
@@ -640,6 +680,24 @@ export function apply(ctx) {
|
|
|
640
680
|
: result({ error: 'Card not found: ' + str(a.id, '') })
|
|
641
681
|
}
|
|
642
682
|
|
|
683
|
+
case 'addComment': {
|
|
684
|
+
const card = findCard(board, str(a.id, ''))
|
|
685
|
+
if (!card) return result({ error: 'Card not found: ' + str(a.id, '') })
|
|
686
|
+
const content = str(a.content, '').trim()
|
|
687
|
+
if (!content) return result({ error: 'Comment content required' })
|
|
688
|
+
if (content.length > 2000) return result({ error: 'Comment exceeds 2000 characters' })
|
|
689
|
+
const comment = {
|
|
690
|
+
id: nextId('m'),
|
|
691
|
+
content,
|
|
692
|
+
source: actor,
|
|
693
|
+
createdAt: new Date().toISOString(),
|
|
694
|
+
}
|
|
695
|
+
card.comments.push(comment)
|
|
696
|
+
record(board, { cardId: card.id, type: 'card_comment_added', source: actor, meta: { title: card.title } })
|
|
697
|
+
await save(workspace, session)
|
|
698
|
+
return result({ message: 'Comment added' })
|
|
699
|
+
}
|
|
700
|
+
|
|
643
701
|
case 'deleteCard': {
|
|
644
702
|
const id = str(a.id, '')
|
|
645
703
|
const card = findCard(board, id)
|
|
@@ -1033,7 +1091,8 @@ export function apply(ctx) {
|
|
|
1033
1091
|
' - [' + card.id + '] ' +
|
|
1034
1092
|
(card.priority ? '[' + card.priority + '] ' : '') +
|
|
1035
1093
|
(card.label ? '[' + card.label + '] ' : '') +
|
|
1036
|
-
card.title
|
|
1094
|
+
card.title +
|
|
1095
|
+
(card.commentCount ? ' (' + card.commentCount + ' comments)' : ''),
|
|
1037
1096
|
)
|
|
1038
1097
|
}
|
|
1039
1098
|
}
|
|
@@ -1054,7 +1113,7 @@ export function apply(ctx) {
|
|
|
1054
1113
|
},
|
|
1055
1114
|
{
|
|
1056
1115
|
name: 'kanban_get_card',
|
|
1057
|
-
description: "Read one card's full details (title, note, label, priority) by id from the current project (workspace) board. Use kanban_get first to discover card ids, then this tool to read
|
|
1116
|
+
description: "Read one card's full details (title, note, label, priority and comments) by id from the current project (workspace) board. Use kanban_get first to discover card ids, then this tool to read the complete card and comment history.",
|
|
1058
1117
|
parameters: {
|
|
1059
1118
|
type: 'object',
|
|
1060
1119
|
properties: {
|
|
@@ -1086,6 +1145,12 @@ export function apply(ctx) {
|
|
|
1086
1145
|
if (c.priority) lines.push('Priority: ' + c.priority)
|
|
1087
1146
|
if (c.label) lines.push('Label: ' + c.label)
|
|
1088
1147
|
if (c.note) lines.push('Note: ' + c.note)
|
|
1148
|
+
if (Array.isArray(c.comments) && c.comments.length > 0) {
|
|
1149
|
+
lines.push('Comments:')
|
|
1150
|
+
for (const comment of c.comments) {
|
|
1151
|
+
lines.push(' - [' + comment.createdAt + '] ' + comment.source + ': ' + comment.content)
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1089
1154
|
}
|
|
1090
1155
|
return [{ type: 'text', text: lines.join('\n') }]
|
|
1091
1156
|
},
|
|
@@ -1099,6 +1164,22 @@ export function apply(ctx) {
|
|
|
1099
1164
|
return { ok: true, message: 'Card ' + String(args.id || '') + ' details', card: r.card, warnings: Array.isArray(r.warnings) ? r.warnings : [] }
|
|
1100
1165
|
},
|
|
1101
1166
|
},
|
|
1167
|
+
{
|
|
1168
|
+
name: 'kanban_add_comment',
|
|
1169
|
+
description: 'Add a comment to a card in the current project (workspace). Use it to leave progress updates, decisions, questions, or review feedback.',
|
|
1170
|
+
parameters: {
|
|
1171
|
+
type: 'object',
|
|
1172
|
+
properties: {
|
|
1173
|
+
id: { type: 'string', description: 'Card id' },
|
|
1174
|
+
content: { type: 'string', description: 'Comment text (1-2000 characters)' },
|
|
1175
|
+
},
|
|
1176
|
+
required: ['id', 'content'],
|
|
1177
|
+
},
|
|
1178
|
+
output: output(renderBoard),
|
|
1179
|
+
async execute(args, exec) {
|
|
1180
|
+
return runTool('addComment', args, exec)
|
|
1181
|
+
},
|
|
1182
|
+
},
|
|
1102
1183
|
{
|
|
1103
1184
|
name: 'kanban_add_card',
|
|
1104
1185
|
description: 'Add a card to the current project (workspace) kanban board. Write feature breakdowns and plans to the board: one card per task step.',
|