dsh-taskboard 0.6.6 → 0.7.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 +305 -288
- package/lib/client.js +985 -931
- package/lib/host/archive-sessions.js +30 -0
- package/lib/host/archive-sessions.js.map +1 -0
- package/lib/host/assets.js +139 -0
- package/lib/host/assets.js.map +1 -0
- package/lib/host/execution.js +3 -0
- package/lib/host/execution.js.map +1 -1
- package/lib/host/locale.js +17 -0
- package/lib/host/locale.js.map +1 -0
- package/lib/host/routes.js +132 -6
- package/lib/host/routes.js.map +1 -1
- package/lib/host/scheduler.js +2 -0
- package/lib/host/scheduler.js.map +1 -1
- package/lib/host/session-sync.js +4 -1
- package/lib/host/session-sync.js.map +1 -1
- package/lib/host/storage-queue.js +14 -0
- package/lib/host/storage-queue.js.map +1 -0
- package/lib/host/storage.js +249 -0
- package/lib/host/storage.js.map +1 -0
- package/lib/host/store.js +34 -7
- package/lib/host/store.js.map +1 -1
- package/lib/host/templates.js +73 -113
- package/lib/host/templates.js.map +1 -1
- package/lib/host/tools.js +23 -8
- package/lib/host/tools.js.map +1 -1
- package/lib/index.js +83 -27
- package/lib/index.js.map +1 -1
- package/lib/shared/api.js.map +1 -1
- package/lib/shared/builtin-templates.js +155 -0
- package/lib/shared/builtin-templates.js.map +1 -0
- package/lib/shared/protocol.js +39 -2
- package/lib/shared/protocol.js.map +1 -1
- package/package.json +90 -89
- package/src/client/api.ts +35 -1
- package/src/client/board/SettingsModal.tsx +67 -4
- package/src/client/board/SlashPromptInput.tsx +80 -1
- package/src/client/board/TaskBoard.tsx +16 -11
- package/src/client/board/TaskDetail.tsx +186 -17
- package/src/client/board/TaskFormModal.tsx +8 -5
- package/src/client/board/TemplateManager.tsx +22 -10
- package/src/client/controller.ts +67 -5
- package/src/client/i18n/en.ts +35 -3
- package/src/client/i18n/templates.ts +25 -0
- package/src/client/i18n/zh.ts +35 -3
- package/src/client/image-insert.ts +29 -0
- package/src/client/styles.ts +35 -2
- package/src/host/archive-sessions.ts +18 -0
- package/src/host/assets.ts +120 -0
- package/src/host/execution.ts +4 -1
- package/src/host/locale.ts +44 -0
- package/src/host/routes.ts +132 -7
- package/src/host/scheduler.ts +2 -0
- package/src/host/session-sync.ts +4 -1
- package/src/host/storage-queue.ts +10 -0
- package/src/host/storage.ts +212 -0
- package/src/host/store.ts +40 -12
- package/src/host/templates.ts +38 -66
- package/src/host/tools.ts +28 -11
- package/src/index.ts +88 -33
- package/src/shared/api.ts +32 -3
- package/src/shared/builtin-templates.ts +153 -0
- package/src/shared/protocol.ts +64 -0
- package/src/shared/version.ts +9 -9
package/src/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Host loader entry for dsh-taskboard.
|
|
3
3
|
*
|
|
4
|
-
* Wiring: the
|
|
4
|
+
* Wiring: the configurable local data stores, the ten
|
|
5
5
|
* `taskboard_*` agent tools, the agent workflow-protocol system-prompt
|
|
6
6
|
* section, the /taskboard JSON+SSE routes (when a webServer is served),
|
|
7
7
|
* the host execution service (fresh in-project sessions, pinned models), and
|
|
@@ -29,14 +29,19 @@ import { dshHomePath } from './host/sdk.ts'
|
|
|
29
29
|
import { TaskStore } from './host/store.ts'
|
|
30
30
|
import { TemplateStore } from './host/templates.ts'
|
|
31
31
|
import { ExternalSessionSyncService } from './host/session-sync.ts'
|
|
32
|
-
import { registerTaskboardTools, workspaceFace } from './host/tools.ts'
|
|
32
|
+
import { ERR, ToolError, registerTaskboardTools, workspaceFace, type WorkspaceFace } from './host/tools.ts'
|
|
33
|
+
import { AssetStore } from './host/assets.ts'
|
|
34
|
+
import { STORAGE_CONFIG_FILE, StorageCoordinator } from './host/storage.ts'
|
|
33
35
|
|
|
34
|
-
/** Ledger file name under the
|
|
36
|
+
/** Ledger file name under the active taskboard data directory. */
|
|
35
37
|
export const LEDGER_FILE = 'dsh-taskboard.json'
|
|
36
38
|
|
|
37
|
-
/** Task-template side file name under the
|
|
39
|
+
/** Task-template side file name under the active data directory. */
|
|
38
40
|
export const TEMPLATES_FILE = 'dsh-taskboard-templates.json'
|
|
39
41
|
|
|
42
|
+
/** Content-addressed image attachment directory under the active data directory. */
|
|
43
|
+
export const ASSETS_DIR = 'dsh-taskboard-assets'
|
|
44
|
+
|
|
40
45
|
/** Cordis plugin name. */
|
|
41
46
|
export const name = 'dsh-taskboard'
|
|
42
47
|
|
|
@@ -48,14 +53,24 @@ export const inject = ['tools', 'systemPrompt']
|
|
|
48
53
|
* @param ctx - the plugin context (tools + systemPrompt injected).
|
|
49
54
|
*/
|
|
50
55
|
export function apply(ctx: Context): void {
|
|
51
|
-
const
|
|
52
|
-
|
|
56
|
+
const storage = new StorageCoordinator({
|
|
57
|
+
defaultDirectory: dshHomePath(),
|
|
58
|
+
configFile: dshHomePath(STORAGE_CONFIG_FILE),
|
|
59
|
+
ledgerName: LEDGER_FILE,
|
|
60
|
+
templatesName: TEMPLATES_FILE,
|
|
61
|
+
assetsName: ASSETS_DIR,
|
|
62
|
+
})
|
|
63
|
+
const store = new TaskStore({ file: storage.ledgerPath(), queue: storage.queue })
|
|
64
|
+
const templates = new TemplateStore(storage.templatesPath(), storage.queue)
|
|
65
|
+
const assets = new AssetStore(storage.assetsPath(), () => Date.now(), storage.queue)
|
|
66
|
+
storage.attach({ ledger: store, templates, assets })
|
|
53
67
|
// Eager first load: the tools and most routes read snapshot()/get() without
|
|
54
68
|
// triggering the lazy load, so a fresh boot used to serve an EMPTY board to
|
|
55
69
|
// taskboard_list/get until the scheduler catchup tick or the first
|
|
56
70
|
// GET /state happened to load the file (review P0). load() never throws —
|
|
57
71
|
// a corrupt ledger is quarantined instead.
|
|
58
|
-
|
|
72
|
+
const storeReady = storage.ready().then(() => store.load())
|
|
73
|
+
void storeReady.then(() => assets.cleanup(JSON.stringify(store.snapshot())))
|
|
59
74
|
const now = () => Date.now()
|
|
60
75
|
// Global execution concurrency cap (DSH_TASKBOARD_MAX_CONCURRENT overrides).
|
|
61
76
|
const maxConcurrent = Math.max(1, Number.parseInt(process.env.DSH_TASKBOARD_MAX_CONCURRENT ?? '', 10) || DEFAULT_MAX_CONCURRENT)
|
|
@@ -68,29 +83,60 @@ export function apply(ctx: Context): void {
|
|
|
68
83
|
})
|
|
69
84
|
ctx.effect(() => disposeSection, 'dsh-taskboard: protocol section')
|
|
70
85
|
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const modelProviders = (): string[] | undefined => {
|
|
80
|
-
try {
|
|
81
|
-
const llm = wsCtx.get('llm') as { listProviders?: () => Array<{ id: string }> } | undefined
|
|
82
|
-
return llm === undefined || typeof llm.listProviders !== 'function'
|
|
83
|
-
? undefined
|
|
84
|
-
: llm.listProviders().map(p => p.id)
|
|
85
|
-
} catch { return undefined }
|
|
86
|
+
// Register the complete tool schema in the same synchronous mount as the
|
|
87
|
+
// protocol. Keeping schemas stable from the first request preserves the
|
|
88
|
+
// provider's prefix cache; calls use the live workspace service below.
|
|
89
|
+
let activeWorkspaces: WorkspaceFace | undefined
|
|
90
|
+
let activeWorkspaceContext: Context | undefined
|
|
91
|
+
const requireWorkspaces = (): WorkspaceFace => {
|
|
92
|
+
if (activeWorkspaces === undefined) {
|
|
93
|
+
throw new ToolError(ERR.notReady, 'workspace service is not ready; retry after host startup completes')
|
|
86
94
|
}
|
|
95
|
+
return activeWorkspaces
|
|
96
|
+
}
|
|
97
|
+
const workspaces: WorkspaceFace = {
|
|
98
|
+
resolveByPath: path => requireWorkspaces().resolveByPath(path),
|
|
99
|
+
get: id => requireWorkspaces().get(id),
|
|
100
|
+
list: () => requireWorkspaces().list(),
|
|
101
|
+
}
|
|
102
|
+
// Preserve the optional archive capability without replacing the stable
|
|
103
|
+
// facade captured by the tool definitions.
|
|
104
|
+
Object.defineProperty(workspaces, 'archiveSession', {
|
|
105
|
+
enumerable: true,
|
|
106
|
+
get: () => activeWorkspaces?.archiveSession === undefined
|
|
107
|
+
? undefined
|
|
108
|
+
: (sessionId: string) => requireWorkspaces().archiveSession!(sessionId),
|
|
109
|
+
})
|
|
110
|
+
const modelProviders = (): string[] | undefined => {
|
|
111
|
+
try {
|
|
112
|
+
const llm = activeWorkspaceContext?.get('llm') as { listProviders?: () => Array<{ id: string }> } | undefined
|
|
113
|
+
return llm === undefined || typeof llm.listProviders !== 'function'
|
|
114
|
+
? undefined
|
|
115
|
+
: llm.listProviders().map(p => p.id)
|
|
116
|
+
} catch { return undefined }
|
|
117
|
+
}
|
|
118
|
+
const disposeTools = registerTaskboardTools(ctx, {
|
|
119
|
+
store,
|
|
120
|
+
workspaces,
|
|
121
|
+
now,
|
|
122
|
+
modelProviders,
|
|
123
|
+
ready: async () => {
|
|
124
|
+
if (activeWorkspaces === undefined) {
|
|
125
|
+
throw new ToolError(ERR.notReady, 'workspace service is not ready; retry after host startup completes')
|
|
126
|
+
}
|
|
127
|
+
await storeReady
|
|
128
|
+
},
|
|
129
|
+
})
|
|
130
|
+
ctx.effect(() => () => {
|
|
131
|
+
for (const dispose of disposeTools.splice(0)) dispose()
|
|
132
|
+
}, 'dsh-taskboard: tools')
|
|
87
133
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
134
|
+
// Runtime services come and go with the workspace registry. Tool schemas
|
|
135
|
+
// remain mounted and resolve this current service only when called.
|
|
136
|
+
ctx.inject(['workspaceRegistry'], (wsCtx: Context) => {
|
|
137
|
+
const workspaceDisposers: Array<() => void> = []
|
|
138
|
+
activeWorkspaces = workspaceFace(wsCtx.workspaceRegistry)
|
|
139
|
+
activeWorkspaceContext = wsCtx
|
|
94
140
|
|
|
95
141
|
// Settlement listener over the session event bus.
|
|
96
142
|
const events: EventsFace = {
|
|
@@ -122,7 +168,7 @@ export function apply(ctx: Context): void {
|
|
|
122
168
|
},
|
|
123
169
|
now,
|
|
124
170
|
})
|
|
125
|
-
|
|
171
|
+
workspaceDisposers.push(() => sessionSync.dispose())
|
|
126
172
|
|
|
127
173
|
// The narrow git face shared by execution (worktree isolation) and the
|
|
128
174
|
// routes (merge / remove / workspace detection), plus the shared
|
|
@@ -131,6 +177,7 @@ export function apply(ctx: Context): void {
|
|
|
131
177
|
const scanner = createRepoScanner()
|
|
132
178
|
|
|
133
179
|
wsCtx.inject(['agents'], (agentCtx: Context) => {
|
|
180
|
+
const agentDisposers: Array<() => void> = []
|
|
134
181
|
agentSessions = agentCtx.get('sessions') as { get?: (id: string) => unknown; list?: () => unknown[] } | undefined
|
|
135
182
|
const execution = new ExecutionService({
|
|
136
183
|
store,
|
|
@@ -207,6 +254,9 @@ export function apply(ctx: Context): void {
|
|
|
207
254
|
git,
|
|
208
255
|
scanner,
|
|
209
256
|
templates,
|
|
257
|
+
assets,
|
|
258
|
+
storage,
|
|
259
|
+
ready: async () => { await storeReady },
|
|
210
260
|
promptCompletions: async () => {
|
|
211
261
|
try {
|
|
212
262
|
const skillsService = agentCtx.get('skills') as { list?(options?: unknown): Promise<Array<{ name: string; description?: string }>> } | undefined
|
|
@@ -307,19 +357,24 @@ export function apply(ctx: Context): void {
|
|
|
307
357
|
// browser open. Shares the execution concurrency cap.
|
|
308
358
|
const scheduler = new SchedulerService({ store, execution, now, maxConcurrent })
|
|
309
359
|
scheduler.start()
|
|
310
|
-
|
|
360
|
+
agentDisposers.push(() => scheduler.dispose())
|
|
311
361
|
// Detach the settlement listener with the plugin — a hot reload must
|
|
312
362
|
// not leave stale services reacting to turn/end errors (review P1).
|
|
313
|
-
|
|
363
|
+
agentDisposers.push(() => execution.dispose())
|
|
314
364
|
|
|
315
365
|
return () => {
|
|
316
366
|
disposeRoutes?.()
|
|
317
|
-
|
|
367
|
+
agentSessions = undefined
|
|
368
|
+
for (const dispose of agentDisposers.splice(0)) dispose()
|
|
318
369
|
}
|
|
319
370
|
})
|
|
320
371
|
|
|
321
372
|
return () => {
|
|
322
|
-
|
|
373
|
+
if (activeWorkspaceContext === wsCtx) {
|
|
374
|
+
activeWorkspaceContext = undefined
|
|
375
|
+
activeWorkspaces = undefined
|
|
376
|
+
}
|
|
377
|
+
for (const dispose of workspaceDisposers.splice(0)) dispose()
|
|
323
378
|
}
|
|
324
379
|
})
|
|
325
380
|
}
|
package/src/shared/api.ts
CHANGED
|
@@ -38,7 +38,7 @@ export type ApiResult<T> = ApiOk<T> | ApiFail
|
|
|
38
38
|
// ---------------------------------------------------------------------------
|
|
39
39
|
|
|
40
40
|
/** Full-state response (the reconnect baseline after an SSE gap). */
|
|
41
|
-
export type StateResponse = TaskLedger
|
|
41
|
+
export type StateResponse = TaskLedger & { capabilities?: { archiveSessions: boolean } }
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
44
|
* Workspace listing for the UI pickers. `repoCount` (0.6.3): how many repos a
|
|
@@ -89,7 +89,10 @@ export type UpdateTaskBody = {
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
/** Move-task request body (ifVersion mandatory; the user MAY move to done). */
|
|
92
|
-
export type MoveTaskBody = { ifVersion: number; status: string }
|
|
92
|
+
export type MoveTaskBody = { ifVersion: number; status: string; archiveSessions?: boolean }
|
|
93
|
+
|
|
94
|
+
export type SessionArchiveResult = { archived: string[]; failed: Array<{ sessionId: string; error: string }>; unsupported: string[] }
|
|
95
|
+
export type MoveTaskResponse = TaskSummary & { sessionArchive?: SessionArchiveResult }
|
|
93
96
|
|
|
94
97
|
/**
|
|
95
98
|
* Quick-reject request body (card ✗ button): move back to todo plus an
|
|
@@ -101,6 +104,16 @@ export type RejectTaskBody = { ifVersion: number; body?: string }
|
|
|
101
104
|
/** Comment request body. */
|
|
102
105
|
export type CommentBody = { body: string }
|
|
103
106
|
|
|
107
|
+
/** One content-addressed image uploaded outside the ledger. */
|
|
108
|
+
export type AttachmentUpload = {
|
|
109
|
+
id: string
|
|
110
|
+
name: string
|
|
111
|
+
size: number
|
|
112
|
+
url: string
|
|
113
|
+
extension: 'png' | 'jpg' | 'gif' | 'webp'
|
|
114
|
+
mime: 'image/png' | 'image/jpeg' | 'image/gif' | 'image/webp'
|
|
115
|
+
}
|
|
116
|
+
|
|
104
117
|
/** Delete request body (purge=true physically removes a trashed task). */
|
|
105
118
|
export type DeleteTaskBody = { ifVersion?: number; purge?: boolean }
|
|
106
119
|
|
|
@@ -184,6 +197,22 @@ export type TemplatesResponse = { templates: TaskTemplate[] }
|
|
|
184
197
|
/** Board-settings response (0.5.0; absent fields follow factory defaults). */
|
|
185
198
|
export type SettingsResponse = BoardSettings
|
|
186
199
|
|
|
200
|
+
/** Current host-side location of all durable taskboard data. */
|
|
201
|
+
export type StorageStatus = {
|
|
202
|
+
currentDirectory: string
|
|
203
|
+
defaultDirectory: string
|
|
204
|
+
isDefault: boolean
|
|
205
|
+
configured: boolean
|
|
206
|
+
writable: boolean
|
|
207
|
+
assetCount: number
|
|
208
|
+
assetBytes: number
|
|
209
|
+
checkedDirectory?: string
|
|
210
|
+
error?: string
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** Completed storage relocation, including non-fatal old-file cleanup failures. */
|
|
214
|
+
export type StorageMigrationResult = StorageStatus & { migrated: boolean; warnings: string[] }
|
|
215
|
+
|
|
187
216
|
/** Update-board-settings request body (0.5.0; whole-object replace semantics). */
|
|
188
217
|
export type UpdateSettingsBody = {
|
|
189
218
|
/** Default code isolation for NEW tasks ('worktree' | 'none'). */
|
|
@@ -270,4 +299,4 @@ export type ChangeEvent = {
|
|
|
270
299
|
revision: number
|
|
271
300
|
kind: 'task-created' | 'task-updated' | 'task-moved' | 'task-deleted' | 'comment-added' | 'execution-recorded' | 'settings-updated' | 'ledger-replaced'
|
|
272
301
|
tasks: TaskSummary[]
|
|
273
|
-
}
|
|
302
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in task-template content (0.6.4): the four factory templates seeded
|
|
3
|
+
* into the template side file, in both shipped locales. The host seeds the zh
|
|
4
|
+
* copy as the persisted fallback (the side file is plain data); the client
|
|
5
|
+
* resolves the ACTIVE locale's copy at render / prefill time via
|
|
6
|
+
* {@link builtinTemplateContent}, so an English GUI shows English template
|
|
7
|
+
* text without ever re-seeding the side file.
|
|
8
|
+
*
|
|
9
|
+
* Kept in shared/ (not the client i18n dictionaries) because the content is
|
|
10
|
+
* structured task DATA — a checklist is an array and the prompt is multi-line
|
|
11
|
+
* — rather than UI-chrome strings, and because the host needs the same seed
|
|
12
|
+
* content to write the side file.
|
|
13
|
+
*
|
|
14
|
+
* @module dsh-taskboard/shared/builtin-templates
|
|
15
|
+
*/
|
|
16
|
+
import type { TaskTemplateSpec } from './api.ts'
|
|
17
|
+
|
|
18
|
+
/** Shipped built-in template locales (mirrors the client i18n LocaleId). */
|
|
19
|
+
export type BuiltinTemplateLocale = 'zh' | 'en'
|
|
20
|
+
|
|
21
|
+
/** One built-in template's localized name + task spec. */
|
|
22
|
+
export interface BuiltinTemplateContent {
|
|
23
|
+
name: string
|
|
24
|
+
task: TaskTemplateSpec
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Stable ids of the factory templates (persisted in the side file). */
|
|
28
|
+
export const BUILTIN_TEMPLATE_IDS = ['tpl-feature', 'tpl-bugfix', 'tpl-release', 'tpl-patrol'] as const
|
|
29
|
+
|
|
30
|
+
export type BuiltinTemplateId = (typeof BUILTIN_TEMPLATE_IDS)[number]
|
|
31
|
+
|
|
32
|
+
/** Built-in template content per locale (name + prefilled task spec). */
|
|
33
|
+
export const BUILTIN_TEMPLATE_CONTENT: Readonly<Record<BuiltinTemplateLocale, Readonly<Record<BuiltinTemplateId, BuiltinTemplateContent>>>> = {
|
|
34
|
+
zh: {
|
|
35
|
+
'tpl-feature': {
|
|
36
|
+
name: '新增功能',
|
|
37
|
+
task: {
|
|
38
|
+
title: '新增:',
|
|
39
|
+
prompt: [
|
|
40
|
+
'实现以上新功能并按序交接:',
|
|
41
|
+
'1. 明确需求边界与验收标准,列出实现要点',
|
|
42
|
+
'2. 实现功能(含类型定义与错误处理)',
|
|
43
|
+
'3. 补充测试(单测/回归)',
|
|
44
|
+
'4. 运行相关测试套件确认通过',
|
|
45
|
+
].join('\n'),
|
|
46
|
+
urgency: 'normal',
|
|
47
|
+
checklist: ['实现要点已明确(需求边界与验收标准)', '功能已实现并补充测试', '相关测试套件通过'],
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
'tpl-bugfix': {
|
|
51
|
+
name: 'Bug 修复',
|
|
52
|
+
task: {
|
|
53
|
+
title: '修复:',
|
|
54
|
+
prompt: [
|
|
55
|
+
'修复以上问题并按序交接:',
|
|
56
|
+
'1. 复现问题(写最小复现步骤或测试)',
|
|
57
|
+
'2. 定位根因,说明为什么会发生',
|
|
58
|
+
'3. 修复并补回归测试',
|
|
59
|
+
'4. 运行相关测试套件确认无回归',
|
|
60
|
+
].join('\n'),
|
|
61
|
+
urgency: 'urgent',
|
|
62
|
+
checklist: ['已复现并定位根因', '修复已提交到任务分支', '回归测试通过'],
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
'tpl-release': {
|
|
66
|
+
name: '发布检查',
|
|
67
|
+
task: {
|
|
68
|
+
title: '发布:',
|
|
69
|
+
prompt: '执行发布流程:版本号更新、构建、测试、变更记录,完成后按序交接(不要实际推送/发布,等用户确认)。',
|
|
70
|
+
urgency: 'normal',
|
|
71
|
+
checklist: ['版本号已更新(package.json 与版本常量同步)', '构建通过', '全部测试通过', '变更记录已写'],
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
'tpl-patrol': {
|
|
75
|
+
name: '例行巡检',
|
|
76
|
+
task: {
|
|
77
|
+
title: '巡检:',
|
|
78
|
+
prompt: [
|
|
79
|
+
'例行巡检:检查依赖更新、失败测试、明显代码问题与未处理的告警。',
|
|
80
|
+
'发现的问题逐条列出(严重度/位置/建议),小问题直接修复,大问题只报告不动手。',
|
|
81
|
+
'输出巡检摘要(用 {{lastComments}} 可回看上次巡检结论)。',
|
|
82
|
+
].join('\n'),
|
|
83
|
+
urgency: 'relaxed',
|
|
84
|
+
execution: { mode: 'scheduled', cron: '0 9 * * 1' },
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
en: {
|
|
89
|
+
'tpl-feature': {
|
|
90
|
+
name: 'New feature',
|
|
91
|
+
task: {
|
|
92
|
+
title: 'New feature:',
|
|
93
|
+
prompt: [
|
|
94
|
+
'Implement the new feature above and hand off in order:',
|
|
95
|
+
'1. Clarify the requirement boundaries and acceptance criteria; list the implementation points',
|
|
96
|
+
'2. Implement the feature (including type definitions and error handling)',
|
|
97
|
+
'3. Add tests (unit / regression)',
|
|
98
|
+
'4. Run the relevant test suites and confirm they pass',
|
|
99
|
+
].join('\n'),
|
|
100
|
+
urgency: 'normal',
|
|
101
|
+
checklist: ['Implementation points clarified (requirement boundaries and acceptance criteria)', 'Feature implemented with tests added', 'Relevant test suites pass'],
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
'tpl-bugfix': {
|
|
105
|
+
name: 'Bug fix',
|
|
106
|
+
task: {
|
|
107
|
+
title: 'Fix:',
|
|
108
|
+
prompt: [
|
|
109
|
+
'Fix the issue above and hand off in order:',
|
|
110
|
+
'1. Reproduce the issue (write minimal reproduction steps or a test)',
|
|
111
|
+
'2. Locate the root cause and explain why it happens',
|
|
112
|
+
'3. Fix it and add regression tests',
|
|
113
|
+
'4. Run the relevant test suites and confirm there are no regressions',
|
|
114
|
+
].join('\n'),
|
|
115
|
+
urgency: 'urgent',
|
|
116
|
+
checklist: ['Issue reproduced and root cause located', 'Fix committed to the task branch', 'Regression tests pass'],
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
'tpl-release': {
|
|
120
|
+
name: 'Release check',
|
|
121
|
+
task: {
|
|
122
|
+
title: 'Release:',
|
|
123
|
+
prompt: 'Run the release process: bump the version, build, test, and update the changelog, then hand off in order (do not actually push / publish — wait for user confirmation).',
|
|
124
|
+
urgency: 'normal',
|
|
125
|
+
checklist: ['Version bumped (package.json and version constants in sync)', 'Build passes', 'All tests pass', 'Changelog written'],
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
'tpl-patrol': {
|
|
129
|
+
name: 'Routine patrol',
|
|
130
|
+
task: {
|
|
131
|
+
title: 'Patrol:',
|
|
132
|
+
prompt: [
|
|
133
|
+
'Routine patrol: check dependency updates, failing tests, obvious code issues, and unhandled alerts.',
|
|
134
|
+
'List each finding (severity / location / suggestion); fix small issues directly, and only report (do not touch) large ones.',
|
|
135
|
+
'Output a patrol summary (use {{lastComments}} to review the last patrol\u2019s conclusions).',
|
|
136
|
+
].join('\n'),
|
|
137
|
+
urgency: 'relaxed',
|
|
138
|
+
execution: { mode: 'scheduled', cron: '0 9 * * 1' },
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Resolve one built-in template's localized content.
|
|
146
|
+
* @param id - the template id.
|
|
147
|
+
* @param locale - the requested locale.
|
|
148
|
+
* @returns the content, or undefined when `id` is not a built-in template id.
|
|
149
|
+
*/
|
|
150
|
+
export function builtinTemplateContent(id: string, locale: BuiltinTemplateLocale): BuiltinTemplateContent | undefined {
|
|
151
|
+
if (!(BUILTIN_TEMPLATE_IDS as readonly string[]).includes(id)) return undefined
|
|
152
|
+
return BUILTIN_TEMPLATE_CONTENT[locale][id as BuiltinTemplateId]
|
|
153
|
+
}
|
package/src/shared/protocol.ts
CHANGED
|
@@ -335,6 +335,15 @@ export type Actor =
|
|
|
335
335
|
| { kind: 'agent'; sessionId: string }
|
|
336
336
|
| { kind: 'system' }
|
|
337
337
|
|
|
338
|
+
/** Structured row of a multi-repo merge system comment (0.6.4). */
|
|
339
|
+
export type SystemCommentRow = {
|
|
340
|
+
/** Repo path relative to the workspace ('' = the workspace root repo). */
|
|
341
|
+
repo: string
|
|
342
|
+
outcome: 'merged' | 'noop' | 'failed'
|
|
343
|
+
/** Failure reason (verbatim) when outcome = 'failed'. */
|
|
344
|
+
error?: string
|
|
345
|
+
}
|
|
346
|
+
|
|
338
347
|
/** A progress/report comment on a task. */
|
|
339
348
|
export type CommentRecord = {
|
|
340
349
|
id: string
|
|
@@ -345,6 +354,16 @@ export type CommentRecord = {
|
|
|
345
354
|
createdAt: number
|
|
346
355
|
/** The session that wrote this comment; absent for user-written ones. */
|
|
347
356
|
threadId?: string
|
|
357
|
+
/**
|
|
358
|
+
* i18n key of a host-generated system message (0.6.4). The GUI localizes it
|
|
359
|
+
* at render time; `body` stays a zh fallback for agent tools / CSV / raw
|
|
360
|
+
* JSON views.
|
|
361
|
+
*/
|
|
362
|
+
systemKey?: string
|
|
363
|
+
/** Flat {name} interpolation params for the system message. */
|
|
364
|
+
systemParams?: Record<string, string>
|
|
365
|
+
/** Structured per-repo rows for the multi-repo merge summary (0.6.4). */
|
|
366
|
+
systemRows?: SystemCommentRow[]
|
|
348
367
|
}
|
|
349
368
|
|
|
350
369
|
/** One commit produced by an isolated execution (hash + subject). */
|
|
@@ -756,6 +775,36 @@ export function syncClaim(task: TaskRecord, to: TaskStatus, now: number, holder?
|
|
|
756
775
|
}
|
|
757
776
|
}
|
|
758
777
|
|
|
778
|
+
/**
|
|
779
|
+
* Collect unique execution session IDs associated with a task:
|
|
780
|
+
* - executions with a non-empty `sessionId`
|
|
781
|
+
* Creator and claim sessions may serve other tasks and are never included.
|
|
782
|
+
* @param task - the task record to inspect.
|
|
783
|
+
* @returns an array of distinct session IDs in stable discovery order.
|
|
784
|
+
*/
|
|
785
|
+
export function taskAssociatedSessionIds(task: TaskRecord): string[] {
|
|
786
|
+
const seen = new Set<string>()
|
|
787
|
+
const result: string[] = []
|
|
788
|
+
const push = (raw: unknown) => {
|
|
789
|
+
if (typeof raw === 'string') {
|
|
790
|
+
const trimmed = raw.trim()
|
|
791
|
+
if (trimmed.length > 0 && !seen.has(trimmed)) {
|
|
792
|
+
seen.add(trimmed)
|
|
793
|
+
result.push(trimmed)
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
if (Array.isArray(task.executions)) {
|
|
799
|
+
for (const ex of task.executions) {
|
|
800
|
+
if (ex !== null && typeof ex === 'object') {
|
|
801
|
+
push((ex as { sessionId?: unknown }).sessionId)
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
return result
|
|
806
|
+
}
|
|
807
|
+
|
|
759
808
|
/**
|
|
760
809
|
* Validate and normalize a pinned model: `{ provider, model, reasoningEffort? }`,
|
|
761
810
|
* provider and model must be non-empty trimmed strings.
|
|
@@ -1020,6 +1069,21 @@ export function validateImportedTask(raw: unknown, now: number): { ok: true; tas
|
|
|
1020
1069
|
version: numOr(ce, 'version', 1),
|
|
1021
1070
|
createdAt: numOr(ce, 'createdAt', now),
|
|
1022
1071
|
...(typeof ce.threadId === 'string' ? { threadId: ce.threadId } : {}),
|
|
1072
|
+
...(typeof ce.systemKey === 'string' && /^sys\.[A-Za-z0-9]+$/.test(ce.systemKey) && ce.systemKey.length <= 100
|
|
1073
|
+
? {
|
|
1074
|
+
systemKey: ce.systemKey,
|
|
1075
|
+
...(typeof ce.systemParams === 'object' && ce.systemParams !== null && !Array.isArray(ce.systemParams)
|
|
1076
|
+
? { systemParams: Object.fromEntries(Object.entries(ce.systemParams).filter(([key, value]) => key.length <= 100 && typeof value === 'string' && value.length <= 4000).slice(0, 20)) as Record<string, string> }
|
|
1077
|
+
: {}),
|
|
1078
|
+
...(Array.isArray(ce.systemRows)
|
|
1079
|
+
? { systemRows: ce.systemRows.filter((row): row is SystemCommentRow => typeof row === 'object' && row !== null
|
|
1080
|
+
&& typeof row.repo === 'string' && (row.repo === '' || isValidRelRepoPath(row.repo))
|
|
1081
|
+
&& ['merged', 'noop', 'failed'].includes(row.outcome)
|
|
1082
|
+
&& (row.error === undefined || typeof row.error === 'string'))
|
|
1083
|
+
.slice(0, MAX_MIRROR_REPOS).map(row => ({ repo: row.repo, outcome: row.outcome, ...(row.error !== undefined ? { error: row.error.slice(0, 4000) } : {}) })) }
|
|
1084
|
+
: {}),
|
|
1085
|
+
}
|
|
1086
|
+
: {}),
|
|
1023
1087
|
})
|
|
1024
1088
|
}
|
|
1025
1089
|
} else return fail('comments must be an array')
|
package/src/shared/version.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The plugin package version shown in the board UI. Kept in sync with
|
|
3
|
-
* package.json by a regression test (tests lock drift).
|
|
4
|
-
*
|
|
5
|
-
* @module dsh-taskboard/shared/version
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/** The package version (must equal package.json "version"). */
|
|
9
|
-
export const PLUGIN_VERSION = '0.
|
|
1
|
+
/**
|
|
2
|
+
* The plugin package version shown in the board UI. Kept in sync with
|
|
3
|
+
* package.json by a regression test (tests lock drift).
|
|
4
|
+
*
|
|
5
|
+
* @module dsh-taskboard/shared/version
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** The package version (must equal package.json "version"). */
|
|
9
|
+
export const PLUGIN_VERSION = '0.7.0'
|