@alpacachen/dsh-kanban 1.4.0 → 1.5.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.
Files changed (3) hide show
  1. package/index.js +40 -21
  2. package/lib/client.js +6 -6
  3. package/package.json +19 -3
package/index.js CHANGED
@@ -51,6 +51,15 @@ export const LEGACY_VERSION = 0
51
51
  // 活动日志:追加式只读记录,随看板一起落盘;超过上限丢弃最旧事件,防止日志无界增长。
52
52
  const ACTIVITY_LIMIT = 5000
53
53
 
54
+ // 输入长度上限:与工具 schema 描述、客户端编辑器(CardDialog)保持一致。
55
+ // 超长输入在写入前被截断,并通过看板 warnings 通道发出一次性警告(见 clampText),
56
+ // 因此 agent 与用户都能感知到内容被裁剪,而不是静默丢失。
57
+ export const TITLE_LIMIT = 120
58
+ export const NOTE_LIMIT = 2000
59
+ export const LABEL_LIMIT = 20
60
+ export const COLUMN_TITLE_LIMIT = 40
61
+ export const COMMENT_LIMIT = 2000
62
+
54
63
  const isObj = (v) => typeof v === 'object' && v !== null && !Array.isArray(v)
55
64
  const strField = (v, fb) => (typeof v === 'string' && v ? v : fb)
56
65
  const normColor = (v) => (typeof v === 'string' && /^#[0-9a-fA-F]{6}$/.test(v) ? v.toLowerCase() : '#94a3b8')
@@ -526,6 +535,16 @@ export function apply(ctx) {
526
535
 
527
536
  // ---- 校验 / 查找 / 序列化 ----
528
537
  const str = (v, fb) => (typeof v === 'string' ? v : fb)
538
+
539
+ // 截断辅助:把输入裁到 limit 以内;一旦实际发生截断,就经看板 warnings 通道
540
+ // 发出一次性警告(同时写入宿主日志),避免内容被静默丢弃。
541
+ const clampText = (value, limit, field, board) => {
542
+ const s = str(value, '')
543
+ if (s.length <= limit) return s
544
+ warn(board, field + ' truncated to ' + limit + ' characters')
545
+ return s.slice(0, limit)
546
+ }
547
+
529
548
  const PRIORITIES = ['high', 'medium', 'low']
530
549
  const normPriority = (v) => (typeof v === 'string' && PRIORITIES.includes(v) ? v : undefined)
531
550
  const normColor = (v) => (typeof v === 'string' && /^#[0-9a-fA-F]{6}$/.test(v) ? v.toLowerCase() : undefined)
@@ -611,13 +630,13 @@ export function apply(ctx) {
611
630
  case 'addCard': {
612
631
  const col = findColumn(board, str(a.columnId, '')) || board.columns[0]
613
632
  if (!col) return result({ error: 'No list available' })
614
- const label = typeof a.label === 'string' ? a.label.slice(0, 20) : undefined
633
+ const label = typeof a.label === 'string' ? clampText(a.label, LABEL_LIMIT, 'Label', board) : undefined
615
634
  if (label && !findLabel(board, label)) return result({ error: 'Label not found: ' + label })
616
635
  const card = {
617
636
  id: nextId('k'),
618
637
  columnId: col.id,
619
- title: str(a.title, '').slice(0, 120) || 'Untitled card',
620
- note: str(a.note, '').slice(0, 500),
638
+ title: clampText(a.title, TITLE_LIMIT, 'Title', board) || 'Untitled card',
639
+ note: clampText(a.note, NOTE_LIMIT, 'Note', board),
621
640
  label,
622
641
  priority: normPriority(a.priority),
623
642
  createdAt: new Date().toISOString(),
@@ -643,7 +662,7 @@ export function apply(ctx) {
643
662
  case 'updateCard': {
644
663
  const card = findCard(board, str(a.id, ''))
645
664
  if (card) {
646
- const nextLabel = typeof a.label === 'string' ? a.label.slice(0, 20) : undefined
665
+ const nextLabel = typeof a.label === 'string' ? clampText(a.label, LABEL_LIMIT, 'Label', board) : undefined
647
666
  if (nextLabel && !findLabel(board, nextLabel)) return result({ error: 'Label not found: ' + nextLabel })
648
667
  const before = {
649
668
  title: card.title,
@@ -651,8 +670,8 @@ export function apply(ctx) {
651
670
  label: card.label ?? null,
652
671
  priority: card.priority ?? null,
653
672
  }
654
- if (typeof a.title === 'string') card.title = a.title.slice(0, 120) || card.title
655
- if (typeof a.note === 'string') card.note = a.note.slice(0, 500)
673
+ if (typeof a.title === 'string') card.title = clampText(a.title, TITLE_LIMIT, 'Title', board) || card.title
674
+ if (typeof a.note === 'string') card.note = clampText(a.note, NOTE_LIMIT, 'Note', board)
656
675
  if (typeof a.label === 'string') card.label = nextLabel || undefined
657
676
  if (typeof a.priority === 'string') card.priority = normPriority(a.priority)
658
677
  const after = {
@@ -685,7 +704,7 @@ export function apply(ctx) {
685
704
  if (!card) return result({ error: 'Card not found: ' + str(a.id, '') })
686
705
  const content = str(a.content, '').trim()
687
706
  if (!content) return result({ error: 'Comment content required' })
688
- if (content.length > 2000) return result({ error: 'Comment exceeds 2000 characters' })
707
+ if (content.length > COMMENT_LIMIT) return result({ error: 'Comment exceeds ' + COMMENT_LIMIT + ' characters' })
689
708
  const comment = {
690
709
  id: nextId('m'),
691
710
  content,
@@ -731,7 +750,7 @@ export function apply(ctx) {
731
750
  }
732
751
 
733
752
  case 'addColumn': {
734
- const title = str(a.title, '').slice(0, 40) || 'New list'
753
+ const title = clampText(a.title, COLUMN_TITLE_LIMIT, 'List title', board) || 'New list'
735
754
  board.columns.push({ id: nextId('c'), title })
736
755
  record(board, { cardId: null, type: 'column_added', source: actor, meta: { column: title } })
737
756
  await save(workspace, session)
@@ -742,7 +761,7 @@ export function apply(ctx) {
742
761
  const col = findColumn(board, str(a.id, ''))
743
762
  if (col && typeof a.title === 'string') {
744
763
  const before = col.title
745
- col.title = a.title.slice(0, 40) || col.title
764
+ col.title = clampText(a.title, COLUMN_TITLE_LIMIT, 'List title', board) || col.title
746
765
  if (col.title !== before) {
747
766
  record(board, { cardId: null, type: 'column_renamed', source: actor, field: 'title', from: before, to: col.title, meta: { column: col.title } })
748
767
  }
@@ -784,7 +803,7 @@ export function apply(ctx) {
784
803
  }
785
804
 
786
805
  case 'addLabel': {
787
- const name = str(a.name, '').slice(0, 20)
806
+ const name = clampText(a.name, LABEL_LIMIT, 'Label name', board)
788
807
  if (!name) return result({ error: 'Label name required' })
789
808
  if (findLabel(board, name)) return result({ error: 'Label already exists' })
790
809
  board.labels.push({ name, color: normColor(a.color) || '#94a3b8' })
@@ -797,7 +816,7 @@ export function apply(ctx) {
797
816
  const name = str(a.name, '')
798
817
  const label = findLabel(board, name)
799
818
  if (!label) return result({ error: 'Label not found' })
800
- const newName = str(a.newName, '').slice(0, 20)
819
+ const newName = clampText(a.newName, LABEL_LIMIT, 'Label name', board)
801
820
  if (newName && newName !== name) {
802
821
  if (findLabel(board, newName)) return result({ error: 'Label name already exists' })
803
822
  label.name = newName
@@ -1186,10 +1205,10 @@ export function apply(ctx) {
1186
1205
  parameters: {
1187
1206
  type: 'object',
1188
1207
  properties: {
1189
- title: { type: 'string', description: 'Card title (task name, concise and actionable)' },
1208
+ title: { type: 'string', description: `Card title (task name, concise and actionable; max ${TITLE_LIMIT} chars)` },
1190
1209
  columnId: { type: 'string', description: 'Target list id; defaults to the first list (Todo)' },
1191
- note: { type: 'string', description: 'Note: background, acceptance criteria or breakdown details (optional)' },
1192
- label: { type: 'string', description: 'Label name (optional): e.g. New Feature, bug, Feedback' },
1210
+ note: { type: 'string', description: `Note: background, acceptance criteria or breakdown details (optional; max ${NOTE_LIMIT} chars)` },
1211
+ label: { type: 'string', description: `Label name (optional; max ${LABEL_LIMIT} chars): e.g. New Feature, bug, Feedback` },
1193
1212
  priority: { type: 'string', enum: ['high', 'medium', 'low'], description: 'Priority (optional): high=P0 / medium=P1 / low=P2' },
1194
1213
  },
1195
1214
  required: ['title'],
@@ -1206,9 +1225,9 @@ export function apply(ctx) {
1206
1225
  type: 'object',
1207
1226
  properties: {
1208
1227
  id: { type: 'string', description: 'Card id' },
1209
- title: { type: 'string', description: 'New title (optional)' },
1210
- note: { type: 'string', description: 'New note (optional)' },
1211
- label: { type: 'string', description: 'New label name (optional); pass empty string to clear' },
1228
+ title: { type: 'string', description: `New title (optional; max ${TITLE_LIMIT} chars)` },
1229
+ note: { type: 'string', description: `New note (optional; max ${NOTE_LIMIT} chars)` },
1230
+ label: { type: 'string', description: `New label name (optional; max ${LABEL_LIMIT} chars); pass empty string to clear` },
1212
1231
  priority: { type: 'string', enum: ['high', 'medium', 'low', ''], description: 'New priority (optional): high=P0 / medium=P1 / low=P2; pass empty string to clear' },
1213
1232
  },
1214
1233
  required: ['id'],
@@ -1253,7 +1272,7 @@ export function apply(ctx) {
1253
1272
  description: 'Add a list (column) to the board. Use when a new workflow stage (e.g. Review, Blocked) is needed.',
1254
1273
  parameters: {
1255
1274
  type: 'object',
1256
- properties: { title: { type: 'string', description: 'List name' } },
1275
+ properties: { title: { type: 'string', description: `List name (max ${COLUMN_TITLE_LIMIT} chars)` } },
1257
1276
  required: ['title'],
1258
1277
  },
1259
1278
  output: output(renderBoard),
@@ -1268,7 +1287,7 @@ export function apply(ctx) {
1268
1287
  type: 'object',
1269
1288
  properties: {
1270
1289
  id: { type: 'string', description: 'List id' },
1271
- title: { type: 'string', description: 'New name' },
1290
+ title: { type: 'string', description: `New name (max ${COLUMN_TITLE_LIMIT} chars)` },
1272
1291
  },
1273
1292
  required: ['id', 'title'],
1274
1293
  },
@@ -1312,7 +1331,7 @@ export function apply(ctx) {
1312
1331
  parameters: {
1313
1332
  type: 'object',
1314
1333
  properties: {
1315
- name: { type: 'string', description: 'Label name (unique, e.g. Urgent, Refactor)' },
1334
+ name: { type: 'string', description: `Label name (unique, max ${LABEL_LIMIT} chars; e.g. Urgent, Refactor)` },
1316
1335
  color: { type: 'string', description: 'Label color (optional): #rrggbb hex; defaults to gray' },
1317
1336
  },
1318
1337
  required: ['name'],
@@ -1329,7 +1348,7 @@ export function apply(ctx) {
1329
1348
  type: 'object',
1330
1349
  properties: {
1331
1350
  name: { type: 'string', description: 'Current label name' },
1332
- newName: { type: 'string', description: 'New name (optional)' },
1351
+ newName: { type: 'string', description: `New name (optional; max ${LABEL_LIMIT} chars)` },
1333
1352
  color: { type: 'string', description: 'New color (optional): #rrggbb hex' },
1334
1353
  },
1335
1354
  required: ['name'],