@linxin666/dsh-client-ui-task-board 0.4.5 → 0.4.7
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 +34 -18
- package/README.zh.md +34 -18
- package/lib/client.js +2804 -1024
- package/lib/client.js.map +1 -1
- package/lib/index.js +2576 -416
- package/lib/types/client/CardDefaultPresetField.d.ts +35 -0
- package/lib/types/client/TaskBoardSettingsCard.d.ts +61 -0
- package/lib/types/client/board/ReasoningEffortField.d.ts +25 -0
- package/lib/types/client/board/model-options.d.ts +67 -0
- package/lib/types/client/board/preset-label.d.ts +21 -0
- package/lib/types/client/board/preset-roster.d.ts +45 -0
- package/lib/types/client/board/team-run-promotion.d.ts +26 -0
- package/lib/types/client/index.d.ts +4 -5
- package/lib/types/client/locales.d.ts +81 -0
- package/lib/types/client/plugin-card-seat.d.ts +5 -0
- package/lib/types/client/schedule-zone.d.ts +50 -0
- package/lib/types/core/card-defaults.d.ts +78 -0
- package/lib/types/core/controller.d.ts +41 -17
- package/lib/types/core/launch-width.d.ts +31 -0
- package/lib/types/core/poll-cadence.d.ts +31 -0
- package/lib/types/core/reasoning-effort.d.ts +57 -0
- package/lib/types/core/subtask.d.ts +30 -8
- package/lib/types/core/tasks.d.ts +163 -14
- package/lib/types/core/use-cases/task-create.d.ts +10 -5
- package/lib/types/core/use-cases/task-schedule.d.ts +68 -10
- package/lib/types/core/use-cases/task-update.d.ts +1 -1
- package/lib/types/core/verification-budget.d.ts +52 -0
- package/lib/types/core/verification.d.ts +376 -9
- package/lib/types/host/launch-admission.d.ts +72 -0
- package/lib/types/host/verification-gate.d.ts +24 -0
- package/lib/types/host/verification-runner.d.ts +38 -7
- package/lib/types/host-ledger.d.ts +91 -11
- package/lib/types/host-runner.d.ts +111 -13
- package/lib/types/host-service.d.ts +199 -8
- package/lib/types/index.d.ts +141 -0
- package/lib/types/protocol.d.ts +63 -12
- package/package.json +29 -33
- package/src/client/CardDefaultPresetField.tsx +101 -0
- package/src/client/TaskBoardSettingsCard.tsx +288 -17
- package/src/client/board/LinkSubtaskModal.tsx +8 -0
- package/src/client/board/NewTaskModal.tsx +237 -60
- package/src/client/board/ReasoningEffortField.tsx +50 -0
- package/src/client/board/TaskCard.tsx +3 -3
- package/src/client/board/TaskDetail.tsx +261 -24
- package/src/client/board/VerificationReport.tsx +132 -13
- package/src/client/board/model-options.ts +178 -0
- package/src/client/board/preset-label.ts +32 -0
- package/src/client/board/preset-roster.ts +58 -0
- package/src/client/board/status-key.ts +3 -0
- package/src/client/board/team-run-promotion.ts +39 -0
- package/src/client/index.ts +105 -108
- package/src/client/locales.ts +171 -6
- package/src/client/plugin-card-seat.ts +7 -0
- package/src/client/schedule-zone.ts +100 -2
- package/src/client/settings-entry-form.ts +0 -1
- package/src/core/card-defaults.ts +132 -0
- package/src/core/controller.ts +55 -16
- package/src/core/launch-width.ts +38 -0
- package/src/core/poll-cadence.ts +41 -0
- package/src/core/reasoning-effort.ts +92 -0
- package/src/core/store.ts +69 -15
- package/src/core/subtask.ts +48 -13
- package/src/core/tasks.ts +213 -39
- package/src/core/use-cases/task-create.ts +57 -25
- package/src/core/use-cases/task-schedule.ts +190 -25
- package/src/core/use-cases/task-update.ts +9 -4
- package/src/core/verification-budget.ts +115 -0
- package/src/core/verification.ts +785 -17
- package/src/host/agent-tools.ts +127 -29
- package/src/host/launch-admission.ts +140 -0
- package/src/host/verification-gate.ts +172 -16
- package/src/host/verification-runner.ts +207 -40
- package/src/host-ledger.ts +346 -48
- package/src/host-runner.ts +234 -35
- package/src/host-service.ts +539 -52
- package/src/index.ts +250 -7
- package/src/protocol.ts +160 -32
- package/lib/invariant.js +0 -6
- package/lib/types/client/board-card-seat.d.ts +0 -34
- package/lib/types/invariant.d.ts +0 -3
- package/src/client/board-card-seat.ts +0 -102
- package/src/invariant.ts +0 -4
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.7",
|
|
5
5
|
"icon": "icon.svg",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "lib/index.js",
|
|
@@ -11,10 +11,6 @@
|
|
|
11
11
|
"types": "./lib/types/index.d.ts",
|
|
12
12
|
"default": "./lib/index.js"
|
|
13
13
|
},
|
|
14
|
-
"./invariant": {
|
|
15
|
-
"types": "./lib/types/invariant.d.ts",
|
|
16
|
-
"default": "./lib/invariant.js"
|
|
17
|
-
},
|
|
18
14
|
"./client": {
|
|
19
15
|
"types": "./lib/types/client/index.d.ts",
|
|
20
16
|
"default": "./lib/client.js"
|
|
@@ -24,7 +20,7 @@
|
|
|
24
20
|
},
|
|
25
21
|
"dsh": {
|
|
26
22
|
"engines": {
|
|
27
|
-
"dsh": ">=0.2.
|
|
23
|
+
"dsh": ">=0.2.1-rc.2"
|
|
28
24
|
},
|
|
29
25
|
"bundle": {
|
|
30
26
|
"patch": "./cordis.patch.yml"
|
|
@@ -44,37 +40,37 @@
|
|
|
44
40
|
}
|
|
45
41
|
},
|
|
46
42
|
"peerDependencies": {
|
|
47
|
-
"@deepseek-ai/dsh": ">=0.2.
|
|
43
|
+
"@deepseek-ai/dsh": ">=0.2.1-rc.2",
|
|
48
44
|
"react": "^18.2.0"
|
|
49
45
|
},
|
|
50
46
|
"dependencies": {
|
|
51
|
-
"@deepseek-ai/schemastery": "^3.18.
|
|
47
|
+
"@deepseek-ai/schemastery": "^3.18.5",
|
|
52
48
|
"marked": "^18.0.14"
|
|
53
49
|
},
|
|
54
50
|
"devDependencies": {
|
|
55
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
56
|
-
"@deepseek-ai/dsh-agent": "^0.2.
|
|
57
|
-
"@deepseek-ai/dsh-agent-preset-registry": "^0.2.
|
|
58
|
-
"@deepseek-ai/dsh-api-gateway": "^0.2.
|
|
59
|
-
"@deepseek-ai/dsh-api-remotes": "^0.2.
|
|
60
|
-
"@deepseek-ai/dsh-api-session-controller": "^0.2.
|
|
61
|
-
"@deepseek-ai/dsh-api-workspace-controller": "^0.2.
|
|
62
|
-
"@deepseek-ai/dsh-client-connection": "^0.2.
|
|
63
|
-
"@deepseek-ai/dsh-client-locale": "^0.2.
|
|
64
|
-
"@deepseek-ai/dsh-client-store": "^0.2.
|
|
65
|
-
"@deepseek-ai/dsh-client-ui-layout": "^0.2.
|
|
66
|
-
"@deepseek-ai/dsh-client-ui-renderer": "^0.2.
|
|
67
|
-
"@deepseek-ai/dsh-client-ui-settings": "^0.2.
|
|
68
|
-
"@deepseek-ai/dsh-client-ui-sidebar": "^0.2.
|
|
69
|
-
"@deepseek-ai/dsh-client-ui-slots": "^0.2.
|
|
70
|
-
"@deepseek-ai/dsh-client-ui-workspace": "^0.2.
|
|
71
|
-
"@deepseek-ai/dsh-commands": "^0.2.
|
|
72
|
-
"@deepseek-ai/dsh-host-webserver": "^0.2.
|
|
73
|
-
"@deepseek-ai/dsh-llm": "^0.2.
|
|
74
|
-
"@deepseek-ai/dsh-settings": "^0.2.
|
|
75
|
-
"@deepseek-ai/dsh-system-prompt": "^0.2.
|
|
76
|
-
"@deepseek-ai/dsh-tools": "^0.2.
|
|
77
|
-
"@deepseek-ai/dsh-workspace": "^0.2.
|
|
51
|
+
"@deepseek-ai/cordis": "^4.0.5",
|
|
52
|
+
"@deepseek-ai/dsh-agent": "^0.2.1-rc.2",
|
|
53
|
+
"@deepseek-ai/dsh-agent-preset-registry": "^0.2.1-rc.2",
|
|
54
|
+
"@deepseek-ai/dsh-api-gateway": "^0.2.1-rc.2",
|
|
55
|
+
"@deepseek-ai/dsh-api-remotes": "^0.2.1-rc.2",
|
|
56
|
+
"@deepseek-ai/dsh-api-session-controller": "^0.2.1-rc.2",
|
|
57
|
+
"@deepseek-ai/dsh-api-workspace-controller": "^0.2.1-rc.2",
|
|
58
|
+
"@deepseek-ai/dsh-client-connection": "^0.2.1-rc.2",
|
|
59
|
+
"@deepseek-ai/dsh-client-locale": "^0.2.1-rc.2",
|
|
60
|
+
"@deepseek-ai/dsh-client-store": "^0.2.1-rc.2",
|
|
61
|
+
"@deepseek-ai/dsh-client-ui-layout": "^0.2.1-rc.2",
|
|
62
|
+
"@deepseek-ai/dsh-client-ui-renderer": "^0.2.1-rc.2",
|
|
63
|
+
"@deepseek-ai/dsh-client-ui-settings": "^0.2.1-rc.2",
|
|
64
|
+
"@deepseek-ai/dsh-client-ui-sidebar": "^0.2.1-rc.2",
|
|
65
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.2.1-rc.2",
|
|
66
|
+
"@deepseek-ai/dsh-client-ui-workspace": "^0.2.1-rc.2",
|
|
67
|
+
"@deepseek-ai/dsh-commands": "^0.2.1-rc.2",
|
|
68
|
+
"@deepseek-ai/dsh-host-webserver": "^0.2.1-rc.2",
|
|
69
|
+
"@deepseek-ai/dsh-llm": "^0.2.1-rc.2",
|
|
70
|
+
"@deepseek-ai/dsh-settings": "^0.2.1-rc.2",
|
|
71
|
+
"@deepseek-ai/dsh-system-prompt": "^0.2.1-rc.2",
|
|
72
|
+
"@deepseek-ai/dsh-tools": "^0.2.1-rc.2",
|
|
73
|
+
"@deepseek-ai/dsh-workspace": "^0.2.1-rc.2",
|
|
78
74
|
"@types/node": "^22.20.0",
|
|
79
75
|
"@types/react": "~18.3.1",
|
|
80
76
|
"@types/react-dom": "^18.3.5",
|
|
@@ -101,10 +97,10 @@
|
|
|
101
97
|
"url": "https://github.com/zhu1090093659/dsh-web.git"
|
|
102
98
|
},
|
|
103
99
|
"scripts": {
|
|
104
|
-
"build": "tsc -
|
|
100
|
+
"build": "tsc -b tsconfig.build.json && tsdown",
|
|
105
101
|
"bundle": "tsdown",
|
|
106
102
|
"watch": "tsdown --watch",
|
|
107
|
-
"typecheck": "
|
|
103
|
+
"typecheck": "tsgo --noEmit",
|
|
108
104
|
"test": "vitest run",
|
|
109
105
|
"test:watch": "vitest"
|
|
110
106
|
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The default-agent-preset picker of the settings card's new-card-defaults
|
|
3
|
+
* topic.
|
|
4
|
+
*
|
|
5
|
+
* It renders the rows the create dialog's own mode picker renders — built-in
|
|
6
|
+
* presets first, then user presets, each labelled with its default and
|
|
7
|
+
* unavailable suffixes (board/preset-label.ts owns both) — and it renders a
|
|
8
|
+
* preset that cannot compose a session as a row that is visible but NOT
|
|
9
|
+
* selectable, so the board never stores a default no session could be composed
|
|
10
|
+
* from.
|
|
11
|
+
*
|
|
12
|
+
* The shared ChoiceField cannot express that: its choices carry no disabled
|
|
13
|
+
* state, and a stored pin the roster no longer serves would render as an empty
|
|
14
|
+
* trigger rather than as the unavailable preset it is. Both need a native
|
|
15
|
+
* `<select>` (the fallback the shared SelectField itself takes under an
|
|
16
|
+
* appearance skin), styled with the settings card's own field classes so the
|
|
17
|
+
* control stays a sibling of the fields beside it.
|
|
18
|
+
*/
|
|
19
|
+
import type { ExecutionPresetOption } from '../core/controller.ts'
|
|
20
|
+
import { inheritPresetLabel, presetChoiceRows } from './board/preset-label.ts'
|
|
21
|
+
import { t } from './locales.ts'
|
|
22
|
+
import type { FieldProps } from './PluginSettingsCard.tsx'
|
|
23
|
+
import css from './settings-card.module.css'
|
|
24
|
+
|
|
25
|
+
/** Props of the default-preset field: the shared field contract plus the roster. */
|
|
26
|
+
export interface CardDefaultPresetFieldProps extends FieldProps {
|
|
27
|
+
/**
|
|
28
|
+
* The deployment's agent-preset roster, or undefined while its runtime read
|
|
29
|
+
* has not answered: an unread roster offers the inherit choice alone instead
|
|
30
|
+
* of calling every stored pin "removed".
|
|
31
|
+
*/
|
|
32
|
+
presets: readonly ExecutionPresetOption[] | undefined
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Render the default-preset select.
|
|
37
|
+
* @param props - the field contract and the roster to list.
|
|
38
|
+
* @returns the field.
|
|
39
|
+
*/
|
|
40
|
+
export function CardDefaultPresetField(props: CardDefaultPresetFieldProps) {
|
|
41
|
+
const presets = props.presets
|
|
42
|
+
const rows = presetChoiceRows(presets ?? [])
|
|
43
|
+
// A stored pin the roster no longer serves stays visible and pickable: it is
|
|
44
|
+
// what the board currently holds, so the control must show it rather than
|
|
45
|
+
// silently reading as the inherit choice.
|
|
46
|
+
const unlisted = props.text !== '' && presets !== undefined && !presets.some(preset => preset.id === props.text)
|
|
47
|
+
return (
|
|
48
|
+
<div className={css.field}>
|
|
49
|
+
<div className={css.head}>
|
|
50
|
+
<label className={css.label} htmlFor={props.id}>{props.label}</label>
|
|
51
|
+
{props.overridden
|
|
52
|
+
? (
|
|
53
|
+
<span className={css.badges}>
|
|
54
|
+
<span className={css.badge}>{props.overriddenLabel}</span>
|
|
55
|
+
<button
|
|
56
|
+
type="button"
|
|
57
|
+
className={css.reset}
|
|
58
|
+
disabled={props.disabled}
|
|
59
|
+
onClick={props.onReset}
|
|
60
|
+
>
|
|
61
|
+
{props.resetLabel}
|
|
62
|
+
</button>
|
|
63
|
+
</span>
|
|
64
|
+
)
|
|
65
|
+
: null}
|
|
66
|
+
</div>
|
|
67
|
+
<select
|
|
68
|
+
id={props.id}
|
|
69
|
+
className={css.select}
|
|
70
|
+
value={props.text}
|
|
71
|
+
disabled={props.disabled}
|
|
72
|
+
aria-invalid={props.invalid || undefined}
|
|
73
|
+
onChange={(event) => { props.onEdit(event.target.value) }}
|
|
74
|
+
>
|
|
75
|
+
<option value="">{inheritPresetLabel(presets ?? [])}</option>
|
|
76
|
+
{unlisted ? <option value={props.text}>{props.text}{t('exec.mode.removed')}</option> : null}
|
|
77
|
+
{rows.builtin.length > 0
|
|
78
|
+
? (
|
|
79
|
+
<optgroup label={t('exec.mode.builtinGroup')}>
|
|
80
|
+
{rows.builtin.map(row => (
|
|
81
|
+
<option key={row.id} value={row.id} disabled={row.disabled}>{row.label}</option>
|
|
82
|
+
))}
|
|
83
|
+
</optgroup>
|
|
84
|
+
)
|
|
85
|
+
: null}
|
|
86
|
+
{rows.custom.length > 0
|
|
87
|
+
? (
|
|
88
|
+
<optgroup label={t('exec.mode.customGroup')}>
|
|
89
|
+
{rows.custom.map(row => (
|
|
90
|
+
<option key={row.id} value={row.id} disabled={row.disabled}>{row.label}</option>
|
|
91
|
+
))}
|
|
92
|
+
</optgroup>
|
|
93
|
+
)
|
|
94
|
+
: null}
|
|
95
|
+
</select>
|
|
96
|
+
<p className={props.invalid ? css.invalid : css.hint}>
|
|
97
|
+
{props.invalid ? props.invalidLabel : props.hint}
|
|
98
|
+
</p>
|
|
99
|
+
</div>
|
|
100
|
+
)
|
|
101
|
+
}
|
|
@@ -10,11 +10,34 @@ import type { ConfigForm } from '@deepseek-ai/dsh-client-ui-settings/client'
|
|
|
10
10
|
import { useEffect, useState } from 'react'
|
|
11
11
|
import type { TaskBoardPowerSnapshot, TaskBoardVerificationOptions } from '../protocol.ts'
|
|
12
12
|
import type { TaskBoardExtensionDispatch } from '../core/extension.ts'
|
|
13
|
+
import type { ExecutionPresetOption } from '../core/controller.ts'
|
|
14
|
+
import { TASK_PERMISSIONS } from '../core/tasks.ts'
|
|
13
15
|
import { resolveContract, type ModelCatalogView, type VerificationSettings } from '../core/verification.ts'
|
|
14
16
|
import { parseModelRoute } from '../core/verification.ts'
|
|
15
|
-
import {
|
|
17
|
+
import { CardDefaultPresetField } from './CardDefaultPresetField.tsx'
|
|
18
|
+
import { PluginSettingsCard, BooleanField, ChoiceField, ValueField } from './PluginSettingsCard.tsx'
|
|
19
|
+
import {
|
|
20
|
+
modelChoiceRows,
|
|
21
|
+
modelOptionsFromCatalog,
|
|
22
|
+
reasoningEffortsFor,
|
|
23
|
+
reasoningHintKey,
|
|
24
|
+
reasoningPickerEnabled,
|
|
25
|
+
reconcileReasoningEffort,
|
|
26
|
+
} from './board/model-options.ts'
|
|
16
27
|
import { SUBTASK_DEPTH_MAX, SUBTASK_DEPTH_MIN } from '../core/subtask.ts'
|
|
17
|
-
import {
|
|
28
|
+
import { SESSION_POLL_MAX_SECONDS, SESSION_POLL_MIN_SECONDS } from '../core/poll-cadence.ts'
|
|
29
|
+
import { RUN_LAUNCH_WIDTH_MAX, RUN_LAUNCH_WIDTH_MIN } from '../core/launch-width.ts'
|
|
30
|
+
import {
|
|
31
|
+
CardForm,
|
|
32
|
+
booleanField,
|
|
33
|
+
choiceField,
|
|
34
|
+
textField,
|
|
35
|
+
type CardActions,
|
|
36
|
+
type CardShell,
|
|
37
|
+
type FieldSpec,
|
|
38
|
+
type FieldState as CardFieldState,
|
|
39
|
+
} from './settings-form.ts'
|
|
40
|
+
import type { TaskBoardKey } from './locales.ts'
|
|
18
41
|
import settingsCss from './board-settings.module.css'
|
|
19
42
|
|
|
20
43
|
/** The depth choices the card offers, derived from the supported range. */
|
|
@@ -23,6 +46,28 @@ const SUBTASK_DEPTH_CHOICES: readonly string[] = Array.from(
|
|
|
23
46
|
(_, index) => String(SUBTASK_DEPTH_MIN + index),
|
|
24
47
|
)
|
|
25
48
|
|
|
49
|
+
/**
|
|
50
|
+
* The roster-poll cadence field: a whole number of seconds inside the
|
|
51
|
+
* supported range, because the Host schema (`sessionPollSeconds`) is numeric.
|
|
52
|
+
* A draft outside the range blocks the save instead of staging a value the
|
|
53
|
+
* Host would clamp behind the user's back.
|
|
54
|
+
*/
|
|
55
|
+
function sessionPollField(): FieldSpec {
|
|
56
|
+
return {
|
|
57
|
+
field: 'sessionPollSeconds',
|
|
58
|
+
format: value => typeof value === 'number' && Number.isInteger(value) ? String(value) : '',
|
|
59
|
+
parse: (text) => {
|
|
60
|
+
const trimmed = text.trim()
|
|
61
|
+
if (trimmed === '') return { kind: 'clear' }
|
|
62
|
+
if (!/^\d+$/.test(trimmed)) return undefined
|
|
63
|
+
const seconds = Number(trimmed)
|
|
64
|
+
return seconds >= SESSION_POLL_MIN_SECONDS && seconds <= SESSION_POLL_MAX_SECONDS
|
|
65
|
+
? { kind: 'set', value: seconds }
|
|
66
|
+
: undefined
|
|
67
|
+
},
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
26
71
|
/**
|
|
27
72
|
* The depth field: a choice among the supported levels whose draft text is a
|
|
28
73
|
* number, because the Host schema (`maxSubtaskDepth`) is numeric. A draft
|
|
@@ -42,34 +87,42 @@ function subtaskDepthField(): FieldSpec {
|
|
|
42
87
|
}
|
|
43
88
|
|
|
44
89
|
/**
|
|
45
|
-
* The
|
|
46
|
-
*
|
|
47
|
-
* draft blocks the save instead of
|
|
90
|
+
* The run-launch width field: a whole number of concurrent launches inside the
|
|
91
|
+
* supported range, because the Host schema (`runLaunchWidth`) is numeric. A
|
|
92
|
+
* draft outside the range blocks the save instead of staging a value the Host
|
|
93
|
+
* would clamp behind the user's back.
|
|
48
94
|
*/
|
|
49
|
-
function
|
|
95
|
+
function runLaunchWidthField(): FieldSpec {
|
|
50
96
|
return {
|
|
51
|
-
field: '
|
|
52
|
-
format: value => typeof value === '
|
|
97
|
+
field: 'runLaunchWidth',
|
|
98
|
+
format: value => typeof value === 'number' && Number.isInteger(value) ? String(value) : '',
|
|
53
99
|
parse: (text) => {
|
|
54
100
|
const trimmed = text.trim()
|
|
55
101
|
if (trimmed === '') return { kind: 'clear' }
|
|
56
|
-
|
|
102
|
+
if (!/^\d+$/.test(trimmed)) return undefined
|
|
103
|
+
const width = Number(trimmed)
|
|
104
|
+
return width >= RUN_LAUNCH_WIDTH_MIN && width <= RUN_LAUNCH_WIDTH_MAX
|
|
105
|
+
? { kind: 'set', value: width }
|
|
106
|
+
: undefined
|
|
57
107
|
},
|
|
58
108
|
}
|
|
59
109
|
}
|
|
60
110
|
|
|
61
111
|
/**
|
|
62
|
-
* The
|
|
63
|
-
*
|
|
64
|
-
*
|
|
112
|
+
* The judge-model field: blank inherits the host model catalog default, and a
|
|
113
|
+
* non-blank draft must be a qualified provider/model route. An unparseable
|
|
114
|
+
* draft blocks the save instead of storing a route nothing can resolve.
|
|
115
|
+
* The new-card default model uses the same gate, so both fields accept exactly
|
|
116
|
+
* the routes an execution can pin.
|
|
65
117
|
*/
|
|
66
|
-
function
|
|
118
|
+
function modelRouteField(field: string): FieldSpec {
|
|
67
119
|
return {
|
|
68
|
-
field
|
|
120
|
+
field,
|
|
69
121
|
format: value => typeof value === 'string' ? value : '',
|
|
70
122
|
parse: (text) => {
|
|
71
123
|
const trimmed = text.trim()
|
|
72
|
-
|
|
124
|
+
if (trimmed === '') return { kind: 'clear' }
|
|
125
|
+
return parseModelRoute(trimmed) === undefined ? undefined : { kind: 'set', value: trimmed }
|
|
73
126
|
},
|
|
74
127
|
}
|
|
75
128
|
}
|
|
@@ -84,12 +137,40 @@ export interface TaskBoardSettings {
|
|
|
84
137
|
preventIdleSleep?: boolean
|
|
85
138
|
/** Subtask depth limit (1..3); 1 means a single level of subtasks. */
|
|
86
139
|
maxSubtaskDepth?: number
|
|
140
|
+
/** Roster-poll cadence in seconds while the board has work to reconcile. */
|
|
141
|
+
sessionPollSeconds?: number
|
|
142
|
+
/**
|
|
143
|
+
* How many run launches may be in flight at once (1..16, default 3). One run
|
|
144
|
+
* opens its whole subtree, so this bounds the burst of sessions a single run
|
|
145
|
+
* action creates; members enter the running column in batches, none is dropped.
|
|
146
|
+
*/
|
|
147
|
+
runLaunchWidth?: number
|
|
148
|
+
/**
|
|
149
|
+
* GLOBAL native /goal switch (default off): the master control over whether
|
|
150
|
+
* this board starts a run with dsh's built-in /goal at all. Frozen per
|
|
151
|
+
* execution.
|
|
152
|
+
*/
|
|
153
|
+
goalRunEnabled?: boolean
|
|
87
154
|
/** Goal acceptance for this board's executions (default on). */
|
|
88
155
|
goalVerification?: boolean
|
|
89
156
|
/** Judge model route for goal acceptance; blank inherits the host default. */
|
|
90
157
|
goalVerificationModel?: string
|
|
91
158
|
/** Judge reasoning effort; blank inherits the host default level. */
|
|
92
159
|
goalVerificationReasoningEffort?: string
|
|
160
|
+
/**
|
|
161
|
+
* Agent preset the create dialog of this board opens with. Blank pins
|
|
162
|
+
* nothing, so a new card follows the deployment default as before.
|
|
163
|
+
*/
|
|
164
|
+
cardDefaultMode?: string
|
|
165
|
+
/** Permission the create dialog opens with; blank applies no default. */
|
|
166
|
+
cardDefaultPermission?: string
|
|
167
|
+
/** Execution model route the create dialog opens with; blank applies none. */
|
|
168
|
+
cardDefaultModel?: string
|
|
169
|
+
/**
|
|
170
|
+
* Reasoning level bound to `cardDefaultModel`; it applies only while a
|
|
171
|
+
* card's final model IS that configured route, and is inert without one.
|
|
172
|
+
*/
|
|
173
|
+
cardDefaultReasoningEffort?: string
|
|
93
174
|
}
|
|
94
175
|
|
|
95
176
|
/** What the task-board card renders. */
|
|
@@ -102,12 +183,32 @@ export interface TaskBoardSettingsCardState extends CardShell {
|
|
|
102
183
|
preventIdleSleep: CardFieldState
|
|
103
184
|
/** Subtask depth limit field. */
|
|
104
185
|
maxSubtaskDepth: CardFieldState
|
|
186
|
+
/** Roster-poll cadence field. */
|
|
187
|
+
sessionPollSeconds: CardFieldState
|
|
188
|
+
/** Run-launch admission width field. */
|
|
189
|
+
runLaunchWidth: CardFieldState
|
|
190
|
+
/** GLOBAL native /goal switch. */
|
|
191
|
+
goalRunEnabled: CardFieldState
|
|
105
192
|
/** Goal-acceptance switch. */
|
|
106
193
|
goalVerification: CardFieldState
|
|
107
194
|
/** Judge model route field. */
|
|
108
195
|
goalVerificationModel: CardFieldState
|
|
109
196
|
/** Judge reasoning-effort field. */
|
|
110
197
|
goalVerificationReasoningEffort: CardFieldState
|
|
198
|
+
/** New-card default agent preset. */
|
|
199
|
+
cardDefaultMode: CardFieldState
|
|
200
|
+
/** New-card default permission. */
|
|
201
|
+
cardDefaultPermission: CardFieldState
|
|
202
|
+
/** New-card default model route. */
|
|
203
|
+
cardDefaultModel: CardFieldState
|
|
204
|
+
/** New-card default reasoning level. */
|
|
205
|
+
cardDefaultReasoningEffort: CardFieldState
|
|
206
|
+
/**
|
|
207
|
+
* The deployment's agent-preset roster, published by the runtime read the
|
|
208
|
+
* plugin wires; undefined until it answers, which leaves the default-preset
|
|
209
|
+
* field offering the inherit choice alone.
|
|
210
|
+
*/
|
|
211
|
+
presets?: readonly ExecutionPresetOption[]
|
|
111
212
|
}
|
|
112
213
|
|
|
113
214
|
/** The registration-side face the card's slot entry injects. */
|
|
@@ -124,6 +225,8 @@ export interface TaskBoardSettingsCardFace extends CardActions {
|
|
|
124
225
|
export class TaskBoardSettingsCardController {
|
|
125
226
|
private readonly form: CardForm<TaskBoardSettings>
|
|
126
227
|
private readonly store: SnapshotStore<TaskBoardSettingsCardState>
|
|
228
|
+
/** The deployment's agent-preset roster, once its runtime read has answered. */
|
|
229
|
+
private presets: readonly ExecutionPresetOption[] | undefined
|
|
127
230
|
|
|
128
231
|
/** @param scope - the bound configuration form for the `task-board` namespace. */
|
|
129
232
|
constructor(scope: ConfigForm<TaskBoardSettings>, private readonly dispatch: TaskBoardExtensionDispatch) {
|
|
@@ -132,9 +235,19 @@ export class TaskBoardSettingsCardController {
|
|
|
132
235
|
booleanField('announceToAgent'),
|
|
133
236
|
booleanField('preventIdleSleep'),
|
|
134
237
|
subtaskDepthField(),
|
|
238
|
+
sessionPollField(),
|
|
239
|
+
runLaunchWidthField(),
|
|
240
|
+
booleanField('goalRunEnabled'),
|
|
135
241
|
booleanField('goalVerification'),
|
|
136
|
-
|
|
137
|
-
|
|
242
|
+
modelRouteField('goalVerificationModel'),
|
|
243
|
+
textField('goalVerificationReasoningEffort'),
|
|
244
|
+
// The new-card defaults: blank is a real value every one of them keeps
|
|
245
|
+
// ("apply no default"), which is why they are staged like any other field
|
|
246
|
+
// and only their parse rules differ.
|
|
247
|
+
textField('cardDefaultMode'),
|
|
248
|
+
choiceField('cardDefaultPermission', TASK_PERMISSIONS),
|
|
249
|
+
modelRouteField('cardDefaultModel'),
|
|
250
|
+
textField('cardDefaultReasoningEffort'),
|
|
138
251
|
])
|
|
139
252
|
this.store = this.form.bind(() => this.projection())
|
|
140
253
|
}
|
|
@@ -146,12 +259,34 @@ export class TaskBoardSettingsCardController {
|
|
|
146
259
|
announceToAgent: this.form.field('announceToAgent'),
|
|
147
260
|
preventIdleSleep: this.form.field('preventIdleSleep'),
|
|
148
261
|
maxSubtaskDepth: this.form.field('maxSubtaskDepth'),
|
|
262
|
+
sessionPollSeconds: this.form.field('sessionPollSeconds'),
|
|
263
|
+
runLaunchWidth: this.form.field('runLaunchWidth'),
|
|
264
|
+
goalRunEnabled: this.form.field('goalRunEnabled'),
|
|
149
265
|
goalVerification: this.form.field('goalVerification'),
|
|
150
266
|
goalVerificationModel: this.form.field('goalVerificationModel'),
|
|
151
267
|
goalVerificationReasoningEffort: this.form.field('goalVerificationReasoningEffort'),
|
|
268
|
+
cardDefaultMode: this.form.field('cardDefaultMode'),
|
|
269
|
+
cardDefaultPermission: this.form.field('cardDefaultPermission'),
|
|
270
|
+
cardDefaultModel: this.form.field('cardDefaultModel'),
|
|
271
|
+
cardDefaultReasoningEffort: this.form.field('cardDefaultReasoningEffort'),
|
|
272
|
+
...(this.presets === undefined ? {} : { presets: this.presets }),
|
|
152
273
|
}
|
|
153
274
|
}
|
|
154
275
|
|
|
276
|
+
/**
|
|
277
|
+
* Publish the deployment's agent-preset roster to the default-preset field.
|
|
278
|
+
* The roster is a runtime fact read outside this card (src/client/index.ts
|
|
279
|
+
* wires it once for both the board's pickers and this topic); it can arrive
|
|
280
|
+
* late or not at all, and until it does the field lists the inherit choice
|
|
281
|
+
* alone. This never blocks a save: a deployment that serves no roster still
|
|
282
|
+
* has a writable settings document.
|
|
283
|
+
* @param presets - the roster rows.
|
|
284
|
+
*/
|
|
285
|
+
setPresetRoster(presets: readonly ExecutionPresetOption[]): void {
|
|
286
|
+
this.presets = presets
|
|
287
|
+
this.store.set(this.projection())
|
|
288
|
+
}
|
|
289
|
+
|
|
155
290
|
/**
|
|
156
291
|
* Build the face the card's slot registration injects.
|
|
157
292
|
* @returns the card's snapshot and its form actions.
|
|
@@ -265,6 +400,25 @@ export function TaskBoardSettingsCard(props: TaskBoardSettingsCardProps) {
|
|
|
265
400
|
invalidLabel: t('settings.invalidNumber'),
|
|
266
401
|
disabled,
|
|
267
402
|
}
|
|
403
|
+
// The new-card-defaults topic reads the SAME catalog the acceptance fields
|
|
404
|
+
// and the create dialog read (session/modelCatalog, served here through the
|
|
405
|
+
// verification payload), normalized once into the roster the reasoning
|
|
406
|
+
// helpers below consume, so the default level can only offer levels the
|
|
407
|
+
// chosen default model really declares.
|
|
408
|
+
const defaultModels = modelOptionsFromCatalog(catalog)
|
|
409
|
+
const defaultModel = state.cardDefaultModel.text
|
|
410
|
+
const defaultEffortChoices = reasoningEffortsFor(defaultModels, defaultModel)
|
|
411
|
+
.map(effort => ({ value: effort, label: effort }))
|
|
412
|
+
/**
|
|
413
|
+
* Drop a staged default level the newly picked default model cannot honour,
|
|
414
|
+
* exactly as the create dialog's own model picker does (one shared rule):
|
|
415
|
+
* the level belongs to the route it was chosen for.
|
|
416
|
+
*/
|
|
417
|
+
const dropUnhonouredEffort = (nextModel: string): void => {
|
|
418
|
+
const current = state.cardDefaultReasoningEffort.text
|
|
419
|
+
const kept = reconcileReasoningEffort(defaultModels, nextModel, current)
|
|
420
|
+
if (kept !== current) props.edit('cardDefaultReasoningEffort', kept)
|
|
421
|
+
}
|
|
268
422
|
// Chrome state of the nested disclosure cards below: they stage into this
|
|
269
423
|
// card's form and are written by its one save, so they carry no footer and no
|
|
270
424
|
// unsaved pill of their own (the outer header owns the pill).
|
|
@@ -342,6 +496,16 @@ export function TaskBoardSettingsCard(props: TaskBoardSettingsCardProps) {
|
|
|
342
496
|
onEdit={(text) => { props.edit('preventIdleSleep', text) }}
|
|
343
497
|
onReset={() => { props.resetField('preventIdleSleep') }}
|
|
344
498
|
/>
|
|
499
|
+
<ValueField
|
|
500
|
+
id="settings-task-board-session-poll"
|
|
501
|
+
label={t('settings.sessionPollSeconds')}
|
|
502
|
+
hint={t('settings.sessionPollSecondsHint')}
|
|
503
|
+
numeric
|
|
504
|
+
{...fieldProps}
|
|
505
|
+
{...state.sessionPollSeconds}
|
|
506
|
+
onEdit={(text) => { props.edit('sessionPollSeconds', text) }}
|
|
507
|
+
onReset={() => { props.resetField('sessionPollSeconds') }}
|
|
508
|
+
/>
|
|
345
509
|
<ChoiceField
|
|
346
510
|
id="settings-task-board-subtask-depth"
|
|
347
511
|
label={t('settings.maxSubtaskDepth')}
|
|
@@ -356,6 +520,40 @@ export function TaskBoardSettingsCard(props: TaskBoardSettingsCardProps) {
|
|
|
356
520
|
onEdit={(text) => { props.edit('maxSubtaskDepth', text) }}
|
|
357
521
|
onReset={() => { props.resetField('maxSubtaskDepth') }}
|
|
358
522
|
/>
|
|
523
|
+
<ValueField
|
|
524
|
+
id="settings-task-board-run-launch-width"
|
|
525
|
+
label={t('settings.runLaunchWidth')}
|
|
526
|
+
hint={t('settings.runLaunchWidthHint')}
|
|
527
|
+
numeric
|
|
528
|
+
{...fieldProps}
|
|
529
|
+
{...state.runLaunchWidth}
|
|
530
|
+
onEdit={(text) => { props.edit('runLaunchWidth', text) }}
|
|
531
|
+
onReset={() => { props.resetField('runLaunchWidth') }}
|
|
532
|
+
/>
|
|
533
|
+
</PluginSettingsCard>
|
|
534
|
+
|
|
535
|
+
<PluginSettingsCard
|
|
536
|
+
t={t}
|
|
537
|
+
titleKey="settings.runBehaviorTitle"
|
|
538
|
+
descriptionKey="settings.runBehaviorCardHint"
|
|
539
|
+
defaultOpen={false}
|
|
540
|
+
hideFooter
|
|
541
|
+
state={nestedShell}
|
|
542
|
+
onSave={props.save}
|
|
543
|
+
onDiscard={props.discard}
|
|
544
|
+
>
|
|
545
|
+
<BooleanField
|
|
546
|
+
id="settings-task-board-goal-run"
|
|
547
|
+
label={t('settings.goalRunGlobal')}
|
|
548
|
+
hint={t('settings.goalRunGlobalHint')}
|
|
549
|
+
inheritLabel={t('settings.inherit')}
|
|
550
|
+
onLabel={t('settings.on')}
|
|
551
|
+
offLabel={t('settings.off')}
|
|
552
|
+
{...fieldProps}
|
|
553
|
+
{...state.goalRunEnabled}
|
|
554
|
+
onEdit={(text) => { props.edit('goalRunEnabled', text) }}
|
|
555
|
+
onReset={() => { props.resetField('goalRunEnabled') }}
|
|
556
|
+
/>
|
|
359
557
|
</PluginSettingsCard>
|
|
360
558
|
|
|
361
559
|
<PluginSettingsCard
|
|
@@ -438,6 +636,79 @@ export function TaskBoardSettingsCard(props: TaskBoardSettingsCardProps) {
|
|
|
438
636
|
)}
|
|
439
637
|
</PluginSettingsCard>
|
|
440
638
|
|
|
639
|
+
{/* New-card defaults: what the create dialog opens with, and what the
|
|
640
|
+
Host create path fills an omitted target from when the caller opts
|
|
641
|
+
in. Every field's blank choice is a real value — "apply no default"
|
|
642
|
+
— so a deployment that configures none of them creates cards
|
|
643
|
+
exactly as it did before this topic existed. */}
|
|
644
|
+
<PluginSettingsCard
|
|
645
|
+
t={t}
|
|
646
|
+
titleKey="settings.cardDefaultsTitle"
|
|
647
|
+
descriptionKey="settings.cardDefaultsCardHint"
|
|
648
|
+
defaultOpen={false}
|
|
649
|
+
hideFooter
|
|
650
|
+
state={nestedShell}
|
|
651
|
+
onSave={props.save}
|
|
652
|
+
onDiscard={props.discard}
|
|
653
|
+
>
|
|
654
|
+
<CardDefaultPresetField
|
|
655
|
+
id="settings-task-board-card-default-mode"
|
|
656
|
+
label={t('new.agentPreset')}
|
|
657
|
+
hint={t('settings.cardDefaultModeHint')}
|
|
658
|
+
presets={state.presets}
|
|
659
|
+
{...fieldProps}
|
|
660
|
+
{...state.cardDefaultMode}
|
|
661
|
+
onEdit={(text) => { props.edit('cardDefaultMode', text) }}
|
|
662
|
+
onReset={() => { props.resetField('cardDefaultMode') }}
|
|
663
|
+
/>
|
|
664
|
+
<ChoiceField
|
|
665
|
+
id="settings-task-board-card-default-permission"
|
|
666
|
+
label={t('new.permission')}
|
|
667
|
+
hint={t('settings.cardDefaultPermissionHint')}
|
|
668
|
+
inheritLabel={t('exec.permission.default')}
|
|
669
|
+
choices={TASK_PERMISSIONS.map(id => ({ value: id, label: t(`exec.permission.${id}` as TaskBoardKey) }))}
|
|
670
|
+
{...fieldProps}
|
|
671
|
+
{...state.cardDefaultPermission}
|
|
672
|
+
onEdit={(text) => { props.edit('cardDefaultPermission', text) }}
|
|
673
|
+
onReset={() => { props.resetField('cardDefaultPermission') }}
|
|
674
|
+
/>
|
|
675
|
+
<ChoiceField
|
|
676
|
+
id="settings-task-board-card-default-model"
|
|
677
|
+
label={t('new.model')}
|
|
678
|
+
hint={t('settings.cardDefaultModelHint')}
|
|
679
|
+
inheritLabel={t('exec.model.default')}
|
|
680
|
+
choices={modelChoiceRows(defaultModels)}
|
|
681
|
+
{...fieldProps}
|
|
682
|
+
{...state.cardDefaultModel}
|
|
683
|
+
onEdit={(text) => {
|
|
684
|
+
props.edit('cardDefaultModel', text)
|
|
685
|
+
dropUnhonouredEffort(text)
|
|
686
|
+
}}
|
|
687
|
+
onReset={() => {
|
|
688
|
+
props.resetField('cardDefaultModel')
|
|
689
|
+
// A blank default model carries no route for a level to bind to,
|
|
690
|
+
// so the staged default level goes with it.
|
|
691
|
+
dropUnhonouredEffort('')
|
|
692
|
+
}}
|
|
693
|
+
/>
|
|
694
|
+
<ChoiceField
|
|
695
|
+
id="settings-task-board-card-default-effort"
|
|
696
|
+
label={t('exec.reasoning')}
|
|
697
|
+
hint={t(reasoningHintKey(defaultModels, defaultModel))}
|
|
698
|
+
inheritLabel={t('exec.reasoning.inherit')}
|
|
699
|
+
choices={defaultEffortChoices}
|
|
700
|
+
{...fieldProps}
|
|
701
|
+
{...state.cardDefaultReasoningEffort}
|
|
702
|
+
// The levels on offer are the declared ones of the picked default
|
|
703
|
+
// model, so without a model that declares any there is nothing to
|
|
704
|
+
// choose: the selector locks instead of offering a level no model
|
|
705
|
+
// would accept.
|
|
706
|
+
disabled={disabled || !reasoningPickerEnabled(defaultModels, defaultModel)}
|
|
707
|
+
onEdit={(text) => { props.edit('cardDefaultReasoningEffort', text) }}
|
|
708
|
+
onReset={() => { props.resetField('cardDefaultReasoningEffort') }}
|
|
709
|
+
/>
|
|
710
|
+
</PluginSettingsCard>
|
|
711
|
+
|
|
441
712
|
{/* Provider sections: a family extension contributes its own nested
|
|
442
713
|
settings card into this list through the seat this card declares —
|
|
443
714
|
the family provider extension is the one that does. Each
|
|
@@ -12,6 +12,7 @@ import { t } from '../locales.ts'
|
|
|
12
12
|
import css from '../board.module.css'
|
|
13
13
|
import { useDialog, type OverlayPhase } from './overlay.tsx'
|
|
14
14
|
import { STATUS_KEY } from './status-key.ts'
|
|
15
|
+
import { parentAwaitsTeamRunPromotion, promoteParentForTeamRun } from './team-run-promotion.ts'
|
|
15
16
|
|
|
16
17
|
/** Link-subtask overlay props. */
|
|
17
18
|
export interface LinkSubtaskModalProps {
|
|
@@ -56,6 +57,10 @@ export function LinkSubtaskModal({ controller, parent, onClose, phase = 'open' }
|
|
|
56
57
|
setPending(true)
|
|
57
58
|
setError(undefined)
|
|
58
59
|
if (await controller.setParent(taskId, parent.id)) {
|
|
60
|
+
// The link is confirmed, so a parent that just gained a subtask is opted
|
|
61
|
+
// in where Agent Teams are served; a refused opt-in never undoes a link
|
|
62
|
+
// the Host already committed.
|
|
63
|
+
await promoteParentForTeamRun(controller, parent)
|
|
59
64
|
onClose()
|
|
60
65
|
return
|
|
61
66
|
}
|
|
@@ -67,6 +72,9 @@ export function LinkSubtaskModal({ controller, parent, onClose, phase = 'open' }
|
|
|
67
72
|
<div ref={dialog.attach} className={css.modal} role="dialog" aria-modal="true" aria-label={t('link.subtask.title')} tabIndex={-1}>
|
|
68
73
|
<h2 className={css.modalTitle}>{t('link.subtask.title')}</h2>
|
|
69
74
|
<p className={css.fieldHint}>{t('link.subtask.hint')}</p>
|
|
75
|
+
{parentAwaitsTeamRunPromotion(controller, parent) && (
|
|
76
|
+
<p className={css.fieldHint}>{t('exec.teamRunAutoHint')}</p>
|
|
77
|
+
)}
|
|
70
78
|
{candidates.length === 0
|
|
71
79
|
? <p className={css.detailText}>{t('link.subtask.empty')}</p>
|
|
72
80
|
: (
|