dsh-taskboard 0.5.2 → 0.5.4
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 +20 -4
- package/lib/client.js +255 -28
- package/lib/host/execution.js +5 -3
- package/lib/host/execution.js.map +1 -1
- package/lib/host/templates.js +21 -1
- package/lib/host/templates.js.map +1 -1
- package/lib/host/tools.js +7 -3
- package/lib/host/tools.js.map +1 -1
- package/lib/shared/api.js.map +1 -1
- package/lib/shared/protocol.js +10 -7
- package/lib/shared/protocol.js.map +1 -1
- package/package.json +1 -1
- package/src/client/board/TaskBoard.tsx +2 -0
- package/src/client/board/TaskCard.tsx +43 -1
- package/src/client/board/TaskDetail.tsx +32 -6
- package/src/client/board/TaskFormModal.tsx +116 -8
- package/src/client/board/TemplateManager.tsx +4 -1
- package/src/client/controller.ts +29 -5
- package/src/client/index.ts +36 -5
- package/src/client/styles.ts +86 -8
- package/src/host/execution.ts +13 -5
- package/src/host/templates.ts +17 -1
- package/src/host/tools.ts +4 -3
- package/src/shared/api.ts +5 -5
- package/src/shared/protocol.ts +12 -8
- package/src/shared/version.ts +1 -1
|
@@ -16,7 +16,51 @@ import { MAX_CHECKLIST_ITEMS, defaultIsolationOf, nextCronTime, parseCron } from
|
|
|
16
16
|
import { fmtTime } from './format.ts'
|
|
17
17
|
|
|
18
18
|
/** One row of the configured model catalog (from llm.models). */
|
|
19
|
-
export interface CatalogModel {
|
|
19
|
+
export interface CatalogModel {
|
|
20
|
+
provider: string
|
|
21
|
+
model: string
|
|
22
|
+
name?: string
|
|
23
|
+
reasoning?: {
|
|
24
|
+
efforts: Array<{ id: string; name: string; description?: string }>
|
|
25
|
+
defaultEffort?: string
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Local storage key for remembering the last selected model in create mode. */
|
|
30
|
+
export const LAST_MODEL_KEY = 'dsh-taskboard-last-model-v1'
|
|
31
|
+
|
|
32
|
+
/** Read the remembered model from localStorage. */
|
|
33
|
+
export function loadLastModel(): { provider: string; model: string; reasoningEffort?: string } | undefined {
|
|
34
|
+
try {
|
|
35
|
+
const raw = localStorage.getItem(LAST_MODEL_KEY)
|
|
36
|
+
if (raw === null) return undefined
|
|
37
|
+
const parsed = JSON.parse(raw) as unknown
|
|
38
|
+
if (typeof parsed === 'object' && parsed !== null) {
|
|
39
|
+
const { provider, model, reasoningEffort } = parsed as { provider?: unknown; model?: unknown; reasoningEffort?: unknown }
|
|
40
|
+
if (typeof provider === 'string' && typeof model === 'string' && provider.trim().length > 0 && model.trim().length > 0) {
|
|
41
|
+
return {
|
|
42
|
+
provider: provider.trim(),
|
|
43
|
+
model: model.trim(),
|
|
44
|
+
...(typeof reasoningEffort === 'string' && reasoningEffort.trim().length > 0 ? { reasoningEffort: reasoningEffort.trim() } : {}),
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return undefined
|
|
49
|
+
} catch {
|
|
50
|
+
return undefined
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Save the remembered model to localStorage. */
|
|
55
|
+
export function saveLastModel(model?: { provider: string; model: string; reasoningEffort?: string }): void {
|
|
56
|
+
try {
|
|
57
|
+
if (model === undefined) {
|
|
58
|
+
localStorage.removeItem(LAST_MODEL_KEY)
|
|
59
|
+
} else {
|
|
60
|
+
localStorage.setItem(LAST_MODEL_KEY, JSON.stringify(model))
|
|
61
|
+
}
|
|
62
|
+
} catch { /* storage unavailable */ }
|
|
63
|
+
}
|
|
20
64
|
|
|
21
65
|
/** Urgency segmented options with a one-line hint each. */
|
|
22
66
|
const URGENCY_OPTIONS: ReadonlyArray<{ value: Urgency; label: string; hint: string }> = [
|
|
@@ -126,7 +170,12 @@ export function TaskFormModal({ controller, task }: { controller: BoardControlle
|
|
|
126
170
|
const [mode, setMode] = useState<'claim' | 'scheduled'>(task?.execution.mode === 'scheduled' || prefill?.execution?.mode === 'scheduled' ? 'scheduled' : 'claim')
|
|
127
171
|
const [cron, setCron] = useState(task?.execution.cron ?? prefill?.execution?.cron ?? '0 9 * * *')
|
|
128
172
|
const [catalog, setCatalog] = useState<CatalogModel[]>([])
|
|
129
|
-
|
|
173
|
+
|
|
174
|
+
// Model & reasoning effort selection:
|
|
175
|
+
// In create mode (when not pinned by template), prefill from remembered last choice.
|
|
176
|
+
const initialModel = task?.model ?? prefill?.model ?? (!editing ? loadLastModel() : undefined)
|
|
177
|
+
const [model, setModel] = useState(initialModel !== undefined ? JSON.stringify({ provider: initialModel.provider, model: initialModel.model }) : '')
|
|
178
|
+
const [reasoningEffort, setReasoningEffort] = useState(initialModel?.reasoningEffort ?? '')
|
|
130
179
|
// Preset roster (0.3.3): create mode PRE-SELECTS the deployment default
|
|
131
180
|
// (标准模式 in this deployment); '' = 跟随部署默认 (submit omits the field).
|
|
132
181
|
const initialPreset = task?.presetId ?? prefill?.presetId ?? ''
|
|
@@ -215,9 +264,24 @@ export function TaskFormModal({ controller, task }: { controller: BoardControlle
|
|
|
215
264
|
/** Checklist rows with non-empty text (blank rows are dropped on submit). */
|
|
216
265
|
const filledRows = (): CheckRow[] => checkRows.map(r => ({ ...r, text: r.text.trim() })).filter(r => r.text.length > 0)
|
|
217
266
|
|
|
267
|
+
const parsedModel = model !== '' ? (JSON.parse(model) as { provider: string; model: string }) : undefined
|
|
268
|
+
const currentCatalogModel = parsedModel !== undefined ? catalog.find(m => m.provider === parsedModel.provider && m.model === parsedModel.model) : undefined
|
|
269
|
+
const modelReasoning = currentCatalogModel?.reasoning
|
|
270
|
+
|
|
271
|
+
const buildPickedModel = (): { provider: string; model: string; reasoningEffort?: string } | undefined => {
|
|
272
|
+
if (parsedModel === undefined) return undefined
|
|
273
|
+
const eff = reasoningEffort.trim()
|
|
274
|
+
return {
|
|
275
|
+
provider: parsedModel.provider,
|
|
276
|
+
model: parsedModel.model,
|
|
277
|
+
...(eff.length > 0 ? { reasoningEffort: eff } : {}),
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
218
281
|
const submit = (): void => {
|
|
219
282
|
if (!valid || busy) return
|
|
220
|
-
const picked =
|
|
283
|
+
const picked = buildPickedModel()
|
|
284
|
+
if (!editing) saveLastModel(picked)
|
|
221
285
|
const isolationOut = isolationPayload()
|
|
222
286
|
const presetOut = presetPayload()
|
|
223
287
|
const rows = filledRows()
|
|
@@ -255,7 +319,8 @@ export function TaskFormModal({ controller, task }: { controller: BoardControlle
|
|
|
255
319
|
/** Save the form, then immediately trigger a manual run of the task. */
|
|
256
320
|
const submitAndRun = (): void => {
|
|
257
321
|
if (!valid || runBlocked || busy) return
|
|
258
|
-
const picked =
|
|
322
|
+
const picked = buildPickedModel()
|
|
323
|
+
if (!editing) saveLastModel(picked)
|
|
259
324
|
const isolationOut = isolationPayload()
|
|
260
325
|
const presetOut = presetPayload()
|
|
261
326
|
const rows = filledRows()
|
|
@@ -325,7 +390,24 @@ export function TaskFormModal({ controller, task }: { controller: BoardControlle
|
|
|
325
390
|
</Field>
|
|
326
391
|
|
|
327
392
|
<Field label="模型(默认 = 会话默认模型)">
|
|
328
|
-
<select
|
|
393
|
+
<select
|
|
394
|
+
value={model}
|
|
395
|
+
onChange={e => {
|
|
396
|
+
const val = e.target.value
|
|
397
|
+
setModel(val)
|
|
398
|
+
if (val === '') {
|
|
399
|
+
setReasoningEffort('')
|
|
400
|
+
} else {
|
|
401
|
+
const pm = JSON.parse(val) as { provider: string; model: string }
|
|
402
|
+
const cm = catalog.find(m => m.provider === pm.provider && m.model === pm.model)
|
|
403
|
+
if (cm?.reasoning?.defaultEffort !== undefined) {
|
|
404
|
+
setReasoningEffort(cm.reasoning.defaultEffort)
|
|
405
|
+
} else {
|
|
406
|
+
setReasoningEffort('')
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}}
|
|
410
|
+
>
|
|
329
411
|
<option value="">默认模型</option>
|
|
330
412
|
{catalog.map(m => (
|
|
331
413
|
<option key={`${m.provider}/${m.model}`} value={JSON.stringify({ provider: m.provider, model: m.model })}>
|
|
@@ -335,6 +417,32 @@ export function TaskFormModal({ controller, task }: { controller: BoardControlle
|
|
|
335
417
|
</select>
|
|
336
418
|
</Field>
|
|
337
419
|
|
|
420
|
+
{parsedModel !== undefined && (
|
|
421
|
+
<Field label="思考强度(Reasoning Effort)">
|
|
422
|
+
<select
|
|
423
|
+
value={reasoningEffort}
|
|
424
|
+
onChange={e => setReasoningEffort(e.target.value)}
|
|
425
|
+
title="设置模型的思考强度(如 low/medium/high);默认 = 跟随模型/提供商默认"
|
|
426
|
+
>
|
|
427
|
+
<option value="">跟随模型默认{modelReasoning?.defaultEffort !== undefined ? `(当前:${modelReasoning.efforts.find(ef => ef.id === modelReasoning.defaultEffort)?.name ?? modelReasoning.defaultEffort})` : ''}</option>
|
|
428
|
+
{modelReasoning !== undefined && modelReasoning.efforts.length > 0 ? (
|
|
429
|
+
modelReasoning.efforts.map(eff => (
|
|
430
|
+
<option key={eff.id} value={eff.id}>
|
|
431
|
+
{eff.name}{eff.description ? ` (${eff.description})` : ''}
|
|
432
|
+
</option>
|
|
433
|
+
))
|
|
434
|
+
) : (
|
|
435
|
+
<>
|
|
436
|
+
<option value="low">低 (low)</option>
|
|
437
|
+
<option value="medium">中 (medium)</option>
|
|
438
|
+
<option value="high">高 (high)</option>
|
|
439
|
+
<option value="none">关闭思考 (none)</option>
|
|
440
|
+
</>
|
|
441
|
+
)}
|
|
442
|
+
</select>
|
|
443
|
+
</Field>
|
|
444
|
+
)}
|
|
445
|
+
|
|
338
446
|
{presets.length > 0 && (
|
|
339
447
|
<Field label="执行模式(preset)">
|
|
340
448
|
<select value={presetId} onChange={e => setPresetId(e.target.value)} title="执行会话按该 preset 组合(决定工具集与人设);默认 = 部署默认 preset">
|
|
@@ -370,8 +478,8 @@ export function TaskFormModal({ controller, task }: { controller: BoardControlle
|
|
|
370
478
|
<textarea value={description} onChange={e => setDescription(e.target.value)} placeholder="需求细节、验收标准…" />
|
|
371
479
|
</Field>
|
|
372
480
|
|
|
373
|
-
<Field label={editing ? '执行 Prompt' : '执行 Prompt
|
|
374
|
-
<textarea value={prompt} onChange={e => setPrompt(e.target.value)} placeholder={'
|
|
481
|
+
<Field label={editing ? '执行 Prompt(实际 Prompt = 标题+任务描述+Prompt)' : '执行 Prompt(可选;实际 Prompt = 标题+任务描述+Prompt)'} full>
|
|
482
|
+
<textarea value={prompt} onChange={e => setPrompt(e.target.value)} placeholder={'追加在「标题+任务描述」之后发给执行会话的补充指令。支持模板变量:{{lastExecution}}(上次执行结果)、{{lastComments}}(最近 3 条评论)'} />
|
|
375
483
|
</Field>
|
|
376
484
|
|
|
377
485
|
<Field label="执行方式" full>
|
|
@@ -484,7 +592,7 @@ interface TaskRecordLike {
|
|
|
484
592
|
workspaceId: string
|
|
485
593
|
urgency: Urgency
|
|
486
594
|
execution: { mode: 'claim' | 'scheduled'; cron?: string }
|
|
487
|
-
model?: { provider: string; model: string }
|
|
595
|
+
model?: { provider: string; model: string; reasoningEffort?: string }
|
|
488
596
|
isolation?: IsolationMode
|
|
489
597
|
presetId?: string
|
|
490
598
|
checklist?: ChecklistItem[]
|
|
@@ -64,7 +64,10 @@ export function TemplateManager({ controller }: { controller: BoardController })
|
|
|
64
64
|
if (e.key === 'Enter') save(t.id, nameOf(t.id, t.name))
|
|
65
65
|
}}
|
|
66
66
|
/>
|
|
67
|
-
<span
|
|
67
|
+
<span
|
|
68
|
+
className="dsh-atb-tplm-meta"
|
|
69
|
+
title={`${t.builtin === true ? '内置' : '自建'}${t.task.checklist !== undefined && t.task.checklist.length > 0 ? ` · 清单 ${t.task.checklist.length} 项` : ''}${t.task.urgency !== undefined ? ` · ${t.task.urgency}` : ''}`}
|
|
70
|
+
>
|
|
68
71
|
{t.builtin === true ? '内置' : '自建'}
|
|
69
72
|
{t.task.checklist !== undefined && t.task.checklist.length > 0 ? ` · 清单 ${t.task.checklist.length} 项` : ''}
|
|
70
73
|
{t.task.urgency !== undefined ? ` · ${t.task.urgency}` : ''}
|
package/src/client/controller.ts
CHANGED
|
@@ -22,7 +22,7 @@ export interface BoardFilters {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
/** Column sort orders. */
|
|
25
|
-
export type SortBy = 'default' | 'updated' | 'urgency' | 'created'
|
|
25
|
+
export type SortBy = 'default' | 'updated' | 'urgency' | 'created' | 'title'
|
|
26
26
|
|
|
27
27
|
/** localStorage key for persisted view state (filters + sort). */
|
|
28
28
|
const VIEW_KEY = 'dsh-taskboard-view-v1'
|
|
@@ -33,7 +33,7 @@ function loadView(): { workspaceId?: string; urgencies: Urgency[]; sortBy: SortB
|
|
|
33
33
|
const raw = localStorage.getItem(VIEW_KEY)
|
|
34
34
|
if (raw === null) return { urgencies: [], sortBy: 'default' }
|
|
35
35
|
const parsed = JSON.parse(raw) as { workspaceId?: string; urgencies?: Urgency[]; sortBy?: SortBy }
|
|
36
|
-
const sortBy = parsed.sortBy === 'updated' || parsed.sortBy === 'urgency' || parsed.sortBy === 'created' ? parsed.sortBy : 'default'
|
|
36
|
+
const sortBy = parsed.sortBy === 'updated' || parsed.sortBy === 'urgency' || parsed.sortBy === 'created' || parsed.sortBy === 'title' ? parsed.sortBy : 'default'
|
|
37
37
|
return {
|
|
38
38
|
workspaceId: typeof parsed.workspaceId === 'string' ? parsed.workspaceId : undefined,
|
|
39
39
|
urgencies: Array.isArray(parsed.urgencies) ? parsed.urgencies.filter(u => u === 'urgent' || u === 'normal' || u === 'relaxed') : [],
|
|
@@ -114,7 +114,15 @@ export class BoardController {
|
|
|
114
114
|
private sessionJumper: ((sessionId: string) => Promise<SessionJumpResult>) | undefined
|
|
115
115
|
/** Composer catalog faces, installed formally by the client entry (T13). */
|
|
116
116
|
private readonly catalogFaces: {
|
|
117
|
-
models?: () => Promise<Array<{
|
|
117
|
+
models?: () => Promise<Array<{
|
|
118
|
+
provider: string
|
|
119
|
+
model: string
|
|
120
|
+
name?: string
|
|
121
|
+
reasoning?: {
|
|
122
|
+
efforts: Array<{ id: string; name: string; description?: string }>
|
|
123
|
+
defaultEffort?: string
|
|
124
|
+
}
|
|
125
|
+
}>>
|
|
118
126
|
presets?: () => Promise<{ presets: Array<{ id: string; name?: string }>; defaultId?: string }>
|
|
119
127
|
} = {}
|
|
120
128
|
|
|
@@ -278,7 +286,15 @@ export class BoardController {
|
|
|
278
286
|
}
|
|
279
287
|
|
|
280
288
|
/** T13: formal installers for the composer catalog faces (was a monkeypatch from the client entry). */
|
|
281
|
-
installModelCatalog(fn: () => Promise<Array<{
|
|
289
|
+
installModelCatalog(fn: () => Promise<Array<{
|
|
290
|
+
provider: string
|
|
291
|
+
model: string
|
|
292
|
+
name?: string
|
|
293
|
+
reasoning?: {
|
|
294
|
+
efforts: Array<{ id: string; name: string; description?: string }>
|
|
295
|
+
defaultEffort?: string
|
|
296
|
+
}
|
|
297
|
+
}>>): void {
|
|
282
298
|
this.catalogFaces.models = fn
|
|
283
299
|
}
|
|
284
300
|
|
|
@@ -288,7 +304,15 @@ export class BoardController {
|
|
|
288
304
|
}
|
|
289
305
|
|
|
290
306
|
/** The installed model catalog face, when the runtime provides one. */
|
|
291
|
-
get modelCatalog(): (() => Promise<Array<{
|
|
307
|
+
get modelCatalog(): (() => Promise<Array<{
|
|
308
|
+
provider: string
|
|
309
|
+
model: string
|
|
310
|
+
name?: string
|
|
311
|
+
reasoning?: {
|
|
312
|
+
efforts: Array<{ id: string; name: string; description?: string }>
|
|
313
|
+
defaultEffort?: string
|
|
314
|
+
}
|
|
315
|
+
}>>) | undefined {
|
|
292
316
|
return this.catalogFaces.models
|
|
293
317
|
}
|
|
294
318
|
|
package/src/client/index.ts
CHANGED
|
@@ -28,7 +28,25 @@ export const inject = ['connection']
|
|
|
28
28
|
interface ConnectionFace {
|
|
29
29
|
api: {
|
|
30
30
|
llm: {
|
|
31
|
-
models(payload: Record<string, never>): Promise<{
|
|
31
|
+
models(payload: Record<string, never>): Promise<{
|
|
32
|
+
result: {
|
|
33
|
+
ok: true
|
|
34
|
+
value: {
|
|
35
|
+
groups: Array<{
|
|
36
|
+
id: string
|
|
37
|
+
name: string
|
|
38
|
+
models: Array<{
|
|
39
|
+
id: string
|
|
40
|
+
name?: string
|
|
41
|
+
reasoning?: {
|
|
42
|
+
efforts: Array<{ id: string; name: string; description?: string }>
|
|
43
|
+
defaultEffort?: string
|
|
44
|
+
}
|
|
45
|
+
}>
|
|
46
|
+
}>
|
|
47
|
+
}
|
|
48
|
+
} | { ok: false }
|
|
49
|
+
}>
|
|
32
50
|
}
|
|
33
51
|
agentPresets?: {
|
|
34
52
|
list(payload: Record<string, never>): Promise<{ result: { ok: true; value: { presets: Array<{ id: string; name?: string; isDefault: boolean }> } } | { ok: false } }>
|
|
@@ -43,8 +61,8 @@ interface ClientContextFace {
|
|
|
43
61
|
}
|
|
44
62
|
|
|
45
63
|
/**
|
|
46
|
-
*
|
|
47
|
-
* @param ctx - the client context
|
|
64
|
+
* Client entry: installs styles, starts the controller, mounts DOM seats.
|
|
65
|
+
* @param ctx - the cordis client context.
|
|
48
66
|
*/
|
|
49
67
|
export function apply(ctx: ClientContextFace): void {
|
|
50
68
|
try {
|
|
@@ -57,14 +75,27 @@ export function apply(ctx: ClientContextFace): void {
|
|
|
57
75
|
// monkeypatched instance properties).
|
|
58
76
|
const connection = ctx.get?.('connection') as ConnectionFace | undefined
|
|
59
77
|
if (connection !== undefined) {
|
|
60
|
-
type CatalogRow = {
|
|
78
|
+
type CatalogRow = {
|
|
79
|
+
provider: string
|
|
80
|
+
model: string
|
|
81
|
+
name?: string
|
|
82
|
+
reasoning?: {
|
|
83
|
+
efforts: Array<{ id: string; name: string; description?: string }>
|
|
84
|
+
defaultEffort?: string
|
|
85
|
+
}
|
|
86
|
+
}
|
|
61
87
|
controller.installModelCatalog(async (): Promise<CatalogRow[]> => {
|
|
62
88
|
const response = await connection.api.llm.models({})
|
|
63
89
|
if (!response.result.ok) return []
|
|
64
90
|
const out: CatalogRow[] = []
|
|
65
91
|
for (const group of response.result.value.groups) {
|
|
66
92
|
for (const model of group.models) {
|
|
67
|
-
out.push({
|
|
93
|
+
out.push({
|
|
94
|
+
provider: group.id,
|
|
95
|
+
model: model.id,
|
|
96
|
+
name: model.name,
|
|
97
|
+
...(model.reasoning !== undefined ? { reasoning: model.reasoning } : {}),
|
|
98
|
+
})
|
|
68
99
|
}
|
|
69
100
|
}
|
|
70
101
|
return out
|
package/src/client/styles.ts
CHANGED
|
@@ -95,8 +95,43 @@ html[data-dsh-atb-active] .dsh-atb-view { display: flex; flex-direction: column;
|
|
|
95
95
|
.dsh-atb-spacer { flex: 1; }
|
|
96
96
|
.dsh-atb-select, .dsh-atb-input {
|
|
97
97
|
font: inherit; font-size: 12.5px; padding: 5px 8px; border-radius: 7px;
|
|
98
|
-
border: 1px solid var(--dsw-border, rgba(128,128,128,.35));
|
|
99
|
-
background: var(--dsw-bg, transparent);
|
|
98
|
+
border: 1px solid var(--dsw-border, var(--dsw-alias-border-l2, rgba(128,128,128,.35)));
|
|
99
|
+
background: var(--dsw-alias-bg-module-platform, var(--dsw-bg-elevated, var(--dsw-bg, transparent)));
|
|
100
|
+
color: var(--dsw-alias-label-primary, var(--dsw-text-primary, inherit));
|
|
101
|
+
color-scheme: light dark;
|
|
102
|
+
}
|
|
103
|
+
.dsh-atb-select option,
|
|
104
|
+
.dsh-atb-modal-body select option {
|
|
105
|
+
background: var(--dsw-alias-bg-overlay, var(--dsw-alias-bg-module-platform, #252830));
|
|
106
|
+
color: var(--dsw-alias-label-primary, var(--dsw-text-primary, #e6e8eb));
|
|
107
|
+
}
|
|
108
|
+
body[data-ds-dark-theme] .dsh-atb-board,
|
|
109
|
+
body[data-ds-dark-theme] .dsh-atb-modal,
|
|
110
|
+
body[data-ds-dark-theme] .dsh-atb-select,
|
|
111
|
+
body[data-ds-dark-theme] .dsh-atb-input,
|
|
112
|
+
body[data-ds-dark-theme] .dsh-atb-modal-body select,
|
|
113
|
+
body[data-ds-dark-theme] .dsh-atb-modal-body input,
|
|
114
|
+
body[data-ds-dark-theme] .dsh-atb-modal-body textarea {
|
|
115
|
+
color-scheme: dark;
|
|
116
|
+
}
|
|
117
|
+
body[data-ds-dark-theme] .dsh-atb-select option,
|
|
118
|
+
body[data-ds-dark-theme] .dsh-atb-modal-body select option {
|
|
119
|
+
background: #252830;
|
|
120
|
+
color: #e6e8eb;
|
|
121
|
+
}
|
|
122
|
+
@media (prefers-color-scheme: dark) {
|
|
123
|
+
.dsh-atb-select,
|
|
124
|
+
.dsh-atb-input,
|
|
125
|
+
.dsh-atb-modal-body select,
|
|
126
|
+
.dsh-atb-modal-body input,
|
|
127
|
+
.dsh-atb-modal-body textarea {
|
|
128
|
+
color-scheme: dark;
|
|
129
|
+
}
|
|
130
|
+
.dsh-atb-select option,
|
|
131
|
+
.dsh-atb-modal-body select option {
|
|
132
|
+
background: #252830;
|
|
133
|
+
color: #e6e8eb;
|
|
134
|
+
}
|
|
100
135
|
}
|
|
101
136
|
.dsh-atb-chip {
|
|
102
137
|
display: inline-flex; align-items: center; gap: 5px;
|
|
@@ -172,6 +207,19 @@ html[data-dsh-atb-active] .dsh-atb-view { display: flex; flex-direction: column;
|
|
|
172
207
|
.dsh-atb-badge[data-kind="trashed"] { background: rgba(229,72,77,.14); color: #e5484d; text-decoration: line-through; }
|
|
173
208
|
.dsh-atb-badge[data-kind="done"] { background: rgba(46,160,67,.16); color: #2ea043; }
|
|
174
209
|
.dsh-atb-badge[data-kind="running"] { background: rgba(229,152,42,.16); color: #e69842; }
|
|
210
|
+
.dsh-atb-card-session {
|
|
211
|
+
font: inherit; font-size: 10.5px; line-height: 1; padding: 2px 6px; border-radius: 5px;
|
|
212
|
+
border: 1px solid var(--dsw-border, rgba(128,128,128,.3));
|
|
213
|
+
background: var(--dsw-bg-elevated, rgba(128,128,128,.14));
|
|
214
|
+
color: var(--dsw-text-secondary, inherit);
|
|
215
|
+
cursor: pointer; display: inline-flex; align-items: center; gap: 3px;
|
|
216
|
+
transition: all .15s ease;
|
|
217
|
+
}
|
|
218
|
+
.dsh-atb-card-session:hover {
|
|
219
|
+
border-color: var(--dsw-alias-brand-primary, #1f2328);
|
|
220
|
+
background: var(--dsw-hover, rgba(128,128,128,.24));
|
|
221
|
+
color: var(--dsw-alias-label-primary, inherit);
|
|
222
|
+
}
|
|
175
223
|
|
|
176
224
|
/* ---------- card quick review (in_review column) ---------- */
|
|
177
225
|
.dsh-atb-quick { display: flex; gap: 6px; margin-top: 7px; }
|
|
@@ -219,6 +267,17 @@ html[data-dsh-atb-active] .dsh-atb-view { display: flex; flex-direction: column;
|
|
|
219
267
|
background: var(--dsw-bg-elevated, rgba(128,128,128,.07)); color: var(--dsw-text-secondary, inherit);
|
|
220
268
|
}
|
|
221
269
|
.dsh-atb-detail-edit:hover { border-color: var(--dsw-alias-brand-primary, #1f2328); color: var(--dsw-alias-label-primary, inherit); }
|
|
270
|
+
.dsh-atb-detail-session {
|
|
271
|
+
font: inherit; font-size: 12px; padding: 4px 10px; border-radius: 7px; cursor: pointer;
|
|
272
|
+
border: 1px solid var(--dsw-border, rgba(128,128,128,.32));
|
|
273
|
+
background: var(--dsw-bg-elevated, rgba(128,128,128,.1));
|
|
274
|
+
color: var(--dsw-alias-label-primary, inherit);
|
|
275
|
+
display: inline-flex; align-items: center; gap: 4px;
|
|
276
|
+
}
|
|
277
|
+
.dsh-atb-detail-session:hover {
|
|
278
|
+
border-color: var(--dsw-alias-brand-primary, #1f2328);
|
|
279
|
+
background: var(--dsw-hover, rgba(128,128,128,.22));
|
|
280
|
+
}
|
|
222
281
|
|
|
223
282
|
.dsh-atb-statuspill {
|
|
224
283
|
flex: none; font-size: 11px; font-weight: 600; padding: 2px 9px; border-radius: 999px; letter-spacing: .02em;
|
|
@@ -236,6 +295,14 @@ html[data-dsh-atb-active] .dsh-atb-view { display: flex; flex-direction: column;
|
|
|
236
295
|
padding: 3px 8px; border-radius: 6px;
|
|
237
296
|
background: var(--dsw-bg-inset, rgba(128,128,128,.09)); color: var(--dsw-text-secondary, #999);
|
|
238
297
|
}
|
|
298
|
+
button.dsh-atb-chip2.dsh-atb-chip-btn {
|
|
299
|
+
font: inherit; border: 1px solid var(--dsw-border, rgba(128,128,128,.25)); cursor: pointer;
|
|
300
|
+
}
|
|
301
|
+
button.dsh-atb-chip2.dsh-atb-chip-btn:hover {
|
|
302
|
+
border-color: var(--dsw-alias-brand-primary, #1f2328);
|
|
303
|
+
color: var(--dsw-alias-label-primary, inherit);
|
|
304
|
+
background: var(--dsw-hover, rgba(128,128,128,.2));
|
|
305
|
+
}
|
|
239
306
|
.dsh-atb-chip2-icon { font-size: 10.5px; opacity: .85; }
|
|
240
307
|
.dsh-atb-chip2[data-tone="urgent"] { background: rgba(229,72,77,.15); color: #e5484d; }
|
|
241
308
|
.dsh-atb-chip2[data-tone="normal"] { background: rgba(142,78,198,.14); color: #a06ce0; }
|
|
@@ -649,19 +716,30 @@ html[data-dsh-atb-active] .dsh-atb-view { display: flex; flex-direction: column;
|
|
|
649
716
|
.dsh-atb-newmenu-opt:hover { background: var(--dsw-alias-bg-layer-2, rgba(128,128,128,.1)); }
|
|
650
717
|
.dsh-atb-newmenu-sep { height: 1px; margin: 4px 6px; background: var(--dsw-alias-border-l2, rgba(128,128,128,.25)); }
|
|
651
718
|
|
|
652
|
-
.dsh-atb-tplm { max-width:
|
|
653
|
-
.dsh-atb-tplm
|
|
719
|
+
.dsh-atb-tplm { max-width: 600px; width: min(600px, 92vw); }
|
|
720
|
+
.dsh-atb-tplm .dsh-atb-modal-body,
|
|
721
|
+
.dsh-atb-set .dsh-atb-modal-body,
|
|
722
|
+
.dsh-atb-diag .dsh-atb-modal-body,
|
|
723
|
+
.dsh-atb-imp .dsh-atb-modal-body {
|
|
724
|
+
display: flex; flex-direction: column; gap: 10px;
|
|
725
|
+
}
|
|
726
|
+
.dsh-atb-tplm-list { display: flex; flex-direction: column; gap: 8px; width: 100%; }
|
|
654
727
|
.dsh-atb-tplm-row {
|
|
655
|
-
display: flex; align-items: center; gap:
|
|
728
|
+
display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 8px 12px; border-radius: 8px;
|
|
656
729
|
border: 1px solid var(--dsw-alias-border-l2, rgba(128,128,128,.25));
|
|
730
|
+
background: var(--dsw-alias-bg-layer-1, rgba(128,128,128,.02));
|
|
657
731
|
}
|
|
658
732
|
.dsh-atb-tplm-name {
|
|
659
|
-
flex: 0 0
|
|
733
|
+
flex: 0 0 150px; width: 150px; max-width: 150px; font-size: 12.5px; padding: 5px 8px; border-radius: 7px;
|
|
660
734
|
border: 1px solid var(--dsw-alias-border-l2, rgba(128,128,128,.3));
|
|
661
735
|
background: var(--dsw-alias-bg-layer-1, transparent); color: inherit;
|
|
736
|
+
box-sizing: border-box;
|
|
737
|
+
}
|
|
738
|
+
.dsh-atb-tplm-meta {
|
|
739
|
+
flex: 1; min-width: 0; font-size: 11px; color: var(--dsw-alias-label-tertiary, gray);
|
|
740
|
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
662
741
|
}
|
|
663
|
-
.dsh-atb-tplm-
|
|
664
|
-
.dsh-atb-tplm-btns { display: flex; gap: 6px; flex-shrink: 0; }
|
|
742
|
+
.dsh-atb-tplm-btns { display: flex; align-items: center; gap: 6px; flex-shrink: 0; }
|
|
665
743
|
|
|
666
744
|
.dsh-atb-imp { max-width: 600px; width: min(600px, 92vw); }
|
|
667
745
|
.dsh-atb-imp-picker { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
|
package/src/host/execution.ts
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
normalizeBody,
|
|
19
19
|
type ExecutionRecord,
|
|
20
20
|
type IsolationMode,
|
|
21
|
+
type TaskModel,
|
|
21
22
|
type TaskRecord,
|
|
22
23
|
} from '../shared/protocol.ts'
|
|
23
24
|
import { sanitizeBranchName, worktreePathOf, type GitFace, type SettlementFacts } from './git.ts'
|
|
@@ -32,7 +33,7 @@ export interface AgentsFace {
|
|
|
32
33
|
create(options: {
|
|
33
34
|
sessionId: string
|
|
34
35
|
meta?: { cwd?: string; agentPreset?: string }
|
|
35
|
-
agentOptions?: { provider?: string; model?: string }
|
|
36
|
+
agentOptions?: { provider?: string; model?: string; reasoningEffort?: string }
|
|
36
37
|
/** Preset composition callback: mounts tools/persona into the agent's scoped context. */
|
|
37
38
|
setup?: (agentCtx: unknown) => Promise<void> | void
|
|
38
39
|
}): Promise<{
|
|
@@ -77,7 +78,7 @@ export interface ExecutionDeps {
|
|
|
77
78
|
events: EventsFace
|
|
78
79
|
now: () => number
|
|
79
80
|
/** The deployment default model (fills sessions of unpinned tasks). */
|
|
80
|
-
defaultModel?: () =>
|
|
81
|
+
defaultModel?: () => TaskModel | undefined
|
|
81
82
|
/** Mint session ids (injectable for tests). */
|
|
82
83
|
mintSessionId?: () => string
|
|
83
84
|
/** Mint message ids (injectable for tests). */
|
|
@@ -404,7 +405,13 @@ export class ExecutionService {
|
|
|
404
405
|
cwd: workspace.path,
|
|
405
406
|
...(composition !== undefined ? { agentPreset: composition.agentPreset } : {}),
|
|
406
407
|
},
|
|
407
|
-
...(model !== undefined ? {
|
|
408
|
+
...(model !== undefined ? {
|
|
409
|
+
agentOptions: {
|
|
410
|
+
provider: model.provider,
|
|
411
|
+
model: model.model,
|
|
412
|
+
...(model.reasoningEffort !== undefined ? { reasoningEffort: model.reasoningEffort } : {}),
|
|
413
|
+
},
|
|
414
|
+
} : {}),
|
|
408
415
|
...(composition !== undefined ? { setup: composition.setup } : {}),
|
|
409
416
|
})
|
|
410
417
|
} catch (error) {
|
|
@@ -720,8 +727,9 @@ export class ExecutionService {
|
|
|
720
727
|
}
|
|
721
728
|
|
|
722
729
|
/**
|
|
723
|
-
* The card body as a normal user bubble: the effective prompt (
|
|
724
|
-
*
|
|
730
|
+
* The card body as a normal user bubble: the effective prompt (title+
|
|
731
|
+
* description, with the explicit prompt appended when set) with template
|
|
732
|
+
* variables resolved from
|
|
725
733
|
* the task's own history at submit time (valuable for recurring patrols):
|
|
726
734
|
* `{{lastExecution}}` → the previous execution's trigger/outcome/error;
|
|
727
735
|
* `{{lastComments}}` → the last three comments (who + body).
|
package/src/host/templates.ts
CHANGED
|
@@ -13,13 +13,29 @@ import type { TaskTemplate } from '../shared/api.ts'
|
|
|
13
13
|
|
|
14
14
|
/** The built-in templates seeded when the side file does not exist yet. */
|
|
15
15
|
export const BUILTIN_TEMPLATES: ReadonlyArray<{ id: string; name: string; task: TaskTemplate['task'] }> = [
|
|
16
|
+
{
|
|
17
|
+
id: 'tpl-feature',
|
|
18
|
+
name: '新增功能',
|
|
19
|
+
task: {
|
|
20
|
+
title: '新增:',
|
|
21
|
+
prompt: [
|
|
22
|
+
'实现以上新功能并按序交接:',
|
|
23
|
+
'1. 明确需求边界与验收标准,列出实现要点',
|
|
24
|
+
'2. 实现功能(含类型定义与错误处理)',
|
|
25
|
+
'3. 补充测试(单测/回归)',
|
|
26
|
+
'4. 运行相关测试套件确认通过',
|
|
27
|
+
].join('\n'),
|
|
28
|
+
urgency: 'normal',
|
|
29
|
+
checklist: ['实现要点已明确(需求边界与验收标准)', '功能已实现并补充测试', '相关测试套件通过'],
|
|
30
|
+
},
|
|
31
|
+
},
|
|
16
32
|
{
|
|
17
33
|
id: 'tpl-bugfix',
|
|
18
34
|
name: 'Bug 修复',
|
|
19
35
|
task: {
|
|
20
36
|
title: '修复:',
|
|
21
37
|
prompt: [
|
|
22
|
-
'
|
|
38
|
+
'修复以上问题并按序交接:',
|
|
23
39
|
'1. 复现问题(写最小复现步骤或测试)',
|
|
24
40
|
'2. 定位根因,说明为什么会发生',
|
|
25
41
|
'3. 修复并补回归测试',
|
package/src/host/tools.ts
CHANGED
|
@@ -91,7 +91,7 @@ function taskDetail(t: TaskRecord & { effectivePrompt?: string }): string {
|
|
|
91
91
|
const holder = isClaimedBy(t)
|
|
92
92
|
if (holder !== undefined) lines.push(`认领: agent ${String(holder).slice(0, 24)}(持有期间其他会话不可移动)`)
|
|
93
93
|
if (t.execution.nextRunAt !== undefined) lines.push(`下次触发: ${new Date(t.execution.nextRunAt).toISOString()}`)
|
|
94
|
-
if (t.model !== undefined) lines.push(`固定模型: ${t.model.provider}/${t.model.model}`)
|
|
94
|
+
if (t.model !== undefined) lines.push(`固定模型: ${t.model.provider}/${t.model.model}${t.model.reasoningEffort !== undefined ? ` (思考强度: ${t.model.reasoningEffort})` : ''}`)
|
|
95
95
|
if (t.presetId !== undefined) lines.push(`执行模式: ${t.presetId}(未指定时为部署默认 preset)`)
|
|
96
96
|
lines.push(`描述: ${t.description.length > 0 ? t.description : '(无)'}`)
|
|
97
97
|
lines.push(`执行 Prompt: ${t.effectivePrompt ?? effectivePrompt(t)}`)
|
|
@@ -369,7 +369,7 @@ export function registerTaskboardTools(ctx: ToolContextFace, deps: ToolDeps): Ar
|
|
|
369
369
|
workspaceId: { type: 'string', required: true, description: 'Project (DSH workspace id) this task belongs to.' },
|
|
370
370
|
urgency: { type: 'string', required: true, description: 'urgent (red) | normal (purple) | relaxed (blue).' },
|
|
371
371
|
description: { type: 'string', description: 'What the task involves (plain text).' },
|
|
372
|
-
prompt: { type: 'string', description: '
|
|
372
|
+
prompt: { type: 'string', description: 'Extra execution instructions; the session receives title+description+this prompt.' },
|
|
373
373
|
status: { type: 'string', description: 'Initial status; default todo. backlog = not approved for execution.' },
|
|
374
374
|
execution: {
|
|
375
375
|
type: 'object',
|
|
@@ -383,10 +383,11 @@ export function registerTaskboardTools(ctx: ToolContextFace, deps: ToolDeps): Ar
|
|
|
383
383
|
model: {
|
|
384
384
|
type: 'object',
|
|
385
385
|
additionalProperties: false,
|
|
386
|
-
description: 'Pin executions to one configured model: { provider, model }. Omit to use the default model.',
|
|
386
|
+
description: 'Pin executions to one configured model: { provider, model, reasoningEffort? }. Omit to use the default model.',
|
|
387
387
|
properties: {
|
|
388
388
|
provider: { type: 'string', description: 'Provider route id.' },
|
|
389
389
|
model: { type: 'string', description: 'Provider-owned model id.' },
|
|
390
|
+
reasoningEffort: { type: 'string', description: 'Optional thinking intensity / reasoning effort (e.g. low, medium, high).' },
|
|
390
391
|
},
|
|
391
392
|
},
|
|
392
393
|
isolation: {
|
package/src/shared/api.ts
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
*
|
|
6
6
|
* @module dsh-taskboard/shared/api
|
|
7
7
|
*/
|
|
8
|
-
import type { BoardSettings, TaskLedger, TaskRecord, TaskSummary } from './protocol.ts'
|
|
8
|
+
import type { BoardSettings, TaskLedger, TaskModel, TaskRecord, TaskSummary } from './protocol.ts'
|
|
9
9
|
|
|
10
|
-
export type { TaskRecord }
|
|
10
|
+
export type { TaskModel, TaskRecord }
|
|
11
11
|
|
|
12
12
|
/** Route prefix on the shared DSH webserver (same origin as the GUI). */
|
|
13
13
|
export const ROUTE_PREFIX = '/dsh-taskboard'
|
|
@@ -51,7 +51,7 @@ export type CreateTaskBody = {
|
|
|
51
51
|
description?: string
|
|
52
52
|
prompt?: string
|
|
53
53
|
execution?: { mode?: string; cron?: string }
|
|
54
|
-
model?:
|
|
54
|
+
model?: TaskModel
|
|
55
55
|
/** Code isolation for executions ('worktree' | 'none'); omitted = default. */
|
|
56
56
|
isolation?: string
|
|
57
57
|
/** Agent preset for execution sessions; omitted = deployment default. */
|
|
@@ -71,7 +71,7 @@ export type UpdateTaskBody = {
|
|
|
71
71
|
/** Rebind the task to another project (GUI owner surface only). */
|
|
72
72
|
workspaceId?: string
|
|
73
73
|
execution?: { mode?: string; cron?: string }
|
|
74
|
-
model?:
|
|
74
|
+
model?: TaskModel | null
|
|
75
75
|
/** Change isolation; locked once the task has execution history. */
|
|
76
76
|
isolation?: string
|
|
77
77
|
/** Change the execution preset (takes effect on the next run). */
|
|
@@ -130,7 +130,7 @@ export type TaskTemplateSpec = {
|
|
|
130
130
|
prompt?: string
|
|
131
131
|
urgency?: string
|
|
132
132
|
execution?: { mode?: string; cron?: string }
|
|
133
|
-
model?:
|
|
133
|
+
model?: TaskModel
|
|
134
134
|
isolation?: string
|
|
135
135
|
presetId?: string
|
|
136
136
|
/** Checklist item texts (host mints ids at create time). */
|