dsh-taskboard 0.7.1 → 0.7.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 +25 -2
- package/lib/client.js +14 -9
- package/lib/host/execution.js +124 -48
- package/lib/host/execution.js.map +1 -1
- package/lib/host/routes.js +1 -0
- package/lib/host/routes.js.map +1 -1
- package/lib/host/scheduled-session.js +50 -0
- package/lib/host/scheduled-session.js.map +1 -0
- package/lib/host/scheduler.js +46 -11
- package/lib/host/scheduler.js.map +1 -1
- package/lib/host/session-sync.js +28 -1
- package/lib/host/session-sync.js.map +1 -1
- package/lib/host/tools.js +21 -6
- package/lib/host/tools.js.map +1 -1
- package/lib/index.js +12 -1
- package/lib/index.js.map +1 -1
- package/lib/shared/api.js.map +1 -1
- package/lib/shared/protocol.js +98 -6
- package/lib/shared/protocol.js.map +1 -1
- package/package.json +88 -89
- package/src/client/board/TaskFormModal.tsx +59 -16
- package/src/client/controller.ts +6 -2
- package/src/client/i18n/en.ts +10 -0
- package/src/client/i18n/zh.ts +10 -0
- package/src/client/styles.ts +5 -0
- package/src/host/execution.ts +138 -45
- package/src/host/routes.ts +4 -3
- package/src/host/scheduled-session.ts +68 -0
- package/src/host/scheduler.ts +65 -16
- package/src/host/session-sync.ts +34 -0
- package/src/host/tools.ts +21 -6
- package/src/index.ts +9 -0
- package/src/shared/api.ts +3 -3
- package/src/shared/protocol.ts +108 -6
- package/src/shared/version.ts +1 -1
package/package.json
CHANGED
|
@@ -1,92 +1,91 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "dsh-taskboard",
|
|
3
|
-
"description": "Agent-first task board for the DSH web GUI: host-authoritative task ledger with taskboard_* agent tools, project (= workspace) claim boundaries, per-task model execution in fresh sessions, optional per-task git-worktree isolation (dedicated task branches, commit evidence, one-click merge), host-side cron scheduling, and a live SSE kanban view. Mounts via the official dsh plugin system — no DSH source changes.",
|
|
4
|
-
"version": "0.7.
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "lib/index.js",
|
|
7
|
-
"exports": {
|
|
8
|
-
".": "./lib/index.js",
|
|
9
|
-
"./invariant": "./lib/invariant.js",
|
|
10
|
-
"./client": "./lib/client.js",
|
|
11
|
-
"./package.json": "./package.json"
|
|
12
|
-
},
|
|
13
|
-
"dsh": {
|
|
14
|
-
"bundle": {
|
|
15
|
-
"patch": "./cordis.patch.yml"
|
|
16
|
-
},
|
|
17
|
-
"client": {
|
|
18
|
-
"inject": [],
|
|
19
|
-
"platform": "web"
|
|
20
|
-
},
|
|
21
|
-
"compatibility": {
|
|
22
|
-
"dshReleases": {
|
|
23
|
-
"0.1.0-rc.8": "unknown",
|
|
24
|
-
"0.1.1-rc.1": "unknown",
|
|
25
|
-
"0.1.1-rc.2": "compatible",
|
|
26
|
-
"0.1.2-alpha.2": "compatible",
|
|
27
|
-
"0.1.2-alpha.3": "compatible",
|
|
28
|
-
"0.1.2-alpha.4": "compatible",
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-taskboard",
|
|
3
|
+
"description": "Agent-first task board for the DSH web GUI: host-authoritative task ledger with taskboard_* agent tools, project (= workspace) claim boundaries, per-task model execution in fresh sessions, optional per-task git-worktree isolation (dedicated task branches, commit evidence, one-click merge), host-side cron scheduling, and a live SSE kanban view. Mounts via the official dsh plugin system — no DSH source changes.",
|
|
4
|
+
"version": "0.7.4",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./lib/index.js",
|
|
9
|
+
"./invariant": "./lib/invariant.js",
|
|
10
|
+
"./client": "./lib/client.js",
|
|
11
|
+
"./package.json": "./package.json"
|
|
12
|
+
},
|
|
13
|
+
"dsh": {
|
|
14
|
+
"bundle": {
|
|
15
|
+
"patch": "./cordis.patch.yml"
|
|
16
|
+
},
|
|
17
|
+
"client": {
|
|
18
|
+
"inject": [],
|
|
19
|
+
"platform": "web"
|
|
20
|
+
},
|
|
21
|
+
"compatibility": {
|
|
22
|
+
"dshReleases": {
|
|
23
|
+
"0.1.0-rc.8": "unknown",
|
|
24
|
+
"0.1.1-rc.1": "unknown",
|
|
25
|
+
"0.1.1-rc.2": "compatible",
|
|
26
|
+
"0.1.2-alpha.2": "compatible",
|
|
27
|
+
"0.1.2-alpha.3": "compatible",
|
|
28
|
+
"0.1.2-alpha.4": "compatible",
|
|
29
29
|
"0.1.2-alpha.5": "compatible",
|
|
30
30
|
"0.1.2-rc.1": "compatible",
|
|
31
31
|
"0.1.5-rc.1": "compatible"
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
},
|
|
35
|
-
"files": [
|
|
36
|
-
"lib/**/*.js",
|
|
37
|
-
"lib/**/*.js.map",
|
|
38
|
-
"src",
|
|
39
|
-
"cordis.patch.yml",
|
|
40
|
-
"LICENSE"
|
|
41
|
-
],
|
|
42
|
-
"license": "Apache-2.0",
|
|
43
|
-
"engines": {
|
|
44
|
-
"node": ">=22"
|
|
45
|
-
},
|
|
46
|
-
"author": "cloader",
|
|
47
|
-
"repository": {
|
|
48
|
-
"type": "git",
|
|
49
|
-
"url": "git+https://github.com/cloader/dsh-taskboard.git"
|
|
50
|
-
},
|
|
51
|
-
"bugs": {
|
|
52
|
-
"url": "https://github.com/cloader/dsh-taskboard/issues"
|
|
53
|
-
},
|
|
54
|
-
"homepage": "https://github.com/cloader/dsh-taskboard#readme",
|
|
55
|
-
"keywords": [
|
|
56
|
-
"dsh",
|
|
57
|
-
"deepseek-harness",
|
|
58
|
-
"dsh-plugin",
|
|
59
|
-
"taskboard",
|
|
60
|
-
"kanban",
|
|
61
|
-
"agent"
|
|
62
|
-
],
|
|
63
|
-
"scripts": {
|
|
64
|
-
"build": "node scripts/clean-lib.mjs && npm run build:host && npm run build:client",
|
|
65
|
-
"build:host": "tsdown -c tsdown.host.config.ts",
|
|
66
|
-
"build:client": "tsdown -c tsdown.client.config.ts && node scripts/wrap-client.mjs",
|
|
67
|
-
"watch": "tsdown -c tsdown.host.config.ts --watch",
|
|
68
|
-
"typecheck": "tsc --noEmit",
|
|
69
|
-
"test": "vitest run"
|
|
70
|
-
},
|
|
71
|
-
"devDependencies": {
|
|
72
|
-
"@deepseek-ai/cordis": "^4.0.2",
|
|
73
|
-
"@deepseek-ai/dsh-agent": "^0.1.5-rc.2",
|
|
74
|
-
"@deepseek-ai/dsh-home-paths": "^0.1.5-rc.2",
|
|
75
|
-
"@deepseek-ai/dsh-host-webserver": "^0.1.5-rc.2",
|
|
76
|
-
"@deepseek-ai/dsh-system-prompt": "^0.1.5-rc.2",
|
|
77
|
-
"@deepseek-ai/dsh-tools": "^0.1.5-rc.2",
|
|
78
|
-
"@deepseek-ai/dsh-workspace": "^0.1.5-rc.2",
|
|
79
|
-
"@deepseek-ai/schemastery": "^3.18.2",
|
|
80
|
-
"@types/node": "^22.20.1",
|
|
81
|
-
"@types/react": "~18.3.1",
|
|
82
|
-
"@types/react-dom": "^18.3.7",
|
|
83
|
-
"jsdom": "^25.0.1",
|
|
84
|
-
"puppeteer-core": "^25.8.0",
|
|
85
|
-
"react": "^18.3.1",
|
|
86
|
-
"react-dom": "^18.3.1",
|
|
87
|
-
"tsdown": "0.22.2",
|
|
88
|
-
"typescript": "~5.7.2",
|
|
89
|
-
"vitest": "^3.0.0"
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"lib/**/*.js",
|
|
37
|
+
"lib/**/*.js.map",
|
|
38
|
+
"src",
|
|
39
|
+
"cordis.patch.yml",
|
|
40
|
+
"LICENSE"
|
|
41
|
+
],
|
|
42
|
+
"license": "Apache-2.0",
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=22"
|
|
45
|
+
},
|
|
46
|
+
"author": "cloader",
|
|
47
|
+
"repository": {
|
|
48
|
+
"type": "git",
|
|
49
|
+
"url": "git+https://github.com/cloader/dsh-taskboard.git"
|
|
50
|
+
},
|
|
51
|
+
"bugs": {
|
|
52
|
+
"url": "https://github.com/cloader/dsh-taskboard/issues"
|
|
53
|
+
},
|
|
54
|
+
"homepage": "https://github.com/cloader/dsh-taskboard#readme",
|
|
55
|
+
"keywords": [
|
|
56
|
+
"dsh",
|
|
57
|
+
"deepseek-harness",
|
|
58
|
+
"dsh-plugin",
|
|
59
|
+
"taskboard",
|
|
60
|
+
"kanban",
|
|
61
|
+
"agent"
|
|
62
|
+
],
|
|
63
|
+
"scripts": {
|
|
64
|
+
"build": "node scripts/clean-lib.mjs && npm run build:host && npm run build:client",
|
|
65
|
+
"build:host": "tsdown -c tsdown.host.config.ts",
|
|
66
|
+
"build:client": "tsdown -c tsdown.client.config.ts && node scripts/wrap-client.mjs",
|
|
67
|
+
"watch": "tsdown -c tsdown.host.config.ts --watch",
|
|
68
|
+
"typecheck": "tsc --noEmit",
|
|
69
|
+
"test": "vitest run"
|
|
70
|
+
},
|
|
71
|
+
"devDependencies": {
|
|
72
|
+
"@deepseek-ai/cordis": "^4.0.2",
|
|
73
|
+
"@deepseek-ai/dsh-agent": "^0.1.5-rc.2",
|
|
74
|
+
"@deepseek-ai/dsh-home-paths": "^0.1.5-rc.2",
|
|
75
|
+
"@deepseek-ai/dsh-host-webserver": "^0.1.5-rc.2",
|
|
76
|
+
"@deepseek-ai/dsh-system-prompt": "^0.1.5-rc.2",
|
|
77
|
+
"@deepseek-ai/dsh-tools": "^0.1.5-rc.2",
|
|
78
|
+
"@deepseek-ai/dsh-workspace": "^0.1.5-rc.2",
|
|
79
|
+
"@deepseek-ai/schemastery": "^3.18.2",
|
|
80
|
+
"@types/node": "^22.20.1",
|
|
81
|
+
"@types/react": "~18.3.1",
|
|
82
|
+
"@types/react-dom": "^18.3.7",
|
|
83
|
+
"jsdom": "^25.0.1",
|
|
84
|
+
"puppeteer-core": "^25.8.0",
|
|
85
|
+
"react": "^18.3.1",
|
|
86
|
+
"react-dom": "^18.3.1",
|
|
87
|
+
"tsdown": "0.22.2",
|
|
88
|
+
"typescript": "~5.7.2",
|
|
89
|
+
"vitest": "^3.0.0"
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -178,8 +178,20 @@ export function TaskFormModal({ controller, task }: { controller: BoardControlle
|
|
|
178
178
|
const [prompt, setPrompt] = useState(task?.prompt ?? prefill?.prompt ?? '')
|
|
179
179
|
const [workspaceId, setWorkspaceId] = useState(task?.workspaceId ?? state.filters.workspaceId ?? state.workspaces[0]?.id ?? '')
|
|
180
180
|
const [urgency, setUrgency] = useState<Urgency>(task?.urgency ?? (prefill?.urgency === 'urgent' || prefill?.urgency === 'relaxed' ? prefill.urgency : 'normal'))
|
|
181
|
-
const
|
|
181
|
+
const initExec = task?.execution ?? prefill?.execution
|
|
182
|
+
const [mode, setMode] = useState<'claim' | 'once' | 'periodic'>(
|
|
183
|
+
initExec?.mode === 'scheduled' ? (initExec.cron !== undefined ? 'periodic' : 'once') : 'claim',
|
|
184
|
+
)
|
|
182
185
|
const [cron, setCron] = useState(task?.execution.cron ?? prefill?.execution?.cron ?? '0 9 * * *')
|
|
186
|
+
// One-shot trigger (定时执行): datetime-local string; '' = unset.
|
|
187
|
+
const initRunAt = initExec?.mode === 'scheduled' && initExec.runAt !== undefined ? initExec.runAt : undefined
|
|
188
|
+
const [runAt, setRunAt] = useState(() => {
|
|
189
|
+
if (initRunAt === undefined) return ''
|
|
190
|
+
const d = new Date(initRunAt)
|
|
191
|
+
if (Number.isNaN(d.getTime())) return ''
|
|
192
|
+
const pad = (n: number): string => String(n).padStart(2, '0')
|
|
193
|
+
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`
|
|
194
|
+
})
|
|
183
195
|
const [catalog, setCatalog] = useState<CatalogModel[]>([])
|
|
184
196
|
|
|
185
197
|
// Model & reasoning effort selection:
|
|
@@ -245,10 +257,19 @@ export function TaskFormModal({ controller, task }: { controller: BoardControlle
|
|
|
245
257
|
}, [controller, editing, task?.presetId, initialPreset])
|
|
246
258
|
|
|
247
259
|
// Live cron validation + next-run preview (same math as the host).
|
|
248
|
-
const cronMatch = mode === '
|
|
260
|
+
const cronMatch = mode === 'periodic' ? parseCron(cron.trim()) : null
|
|
249
261
|
const nextRun = cronMatch !== null ? nextCronTime(cronMatch, Date.now()) : null
|
|
250
|
-
const cronBad = mode === '
|
|
251
|
-
const
|
|
262
|
+
const cronBad = mode === 'periodic' && (cronMatch === null || nextRun === null)
|
|
263
|
+
const runAtMs = mode === 'once' && runAt !== '' ? new Date(runAt).getTime() : NaN
|
|
264
|
+
const runAtBad = mode === 'once' && (runAt === '' || Number.isNaN(runAtMs) || runAtMs <= Date.now())
|
|
265
|
+
const valid = title.trim().length > 0 && workspaceId !== '' && !cronBad && !runAtBad
|
|
266
|
+
|
|
267
|
+
/** Execution payload for submit: claim | periodic (cron) | one-shot (runAt ISO). */
|
|
268
|
+
const executionPayload = (): { mode: 'claim' | 'scheduled'; cron?: string; runAt?: string } => {
|
|
269
|
+
if (mode === 'periodic') return { mode: 'scheduled', cron: cron.trim() }
|
|
270
|
+
if (mode === 'once') return { mode: 'scheduled', runAt: new Date(runAt).toISOString() }
|
|
271
|
+
return { mode: 'claim' }
|
|
272
|
+
}
|
|
252
273
|
|
|
253
274
|
// A task already in progress cannot be run again (host rejects it).
|
|
254
275
|
const runBlocked = editing && task.status === 'in_progress'
|
|
@@ -309,7 +330,7 @@ export function TaskFormModal({ controller, task }: { controller: BoardControlle
|
|
|
309
330
|
prompt,
|
|
310
331
|
urgency,
|
|
311
332
|
workspaceId,
|
|
312
|
-
execution:
|
|
333
|
+
execution: executionPayload(),
|
|
313
334
|
// '' in edit mode clears the pinned model back to the default.
|
|
314
335
|
model: picked ?? null,
|
|
315
336
|
...(isolationOut !== undefined && !isolationLocked ? { isolation: isolationOut } : {}),
|
|
@@ -324,7 +345,7 @@ export function TaskFormModal({ controller, task }: { controller: BoardControlle
|
|
|
324
345
|
urgency,
|
|
325
346
|
description: description.length > 0 ? description : undefined,
|
|
326
347
|
prompt: prompt.length > 0 ? prompt : undefined,
|
|
327
|
-
execution:
|
|
348
|
+
execution: executionPayload(),
|
|
328
349
|
model: picked,
|
|
329
350
|
...(isolationOut !== undefined ? { isolation: isolationOut } : {}),
|
|
330
351
|
...(presetOut !== undefined ? { presetId: presetOut } : {}),
|
|
@@ -351,7 +372,7 @@ export function TaskFormModal({ controller, task }: { controller: BoardControlle
|
|
|
351
372
|
prompt,
|
|
352
373
|
urgency,
|
|
353
374
|
workspaceId,
|
|
354
|
-
execution:
|
|
375
|
+
execution: executionPayload(),
|
|
355
376
|
model: picked ?? null,
|
|
356
377
|
...(isolationOut !== undefined && !isolationLocked ? { isolation: isolationOut } : {}),
|
|
357
378
|
presetId: presetOut ?? null,
|
|
@@ -366,7 +387,7 @@ export function TaskFormModal({ controller, task }: { controller: BoardControlle
|
|
|
366
387
|
urgency,
|
|
367
388
|
description: description.length > 0 ? description : undefined,
|
|
368
389
|
prompt: prompt.length > 0 ? prompt : undefined,
|
|
369
|
-
execution:
|
|
390
|
+
execution: executionPayload(),
|
|
370
391
|
model: picked,
|
|
371
392
|
...(isolationOut !== undefined ? { isolation: isolationOut } : {}),
|
|
372
393
|
...(presetOut !== undefined ? { presetId: presetOut } : {}),
|
|
@@ -379,9 +400,15 @@ export function TaskFormModal({ controller, task }: { controller: BoardControlle
|
|
|
379
400
|
}
|
|
380
401
|
|
|
381
402
|
const hint = !valid
|
|
382
|
-
? (title.trim().length === 0
|
|
383
|
-
|
|
403
|
+
? (title.trim().length === 0
|
|
404
|
+
? t('form.hint.needTitle')
|
|
405
|
+
: workspaceId === ''
|
|
406
|
+
? t('form.hint.needProject')
|
|
407
|
+
: runAtBad ? t('form.hint.runAtBad') : t('form.hint.cronBad'))
|
|
408
|
+
: mode === 'periodic' && nextRun !== null
|
|
384
409
|
? t('form.hint.nextRun', { time: fmtTime(nextRun) })
|
|
410
|
+
: mode === 'once' && !Number.isNaN(runAtMs)
|
|
411
|
+
? t('form.hint.onceAt', { time: fmtTime(runAtMs) })
|
|
385
412
|
: editing
|
|
386
413
|
? t('form.hint.saveVersion', { v: task.version, next: task.version + 1 })
|
|
387
414
|
: t('form.hint.createClaim')
|
|
@@ -516,19 +543,35 @@ export function TaskFormModal({ controller, task }: { controller: BoardControlle
|
|
|
516
543
|
</Field>
|
|
517
544
|
|
|
518
545
|
<Field label={t('form.field.mode')} full>
|
|
519
|
-
<div className="dsh-atb-mode-picker">
|
|
546
|
+
<div className="dsh-atb-mode-picker" data-exec="true">
|
|
520
547
|
<button type="button" className="dsh-atb-mode-opt" data-on={mode === 'claim'} onClick={() => setMode('claim')}>
|
|
521
548
|
<span className="dsh-atb-mode-name">{t('form.mode.claim')}</span>
|
|
522
549
|
<span className="dsh-atb-mode-hint">{t('form.mode.claimHint')}</span>
|
|
523
550
|
</button>
|
|
524
|
-
<button type="button" className="dsh-atb-mode-opt" data-on={mode === '
|
|
525
|
-
<span className="dsh-atb-mode-name">{t('form.mode.
|
|
526
|
-
<span className="dsh-atb-mode-hint">{t('form.mode.
|
|
551
|
+
<button type="button" className="dsh-atb-mode-opt" data-on={mode === 'once'} onClick={() => setMode('once')}>
|
|
552
|
+
<span className="dsh-atb-mode-name">{t('form.mode.once')}</span>
|
|
553
|
+
<span className="dsh-atb-mode-hint">{t('form.mode.onceHint')}</span>
|
|
554
|
+
</button>
|
|
555
|
+
<button type="button" className="dsh-atb-mode-opt" data-on={mode === 'periodic'} onClick={() => setMode('periodic')}>
|
|
556
|
+
<span className="dsh-atb-mode-name">{t('form.mode.periodic')}</span>
|
|
557
|
+
<span className="dsh-atb-mode-hint">{t('form.mode.periodicHint')}</span>
|
|
527
558
|
</button>
|
|
528
559
|
</div>
|
|
529
560
|
</Field>
|
|
530
561
|
|
|
531
|
-
{mode === '
|
|
562
|
+
{mode === 'once' && (
|
|
563
|
+
<Field label={t('form.field.runAt')} required full>
|
|
564
|
+
<input
|
|
565
|
+
type="datetime-local"
|
|
566
|
+
className={runAtBad ? 'dsh-atb-input-bad' : undefined}
|
|
567
|
+
value={runAt}
|
|
568
|
+
onChange={e => setRunAt(e.target.value)}
|
|
569
|
+
spellCheck={false}
|
|
570
|
+
/>
|
|
571
|
+
</Field>
|
|
572
|
+
)}
|
|
573
|
+
|
|
574
|
+
{mode === 'periodic' && (
|
|
532
575
|
<Field label={t('form.field.cron')} required full>
|
|
533
576
|
<input
|
|
534
577
|
className={cronBad ? 'dsh-atb-input-bad' : undefined}
|
|
@@ -653,7 +696,7 @@ interface TaskRecordLike {
|
|
|
653
696
|
prompt: string
|
|
654
697
|
workspaceId: string
|
|
655
698
|
urgency: Urgency
|
|
656
|
-
execution: { mode: 'claim' | 'scheduled'; cron?: string }
|
|
699
|
+
execution: { mode: 'claim' | 'scheduled'; cron?: string; runAt?: number }
|
|
657
700
|
model?: { provider: string; model: string; reasoningEffort?: string }
|
|
658
701
|
isolation?: IsolationMode
|
|
659
702
|
presetId?: string
|
package/src/client/controller.ts
CHANGED
|
@@ -693,7 +693,9 @@ export class BoardController {
|
|
|
693
693
|
prompt: task.prompt.length > 0 ? task.prompt : undefined,
|
|
694
694
|
execution: task.execution.mode === 'scheduled' && task.execution.cron !== undefined
|
|
695
695
|
? { mode: 'scheduled', cron: task.execution.cron }
|
|
696
|
-
:
|
|
696
|
+
: task.execution.mode === 'scheduled' && task.execution.runAt !== undefined && task.execution.runAt > Date.now()
|
|
697
|
+
? { mode: 'scheduled', runAt: new Date(task.execution.runAt).toISOString() }
|
|
698
|
+
: { mode: 'claim' },
|
|
697
699
|
model: task.model,
|
|
698
700
|
isolation: task.isolation,
|
|
699
701
|
...(task.presetId !== undefined ? { presetId: task.presetId } : {}),
|
|
@@ -766,7 +768,9 @@ export class BoardController {
|
|
|
766
768
|
urgency: task.urgency,
|
|
767
769
|
execution: task.execution.mode === 'scheduled' && task.execution.cron !== undefined
|
|
768
770
|
? { mode: 'scheduled', cron: task.execution.cron }
|
|
769
|
-
:
|
|
771
|
+
: task.execution.mode === 'scheduled' && task.execution.runAt !== undefined
|
|
772
|
+
? { mode: 'scheduled', runAt: new Date(task.execution.runAt).toISOString() }
|
|
773
|
+
: { mode: 'claim' },
|
|
770
774
|
model: task.model,
|
|
771
775
|
isolation: task.isolation,
|
|
772
776
|
...(task.presetId !== undefined ? { presetId: task.presetId } : {}),
|
package/src/client/i18n/en.ts
CHANGED
|
@@ -280,6 +280,11 @@ export const en: TaskboardDict = {
|
|
|
280
280
|
'form.field.mode': 'Execution mode',
|
|
281
281
|
'form.mode.claim': '🤝 Claim-based',
|
|
282
282
|
'form.mode.claimHint': 'Sessions in the project claim it',
|
|
283
|
+
'form.mode.once': '⏰ One-shot',
|
|
284
|
+
'form.mode.onceHint': 'Runs once at a set time',
|
|
285
|
+
'form.mode.periodic': '🔁 Periodic',
|
|
286
|
+
'form.mode.periodicHint': 'Repeats on a cron schedule',
|
|
287
|
+
'form.field.runAt': 'Trigger time',
|
|
283
288
|
'form.mode.scheduled': '⏰ Scheduled',
|
|
284
289
|
'form.mode.scheduledHint': 'Starts automatically on schedule',
|
|
285
290
|
'form.field.cron': 'Cron expression',
|
|
@@ -314,7 +319,9 @@ export const en: TaskboardDict = {
|
|
|
314
319
|
'form.hint.needTitle': 'Enter a title',
|
|
315
320
|
'form.hint.needProject': 'Select a project',
|
|
316
321
|
'form.hint.cronBad': 'Invalid cron expression (min hour day month weekday)',
|
|
322
|
+
'form.hint.runAtBad': 'Pick a future trigger time',
|
|
317
323
|
'form.hint.nextRun': 'Next run {time}',
|
|
324
|
+
'form.hint.onceAt': 'Fires once at {time}',
|
|
318
325
|
'form.hint.saveVersion': 'On save: v{v} → v{next}',
|
|
319
326
|
'form.hint.createClaim': 'After creation, sessions in the project can claim and run it',
|
|
320
327
|
'form.action.runBlockedTitle': 'The task is running; cannot start another',
|
|
@@ -488,6 +495,9 @@ export const en: TaskboardDict = {
|
|
|
488
495
|
'sys.sessionError': '[System] Session execution error: {error}; the task was returned to todo.',
|
|
489
496
|
'sys.sessionDone': '[System] The session finished; automatically moved to in review.',
|
|
490
497
|
'sys.cronDead': '[System] The cron expression {cron} has no trigger time within 4 years; scheduling disabled — fix the cron and re-enable.',
|
|
498
|
+
'sys.periodicHandoff': '[System] Periodic run finished; the schedule now continues on the new todo card {nextTaskId}. Review this card, then accept.',
|
|
499
|
+
'sys.spawnedFrom': '[System] The previous periodic run finished (execution {executionId}); this card carries the schedule onward. See {sourceId} for the finished round.',
|
|
500
|
+
'sys.runAtMissed': '[System] The one-shot trigger time was missed (host was down); it will not re-fire. Run manually or reschedule.',
|
|
491
501
|
'sys.mergeSingle': '[System] Branch {branch} merged into the main worktree (--no-ff).',
|
|
492
502
|
'sys.mergeMulti': '[System] Branches merged per repo (--no-ff): {summary}',
|
|
493
503
|
}
|
package/src/client/i18n/zh.ts
CHANGED
|
@@ -282,6 +282,11 @@ export const zh = {
|
|
|
282
282
|
'form.field.mode': '执行方式',
|
|
283
283
|
'form.mode.claim': '🤝 认领制',
|
|
284
284
|
'form.mode.claimHint': '项目内会话认领',
|
|
285
|
+
'form.mode.once': '⏰ 定时执行',
|
|
286
|
+
'form.mode.onceHint': '指定时间只跑一次',
|
|
287
|
+
'form.mode.periodic': '🔁 定期执行',
|
|
288
|
+
'form.mode.periodicHint': '按 cron 到点重复开跑',
|
|
289
|
+
'form.field.runAt': '触发时间',
|
|
285
290
|
'form.mode.scheduled': '⏰ 定时执行',
|
|
286
291
|
'form.mode.scheduledHint': '到点自动开跑',
|
|
287
292
|
'form.field.cron': 'Cron 表达式',
|
|
@@ -316,7 +321,9 @@ export const zh = {
|
|
|
316
321
|
'form.hint.needTitle': '请填写标题',
|
|
317
322
|
'form.hint.needProject': '请选择项目',
|
|
318
323
|
'form.hint.cronBad': 'Cron 表达式无效(分 时 日 月 周)',
|
|
324
|
+
'form.hint.runAtBad': '请选择未来的触发时间',
|
|
319
325
|
'form.hint.nextRun': '下次运行 {time}',
|
|
326
|
+
'form.hint.onceAt': '将于 {time} 触发一次',
|
|
320
327
|
'form.hint.saveVersion': '保存后版本 v{v} → v{next}',
|
|
321
328
|
'form.hint.createClaim': '创建后项目内会话可认领执行',
|
|
322
329
|
'form.action.runBlockedTitle': '任务正在执行中,不能重复发起',
|
|
@@ -490,6 +497,9 @@ export const zh = {
|
|
|
490
497
|
'sys.sessionError': '[系统] 会话执行异常:{error};任务已退回待办。',
|
|
491
498
|
'sys.sessionDone': '[系统] 会话执行完毕,已自动进入待验收。',
|
|
492
499
|
'sys.cronDead': '[系统] 定时表达式 {cron} 在 4 年内没有可触发时间,已停用定时;请修正 cron 后重新开启。',
|
|
500
|
+
'sys.periodicHandoff': '[系统] 定期任务本轮执行完毕,定时已由新待办卡 {nextTaskId} 承接,请审查本卡后验收。',
|
|
501
|
+
'sys.spawnedFrom': '[系统] 定期任务上一轮执行完毕(执行 {executionId}),本卡承接定时继续下一轮;上一轮成果见 {sourceId} 的待验收。',
|
|
502
|
+
'sys.runAtMissed': '[系统] 定时执行错过触发时间(主机当时未运行),本次不再补跑;可手动执行或修改定时。',
|
|
493
503
|
'sys.mergeSingle': '[系统] 分支 {branch} 已合并到主工作区(--no-ff)。',
|
|
494
504
|
'sys.mergeMulti': '[系统] 分支已按仓库合并(--no-ff):{summary}',
|
|
495
505
|
} as const
|
package/src/client/styles.ts
CHANGED
|
@@ -609,6 +609,11 @@ button.dsh-atb-chip2.dsh-atb-chip-btn:hover {
|
|
|
609
609
|
.dsh-atb-urgency-opt[data-on="true"][data-urgency="relaxed"] { border-color: rgba(62,99,221,.65); background: rgba(62,99,221,.1); }
|
|
610
610
|
|
|
611
611
|
.dsh-atb-mode-picker { display: grid; grid-template-columns: 1fr 1fr; gap: 7px; }
|
|
612
|
+
/* Execution mode has THREE options (认领 / 定时 / 定期): keep them on one row. */
|
|
613
|
+
.dsh-atb-mode-picker[data-exec="true"] { grid-template-columns: repeat(3, 1fr); }
|
|
614
|
+
.dsh-atb-mode-picker[data-exec="true"] .dsh-atb-mode-opt { padding: 7px 8px; }
|
|
615
|
+
.dsh-atb-mode-picker[data-exec="true"] .dsh-atb-mode-name { font-size: 12px; }
|
|
616
|
+
.dsh-atb-mode-picker[data-exec="true"] .dsh-atb-mode-hint { font-size: 10px; }
|
|
612
617
|
.dsh-atb-mode-opt {
|
|
613
618
|
display: flex; flex-direction: column; align-items: flex-start; gap: 3px;
|
|
614
619
|
padding: 8px 10px; border-radius: 9px; cursor: pointer;
|