@linxin666/dsh-client-ui-task-board 0.4.2 → 0.4.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.i18n.yaml +2 -2
- package/README.md +21 -13
- package/README.zh.md +21 -13
- package/lib/client.js +1566 -929
- package/lib/client.js.map +1 -1
- package/lib/index.js +934 -176
- package/lib/types/client/board/TaskForm.d.ts +19 -1
- package/lib/types/client/board/preset-label.d.ts +22 -0
- package/lib/types/client/host-api.d.ts +24 -1
- package/lib/types/client/index.d.ts +15 -1
- package/lib/types/client/locales.d.ts +38 -2
- package/lib/types/client/native-panel.d.ts +62 -0
- package/lib/types/client/schedule-zone.d.ts +47 -0
- package/lib/types/client/settings-entry-form.d.ts +49 -0
- package/lib/types/core/controller.d.ts +42 -2
- package/lib/types/core/degraded-reason.d.ts +46 -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 +26 -2
- 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-update.d.ts +1 -1
- package/lib/types/host-ledger.d.ts +65 -4
- package/lib/types/host-runner.d.ts +76 -3
- package/lib/types/host-service.d.ts +77 -5
- package/lib/types/index.d.ts +11 -1
- package/lib/types/mount-once.d.ts +38 -7
- package/lib/types/protocol.d.ts +9 -3
- package/package.json +26 -23
- package/src/client/board/LinkSubtaskModal.tsx +21 -11
- package/src/client/board/NewTaskModal.tsx +271 -150
- package/src/client/board/TaskCard.tsx +24 -5
- package/src/client/board/TaskDetail.tsx +83 -33
- package/src/client/board/TaskForm.tsx +48 -3
- package/src/client/board/preset-label.ts +44 -0
- package/src/client/board.module.css +130 -136
- package/src/client/host-api.ts +109 -6
- package/src/client/index.ts +50 -18
- package/src/client/locales.ts +82 -8
- package/src/client/native-panel.tsx +124 -0
- package/src/client/schedule-zone.ts +135 -0
- package/src/client/settings-entry-form.ts +142 -0
- package/src/core/controller.ts +66 -4
- package/src/core/degraded-reason.ts +81 -0
- package/src/core/package-name.ts +9 -0
- package/src/core/schedule.ts +211 -55
- package/src/core/store.ts +10 -1
- package/src/core/subtask.ts +77 -22
- package/src/core/tasks.ts +38 -4
- package/src/core/use-cases/task-create.ts +14 -2
- package/src/core/use-cases/task-schedule.ts +17 -6
- package/src/core/use-cases/task-update.ts +4 -1
- package/src/host/agent-tools.ts +40 -8
- package/src/host-ledger.ts +223 -23
- package/src/host-routes.ts +37 -1
- package/src/host-runner.ts +164 -14
- package/src/host-service.ts +232 -39
- package/src/index.ts +23 -1
- package/src/mount-once.ts +107 -13
- package/src/protocol.ts +27 -8
- package/lib/types/client/board-mount.d.ts +0 -23
- package/lib/types/client/body-mutations.d.ts +0 -45
- package/lib/types/client/panel-mount-core.d.ts +0 -56
- package/lib/types/client/sidebar-entry-core.d.ts +0 -55
- package/lib/types/client/sidebar-entry.d.ts +0 -29
- package/lib/types/host/run-guarded.d.ts +0 -39
- package/src/client/board-mount.tsx +0 -43
- package/src/client/body-mutations.ts +0 -133
- package/src/client/panel-mount-core.ts +0 -155
- package/src/client/sidebar-entry-core.ts +0 -237
- package/src/client/sidebar-entry.ts +0 -53
- package/src/host/run-guarded.ts +0 -76
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linxin666/dsh-client-ui-task-board",
|
|
3
3
|
"description": "Host-authoritative task board for the DSH Web GUI with real session execution, Host cron scheduling, and optional cross-platform idle-sleep protection; mounted without DSH source changes.",
|
|
4
|
-
"version": "0.4.
|
|
4
|
+
"version": "0.4.4",
|
|
5
5
|
"icon": "icon.svg",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "lib/index.js",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
},
|
|
25
25
|
"dsh": {
|
|
26
26
|
"engines": {
|
|
27
|
-
"dsh": ">=0.
|
|
27
|
+
"dsh": ">=0.2.0-rc.1"
|
|
28
28
|
},
|
|
29
29
|
"bundle": {
|
|
30
30
|
"patch": "./cordis.patch.yml"
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"@deepseek-ai/dsh-client-connection",
|
|
35
35
|
"@deepseek-ai/dsh-client-ui-settings",
|
|
36
36
|
"@deepseek-ai/dsh-client-ui-renderer",
|
|
37
|
+
"@deepseek-ai/dsh-client-ui-layout",
|
|
37
38
|
"@deepseek-ai/dsh-api-session-controller",
|
|
38
39
|
"@deepseek-ai/dsh-api-workspace-controller",
|
|
39
40
|
"@deepseek-ai/dsh-api-remotes",
|
|
@@ -43,7 +44,7 @@
|
|
|
43
44
|
}
|
|
44
45
|
},
|
|
45
46
|
"peerDependencies": {
|
|
46
|
-
"@deepseek-ai/dsh": ">=0.
|
|
47
|
+
"@deepseek-ai/dsh": ">=0.2.0-rc.1",
|
|
47
48
|
"react": "^18.2.0"
|
|
48
49
|
},
|
|
49
50
|
"dependencies": {
|
|
@@ -51,26 +52,28 @@
|
|
|
51
52
|
},
|
|
52
53
|
"devDependencies": {
|
|
53
54
|
"@deepseek-ai/cordis": "^4.0.4",
|
|
54
|
-
"@deepseek-ai/dsh-agent": "^0.
|
|
55
|
-
"@deepseek-ai/dsh-agent-preset-registry": "^0.
|
|
56
|
-
"@deepseek-ai/dsh-api-gateway": "^0.
|
|
57
|
-
"@deepseek-ai/dsh-api-remotes": "^0.
|
|
58
|
-
"@deepseek-ai/dsh-api-session-controller": "^0.
|
|
59
|
-
"@deepseek-ai/dsh-api-workspace-controller": "^0.
|
|
60
|
-
"@deepseek-ai/dsh-client-connection": "^0.
|
|
61
|
-
"@deepseek-ai/dsh-client-locale": "^0.
|
|
62
|
-
"@deepseek-ai/dsh-client-store": "^0.
|
|
63
|
-
"@deepseek-ai/dsh-client-ui-
|
|
64
|
-
"@deepseek-ai/dsh-client-ui-
|
|
65
|
-
"@deepseek-ai/dsh-client-ui-
|
|
66
|
-
"@deepseek-ai/dsh-client-ui-
|
|
67
|
-
"@deepseek-ai/dsh-
|
|
68
|
-
"@deepseek-ai/dsh-
|
|
69
|
-
"@deepseek-ai/dsh-
|
|
70
|
-
"@deepseek-ai/dsh-
|
|
71
|
-
"@deepseek-ai/dsh-
|
|
72
|
-
"@deepseek-ai/dsh-
|
|
73
|
-
"@deepseek-ai/dsh-
|
|
55
|
+
"@deepseek-ai/dsh-agent": "^0.2.0-rc.1",
|
|
56
|
+
"@deepseek-ai/dsh-agent-preset-registry": "^0.2.0-rc.1",
|
|
57
|
+
"@deepseek-ai/dsh-api-gateway": "^0.2.0-rc.1",
|
|
58
|
+
"@deepseek-ai/dsh-api-remotes": "^0.2.0-rc.1",
|
|
59
|
+
"@deepseek-ai/dsh-api-session-controller": "^0.2.0-rc.1",
|
|
60
|
+
"@deepseek-ai/dsh-api-workspace-controller": "^0.2.0-rc.1",
|
|
61
|
+
"@deepseek-ai/dsh-client-connection": "^0.2.0-rc.1",
|
|
62
|
+
"@deepseek-ai/dsh-client-locale": "^0.2.0-rc.1",
|
|
63
|
+
"@deepseek-ai/dsh-client-store": "^0.2.0-rc.1",
|
|
64
|
+
"@deepseek-ai/dsh-client-ui-layout": "^0.2.0-rc.1",
|
|
65
|
+
"@deepseek-ai/dsh-client-ui-renderer": "^0.2.0-rc.1",
|
|
66
|
+
"@deepseek-ai/dsh-client-ui-settings": "^0.2.0-rc.1",
|
|
67
|
+
"@deepseek-ai/dsh-client-ui-sidebar": "^0.2.0-rc.1",
|
|
68
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.2.0-rc.1",
|
|
69
|
+
"@deepseek-ai/dsh-client-ui-workspace": "^0.2.0-rc.1",
|
|
70
|
+
"@deepseek-ai/dsh-commands": "^0.2.0-rc.1",
|
|
71
|
+
"@deepseek-ai/dsh-host-webserver": "^0.2.0-rc.1",
|
|
72
|
+
"@deepseek-ai/dsh-llm": "^0.2.0-rc.1",
|
|
73
|
+
"@deepseek-ai/dsh-settings": "^0.2.0-rc.1",
|
|
74
|
+
"@deepseek-ai/dsh-system-prompt": "^0.2.0-rc.1",
|
|
75
|
+
"@deepseek-ai/dsh-tools": "^0.2.0-rc.1",
|
|
76
|
+
"@deepseek-ai/dsh-workspace": "^0.2.0-rc.1",
|
|
74
77
|
"@types/node": "^22.20.0",
|
|
75
78
|
"@types/react": "~18.3.1",
|
|
76
79
|
"@types/react-dom": "^18.3.5",
|
|
@@ -4,9 +4,9 @@
|
|
|
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'
|
|
9
|
+
import { DEFAULT_SUBTASK_DEPTH, buildLineageIndex, checkParentLink } from '../../core/subtask.ts'
|
|
10
10
|
import type { TaskRecord } from '../../core/tasks.ts'
|
|
11
11
|
import { t } from '../locales.ts'
|
|
12
12
|
import css from '../board.module.css'
|
|
@@ -28,15 +28,25 @@ export function LinkSubtaskModal({ controller, parent, onClose }: LinkSubtaskMod
|
|
|
28
28
|
const limit = snapshot.host?.maxSubtaskDepth ?? DEFAULT_SUBTASK_DEPTH
|
|
29
29
|
// Only root, on-board tasks can move under another parent; the lineage gate
|
|
30
30
|
// decides the rest (depth, cycle, archived parent).
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
31
|
+
//
|
|
32
|
+
// The gate is run once per candidate task, so it is given ONE lineage index
|
|
33
|
+
// for the whole pass: without it every call rebuilds the id/child maps and
|
|
34
|
+
// rescans the ledger, turning a render into quadratic work. The derivation
|
|
35
|
+
// also runs on every controller notification (the SSE heartbeat included),
|
|
36
|
+
// so it is memoized on the inputs it actually reads.
|
|
37
|
+
const tasks = snapshot.tasks
|
|
38
|
+
const candidates = useMemo(() => {
|
|
39
|
+
const lineage = buildLineageIndex(tasks)
|
|
40
|
+
return tasks.filter(task =>
|
|
41
|
+
task.archivedAt === undefined
|
|
42
|
+
// A running task may already be a participant of another run group; the
|
|
43
|
+
// Host refuses the move, so it is not offered here.
|
|
44
|
+
&& task.status !== 'running'
|
|
45
|
+
&& task.parentId === undefined
|
|
46
|
+
&& task.id !== parent.id
|
|
47
|
+
&& checkParentLink(tasks, task.id, parent.id, limit, lineage).ok,
|
|
48
|
+
)
|
|
49
|
+
}, [tasks, parent.id, limit])
|
|
40
50
|
const link = async (taskId: string): Promise<void> => {
|
|
41
51
|
setPending(true)
|
|
42
52
|
setError(undefined)
|
|
@@ -10,8 +10,11 @@ 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 { formatHostTimestamp } from './TaskCard.tsx'
|
|
14
16
|
import { readParseModelPreference, writeParseModelPreference } from './parse-model-pref.ts'
|
|
17
|
+
import { inheritPresetLabel, isBuiltinPreset, presetLabel } from './preset-label.ts'
|
|
15
18
|
import css from '../board.module.css'
|
|
16
19
|
|
|
17
20
|
export interface NewTaskModalProps {
|
|
@@ -51,8 +54,14 @@ export function NewTaskModal({ controller, onClose, initialTask, defaultWorkspac
|
|
|
51
54
|
const [model, setModel] = useState(initialTask?.model ?? parentTask?.model ?? '')
|
|
52
55
|
const inheritedPermission = parentTask === undefined ? undefined : effectiveTaskPermission(parentTask)
|
|
53
56
|
const [reuseSession, setReuseSession] = useState(initialTask?.reuseSession ?? false)
|
|
57
|
+
// Checked by default: a new task starts its runs with dsh's built-in /goal
|
|
58
|
+
// unless the user opts out here. A duplicate keeps the original card's choice.
|
|
59
|
+
const [goalRun, setGoalRun] = useState(initialTask?.goalRun ?? true)
|
|
54
60
|
const [scheduleEnabled, setScheduleEnabled] = useState(initialTask?.schedule?.enabled ?? false)
|
|
55
61
|
const [scheduleCron, setScheduleCron] = useState(initialTask?.schedule?.cron ?? '')
|
|
62
|
+
// '' means "follow the Host zone" (store no zone), which is the default a
|
|
63
|
+
// user gets unless they pick one explicitly.
|
|
64
|
+
const [scheduleZone, setScheduleZone] = useState(initialTask?.schedule?.timeZone ?? '')
|
|
56
65
|
const [scheduleError, setScheduleError] = useState<string | undefined>(undefined)
|
|
57
66
|
const [freezeText, setFreezeText] = useState('')
|
|
58
67
|
const [freezeError, setFreezeError] = useState<string | undefined>(undefined)
|
|
@@ -175,8 +184,11 @@ export function NewTaskModal({ controller, onClose, initialTask, defaultWorkspac
|
|
|
175
184
|
permission: permission === '' ? undefined : permission as TaskPermission,
|
|
176
185
|
model: model === '' ? undefined : model,
|
|
177
186
|
...(reuseSession ? { reuseSession: true } : {}),
|
|
187
|
+
...(goalRun ? {} : { goalRun: false }),
|
|
178
188
|
...(tagList.length > 0 ? { tags: tagList } : {}),
|
|
179
|
-
schedule: scheduleEnabled
|
|
189
|
+
schedule: scheduleEnabled
|
|
190
|
+
? { enabled: true, cron: scheduleCron.trim(), ...(scheduleZone === '' ? {} : { timeZone: scheduleZone }) }
|
|
191
|
+
: undefined,
|
|
180
192
|
})
|
|
181
193
|
if (task === undefined) {
|
|
182
194
|
setPending(false)
|
|
@@ -201,15 +213,61 @@ export function NewTaskModal({ controller, onClose, initialTask, defaultWorkspac
|
|
|
201
213
|
onClose()
|
|
202
214
|
}
|
|
203
215
|
|
|
204
|
-
|
|
216
|
+
// Next-run preview for a valid armed cron (creation-time only), computed in
|
|
217
|
+
// the selected zone so it matches what the Host will arm.
|
|
218
|
+
const hostTimeZone = controller.getSnapshot().host?.scheduler.timeZone
|
|
219
|
+
const scheduleTimeZone = scheduleZone === '' ? hostTimeZone : scheduleZone
|
|
205
220
|
const scheduleNextRun = scheduleEnabled && scheduleCron.trim() !== '' && isValidCron(scheduleCron)
|
|
206
|
-
? nextRunAtMs(scheduleCron, Date.now())
|
|
221
|
+
? nextRunAtMs(scheduleCron, Date.now(), scheduleTimeZone)
|
|
207
222
|
: undefined
|
|
208
223
|
|
|
209
224
|
const modalTitle = parentTask !== undefined
|
|
210
225
|
? t('new.subtaskTitle')
|
|
211
226
|
: isDuplicate ? t('new.duplicateTitle') : t('board.new')
|
|
212
227
|
|
|
228
|
+
// Agent-preset picker rows: the four built-in presets first, then every other
|
|
229
|
+
// roster row (user presets). The inherit option names the preset a run
|
|
230
|
+
// actually lands on when the card pins nothing.
|
|
231
|
+
const builtinPresets = options.presets.filter(preset => isBuiltinPreset(preset.id))
|
|
232
|
+
const customPresets = options.presets.filter(preset => !isBuiltinPreset(preset.id))
|
|
233
|
+
const inheritLabel = inheritPresetLabel(options.presets)
|
|
234
|
+
const selectedPreset = options.presets.find(preset => preset.id === mode)
|
|
235
|
+
const modeLabel = mode === '' ? inheritLabel : selectedPreset === undefined ? mode : presetLabel(selectedPreset)
|
|
236
|
+
const workspaceLabel = workspaceId === ''
|
|
237
|
+
? t('exec.workspace.recent')
|
|
238
|
+
: options.workspaces.find(item => item.workspaceId === workspaceId)?.title ?? workspaceId
|
|
239
|
+
const permissionLabel = permission === ''
|
|
240
|
+
? inheritedPermission === undefined
|
|
241
|
+
? t('exec.permission.default')
|
|
242
|
+
: t('exec.permission.inheritParent', { permission: t(`exec.permission.${inheritedPermission}` as TaskBoardKey) })
|
|
243
|
+
: t(`exec.permission.${permission}` as TaskBoardKey)
|
|
244
|
+
const modelLabel = model === ''
|
|
245
|
+
? t('exec.model.default')
|
|
246
|
+
: (options.models ?? []).find(item => item.id === model)?.name ?? model
|
|
247
|
+
// Collapsed-region summaries: what each closed region currently holds, so a
|
|
248
|
+
// collapse never hides the configuration without a trace.
|
|
249
|
+
// A host-default model adds only the long parenthetical name to the
|
|
250
|
+
// collapsed line, so it is summarized only once it is actually pinned.
|
|
251
|
+
const executionSummary = [
|
|
252
|
+
workspaceLabel,
|
|
253
|
+
modeLabel,
|
|
254
|
+
permissionLabel,
|
|
255
|
+
...(model === '' ? [] : [modelLabel]),
|
|
256
|
+
].join(' · ')
|
|
257
|
+
const tagCount = cleanTags(tags).length
|
|
258
|
+
const labelsSummary = tagCount === 0 ? t('new.summary.none') : t('new.summary.labelCount', { count: String(tagCount) })
|
|
259
|
+
const runSummary = [
|
|
260
|
+
goalRun ? t('new.summary.multiRound') : t('new.summary.singleRound'),
|
|
261
|
+
...(reuseSession ? [t('exec.reuseSession')] : []),
|
|
262
|
+
].join(' · ')
|
|
263
|
+
const handoverSummary = freezeText.trim() !== '' || handoverText.trim() !== ''
|
|
264
|
+
? t('new.summary.filled')
|
|
265
|
+
: t('new.summary.none')
|
|
266
|
+
const scheduleSummary = !scheduleEnabled
|
|
267
|
+
? t('new.summary.scheduleOff')
|
|
268
|
+
: scheduleCron.trim() === '' ? t('new.summary.none') : scheduleCron.trim()
|
|
269
|
+
const parseSummary = parseText.trim() === '' ? t('new.summary.none') : t('new.summary.filled')
|
|
270
|
+
|
|
213
271
|
return (
|
|
214
272
|
<ModalShell
|
|
215
273
|
ariaLabel={modalTitle}
|
|
@@ -222,51 +280,57 @@ export function NewTaskModal({ controller, onClose, initialTask, defaultWorkspac
|
|
|
222
280
|
secondaryAction={{ label: t('new.createAndRun'), onSubmit: () => { void submit(true) } }}
|
|
223
281
|
>
|
|
224
282
|
{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
|
-
|
|
283
|
+
<CollapsibleSection
|
|
284
|
+
title={t('new.section.parse')}
|
|
285
|
+
summary={parseSummary}
|
|
286
|
+
forceOpen={parseError !== undefined}
|
|
287
|
+
>
|
|
288
|
+
<section className={css.aiParse} data-dsh-part="ai-parse">
|
|
289
|
+
<span className={css.fieldLabel}>{t('new.aiParse')}</span>
|
|
290
|
+
<p className={css.fieldHint}>{t('new.aiParseHint')}</p>
|
|
291
|
+
<textarea
|
|
292
|
+
className={css.input}
|
|
293
|
+
rows={3}
|
|
294
|
+
value={parseText}
|
|
295
|
+
placeholder={t('new.aiParsePlaceholder')}
|
|
296
|
+
spellCheck={false}
|
|
297
|
+
onChange={event => { setParseText(event.target.value); setParseError(undefined) }}
|
|
298
|
+
/>
|
|
299
|
+
<div className={css.aiParseRow}>
|
|
300
|
+
<select
|
|
301
|
+
className={css.select}
|
|
302
|
+
value={parseModel}
|
|
303
|
+
aria-label={t('new.aiParseModel')}
|
|
304
|
+
onChange={event => {
|
|
305
|
+
setParseModel(event.target.value)
|
|
306
|
+
writeParseModelPreference(event.target.value)
|
|
307
|
+
}}
|
|
308
|
+
>
|
|
309
|
+
<option value="">{t('exec.model.default')}</option>
|
|
310
|
+
{parseModels.map(option => (
|
|
311
|
+
<option key={option.id} value={option.id}>{option.name ?? option.id}</option>
|
|
312
|
+
))}
|
|
313
|
+
</select>
|
|
314
|
+
{parsePending
|
|
315
|
+
? (
|
|
316
|
+
<button type="button" className={css.ghostButton} onClick={() => { parseAbort.current?.abort() }}>
|
|
317
|
+
{t('new.aiParseCancel')}
|
|
318
|
+
</button>
|
|
319
|
+
)
|
|
320
|
+
: (
|
|
321
|
+
<button
|
|
322
|
+
type="button"
|
|
323
|
+
className={css.primaryButton}
|
|
324
|
+
disabled={parseText.trim() === ''}
|
|
325
|
+
onClick={() => { void runParse() }}
|
|
326
|
+
>
|
|
327
|
+
{t('new.aiParseRun')}
|
|
328
|
+
</button>
|
|
329
|
+
)}
|
|
330
|
+
</div>
|
|
331
|
+
{parseError !== undefined && <p className={css.formError}>{parseError}</p>}
|
|
332
|
+
</section>
|
|
333
|
+
</CollapsibleSection>
|
|
270
334
|
)}
|
|
271
335
|
|
|
272
336
|
{parentTask !== undefined && (
|
|
@@ -275,42 +339,32 @@ export function NewTaskModal({ controller, onClose, initialTask, defaultWorkspac
|
|
|
275
339
|
</p>
|
|
276
340
|
)}
|
|
277
341
|
|
|
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>}
|
|
342
|
+
<CollapsibleSection title={t('new.section.content')} defaultOpen>
|
|
343
|
+
<TaskContentFields
|
|
344
|
+
title={title}
|
|
345
|
+
description={description}
|
|
346
|
+
prompt={prompt}
|
|
347
|
+
onTitleChange={value => { setTitle(value); setError(undefined) }}
|
|
348
|
+
onDescriptionChange={setDescription}
|
|
349
|
+
onPromptChange={setPrompt}
|
|
350
|
+
/>
|
|
351
|
+
{isDuplicate && (
|
|
352
|
+
<label className={css.checkboxLabel}>
|
|
353
|
+
<input
|
|
354
|
+
type="checkbox"
|
|
355
|
+
checked={archiveOriginal}
|
|
356
|
+
onChange={event => { setArchiveOriginal(event.target.checked) }}
|
|
357
|
+
/>
|
|
358
|
+
<span>{t('new.archiveOriginal')}</span>
|
|
359
|
+
</label>
|
|
360
|
+
)}
|
|
361
|
+
</CollapsibleSection>
|
|
301
362
|
|
|
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>
|
|
363
|
+
<CollapsibleSection title={t('new.section.labels')} summary={labelsSummary}>
|
|
364
|
+
<TaskTagFields tags={tags} knownTags={collectKnownTags(controller.getSnapshot().tasks)} onChange={setTags} />
|
|
365
|
+
</CollapsibleSection>
|
|
313
366
|
|
|
367
|
+
<CollapsibleSection title={t('new.section.execution')} summary={executionSummary}>
|
|
314
368
|
<label className={css.field}>
|
|
315
369
|
<span className={css.fieldLabel}>{t('new.workspace')}</span>
|
|
316
370
|
<select
|
|
@@ -327,21 +381,36 @@ export function NewTaskModal({ controller, onClose, initialTask, defaultWorkspac
|
|
|
327
381
|
</label>
|
|
328
382
|
|
|
329
383
|
<label className={css.field}>
|
|
330
|
-
<span className={css.fieldLabel}>{t('new.
|
|
384
|
+
<span className={css.fieldLabel}>{t('new.agentPreset')}</span>
|
|
331
385
|
<select
|
|
332
386
|
className={css.select}
|
|
333
387
|
value={mode}
|
|
334
388
|
onChange={event => { setMode(event.target.value) }}
|
|
335
389
|
>
|
|
336
|
-
<option value="">{
|
|
390
|
+
<option value="">{inheritLabel}</option>
|
|
337
391
|
{!modeKnown && <option value={mode}>{mode}{t('exec.mode.removed')}</option>}
|
|
338
|
-
{
|
|
339
|
-
<
|
|
340
|
-
{preset
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
392
|
+
{builtinPresets.length > 0 && (
|
|
393
|
+
<optgroup label={t('exec.mode.builtinGroup')}>
|
|
394
|
+
{builtinPresets.map(preset => (
|
|
395
|
+
<option key={preset.id} value={preset.id} disabled={preset.broken !== undefined}>
|
|
396
|
+
{presetLabel(preset)}
|
|
397
|
+
{preset.isDefault ? t('exec.mode.defaultSuffix') : ''}
|
|
398
|
+
{preset.broken !== undefined ? t('exec.mode.brokenSuffix') : ''}
|
|
399
|
+
</option>
|
|
400
|
+
))}
|
|
401
|
+
</optgroup>
|
|
402
|
+
)}
|
|
403
|
+
{customPresets.length > 0 && (
|
|
404
|
+
<optgroup label={t('exec.mode.customGroup')}>
|
|
405
|
+
{customPresets.map(preset => (
|
|
406
|
+
<option key={preset.id} value={preset.id} disabled={preset.broken !== undefined}>
|
|
407
|
+
{presetLabel(preset)}
|
|
408
|
+
{preset.isDefault ? t('exec.mode.defaultSuffix') : ''}
|
|
409
|
+
{preset.broken !== undefined ? t('exec.mode.brokenSuffix') : ''}
|
|
410
|
+
</option>
|
|
411
|
+
))}
|
|
412
|
+
</optgroup>
|
|
413
|
+
)}
|
|
345
414
|
</select>
|
|
346
415
|
</label>
|
|
347
416
|
|
|
@@ -375,7 +444,9 @@ export function NewTaskModal({ controller, onClose, initialTask, defaultWorkspac
|
|
|
375
444
|
))}
|
|
376
445
|
</select>
|
|
377
446
|
</label>
|
|
447
|
+
</CollapsibleSection>
|
|
378
448
|
|
|
449
|
+
<CollapsibleSection title={t('new.section.run')} summary={runSummary}>
|
|
379
450
|
<label className={css.scheduleToggle}>
|
|
380
451
|
<input
|
|
381
452
|
type="checkbox"
|
|
@@ -386,65 +457,115 @@ export function NewTaskModal({ controller, onClose, initialTask, defaultWorkspac
|
|
|
386
457
|
</label>
|
|
387
458
|
<p className={css.detailText}>{t('exec.reuseSessionHint')}</p>
|
|
388
459
|
|
|
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
|
-
checked
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
460
|
+
<label className={css.scheduleToggle}>
|
|
461
|
+
<input
|
|
462
|
+
type="checkbox"
|
|
463
|
+
checked={goalRun}
|
|
464
|
+
onChange={event => { setGoalRun(event.target.checked) }}
|
|
465
|
+
/>
|
|
466
|
+
<span>{t('exec.goalRun')}</span>
|
|
467
|
+
</label>
|
|
468
|
+
<p className={css.detailText}>{t('exec.goalRunHint')}</p>
|
|
469
|
+
</CollapsibleSection>
|
|
470
|
+
|
|
471
|
+
<CollapsibleSection
|
|
472
|
+
title={t('new.section.handover')}
|
|
473
|
+
summary={handoverSummary}
|
|
474
|
+
forceOpen={freezeError !== undefined}
|
|
475
|
+
>
|
|
476
|
+
<label className={css.field}>
|
|
477
|
+
<span className={css.fieldLabel}>{t('new.freeze')}</span>
|
|
478
|
+
<textarea
|
|
479
|
+
className={css.input}
|
|
480
|
+
rows={4}
|
|
481
|
+
value={freezeText}
|
|
482
|
+
placeholder={t('new.freezePlaceholder')}
|
|
483
|
+
spellCheck={false}
|
|
484
|
+
onChange={event => { setFreezeText(event.target.value); setFreezeError(undefined) }}
|
|
485
|
+
/>
|
|
486
|
+
</label>
|
|
487
|
+
{freezeError !== undefined && <p className={css.formError}>{freezeError}</p>}
|
|
488
|
+
|
|
489
|
+
<label className={css.field}>
|
|
490
|
+
<span className={css.fieldLabel}>{t('new.handover')}</span>
|
|
491
|
+
<textarea
|
|
492
|
+
className={css.input}
|
|
493
|
+
rows={3}
|
|
494
|
+
value={handoverText}
|
|
495
|
+
placeholder={t('new.handoverPlaceholder')}
|
|
496
|
+
spellCheck={false}
|
|
497
|
+
onChange={event => { setHandoverText(event.target.value) }}
|
|
498
|
+
/>
|
|
499
|
+
</label>
|
|
500
|
+
</CollapsibleSection>
|
|
501
|
+
|
|
502
|
+
<CollapsibleSection
|
|
503
|
+
title={t('new.section.schedule')}
|
|
504
|
+
summary={scheduleSummary}
|
|
505
|
+
forceOpen={scheduleError !== undefined}
|
|
506
|
+
>
|
|
507
|
+
<label className={css.scheduleToggle}>
|
|
508
|
+
<input
|
|
509
|
+
type="checkbox"
|
|
510
|
+
checked={scheduleEnabled}
|
|
511
|
+
onChange={event => {
|
|
512
|
+
setScheduleEnabled(event.target.checked)
|
|
513
|
+
if (!event.target.checked) setScheduleError(undefined)
|
|
514
|
+
}}
|
|
515
|
+
/>
|
|
516
|
+
<span>{t('detail.schedule.enable')}</span>
|
|
517
|
+
</label>
|
|
518
|
+
{scheduleEnabled && (
|
|
519
|
+
<>
|
|
520
|
+
<div className={css.scheduleRow}>
|
|
521
|
+
<input
|
|
522
|
+
className={`${css.input} ${css.scheduleInput}${scheduleError !== undefined ? ` ${css.scheduleInputInvalid}` : ''}`}
|
|
523
|
+
value={scheduleCron}
|
|
524
|
+
placeholder="0 9 * * *"
|
|
525
|
+
spellCheck={false}
|
|
526
|
+
aria-label={t('detail.schedule.cron')}
|
|
527
|
+
onChange={event => { setScheduleCron(event.target.value); setScheduleError(undefined) }}
|
|
528
|
+
/>
|
|
529
|
+
<select
|
|
530
|
+
className={css.schedulePreset}
|
|
531
|
+
value=""
|
|
532
|
+
aria-label={t('detail.schedule.presets')}
|
|
533
|
+
onChange={event => {
|
|
534
|
+
if (event.target.value === '') return
|
|
535
|
+
setScheduleCron(event.target.value)
|
|
536
|
+
setScheduleError(undefined)
|
|
537
|
+
}}
|
|
538
|
+
>
|
|
539
|
+
<option value="">{t('detail.schedule.presets')}…</option>
|
|
540
|
+
{SCHEDULE_PRESETS.map(preset => (
|
|
541
|
+
<option key={preset.cron} value={preset.cron}>{t(preset.label)}</option>
|
|
542
|
+
))}
|
|
543
|
+
</select>
|
|
544
|
+
</div>
|
|
545
|
+
{scheduleError !== undefined && <p className={css.formError}>{scheduleError}</p>}
|
|
546
|
+
<label className={css.scheduleZone}>
|
|
547
|
+
<span>{t('detail.schedule.timeZone')}</span>
|
|
548
|
+
<select
|
|
549
|
+
className={css.schedulePreset}
|
|
550
|
+
value={scheduleZone}
|
|
551
|
+
aria-label={t('detail.schedule.timeZone')}
|
|
552
|
+
title={t('detail.schedule.timeZoneHint')}
|
|
553
|
+
onChange={event => { setScheduleZone(event.target.value); setScheduleError(undefined) }}
|
|
554
|
+
>
|
|
555
|
+
{zoneChoices(hostTimeZone, initialTask?.schedule?.timeZone).map(choice => (
|
|
556
|
+
<option key={choice.id === '' ? '__host' : choice.id} value={choice.id}>{choice.label}</option>
|
|
557
|
+
))}
|
|
558
|
+
</select>
|
|
559
|
+
</label>
|
|
560
|
+
{scheduleError === undefined && scheduleNextRun !== undefined && (
|
|
561
|
+
<p className={css.scheduleMeta}>
|
|
562
|
+
{t('detail.schedule.nextRun')}{' '}
|
|
563
|
+
{nextRunLabel(scheduleNextRun, scheduleTimeZone, formatHostTimestamp, Date.now())}
|
|
564
|
+
</p>
|
|
565
|
+
)}
|
|
566
|
+
</>
|
|
447
567
|
)}
|
|
568
|
+
</CollapsibleSection>
|
|
448
569
|
</ModalShell>
|
|
449
570
|
)
|
|
450
571
|
}
|