@linxin666/dsh-client-ui-task-board 0.4.3 → 0.4.5
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.i18n.yaml +2 -2
- package/README.md +56 -18
- package/README.zh.md +56 -18
- package/lib/client.js +6242 -958
- package/lib/client.js.map +1 -1
- package/lib/index.js +3475 -234
- package/lib/types/client/PluginSettingsCard.d.ts +7 -0
- package/lib/types/client/TaskBoardSettingsCard.d.ts +19 -3
- package/lib/types/client/board/ClampedMarkdown.d.ts +3 -0
- package/lib/types/client/board/ConfirmDialog.d.ts +4 -1
- package/lib/types/client/board/EditTaskModal.d.ts +5 -2
- package/lib/types/client/board/LinkSubtaskModal.d.ts +5 -2
- package/lib/types/client/board/NewTaskModal.d.ts +4 -1
- package/lib/types/client/board/TagManagerModal.d.ts +26 -0
- package/lib/types/client/board/TaskBoard.d.ts +10 -1
- package/lib/types/client/board/TaskCard.d.ts +14 -1
- package/lib/types/client/board/TaskDetail.d.ts +3 -1
- package/lib/types/client/board/TaskForm.d.ts +23 -2
- package/lib/types/client/board/VerificationReport.d.ts +23 -0
- package/lib/types/client/board/card-view.d.ts +113 -0
- package/lib/types/client/board/icons.d.ts +32 -0
- package/lib/types/client/board/overlay.d.ts +69 -0
- package/lib/types/client/board/preset-label.d.ts +22 -0
- package/lib/types/client/board/status-key.d.ts +15 -0
- package/lib/types/client/board/task-markdown.d.ts +19 -0
- package/lib/types/client/board-card-seat.d.ts +34 -0
- package/lib/types/client/host-api.d.ts +24 -1
- package/lib/types/client/index.d.ts +19 -0
- package/lib/types/client/locales.d.ts +141 -3
- package/lib/types/client/native-panel.d.ts +6 -20
- package/lib/types/client/schedule-zone.d.ts +47 -0
- package/lib/types/client/seats.d.ts +18 -0
- package/lib/types/client/service.d.ts +32 -0
- package/lib/types/client/settings-form.d.ts +16 -0
- package/lib/types/core/controller.d.ts +38 -1
- package/lib/types/core/degraded-reason.d.ts +46 -0
- package/lib/types/core/extension.d.ts +283 -0
- package/lib/types/core/handover.d.ts +13 -1
- package/lib/types/core/issue-form.d.ts +16 -0
- package/lib/types/core/package-name.d.ts +9 -0
- package/lib/types/core/schedule.d.ts +95 -20
- package/lib/types/core/subtask.d.ts +30 -11
- package/lib/types/core/tasks.d.ts +110 -5
- package/lib/types/core/use-cases/task-create.d.ts +2 -1
- package/lib/types/core/use-cases/task-schedule.d.ts +8 -4
- package/lib/types/core/use-cases/task-tag.d.ts +48 -0
- package/lib/types/core/use-cases/task-update.d.ts +5 -3
- package/lib/types/core/verification.d.ts +386 -0
- package/lib/types/core/workspace-target.d.ts +41 -0
- package/lib/types/host/agent-tools.d.ts +1 -1
- package/lib/types/host/extension-registry.d.ts +111 -0
- package/lib/types/host/llm-dispatch.d.ts +63 -0
- package/lib/types/host/verification-gate.d.ts +71 -0
- package/lib/types/host/verification-runner.d.ts +108 -0
- package/lib/types/host/workspace-evidence.d.ts +53 -0
- package/lib/types/host-ledger.d.ts +107 -11
- package/lib/types/host-runner.d.ts +99 -4
- package/lib/types/host-service.d.ts +114 -2
- package/lib/types/index.d.ts +80 -26
- package/lib/types/protocol.d.ts +53 -3
- package/lib/types/tool-surface.d.ts +71 -0
- package/package.json +27 -26
- package/src/client/PluginSettingsCard.tsx +9 -0
- package/src/client/TaskBoardSettingsCard.tsx +304 -65
- package/src/client/board/ClampedMarkdown.tsx +69 -0
- package/src/client/board/ConfirmDialog.tsx +7 -3
- package/src/client/board/EditTaskModal.tsx +5 -2
- package/src/client/board/LinkSubtaskModal.tsx +30 -15
- package/src/client/board/NewTaskModal.tsx +288 -151
- package/src/client/board/TagManagerModal.tsx +235 -0
- package/src/client/board/TaskBoard.tsx +259 -103
- package/src/client/board/TaskCard.tsx +238 -86
- package/src/client/board/TaskDetail.tsx +154 -59
- package/src/client/board/TaskForm.tsx +61 -6
- package/src/client/board/VerificationReport.tsx +203 -0
- package/src/client/board/card-view.ts +220 -0
- package/src/client/board/icons.tsx +89 -0
- package/src/client/board/overlay.tsx +189 -0
- package/src/client/board/preset-label.ts +44 -0
- package/src/client/board/status-key.ts +27 -0
- package/src/client/board/task-markdown.module.css +137 -0
- package/src/client/board/task-markdown.tsx +106 -0
- package/src/client/board-card-seat.ts +102 -0
- package/src/client/board-settings.module.css +66 -0
- package/src/client/board.module.css +602 -68
- package/src/client/host-api.ts +109 -6
- package/src/client/index.ts +48 -6
- package/src/client/locales.ts +290 -12
- package/src/client/native-panel.tsx +18 -2
- package/src/client/schedule-zone.ts +135 -0
- package/src/client/seats.tsx +58 -0
- package/src/client/service.ts +94 -0
- package/src/client/settings-card.module.css +31 -5
- package/src/client/settings-form.ts +27 -1
- package/src/core/controller.ts +101 -5
- package/src/core/degraded-reason.ts +81 -0
- package/src/core/extension.ts +376 -0
- package/src/core/freeze-snapshot.ts +2 -2
- package/src/core/handover.ts +16 -1
- package/src/core/issue-form.ts +19 -0
- package/src/core/package-name.ts +9 -0
- package/src/core/schedule.ts +211 -55
- package/src/core/store.ts +26 -1
- package/src/core/subtask.ts +77 -22
- package/src/core/tasks.ts +138 -10
- package/src/core/use-cases/task-create.ts +14 -2
- package/src/core/use-cases/task-parent.ts +3 -2
- package/src/core/use-cases/task-schedule.ts +17 -6
- package/src/core/use-cases/task-tag.ts +110 -0
- package/src/core/use-cases/task-update.ts +13 -4
- package/src/core/verification.ts +826 -0
- package/src/core/workspace-target.ts +59 -0
- package/src/host/agent-tools.ts +58 -24
- package/src/host/extension-registry.ts +509 -0
- package/src/host/llm-dispatch.ts +87 -0
- package/src/host/verification-gate.ts +331 -0
- package/src/host/verification-runner.ts +535 -0
- package/src/host/workspace-evidence.ts +176 -0
- package/src/host-ai.ts +15 -9
- package/src/host-ledger.ts +342 -51
- package/src/host-routes.ts +15 -2
- package/src/host-runner.ts +219 -20
- package/src/host-service.ts +346 -26
- package/src/index.ts +204 -16
- package/src/protocol.ts +122 -9
- package/src/tool-surface.ts +95 -0
- package/lib/types/host/run-guarded.d.ts +0 -39
- package/src/host/run-guarded.ts +0 -76
|
@@ -4,12 +4,13 @@
|
|
|
4
4
|
* a link the Host would refuse is not offered in the first place; the Host
|
|
5
5
|
* still re-checks the action at submit time.
|
|
6
6
|
*/
|
|
7
|
-
import { useState } from 'react'
|
|
7
|
+
import { useMemo, useState } from 'react'
|
|
8
8
|
import type { BoardController } from '../../core/controller.ts'
|
|
9
|
-
import { DEFAULT_SUBTASK_DEPTH, checkParentLink } from '../../core/subtask.ts'
|
|
10
|
-
import type
|
|
9
|
+
import { DEFAULT_SUBTASK_DEPTH, buildLineageIndex, checkParentLink } from '../../core/subtask.ts'
|
|
10
|
+
import { hasOpenExecution, type TaskRecord } from '../../core/tasks.ts'
|
|
11
11
|
import { t } from '../locales.ts'
|
|
12
12
|
import css from '../board.module.css'
|
|
13
|
+
import { useDialog, type OverlayPhase } from './overlay.tsx'
|
|
13
14
|
import { STATUS_KEY } from './status-key.ts'
|
|
14
15
|
|
|
15
16
|
/** Link-subtask overlay props. */
|
|
@@ -18,25 +19,39 @@ export interface LinkSubtaskModalProps {
|
|
|
18
19
|
/** The task the picked card becomes a subtask of. */
|
|
19
20
|
parent: TaskRecord
|
|
20
21
|
onClose: () => void
|
|
22
|
+
/** Which leg of the enter/exit motion pair the surface is on. */
|
|
23
|
+
phase?: OverlayPhase
|
|
21
24
|
}
|
|
22
25
|
|
|
23
26
|
/** Link-subtask overlay. */
|
|
24
|
-
export function LinkSubtaskModal({ controller, parent, onClose }: LinkSubtaskModalProps) {
|
|
27
|
+
export function LinkSubtaskModal({ controller, parent, onClose, phase = 'open' }: LinkSubtaskModalProps) {
|
|
28
|
+
const dialog = useDialog<HTMLDivElement>(onClose, phase)
|
|
25
29
|
const [pending, setPending] = useState(false)
|
|
26
30
|
const [error, setError] = useState<string | undefined>(undefined)
|
|
27
31
|
const snapshot = controller.getSnapshot()
|
|
28
32
|
const limit = snapshot.host?.maxSubtaskDepth ?? DEFAULT_SUBTASK_DEPTH
|
|
29
33
|
// Only root, on-board tasks can move under another parent; the lineage gate
|
|
30
34
|
// decides the rest (depth, cycle, archived parent).
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
//
|
|
36
|
+
// The gate is run once per candidate task, so it is given ONE lineage index
|
|
37
|
+
// for the whole pass: without it every call rebuilds the id/child maps and
|
|
38
|
+
// rescans the ledger, turning a render into quadratic work. The derivation
|
|
39
|
+
// also runs on every controller notification (the SSE heartbeat included),
|
|
40
|
+
// so it is memoized on the inputs it actually reads.
|
|
41
|
+
const tasks = snapshot.tasks
|
|
42
|
+
const candidates = useMemo(() => {
|
|
43
|
+
const lineage = buildLineageIndex(tasks)
|
|
44
|
+
return tasks.filter(task =>
|
|
45
|
+
task.archivedAt === undefined
|
|
46
|
+
// An executing task may already be a participant of another run group;
|
|
47
|
+
// the Host refuses the move, so it is not offered here. A card parked in
|
|
48
|
+
// the running column by hand carries no execution and stays linkable.
|
|
49
|
+
&& !hasOpenExecution(task)
|
|
50
|
+
&& task.parentId === undefined
|
|
51
|
+
&& task.id !== parent.id
|
|
52
|
+
&& checkParentLink(tasks, task.id, parent.id, limit, lineage).ok,
|
|
53
|
+
)
|
|
54
|
+
}, [tasks, parent.id, limit])
|
|
40
55
|
const link = async (taskId: string): Promise<void> => {
|
|
41
56
|
setPending(true)
|
|
42
57
|
setError(undefined)
|
|
@@ -48,8 +63,8 @@ export function LinkSubtaskModal({ controller, parent, onClose }: LinkSubtaskMod
|
|
|
48
63
|
setError(controller.getSnapshot().transportError ?? t('new.required'))
|
|
49
64
|
}
|
|
50
65
|
return (
|
|
51
|
-
<div className={css.modalBackdrop}
|
|
52
|
-
<div className={css.modal} role="dialog" aria-label={t('link.subtask.title')}>
|
|
66
|
+
<div className={css.modalBackdrop} data-state={phase} onMouseDown={dialog.onMouseDown}>
|
|
67
|
+
<div ref={dialog.attach} className={css.modal} role="dialog" aria-modal="true" aria-label={t('link.subtask.title')} tabIndex={-1}>
|
|
53
68
|
<h2 className={css.modalTitle}>{t('link.subtask.title')}</h2>
|
|
54
69
|
<p className={css.fieldHint}>{t('link.subtask.hint')}</p>
|
|
55
70
|
{candidates.length === 0
|
|
@@ -10,8 +10,12 @@ import { effectiveTaskPermission } from '../../core/subtask.ts'
|
|
|
10
10
|
import { collectKnownTags, TASK_PERMISSIONS, type TaskPermission, type TaskRecord, type TaskTag } from '../../core/tasks.ts'
|
|
11
11
|
import { t, type TaskBoardKey } from '../locales.ts'
|
|
12
12
|
import { SCHEDULE_PRESETS } from '../schedule-presets.ts'
|
|
13
|
-
import {
|
|
13
|
+
import { nextRunLabel, zoneChoices } from '../schedule-zone.ts'
|
|
14
|
+
import { CollapsibleSection, ModalShell, TaskContentFields, TaskTagFields, cleanTags } from './TaskForm.tsx'
|
|
15
|
+
import type { OverlayPhase } from './overlay.tsx'
|
|
16
|
+
import { formatHostTimestamp } from './TaskCard.tsx'
|
|
14
17
|
import { readParseModelPreference, writeParseModelPreference } from './parse-model-pref.ts'
|
|
18
|
+
import { inheritPresetLabel, isBuiltinPreset, presetLabel } from './preset-label.ts'
|
|
15
19
|
import css from '../board.module.css'
|
|
16
20
|
|
|
17
21
|
export interface NewTaskModalProps {
|
|
@@ -32,10 +36,12 @@ export interface NewTaskModalProps {
|
|
|
32
36
|
* carries the parent link; unset targets inherit again at run time.
|
|
33
37
|
*/
|
|
34
38
|
parentTask?: TaskRecord
|
|
39
|
+
/** Which leg of the enter/exit motion pair the surface is on. */
|
|
40
|
+
phase?: OverlayPhase
|
|
35
41
|
}
|
|
36
42
|
|
|
37
43
|
/** New-task form overlay. */
|
|
38
|
-
export function NewTaskModal({ controller, onClose, initialTask, defaultWorkspaceId, onDuplicateSuccess, parentTask }: NewTaskModalProps) {
|
|
44
|
+
export function NewTaskModal({ controller, onClose, initialTask, defaultWorkspaceId, onDuplicateSuccess, parentTask, phase }: NewTaskModalProps) {
|
|
39
45
|
const isDuplicate = initialTask !== undefined
|
|
40
46
|
const [title, setTitle] = useState(initialTask?.title ?? '')
|
|
41
47
|
const [description, setDescription] = useState(initialTask?.description ?? '')
|
|
@@ -51,8 +57,15 @@ export function NewTaskModal({ controller, onClose, initialTask, defaultWorkspac
|
|
|
51
57
|
const [model, setModel] = useState(initialTask?.model ?? parentTask?.model ?? '')
|
|
52
58
|
const inheritedPermission = parentTask === undefined ? undefined : effectiveTaskPermission(parentTask)
|
|
53
59
|
const [reuseSession, setReuseSession] = useState(initialTask?.reuseSession ?? false)
|
|
60
|
+
// Checked by default: a new task starts its runs with dsh's built-in /goal
|
|
61
|
+
// unless the user opts out here. A duplicate keeps the original card's choice.
|
|
62
|
+
const [goalRun, setGoalRun] = useState(initialTask?.goalRun ?? true)
|
|
63
|
+
const [skipVerification, setSkipVerification] = useState(initialTask?.skipVerification ?? false)
|
|
54
64
|
const [scheduleEnabled, setScheduleEnabled] = useState(initialTask?.schedule?.enabled ?? false)
|
|
55
65
|
const [scheduleCron, setScheduleCron] = useState(initialTask?.schedule?.cron ?? '')
|
|
66
|
+
// '' means "follow the Host zone" (store no zone), which is the default a
|
|
67
|
+
// user gets unless they pick one explicitly.
|
|
68
|
+
const [scheduleZone, setScheduleZone] = useState(initialTask?.schedule?.timeZone ?? '')
|
|
56
69
|
const [scheduleError, setScheduleError] = useState<string | undefined>(undefined)
|
|
57
70
|
const [freezeText, setFreezeText] = useState('')
|
|
58
71
|
const [freezeError, setFreezeError] = useState<string | undefined>(undefined)
|
|
@@ -175,8 +188,12 @@ export function NewTaskModal({ controller, onClose, initialTask, defaultWorkspac
|
|
|
175
188
|
permission: permission === '' ? undefined : permission as TaskPermission,
|
|
176
189
|
model: model === '' ? undefined : model,
|
|
177
190
|
...(reuseSession ? { reuseSession: true } : {}),
|
|
191
|
+
...(goalRun ? {} : { goalRun: false }),
|
|
192
|
+
...(skipVerification ? { skipVerification: true } : {}),
|
|
178
193
|
...(tagList.length > 0 ? { tags: tagList } : {}),
|
|
179
|
-
schedule: scheduleEnabled
|
|
194
|
+
schedule: scheduleEnabled
|
|
195
|
+
? { enabled: true, cron: scheduleCron.trim(), ...(scheduleZone === '' ? {} : { timeZone: scheduleZone }) }
|
|
196
|
+
: undefined,
|
|
180
197
|
})
|
|
181
198
|
if (task === undefined) {
|
|
182
199
|
setPending(false)
|
|
@@ -201,15 +218,61 @@ export function NewTaskModal({ controller, onClose, initialTask, defaultWorkspac
|
|
|
201
218
|
onClose()
|
|
202
219
|
}
|
|
203
220
|
|
|
204
|
-
|
|
221
|
+
// Next-run preview for a valid armed cron (creation-time only), computed in
|
|
222
|
+
// the selected zone so it matches what the Host will arm.
|
|
223
|
+
const hostTimeZone = controller.getSnapshot().host?.scheduler.timeZone
|
|
224
|
+
const scheduleTimeZone = scheduleZone === '' ? hostTimeZone : scheduleZone
|
|
205
225
|
const scheduleNextRun = scheduleEnabled && scheduleCron.trim() !== '' && isValidCron(scheduleCron)
|
|
206
|
-
? nextRunAtMs(scheduleCron, Date.now())
|
|
226
|
+
? nextRunAtMs(scheduleCron, Date.now(), scheduleTimeZone)
|
|
207
227
|
: undefined
|
|
208
228
|
|
|
209
229
|
const modalTitle = parentTask !== undefined
|
|
210
230
|
? t('new.subtaskTitle')
|
|
211
231
|
: isDuplicate ? t('new.duplicateTitle') : t('board.new')
|
|
212
232
|
|
|
233
|
+
// Agent-preset picker rows: the four built-in presets first, then every other
|
|
234
|
+
// roster row (user presets). The inherit option names the preset a run
|
|
235
|
+
// actually lands on when the card pins nothing.
|
|
236
|
+
const builtinPresets = options.presets.filter(preset => isBuiltinPreset(preset.id))
|
|
237
|
+
const customPresets = options.presets.filter(preset => !isBuiltinPreset(preset.id))
|
|
238
|
+
const inheritLabel = inheritPresetLabel(options.presets)
|
|
239
|
+
const selectedPreset = options.presets.find(preset => preset.id === mode)
|
|
240
|
+
const modeLabel = mode === '' ? inheritLabel : selectedPreset === undefined ? mode : presetLabel(selectedPreset)
|
|
241
|
+
const workspaceLabel = workspaceId === ''
|
|
242
|
+
? t('exec.workspace.recent')
|
|
243
|
+
: options.workspaces.find(item => item.workspaceId === workspaceId)?.title ?? workspaceId
|
|
244
|
+
const permissionLabel = permission === ''
|
|
245
|
+
? inheritedPermission === undefined
|
|
246
|
+
? t('exec.permission.default')
|
|
247
|
+
: t('exec.permission.inheritParent', { permission: t(`exec.permission.${inheritedPermission}` as TaskBoardKey) })
|
|
248
|
+
: t(`exec.permission.${permission}` as TaskBoardKey)
|
|
249
|
+
const modelLabel = model === ''
|
|
250
|
+
? t('exec.model.default')
|
|
251
|
+
: (options.models ?? []).find(item => item.id === model)?.name ?? model
|
|
252
|
+
// Collapsed-region summaries: what each closed region currently holds, so a
|
|
253
|
+
// collapse never hides the configuration without a trace.
|
|
254
|
+
// A host-default model adds only the long parenthetical name to the
|
|
255
|
+
// collapsed line, so it is summarized only once it is actually pinned.
|
|
256
|
+
const executionSummary = [
|
|
257
|
+
workspaceLabel,
|
|
258
|
+
modeLabel,
|
|
259
|
+
permissionLabel,
|
|
260
|
+
...(model === '' ? [] : [modelLabel]),
|
|
261
|
+
].join(' · ')
|
|
262
|
+
const tagCount = cleanTags(tags).length
|
|
263
|
+
const labelsSummary = tagCount === 0 ? t('new.summary.none') : t('new.summary.labelCount', { count: String(tagCount) })
|
|
264
|
+
const runSummary = [
|
|
265
|
+
goalRun ? t('new.summary.multiRound') : t('new.summary.singleRound'),
|
|
266
|
+
...(reuseSession ? [t('exec.reuseSession')] : []),
|
|
267
|
+
].join(' · ')
|
|
268
|
+
const handoverSummary = freezeText.trim() !== '' || handoverText.trim() !== ''
|
|
269
|
+
? t('new.summary.filled')
|
|
270
|
+
: t('new.summary.none')
|
|
271
|
+
const scheduleSummary = !scheduleEnabled
|
|
272
|
+
? t('new.summary.scheduleOff')
|
|
273
|
+
: scheduleCron.trim() === '' ? t('new.summary.none') : scheduleCron.trim()
|
|
274
|
+
const parseSummary = parseText.trim() === '' ? t('new.summary.none') : t('new.summary.filled')
|
|
275
|
+
|
|
213
276
|
return (
|
|
214
277
|
<ModalShell
|
|
215
278
|
ariaLabel={modalTitle}
|
|
@@ -219,54 +282,61 @@ export function NewTaskModal({ controller, onClose, initialTask, defaultWorkspac
|
|
|
219
282
|
submitLabel={t('new.submit')}
|
|
220
283
|
onSubmit={() => { void submit(false) }}
|
|
221
284
|
onClose={onClose}
|
|
285
|
+
phase={phase}
|
|
222
286
|
secondaryAction={{ label: t('new.createAndRun'), onSubmit: () => { void submit(true) } }}
|
|
223
287
|
>
|
|
224
288
|
{canParse && (
|
|
225
|
-
<
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
289
|
+
<CollapsibleSection
|
|
290
|
+
title={t('new.section.parse')}
|
|
291
|
+
summary={parseSummary}
|
|
292
|
+
forceOpen={parseError !== undefined}
|
|
293
|
+
>
|
|
294
|
+
<section className={css.aiParse} data-dsh-part="ai-parse">
|
|
295
|
+
<span className={css.fieldLabel}>{t('new.aiParse')}</span>
|
|
296
|
+
<p className={css.fieldHint}>{t('new.aiParseHint')}</p>
|
|
297
|
+
<textarea
|
|
298
|
+
className={css.input}
|
|
299
|
+
rows={3}
|
|
300
|
+
value={parseText}
|
|
301
|
+
placeholder={t('new.aiParsePlaceholder')}
|
|
302
|
+
spellCheck={false}
|
|
303
|
+
onChange={event => { setParseText(event.target.value); setParseError(undefined) }}
|
|
304
|
+
/>
|
|
305
|
+
<div className={css.aiParseRow}>
|
|
306
|
+
<select
|
|
307
|
+
className={css.select}
|
|
308
|
+
value={parseModel}
|
|
309
|
+
aria-label={t('new.aiParseModel')}
|
|
310
|
+
onChange={event => {
|
|
311
|
+
setParseModel(event.target.value)
|
|
312
|
+
writeParseModelPreference(event.target.value)
|
|
313
|
+
}}
|
|
314
|
+
>
|
|
315
|
+
<option value="">{t('exec.model.default')}</option>
|
|
316
|
+
{parseModels.map(option => (
|
|
317
|
+
<option key={option.id} value={option.id}>{option.name ?? option.id}</option>
|
|
318
|
+
))}
|
|
319
|
+
</select>
|
|
320
|
+
{parsePending
|
|
321
|
+
? (
|
|
322
|
+
<button type="button" className={css.ghostButton} onClick={() => { parseAbort.current?.abort() }}>
|
|
323
|
+
{t('new.aiParseCancel')}
|
|
324
|
+
</button>
|
|
325
|
+
)
|
|
326
|
+
: (
|
|
327
|
+
<button
|
|
328
|
+
type="button"
|
|
329
|
+
className={css.primaryButton}
|
|
330
|
+
disabled={parseText.trim() === ''}
|
|
331
|
+
onClick={() => { void runParse() }}
|
|
332
|
+
>
|
|
333
|
+
{t('new.aiParseRun')}
|
|
334
|
+
</button>
|
|
335
|
+
)}
|
|
336
|
+
</div>
|
|
337
|
+
{parseError !== undefined && <p className={css.formError}>{parseError}</p>}
|
|
338
|
+
</section>
|
|
339
|
+
</CollapsibleSection>
|
|
270
340
|
)}
|
|
271
341
|
|
|
272
342
|
{parentTask !== undefined && (
|
|
@@ -275,42 +345,32 @@ export function NewTaskModal({ controller, onClose, initialTask, defaultWorkspac
|
|
|
275
345
|
</p>
|
|
276
346
|
)}
|
|
277
347
|
|
|
278
|
-
<
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
/>
|
|
299
|
-
</label>
|
|
300
|
-
{freezeError !== undefined && <p className={css.formError}>{freezeError}</p>}
|
|
348
|
+
<CollapsibleSection title={t('new.section.content')} defaultOpen>
|
|
349
|
+
<TaskContentFields
|
|
350
|
+
title={title}
|
|
351
|
+
description={description}
|
|
352
|
+
prompt={prompt}
|
|
353
|
+
onTitleChange={value => { setTitle(value); setError(undefined) }}
|
|
354
|
+
onDescriptionChange={setDescription}
|
|
355
|
+
onPromptChange={setPrompt}
|
|
356
|
+
/>
|
|
357
|
+
{isDuplicate && (
|
|
358
|
+
<label className={css.checkboxLabel}>
|
|
359
|
+
<input
|
|
360
|
+
type="checkbox"
|
|
361
|
+
checked={archiveOriginal}
|
|
362
|
+
onChange={event => { setArchiveOriginal(event.target.checked) }}
|
|
363
|
+
/>
|
|
364
|
+
<span>{t('new.archiveOriginal')}</span>
|
|
365
|
+
</label>
|
|
366
|
+
)}
|
|
367
|
+
</CollapsibleSection>
|
|
301
368
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
className={css.input}
|
|
306
|
-
rows={3}
|
|
307
|
-
value={handoverText}
|
|
308
|
-
placeholder={t('new.handoverPlaceholder')}
|
|
309
|
-
spellCheck={false}
|
|
310
|
-
onChange={event => { setHandoverText(event.target.value) }}
|
|
311
|
-
/>
|
|
312
|
-
</label>
|
|
369
|
+
<CollapsibleSection title={t('new.section.labels')} summary={labelsSummary}>
|
|
370
|
+
<TaskTagFields tags={tags} knownTags={collectKnownTags(controller.getSnapshot().tasks)} onChange={setTags} />
|
|
371
|
+
</CollapsibleSection>
|
|
313
372
|
|
|
373
|
+
<CollapsibleSection title={t('new.section.execution')} summary={executionSummary}>
|
|
314
374
|
<label className={css.field}>
|
|
315
375
|
<span className={css.fieldLabel}>{t('new.workspace')}</span>
|
|
316
376
|
<select
|
|
@@ -327,21 +387,36 @@ export function NewTaskModal({ controller, onClose, initialTask, defaultWorkspac
|
|
|
327
387
|
</label>
|
|
328
388
|
|
|
329
389
|
<label className={css.field}>
|
|
330
|
-
<span className={css.fieldLabel}>{t('new.
|
|
390
|
+
<span className={css.fieldLabel}>{t('new.agentPreset')}</span>
|
|
331
391
|
<select
|
|
332
392
|
className={css.select}
|
|
333
393
|
value={mode}
|
|
334
394
|
onChange={event => { setMode(event.target.value) }}
|
|
335
395
|
>
|
|
336
|
-
<option value="">{
|
|
396
|
+
<option value="">{inheritLabel}</option>
|
|
337
397
|
{!modeKnown && <option value={mode}>{mode}{t('exec.mode.removed')}</option>}
|
|
338
|
-
{
|
|
339
|
-
<
|
|
340
|
-
{preset
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
398
|
+
{builtinPresets.length > 0 && (
|
|
399
|
+
<optgroup label={t('exec.mode.builtinGroup')}>
|
|
400
|
+
{builtinPresets.map(preset => (
|
|
401
|
+
<option key={preset.id} value={preset.id} disabled={preset.broken !== undefined}>
|
|
402
|
+
{presetLabel(preset)}
|
|
403
|
+
{preset.isDefault ? t('exec.mode.defaultSuffix') : ''}
|
|
404
|
+
{preset.broken !== undefined ? t('exec.mode.brokenSuffix') : ''}
|
|
405
|
+
</option>
|
|
406
|
+
))}
|
|
407
|
+
</optgroup>
|
|
408
|
+
)}
|
|
409
|
+
{customPresets.length > 0 && (
|
|
410
|
+
<optgroup label={t('exec.mode.customGroup')}>
|
|
411
|
+
{customPresets.map(preset => (
|
|
412
|
+
<option key={preset.id} value={preset.id} disabled={preset.broken !== undefined}>
|
|
413
|
+
{presetLabel(preset)}
|
|
414
|
+
{preset.isDefault ? t('exec.mode.defaultSuffix') : ''}
|
|
415
|
+
{preset.broken !== undefined ? t('exec.mode.brokenSuffix') : ''}
|
|
416
|
+
</option>
|
|
417
|
+
))}
|
|
418
|
+
</optgroup>
|
|
419
|
+
)}
|
|
345
420
|
</select>
|
|
346
421
|
</label>
|
|
347
422
|
|
|
@@ -375,7 +450,9 @@ export function NewTaskModal({ controller, onClose, initialTask, defaultWorkspac
|
|
|
375
450
|
))}
|
|
376
451
|
</select>
|
|
377
452
|
</label>
|
|
453
|
+
</CollapsibleSection>
|
|
378
454
|
|
|
455
|
+
<CollapsibleSection title={t('new.section.run')} summary={runSummary}>
|
|
379
456
|
<label className={css.scheduleToggle}>
|
|
380
457
|
<input
|
|
381
458
|
type="checkbox"
|
|
@@ -386,65 +463,125 @@ export function NewTaskModal({ controller, onClose, initialTask, defaultWorkspac
|
|
|
386
463
|
</label>
|
|
387
464
|
<p className={css.detailText}>{t('exec.reuseSessionHint')}</p>
|
|
388
465
|
|
|
389
|
-
<
|
|
390
|
-
<
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
466
|
+
<label className={css.scheduleToggle}>
|
|
467
|
+
<input
|
|
468
|
+
type="checkbox"
|
|
469
|
+
checked={goalRun}
|
|
470
|
+
onChange={event => { setGoalRun(event.target.checked) }}
|
|
471
|
+
/>
|
|
472
|
+
<span>{t('exec.goalRun')}</span>
|
|
473
|
+
</label>
|
|
474
|
+
<p className={css.detailText}>{t('exec.goalRunHint')}</p>
|
|
475
|
+
|
|
476
|
+
<label className={css.scheduleToggle}>
|
|
477
|
+
<input
|
|
478
|
+
type="checkbox"
|
|
479
|
+
checked={skipVerification}
|
|
480
|
+
onChange={event => { setSkipVerification(event.target.checked) }}
|
|
481
|
+
/>
|
|
482
|
+
<span>{t('exec.skipVerification')}</span>
|
|
483
|
+
</label>
|
|
484
|
+
<p className={css.detailText}>{t('exec.skipVerificationHint')}</p>
|
|
485
|
+
</CollapsibleSection>
|
|
486
|
+
|
|
487
|
+
<CollapsibleSection
|
|
488
|
+
title={t('new.section.handover')}
|
|
489
|
+
summary={handoverSummary}
|
|
490
|
+
forceOpen={freezeError !== undefined}
|
|
491
|
+
>
|
|
492
|
+
<label className={css.field}>
|
|
493
|
+
<span className={css.fieldLabel}>{t('new.freeze')}</span>
|
|
494
|
+
<textarea
|
|
495
|
+
className={css.input}
|
|
496
|
+
rows={4}
|
|
497
|
+
value={freezeText}
|
|
498
|
+
placeholder={t('new.freezePlaceholder')}
|
|
499
|
+
spellCheck={false}
|
|
500
|
+
onChange={event => { setFreezeText(event.target.value); setFreezeError(undefined) }}
|
|
501
|
+
/>
|
|
502
|
+
</label>
|
|
503
|
+
{freezeError !== undefined && <p className={css.formError}>{freezeError}</p>}
|
|
504
|
+
|
|
505
|
+
<label className={css.field}>
|
|
506
|
+
<span className={css.fieldLabel}>{t('new.handover')}</span>
|
|
507
|
+
<textarea
|
|
508
|
+
className={css.input}
|
|
509
|
+
rows={3}
|
|
510
|
+
value={handoverText}
|
|
511
|
+
placeholder={t('new.handoverPlaceholder')}
|
|
512
|
+
spellCheck={false}
|
|
513
|
+
onChange={event => { setHandoverText(event.target.value) }}
|
|
514
|
+
/>
|
|
515
|
+
</label>
|
|
516
|
+
</CollapsibleSection>
|
|
517
|
+
|
|
518
|
+
<CollapsibleSection
|
|
519
|
+
title={t('new.section.schedule')}
|
|
520
|
+
summary={scheduleSummary}
|
|
521
|
+
forceOpen={scheduleError !== undefined}
|
|
522
|
+
>
|
|
523
|
+
<label className={css.scheduleToggle}>
|
|
524
|
+
<input
|
|
525
|
+
type="checkbox"
|
|
526
|
+
checked={scheduleEnabled}
|
|
527
|
+
onChange={event => {
|
|
528
|
+
setScheduleEnabled(event.target.checked)
|
|
529
|
+
if (!event.target.checked) setScheduleError(undefined)
|
|
530
|
+
}}
|
|
531
|
+
/>
|
|
532
|
+
<span>{t('detail.schedule.enable')}</span>
|
|
533
|
+
</label>
|
|
534
|
+
{scheduleEnabled && (
|
|
535
|
+
<>
|
|
536
|
+
<div className={css.scheduleRow}>
|
|
537
|
+
<input
|
|
538
|
+
className={`${css.input} ${css.scheduleInput}${scheduleError !== undefined ? ` ${css.scheduleInputInvalid}` : ''}`}
|
|
539
|
+
value={scheduleCron}
|
|
540
|
+
placeholder="0 9 * * *"
|
|
541
|
+
spellCheck={false}
|
|
542
|
+
aria-label={t('detail.schedule.cron')}
|
|
543
|
+
onChange={event => { setScheduleCron(event.target.value); setScheduleError(undefined) }}
|
|
544
|
+
/>
|
|
545
|
+
<select
|
|
546
|
+
className={css.schedulePreset}
|
|
547
|
+
value=""
|
|
548
|
+
aria-label={t('detail.schedule.presets')}
|
|
549
|
+
onChange={event => {
|
|
550
|
+
if (event.target.value === '') return
|
|
551
|
+
setScheduleCron(event.target.value)
|
|
552
|
+
setScheduleError(undefined)
|
|
553
|
+
}}
|
|
554
|
+
>
|
|
555
|
+
<option value="">{t('detail.schedule.presets')}…</option>
|
|
556
|
+
{SCHEDULE_PRESETS.map(preset => (
|
|
557
|
+
<option key={preset.cron} value={preset.cron}>{t(preset.label)}</option>
|
|
558
|
+
))}
|
|
559
|
+
</select>
|
|
560
|
+
</div>
|
|
561
|
+
{scheduleError !== undefined && <p className={css.formError}>{scheduleError}</p>}
|
|
562
|
+
<label className={css.scheduleZone}>
|
|
563
|
+
<span>{t('detail.schedule.timeZone')}</span>
|
|
564
|
+
<select
|
|
565
|
+
className={css.schedulePreset}
|
|
566
|
+
value={scheduleZone}
|
|
567
|
+
aria-label={t('detail.schedule.timeZone')}
|
|
568
|
+
title={t('detail.schedule.timeZoneHint')}
|
|
569
|
+
onChange={event => { setScheduleZone(event.target.value); setScheduleError(undefined) }}
|
|
570
|
+
>
|
|
571
|
+
{zoneChoices(hostTimeZone, initialTask?.schedule?.timeZone).map(choice => (
|
|
572
|
+
<option key={choice.id === '' ? '__host' : choice.id} value={choice.id}>{choice.label}</option>
|
|
573
|
+
))}
|
|
574
|
+
</select>
|
|
575
|
+
</label>
|
|
576
|
+
{scheduleError === undefined && scheduleNextRun !== undefined && (
|
|
577
|
+
<p className={css.scheduleMeta}>
|
|
578
|
+
{t('detail.schedule.nextRun')}{' '}
|
|
579
|
+
{nextRunLabel(scheduleNextRun, scheduleTimeZone, formatHostTimestamp, Date.now())}
|
|
580
|
+
</p>
|
|
581
|
+
)}
|
|
582
|
+
</>
|
|
447
583
|
)}
|
|
584
|
+
</CollapsibleSection>
|
|
448
585
|
</ModalShell>
|
|
449
586
|
)
|
|
450
587
|
}
|