@listen1954/board 0.1.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 +29 -0
- package/client.d.ts +2 -0
- package/cordis.patch.yml +3 -0
- package/lib/client.js +2374 -0
- package/lib/domain.d.ts +213 -0
- package/lib/domain.js +669 -0
- package/lib/index.d.ts +13 -0
- package/lib/index.js +56 -0
- package/lib/prompt.d.ts +2 -0
- package/lib/prompt.js +18 -0
- package/lib/rpc.d.ts +21 -0
- package/lib/rpc.js +101 -0
- package/lib/service.d.ts +74 -0
- package/lib/service.js +417 -0
- package/lib/store.d.ts +22 -0
- package/lib/store.js +178 -0
- package/lib/tools.d.ts +23 -0
- package/lib/tools.js +594 -0
- package/package.json +63 -0
package/lib/tools.js
ADDED
|
@@ -0,0 +1,594 @@
|
|
|
1
|
+
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
2
|
+
const SCOPE = {
|
|
3
|
+
type: 'string',
|
|
4
|
+
enum: ['global', 'session'],
|
|
5
|
+
required: true,
|
|
6
|
+
description: 'View/mutation target: global shows every normal and session-linked column; session projects only this conversation task column.',
|
|
7
|
+
};
|
|
8
|
+
const REMINDER_VALUE = {
|
|
9
|
+
oneOf: [
|
|
10
|
+
{ type: 'null' },
|
|
11
|
+
{
|
|
12
|
+
type: 'object',
|
|
13
|
+
additionalProperties: false,
|
|
14
|
+
properties: {
|
|
15
|
+
at: { type: 'string', required: true },
|
|
16
|
+
text: { type: 'string', required: true },
|
|
17
|
+
status: { type: 'string', required: true, enum: ['scheduled', 'fired', 'cancelled'] },
|
|
18
|
+
firedAt: { type: 'string' },
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
};
|
|
23
|
+
const CARD_VALUE = {
|
|
24
|
+
type: 'object',
|
|
25
|
+
additionalProperties: false,
|
|
26
|
+
properties: {
|
|
27
|
+
id: { type: 'string', required: true },
|
|
28
|
+
columnId: { type: 'string', required: true },
|
|
29
|
+
title: { type: 'string', required: true },
|
|
30
|
+
body: { type: 'string', required: true },
|
|
31
|
+
order: { type: 'integer', required: true },
|
|
32
|
+
startAt: { required: true, oneOf: [{ type: 'null' }, { type: 'string' }] },
|
|
33
|
+
endAt: { required: true, oneOf: [{ type: 'null' }, { type: 'string' }] },
|
|
34
|
+
reminder: { ...REMINDER_VALUE, required: true },
|
|
35
|
+
timeReminder: {
|
|
36
|
+
type: 'object',
|
|
37
|
+
required: true,
|
|
38
|
+
additionalProperties: false,
|
|
39
|
+
properties: {
|
|
40
|
+
enabled: { type: 'boolean', required: true },
|
|
41
|
+
startFiredAt: { type: 'string' },
|
|
42
|
+
endFiredAt: { type: 'string' },
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
logs: {
|
|
46
|
+
type: 'array',
|
|
47
|
+
required: true,
|
|
48
|
+
items: {
|
|
49
|
+
type: 'object',
|
|
50
|
+
additionalProperties: false,
|
|
51
|
+
properties: {
|
|
52
|
+
id: { type: 'string', required: true },
|
|
53
|
+
text: { type: 'string', required: true },
|
|
54
|
+
createdAt: { type: 'string', required: true },
|
|
55
|
+
attachments: { type: 'array', required: true, items: { type: 'object', additionalProperties: false, properties: { id: { type: 'string', required: true }, name: { type: 'string', required: true }, type: { type: 'string', required: true }, size: { type: 'integer', required: true } } } },
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
archivedAt: {
|
|
60
|
+
required: true,
|
|
61
|
+
oneOf: [{ type: 'null' }, { type: 'string' }],
|
|
62
|
+
},
|
|
63
|
+
archivedColumn: {
|
|
64
|
+
required: true,
|
|
65
|
+
oneOf: [
|
|
66
|
+
{ type: 'null' },
|
|
67
|
+
{ type: 'object', additionalProperties: false, properties: { id: { type: 'string', required: true }, title: { type: 'string', required: true }, sessionId: { type: 'string' } } },
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
createdAt: { type: 'string', required: true },
|
|
71
|
+
updatedAt: { type: 'string', required: true },
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
const BOARD_VALUE = {
|
|
75
|
+
type: 'object',
|
|
76
|
+
additionalProperties: false,
|
|
77
|
+
properties: {
|
|
78
|
+
schemaVersion: { type: 'integer', const: 1, required: true },
|
|
79
|
+
id: { type: 'string', required: true },
|
|
80
|
+
scope: { type: 'string', required: true, enum: ['global', 'session'] },
|
|
81
|
+
sessionId: { type: 'string' },
|
|
82
|
+
title: { type: 'string', required: true },
|
|
83
|
+
columns: {
|
|
84
|
+
type: 'array',
|
|
85
|
+
required: true,
|
|
86
|
+
items: {
|
|
87
|
+
type: 'object',
|
|
88
|
+
additionalProperties: false,
|
|
89
|
+
properties: {
|
|
90
|
+
id: { type: 'string', required: true },
|
|
91
|
+
title: { type: 'string', required: true },
|
|
92
|
+
order: { type: 'integer', required: true },
|
|
93
|
+
sessionId: { type: 'string', description: 'Present when this is the canonical task column of a conversation.' },
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
cards: {
|
|
98
|
+
type: 'array',
|
|
99
|
+
required: true,
|
|
100
|
+
items: CARD_VALUE,
|
|
101
|
+
},
|
|
102
|
+
revision: { type: 'integer', required: true },
|
|
103
|
+
updatedAt: { type: 'string', required: true },
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
const SUMMARY_VALUE = {
|
|
107
|
+
type: 'object',
|
|
108
|
+
additionalProperties: false,
|
|
109
|
+
properties: {
|
|
110
|
+
scope: { type: 'string', required: true, enum: ['global', 'session'] },
|
|
111
|
+
id: { type: 'string', required: true },
|
|
112
|
+
title: { type: 'string', required: true },
|
|
113
|
+
revision: { type: 'integer', required: true },
|
|
114
|
+
updatedAt: { type: 'string', required: true },
|
|
115
|
+
empty: { type: 'boolean', required: true },
|
|
116
|
+
reminderFiredCount: { type: 'integer', required: true },
|
|
117
|
+
archivedCount: { type: 'integer', required: true },
|
|
118
|
+
columns: {
|
|
119
|
+
type: 'array',
|
|
120
|
+
required: true,
|
|
121
|
+
items: {
|
|
122
|
+
type: 'object',
|
|
123
|
+
additionalProperties: false,
|
|
124
|
+
properties: {
|
|
125
|
+
id: { type: 'string', required: true },
|
|
126
|
+
title: { type: 'string', required: true },
|
|
127
|
+
sessionId: { type: 'string', description: 'Present for a session-linked column.' },
|
|
128
|
+
cards: {
|
|
129
|
+
type: 'array',
|
|
130
|
+
required: true,
|
|
131
|
+
items: {
|
|
132
|
+
type: 'object',
|
|
133
|
+
additionalProperties: false,
|
|
134
|
+
properties: {
|
|
135
|
+
id: { type: 'string', required: true },
|
|
136
|
+
title: { type: 'string', required: true },
|
|
137
|
+
reminderStatus: {
|
|
138
|
+
required: true,
|
|
139
|
+
oneOf: [
|
|
140
|
+
{ type: 'null' },
|
|
141
|
+
{ type: 'string', enum: ['scheduled', 'fired', 'cancelled'] },
|
|
142
|
+
],
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
const SEARCH_RESULT_VALUE = {
|
|
153
|
+
type: 'object',
|
|
154
|
+
additionalProperties: false,
|
|
155
|
+
properties: {
|
|
156
|
+
cardId: { type: 'string', required: true },
|
|
157
|
+
title: { type: 'string', required: true },
|
|
158
|
+
columnId: { type: 'string', required: true },
|
|
159
|
+
columnTitle: { type: 'string', required: true },
|
|
160
|
+
sessionId: { type: 'string' },
|
|
161
|
+
status: { type: 'string', required: true, enum: ['active', 'archived'] },
|
|
162
|
+
archivedAt: { required: true, oneOf: [{ type: 'null' }, { type: 'string' }] },
|
|
163
|
+
matchedIn: { type: 'array', required: true, items: { type: 'string', enum: ['title', 'body', 'log', 'attachment', 'column'] } },
|
|
164
|
+
preview: { type: 'string', required: true },
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
const MUTATE_OP = {
|
|
168
|
+
oneOf: [
|
|
169
|
+
{
|
|
170
|
+
type: 'object',
|
|
171
|
+
additionalProperties: false,
|
|
172
|
+
properties: {
|
|
173
|
+
type: { type: 'string', const: 'addColumn', required: true, description: 'Create a column. Use its returned id for addCard.' },
|
|
174
|
+
title: { type: 'string', required: true, description: 'Column title. Columns have no built-in meaning.' },
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
type: 'object',
|
|
179
|
+
additionalProperties: false,
|
|
180
|
+
properties: {
|
|
181
|
+
type: { type: 'string', const: 'renameColumn', required: true, description: 'Rename an existing column; obtain columnId from board_list.' },
|
|
182
|
+
columnId: { type: 'string', required: true },
|
|
183
|
+
title: { type: 'string', required: true },
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
type: 'object',
|
|
188
|
+
additionalProperties: false,
|
|
189
|
+
properties: {
|
|
190
|
+
type: { type: 'string', const: 'reorderColumns', required: true, description: 'Reorder columns using every current column id exactly once.' },
|
|
191
|
+
columnIds: {
|
|
192
|
+
type: 'array',
|
|
193
|
+
required: true,
|
|
194
|
+
items: { type: 'string' },
|
|
195
|
+
description: 'Every column id, left to right.',
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
type: 'object',
|
|
201
|
+
additionalProperties: false,
|
|
202
|
+
properties: {
|
|
203
|
+
type: { type: 'string', const: 'deleteColumn', required: true, description: 'Permanently delete a column and its active cards. Completed cards and attachments are preserved.' },
|
|
204
|
+
columnId: { type: 'string', required: true },
|
|
205
|
+
confirm: { type: 'boolean', const: true, required: true, description: 'Must be true. Deletes the column and active cards.' },
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
type: 'object',
|
|
210
|
+
additionalProperties: false,
|
|
211
|
+
properties: {
|
|
212
|
+
type: { type: 'string', const: 'addCard', required: true, description: 'Create a task card in an existing column. Include body and task start/end times when known.' },
|
|
213
|
+
columnId: { type: 'string', required: true },
|
|
214
|
+
title: { type: 'string', required: true },
|
|
215
|
+
body: { type: 'string', description: 'Optional notes.' },
|
|
216
|
+
startAt: { type: 'string', description: 'Optional task start time as ISO-8601. May be used without endAt.' },
|
|
217
|
+
endAt: { type: 'string', description: 'Optional task end/deadline as ISO-8601. May be used without startAt.' },
|
|
218
|
+
timeReminder: { type: 'boolean', description: 'Notify at the task start and end times. Requires at least one task time.' },
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
type: 'object',
|
|
223
|
+
additionalProperties: false,
|
|
224
|
+
properties: {
|
|
225
|
+
type: { type: 'string', const: 'updateCard', required: true, description: 'Edit title, notes, task start/end times, and/or task-time notifications in one operation. startAt/endAt:null clears that field.' },
|
|
226
|
+
cardId: { type: 'string', required: true },
|
|
227
|
+
title: { type: 'string' },
|
|
228
|
+
body: { type: 'string' },
|
|
229
|
+
startAt: { oneOf: [{ type: 'null' }, { type: 'string' }], description: 'Set or clear task start time.' },
|
|
230
|
+
endAt: { oneOf: [{ type: 'null' }, { type: 'string' }], description: 'Set or clear task end/deadline.' },
|
|
231
|
+
timeReminder: { type: 'boolean', description: 'Enable or disable notifications at task start/end.' },
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
type: 'object',
|
|
236
|
+
additionalProperties: false,
|
|
237
|
+
properties: {
|
|
238
|
+
type: { type: 'string', const: 'moveCard', required: true, description: 'Move or reorder a card. index is zero-based among active destination cards.' },
|
|
239
|
+
cardId: { type: 'string', required: true },
|
|
240
|
+
columnId: { type: 'string', required: true, description: 'Destination column.' },
|
|
241
|
+
index: { type: 'integer', required: true, description: '0-based index in the destination column.' },
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
type: 'object',
|
|
246
|
+
additionalProperties: false,
|
|
247
|
+
properties: {
|
|
248
|
+
type: { type: 'string', const: 'addLog', required: true, description: 'Append an immutable progress note, blocker, decision, or process memo to a card.' },
|
|
249
|
+
cardId: { type: 'string', required: true },
|
|
250
|
+
text: { type: 'string', required: true },
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
type: 'object',
|
|
255
|
+
additionalProperties: false,
|
|
256
|
+
properties: {
|
|
257
|
+
type: { type: 'string', const: 'archiveCard', required: true, description: 'Mark a task complete by archiving it. Do not delete completed work.' },
|
|
258
|
+
cardId: { type: 'string', required: true },
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
type: 'object',
|
|
263
|
+
additionalProperties: false,
|
|
264
|
+
properties: {
|
|
265
|
+
type: { type: 'string', const: 'restoreCard', required: true, description: 'Restore an archived card into a selected existing column.' },
|
|
266
|
+
cardId: { type: 'string', required: true },
|
|
267
|
+
columnId: { type: 'string', required: true, description: 'Column to restore the archived card into.' },
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
type: 'object',
|
|
272
|
+
additionalProperties: false,
|
|
273
|
+
properties: {
|
|
274
|
+
type: { type: 'string', const: 'restoreCards', required: true, description: 'Atomically restore multiple archived cards.' },
|
|
275
|
+
cards: { type: 'array', required: true, items: { type: 'object', additionalProperties: false, properties: { cardId: { type: 'string', required: true }, columnId: { type: 'string', required: true } } } },
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
type: 'object',
|
|
280
|
+
additionalProperties: false,
|
|
281
|
+
properties: {
|
|
282
|
+
type: { type: 'string', const: 'deleteCard', required: true, description: 'Permanently delete a card. Only use on an explicit user request.' },
|
|
283
|
+
cardId: { type: 'string', required: true },
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
type: 'object',
|
|
288
|
+
additionalProperties: false,
|
|
289
|
+
properties: {
|
|
290
|
+
type: { type: 'string', const: 'deleteCards', required: true, description: 'Atomically and permanently delete multiple cards. Only use on an explicit user request.' },
|
|
291
|
+
cardIds: { type: 'array', required: true, items: { type: 'string' } },
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
],
|
|
295
|
+
};
|
|
296
|
+
export function registerBoardTools(ctx) {
|
|
297
|
+
ctx.tools.register(defineTool({
|
|
298
|
+
name: 'board_list',
|
|
299
|
+
description: 'Summarize the global board and this session\'s board: column names, card titles, fired reminder counts. '
|
|
300
|
+
+ 'Use for overview and browsing, not as a prerequisite for every mutation. Does not return notes or archived card details, but reports archivedCount. '
|
|
301
|
+
+ 'The global summary includes session-linked columns; the session summary is only this conversation. Use board_search when the user gives a title, keyword, note, log, attachment, or column to find.',
|
|
302
|
+
parameters: {},
|
|
303
|
+
output: {
|
|
304
|
+
schema: {
|
|
305
|
+
type: 'object',
|
|
306
|
+
additionalProperties: false,
|
|
307
|
+
properties: {
|
|
308
|
+
boards: { type: 'array', required: true, items: SUMMARY_VALUE },
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
render: (_args, value) => [{ type: 'text', text: renderList(value.boards) }],
|
|
312
|
+
},
|
|
313
|
+
isConcurrencySafe: () => true,
|
|
314
|
+
async execute(_args, exec) {
|
|
315
|
+
const boards = await ctx.board.list(requireCallerSession(exec));
|
|
316
|
+
return { boards: boards.map(toSummaryValue) };
|
|
317
|
+
},
|
|
318
|
+
presentCall: () => ({ card: 'generic', title: 'List boards', kind: 'read' }),
|
|
319
|
+
}));
|
|
320
|
+
ctx.tools.register(defineTool({
|
|
321
|
+
name: 'board_get',
|
|
322
|
+
description: 'Read the global board or this session\'s board. Pass cardId to get one card including notes. '
|
|
323
|
+
+ 'A full board includes active and archived cards; archived cards have archivedAt set. '
|
|
324
|
+
+ 'Global scope includes every session-linked column; session scope only projects this conversation. If cardId is unknown, use board_search for a described task or board_list only for an overview.',
|
|
325
|
+
parameters: {
|
|
326
|
+
scope: SCOPE,
|
|
327
|
+
cardId: { type: 'string', description: 'If set, return that card (with notes) instead of the whole board.' },
|
|
328
|
+
},
|
|
329
|
+
output: {
|
|
330
|
+
schema: {
|
|
331
|
+
oneOf: [
|
|
332
|
+
{
|
|
333
|
+
type: 'object',
|
|
334
|
+
additionalProperties: false,
|
|
335
|
+
properties: { board: { ...BOARD_VALUE, required: true } },
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
type: 'object',
|
|
339
|
+
additionalProperties: false,
|
|
340
|
+
properties: { card: { ...CARD_VALUE, required: true } },
|
|
341
|
+
},
|
|
342
|
+
],
|
|
343
|
+
},
|
|
344
|
+
render: (_args, value) => [{ type: 'text', text: renderGet(value) }],
|
|
345
|
+
},
|
|
346
|
+
isConcurrencySafe: () => true,
|
|
347
|
+
async execute(args, exec) {
|
|
348
|
+
const sessionId = requireCallerSession(exec);
|
|
349
|
+
const scope = args.scope;
|
|
350
|
+
if (args.cardId !== undefined && args.cardId.length > 0) {
|
|
351
|
+
const card = await ctx.board.getCard(sessionId, scope, args.cardId);
|
|
352
|
+
return { card: toCardValue(card) };
|
|
353
|
+
}
|
|
354
|
+
const board = await ctx.board.getBoard(sessionId, scope);
|
|
355
|
+
return { board: toBoardValue(board) };
|
|
356
|
+
},
|
|
357
|
+
presentCall: (args) => ({
|
|
358
|
+
card: 'generic',
|
|
359
|
+
title: args.cardId ? 'Get card' : 'Get board',
|
|
360
|
+
kind: 'read',
|
|
361
|
+
rawInput: args,
|
|
362
|
+
}),
|
|
363
|
+
}));
|
|
364
|
+
ctx.tools.register(defineTool({
|
|
365
|
+
name: 'board_search',
|
|
366
|
+
description: 'Search cards without loading the full board. Matches card titles, notes, process logs, attachment names, and current or archived column names. '
|
|
367
|
+
+ 'Use scope: global from any conversation to search and control the entire board; use session only to restrict results to this conversation. '
|
|
368
|
+
+ 'Results include card IDs for board_get or board_apply.',
|
|
369
|
+
parameters: {
|
|
370
|
+
scope: SCOPE,
|
|
371
|
+
query: { type: 'string', required: true, description: 'Non-empty case-insensitive text to find.' },
|
|
372
|
+
status: { type: 'string', enum: ['all', 'active', 'archived'], description: 'Optional card state filter. Defaults to all.' },
|
|
373
|
+
limit: { type: 'integer', description: 'Maximum returned matches, clamped to 1-100. Defaults to 20.' },
|
|
374
|
+
},
|
|
375
|
+
output: {
|
|
376
|
+
schema: {
|
|
377
|
+
type: 'object',
|
|
378
|
+
additionalProperties: false,
|
|
379
|
+
properties: {
|
|
380
|
+
query: { type: 'string', required: true },
|
|
381
|
+
total: { type: 'integer', required: true },
|
|
382
|
+
results: { type: 'array', required: true, items: SEARCH_RESULT_VALUE },
|
|
383
|
+
},
|
|
384
|
+
},
|
|
385
|
+
render: (_args, value) => [{ type: 'text', text: renderSearch(value) }],
|
|
386
|
+
},
|
|
387
|
+
isConcurrencySafe: () => true,
|
|
388
|
+
async execute(args, exec) {
|
|
389
|
+
const board = await ctx.board.getBoard(requireCallerSession(exec), args.scope);
|
|
390
|
+
return searchBoard(board, args.query, args.status ?? 'all', args.limit ?? 20);
|
|
391
|
+
},
|
|
392
|
+
presentCall: (args) => ({ card: 'generic', title: 'Search board', kind: 'read', rawInput: args }),
|
|
393
|
+
}));
|
|
394
|
+
ctx.tools.register(defineTool({
|
|
395
|
+
name: 'board_apply',
|
|
396
|
+
description: 'Apply one mutation to the global board or this session\'s board. Same operations the UI uses. '
|
|
397
|
+
+ 'Resolve unknown IDs with board_search when the user describes a target, or board_list when browsing; do not call both without a reason. Reuse each successful response instead of reading again. '
|
|
398
|
+
+ 'If a board has no columns, addColumn once and use its returned id for addCard. Never invent IDs. '
|
|
399
|
+
+ 'If an operation fails, read the error and refresh once; do not repeat the identical failed call. Session scope cannot target other sessions; global scope can modify any visible column. '
|
|
400
|
+
+ 'Use archiveCard for completion and restoreCard to return it to a chosen column. '
|
|
401
|
+
+ 'deleteColumn requires confirm: true, deletes active cards in that column, and preserves completed history.',
|
|
402
|
+
parameters: {
|
|
403
|
+
scope: SCOPE,
|
|
404
|
+
op: { ...MUTATE_OP, required: true, description: 'One mutate operation.' },
|
|
405
|
+
},
|
|
406
|
+
output: {
|
|
407
|
+
schema: {
|
|
408
|
+
type: 'object',
|
|
409
|
+
additionalProperties: false,
|
|
410
|
+
properties: { board: { ...BOARD_VALUE, required: true } },
|
|
411
|
+
},
|
|
412
|
+
render: (_args, value) => [{ type: 'text', text: renderBoard(value.board) }],
|
|
413
|
+
},
|
|
414
|
+
async execute(args, exec) {
|
|
415
|
+
const board = await ctx.board.model.mutate(requireCallerSession(exec), args.scope, args.op);
|
|
416
|
+
return { board: toBoardValue(board) };
|
|
417
|
+
},
|
|
418
|
+
presentCall: (args) => ({
|
|
419
|
+
card: 'generic',
|
|
420
|
+
title: `Board ${args.op.type}`,
|
|
421
|
+
kind: 'other',
|
|
422
|
+
rawInput: args,
|
|
423
|
+
}),
|
|
424
|
+
}));
|
|
425
|
+
}
|
|
426
|
+
export function searchBoard(board, rawQuery, status = 'all', limit = 20) {
|
|
427
|
+
const query = rawQuery.trim();
|
|
428
|
+
if (query.length === 0)
|
|
429
|
+
throw new Error('board search query must not be empty');
|
|
430
|
+
const needle = normalizeSearch(query);
|
|
431
|
+
const matches = board.cards.flatMap((card) => {
|
|
432
|
+
const cardStatus = card.archivedAt === null ? 'active' : 'archived';
|
|
433
|
+
if (status !== 'all' && status !== cardStatus)
|
|
434
|
+
return [];
|
|
435
|
+
const column = board.columns.find((item) => item.id === card.columnId);
|
|
436
|
+
const columnTitle = card.archivedColumn?.title ?? column?.title ?? '';
|
|
437
|
+
const sessionId = card.archivedColumn?.sessionId ?? column?.sessionId;
|
|
438
|
+
const fields = [
|
|
439
|
+
['title', [card.title]],
|
|
440
|
+
['body', [card.body]],
|
|
441
|
+
['log', card.logs.map((log) => log.text)],
|
|
442
|
+
['attachment', card.logs.flatMap((log) => log.attachments.map((attachment) => attachment.name))],
|
|
443
|
+
['column', [columnTitle]],
|
|
444
|
+
];
|
|
445
|
+
const matchedIn = fields.flatMap(([field, values]) => values.some((value) => normalizeSearch(value).includes(needle)) ? [field] : []);
|
|
446
|
+
if (matchedIn.length === 0)
|
|
447
|
+
return [];
|
|
448
|
+
const previewSource = fields.find(([field]) => matchedIn.includes(field))?.[1].find((value) => normalizeSearch(value).includes(needle)) ?? card.title;
|
|
449
|
+
const score = (matchedIn.includes('title') ? 100 : 0) + (matchedIn.includes('body') ? 30 : 0) + (matchedIn.includes('log') ? 20 : 0) + (matchedIn.includes('attachment') ? 10 : 0) + (matchedIn.includes('column') ? 5 : 0);
|
|
450
|
+
return [{
|
|
451
|
+
score,
|
|
452
|
+
cardId: card.id,
|
|
453
|
+
title: card.title,
|
|
454
|
+
columnId: card.columnId,
|
|
455
|
+
columnTitle,
|
|
456
|
+
...(sessionId === undefined ? {} : { sessionId }),
|
|
457
|
+
status: cardStatus,
|
|
458
|
+
archivedAt: card.archivedAt,
|
|
459
|
+
matchedIn,
|
|
460
|
+
preview: searchPreview(previewSource, needle),
|
|
461
|
+
updatedAt: card.updatedAt,
|
|
462
|
+
}];
|
|
463
|
+
}).sort((a, b) => b.score - a.score || Date.parse(b.updatedAt) - Date.parse(a.updatedAt));
|
|
464
|
+
return {
|
|
465
|
+
query,
|
|
466
|
+
total: matches.length,
|
|
467
|
+
results: matches.slice(0, Math.max(1, Math.min(100, limit))).map(({ score: _score, updatedAt: _updatedAt, ...result }) => result),
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
function normalizeSearch(value) {
|
|
471
|
+
return value.toLocaleLowerCase();
|
|
472
|
+
}
|
|
473
|
+
function searchPreview(value, needle) {
|
|
474
|
+
const compact = value.replace(/\s+/g, ' ').trim();
|
|
475
|
+
const index = normalizeSearch(compact).indexOf(needle);
|
|
476
|
+
if (index < 0 || compact.length <= 160)
|
|
477
|
+
return compact;
|
|
478
|
+
const start = Math.max(0, index - 60);
|
|
479
|
+
const end = Math.min(compact.length, start + 160);
|
|
480
|
+
return `${start > 0 ? '…' : ''}${compact.slice(start, end)}${end < compact.length ? '…' : ''}`;
|
|
481
|
+
}
|
|
482
|
+
export function requireCallerSession(exec) {
|
|
483
|
+
const agent = exec.agent;
|
|
484
|
+
if (agent === undefined) {
|
|
485
|
+
throw new Error('board tools require an owning agent session');
|
|
486
|
+
}
|
|
487
|
+
return agent.session.id;
|
|
488
|
+
}
|
|
489
|
+
function toReminderValue(reminder) {
|
|
490
|
+
if (reminder === null)
|
|
491
|
+
return null;
|
|
492
|
+
return {
|
|
493
|
+
at: reminder.at,
|
|
494
|
+
text: reminder.text,
|
|
495
|
+
status: reminder.status,
|
|
496
|
+
...(reminder.firedAt === undefined ? {} : { firedAt: reminder.firedAt }),
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
function toCardValue(card) {
|
|
500
|
+
return {
|
|
501
|
+
id: card.id,
|
|
502
|
+
columnId: card.columnId,
|
|
503
|
+
title: card.title,
|
|
504
|
+
body: card.body,
|
|
505
|
+
order: card.order,
|
|
506
|
+
startAt: card.startAt,
|
|
507
|
+
endAt: card.endAt,
|
|
508
|
+
reminder: toReminderValue(card.reminder),
|
|
509
|
+
timeReminder: card.timeReminder,
|
|
510
|
+
logs: card.logs.map((log) => ({ ...log, attachments: log.attachments.map((attachment) => ({ id: attachment.id, name: attachment.name, type: attachment.type, size: attachment.size })) })),
|
|
511
|
+
archivedAt: card.archivedAt,
|
|
512
|
+
archivedColumn: card.archivedColumn,
|
|
513
|
+
createdAt: card.createdAt,
|
|
514
|
+
updatedAt: card.updatedAt,
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
function toBoardValue(board) {
|
|
518
|
+
return {
|
|
519
|
+
schemaVersion: 1,
|
|
520
|
+
id: board.id,
|
|
521
|
+
scope: board.scope,
|
|
522
|
+
...(board.sessionId === undefined ? {} : { sessionId: board.sessionId }),
|
|
523
|
+
title: board.title,
|
|
524
|
+
columns: board.columns.map((column) => ({
|
|
525
|
+
id: column.id,
|
|
526
|
+
title: column.title,
|
|
527
|
+
order: column.order,
|
|
528
|
+
...(column.sessionId === undefined ? {} : { sessionId: column.sessionId }),
|
|
529
|
+
})),
|
|
530
|
+
cards: board.cards.map(toCardValue),
|
|
531
|
+
revision: board.revision,
|
|
532
|
+
updatedAt: board.updatedAt,
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
function toSummaryValue(summary) {
|
|
536
|
+
return {
|
|
537
|
+
scope: summary.scope,
|
|
538
|
+
id: summary.id,
|
|
539
|
+
title: summary.title,
|
|
540
|
+
revision: summary.revision,
|
|
541
|
+
updatedAt: summary.updatedAt,
|
|
542
|
+
empty: summary.empty,
|
|
543
|
+
reminderFiredCount: summary.reminderFiredCount,
|
|
544
|
+
archivedCount: summary.archivedCount,
|
|
545
|
+
columns: summary.columns.map((column) => ({
|
|
546
|
+
id: column.id,
|
|
547
|
+
title: column.title,
|
|
548
|
+
...(column.sessionId === undefined ? {} : { sessionId: column.sessionId }),
|
|
549
|
+
cards: column.cards.map((card) => ({
|
|
550
|
+
id: card.id,
|
|
551
|
+
title: card.title,
|
|
552
|
+
reminderStatus: card.reminderStatus,
|
|
553
|
+
})),
|
|
554
|
+
})),
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
function renderList(boards) {
|
|
558
|
+
return boards.map((board) => {
|
|
559
|
+
const where = board.scope === 'global' ? 'global' : 'this session';
|
|
560
|
+
if (board.empty)
|
|
561
|
+
return `${where}: empty (no columns)`;
|
|
562
|
+
const cols = board.columns.map((column) => {
|
|
563
|
+
const titles = column.cards.map((card) => card.title).join(', ');
|
|
564
|
+
return `${column.title}[${titles || 'no cards'}]`;
|
|
565
|
+
}).join('; ');
|
|
566
|
+
const due = board.reminderFiredCount > 0 ? `; ${String(board.reminderFiredCount)} fired reminder(s)` : '';
|
|
567
|
+
const archived = board.archivedCount > 0 ? `; ${String(board.archivedCount)} archived card(s)` : '';
|
|
568
|
+
return `${where} (rev ${String(board.revision)}): ${cols}${due}${archived}`;
|
|
569
|
+
}).join('\n');
|
|
570
|
+
}
|
|
571
|
+
function renderSearch(value) {
|
|
572
|
+
if (value.total === 0)
|
|
573
|
+
return `No board cards matched "${value.query}".`;
|
|
574
|
+
const lines = value.results.map((result) => {
|
|
575
|
+
const where = result.sessionId === undefined ? result.columnTitle : `${result.columnTitle} (session ${result.sessionId})`;
|
|
576
|
+
const preview = result.preview.length > 0 && result.preview !== result.title ? ` — ${result.preview}` : '';
|
|
577
|
+
return `- ${result.cardId} [${result.status}] ${result.title} · ${where} · matched ${result.matchedIn.join(', ')}${preview}`;
|
|
578
|
+
});
|
|
579
|
+
const suffix = value.total > value.results.length ? `\nShowing ${String(value.results.length)} of ${String(value.total)} matches.` : '';
|
|
580
|
+
return `Found ${String(value.total)} board card(s) matching "${value.query}":\n${lines.join('\n')}${suffix}`;
|
|
581
|
+
}
|
|
582
|
+
function renderGet(value) {
|
|
583
|
+
if ('card' in value) {
|
|
584
|
+
const card = value.card;
|
|
585
|
+
const note = card.body.length > 0 ? `\nnotes: ${card.body}` : '';
|
|
586
|
+
const reminder = card.reminder ? `\nreminder: ${card.reminder.status} at ${card.reminder.at}` : '';
|
|
587
|
+
return `card ${card.id}: ${card.title}${note}${reminder}`;
|
|
588
|
+
}
|
|
589
|
+
return renderBoard(value.board);
|
|
590
|
+
}
|
|
591
|
+
function renderBoard(board) {
|
|
592
|
+
const where = board.scope === 'global' ? 'global' : 'this session';
|
|
593
|
+
return `Updated ${where} board (revision ${String(board.revision)}): ${String(board.columns.length)} column(s), ${String(board.cards.length)} card(s).`;
|
|
594
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@listen1954/board",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Board for DeepSeek Harness: global and session scopes, natural-language tools, and a native Web UI.",
|
|
5
|
+
"author": "listen",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./lib/index.js",
|
|
8
|
+
"types": "./lib/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./lib/index.d.ts",
|
|
12
|
+
"default": "./lib/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./client": {
|
|
15
|
+
"types": "./client.d.ts",
|
|
16
|
+
"default": "./lib/client.js"
|
|
17
|
+
},
|
|
18
|
+
"./package.json": "./package.json"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"lib",
|
|
22
|
+
"cordis.patch.yml",
|
|
23
|
+
"client.d.ts"
|
|
24
|
+
],
|
|
25
|
+
"dsh": {
|
|
26
|
+
"bundle": {
|
|
27
|
+
"patch": "./cordis.patch.yml"
|
|
28
|
+
},
|
|
29
|
+
"client": {
|
|
30
|
+
"platform": "web",
|
|
31
|
+
"inject": [
|
|
32
|
+
"@deepseek-ai/dsh-client-ui-sidebar",
|
|
33
|
+
"@deepseek-ai/dsh-client-ui-conversation",
|
|
34
|
+
"@deepseek-ai/dsh-client-ui-layout",
|
|
35
|
+
"@deepseek-ai/dsh-client-locale",
|
|
36
|
+
"@deepseek-ai/dsh-client-runtime"
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "tsc -p tsconfig.json && node scripts/bundle-client.mjs",
|
|
42
|
+
"test": "tsc -p tsconfig.json && node --experimental-strip-types --test test/domain.test.ts test/store.test.ts test/isolation.test.ts test/service.test.ts test/wire.test.ts test/rpc.test.ts test/client-state.test.ts test/search.test.ts"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
46
|
+
"@deepseek-ai/dsh-tools": "^0.1.0-rc.8",
|
|
47
|
+
"@deepseek-ai/dsh-typert-protocol": "^0.1.0-rc.8",
|
|
48
|
+
"@deepseek-ai/schemastery": "^3.18.1"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
52
|
+
"@deepseek-ai/dsh-llm": "0.1.0-rc.8",
|
|
53
|
+
"@deepseek-ai/dsh-system-prompt": "^0.1.0-rc.8",
|
|
54
|
+
"@deepseek-ai/dsh-tools": "^0.1.0-rc.8",
|
|
55
|
+
"@deepseek-ai/dsh-typert-protocol": "^0.1.0-rc.8",
|
|
56
|
+
"@deepseek-ai/schemastery": "^3.18.1",
|
|
57
|
+
"@types/node": "^22.20.1",
|
|
58
|
+
"@types/react": "~18.3.1",
|
|
59
|
+
"esbuild": "^0.25.0",
|
|
60
|
+
"react": "^18.2.0",
|
|
61
|
+
"typescript": "~5.9.0"
|
|
62
|
+
}
|
|
63
|
+
}
|