@linxin666/dsh-client-ui-task-board 0.4.6 → 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 +14 -6
- package/README.zh.md +14 -6
- package/lib/client.js +1502 -820
- package/lib/client.js.map +1 -1
- package/lib/index.js +674 -75
- package/lib/types/client/CardDefaultPresetField.d.ts +35 -0
- package/lib/types/client/TaskBoardSettingsCard.d.ts +49 -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/locales.d.ts +16 -0
- package/lib/types/core/card-defaults.d.ts +78 -0
- package/lib/types/core/controller.d.ts +32 -7
- package/lib/types/core/launch-width.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 +19 -0
- package/lib/types/core/use-cases/task-create.d.ts +10 -5
- package/lib/types/core/use-cases/task-update.d.ts +1 -1
- package/lib/types/host/launch-admission.d.ts +72 -0
- package/lib/types/host-ledger.d.ts +9 -4
- package/lib/types/host-runner.d.ts +79 -4
- package/lib/types/host-service.d.ts +85 -2
- package/lib/types/index.d.ts +87 -0
- package/lib/types/protocol.d.ts +13 -0
- package/package.json +28 -28
- package/src/client/CardDefaultPresetField.tsx +101 -0
- package/src/client/TaskBoardSettingsCard.tsx +214 -16
- package/src/client/board/LinkSubtaskModal.tsx +8 -0
- package/src/client/board/NewTaskModal.tsx +86 -40
- package/src/client/board/ReasoningEffortField.tsx +50 -0
- package/src/client/board/TaskDetail.tsx +42 -6
- 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/team-run-promotion.ts +39 -0
- package/src/client/index.ts +98 -94
- package/src/client/locales.ts +34 -2
- package/src/client/settings-entry-form.ts +0 -1
- package/src/core/card-defaults.ts +132 -0
- package/src/core/controller.ts +44 -9
- package/src/core/launch-width.ts +38 -0
- package/src/core/reasoning-effort.ts +92 -0
- package/src/core/store.ts +5 -0
- package/src/core/subtask.ts +48 -13
- package/src/core/tasks.ts +20 -0
- package/src/core/use-cases/task-create.ts +27 -16
- package/src/core/use-cases/task-update.ts +6 -1
- package/src/host/agent-tools.ts +21 -8
- package/src/host/launch-admission.ts +140 -0
- package/src/host-ledger.ts +18 -6
- package/src/host-runner.ts +184 -18
- package/src/host-service.ts +247 -23
- package/src/index.ts +149 -2
- package/src/protocol.ts +28 -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",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
},
|
|
21
21
|
"dsh": {
|
|
22
22
|
"engines": {
|
|
23
|
-
"dsh": ">=0.2.
|
|
23
|
+
"dsh": ">=0.2.1-rc.2"
|
|
24
24
|
},
|
|
25
25
|
"bundle": {
|
|
26
26
|
"patch": "./cordis.patch.yml"
|
|
@@ -40,37 +40,37 @@
|
|
|
40
40
|
}
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
|
-
"@deepseek-ai/dsh": ">=0.2.
|
|
43
|
+
"@deepseek-ai/dsh": ">=0.2.1-rc.2",
|
|
44
44
|
"react": "^18.2.0"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@deepseek-ai/schemastery": "^3.18.
|
|
47
|
+
"@deepseek-ai/schemastery": "^3.18.5",
|
|
48
48
|
"marked": "^18.0.14"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
52
|
-
"@deepseek-ai/dsh-agent": "^0.2.
|
|
53
|
-
"@deepseek-ai/dsh-agent-preset-registry": "^0.2.
|
|
54
|
-
"@deepseek-ai/dsh-api-gateway": "^0.2.
|
|
55
|
-
"@deepseek-ai/dsh-api-remotes": "^0.2.
|
|
56
|
-
"@deepseek-ai/dsh-api-session-controller": "^0.2.
|
|
57
|
-
"@deepseek-ai/dsh-api-workspace-controller": "^0.2.
|
|
58
|
-
"@deepseek-ai/dsh-client-connection": "^0.2.
|
|
59
|
-
"@deepseek-ai/dsh-client-locale": "^0.2.
|
|
60
|
-
"@deepseek-ai/dsh-client-store": "^0.2.
|
|
61
|
-
"@deepseek-ai/dsh-client-ui-layout": "^0.2.
|
|
62
|
-
"@deepseek-ai/dsh-client-ui-renderer": "^0.2.
|
|
63
|
-
"@deepseek-ai/dsh-client-ui-settings": "^0.2.
|
|
64
|
-
"@deepseek-ai/dsh-client-ui-sidebar": "^0.2.
|
|
65
|
-
"@deepseek-ai/dsh-client-ui-slots": "^0.2.
|
|
66
|
-
"@deepseek-ai/dsh-client-ui-workspace": "^0.2.
|
|
67
|
-
"@deepseek-ai/dsh-commands": "^0.2.
|
|
68
|
-
"@deepseek-ai/dsh-host-webserver": "^0.2.
|
|
69
|
-
"@deepseek-ai/dsh-llm": "^0.2.
|
|
70
|
-
"@deepseek-ai/dsh-settings": "^0.2.
|
|
71
|
-
"@deepseek-ai/dsh-system-prompt": "^0.2.
|
|
72
|
-
"@deepseek-ai/dsh-tools": "^0.2.
|
|
73
|
-
"@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",
|
|
74
74
|
"@types/node": "^22.20.0",
|
|
75
75
|
"@types/react": "~18.3.1",
|
|
76
76
|
"@types/react-dom": "^18.3.5",
|
|
@@ -100,7 +100,7 @@
|
|
|
100
100
|
"build": "tsc -b tsconfig.build.json && tsdown",
|
|
101
101
|
"bundle": "tsdown",
|
|
102
102
|
"watch": "tsdown --watch",
|
|
103
|
-
"typecheck": "
|
|
103
|
+
"typecheck": "tsgo --noEmit",
|
|
104
104
|
"test": "vitest run",
|
|
105
105
|
"test:watch": "vitest"
|
|
106
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,12 +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'
|
|
17
|
+
import { CardDefaultPresetField } from './CardDefaultPresetField.tsx'
|
|
15
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
28
|
import { SESSION_POLL_MAX_SECONDS, SESSION_POLL_MIN_SECONDS } from '../core/poll-cadence.ts'
|
|
18
|
-
import {
|
|
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'
|
|
19
41
|
import settingsCss from './board-settings.module.css'
|
|
20
42
|
|
|
21
43
|
/** The depth choices the card offers, derived from the supported range. */
|
|
@@ -65,34 +87,42 @@ function subtaskDepthField(): FieldSpec {
|
|
|
65
87
|
}
|
|
66
88
|
|
|
67
89
|
/**
|
|
68
|
-
* The
|
|
69
|
-
*
|
|
70
|
-
* 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.
|
|
71
94
|
*/
|
|
72
|
-
function
|
|
95
|
+
function runLaunchWidthField(): FieldSpec {
|
|
73
96
|
return {
|
|
74
|
-
field: '
|
|
75
|
-
format: value => typeof value === '
|
|
97
|
+
field: 'runLaunchWidth',
|
|
98
|
+
format: value => typeof value === 'number' && Number.isInteger(value) ? String(value) : '',
|
|
76
99
|
parse: (text) => {
|
|
77
100
|
const trimmed = text.trim()
|
|
78
101
|
if (trimmed === '') return { kind: 'clear' }
|
|
79
|
-
|
|
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
|
|
80
107
|
},
|
|
81
108
|
}
|
|
82
109
|
}
|
|
83
110
|
|
|
84
111
|
/**
|
|
85
|
-
* The
|
|
86
|
-
*
|
|
87
|
-
*
|
|
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.
|
|
88
117
|
*/
|
|
89
|
-
function
|
|
118
|
+
function modelRouteField(field: string): FieldSpec {
|
|
90
119
|
return {
|
|
91
|
-
field
|
|
120
|
+
field,
|
|
92
121
|
format: value => typeof value === 'string' ? value : '',
|
|
93
122
|
parse: (text) => {
|
|
94
123
|
const trimmed = text.trim()
|
|
95
|
-
|
|
124
|
+
if (trimmed === '') return { kind: 'clear' }
|
|
125
|
+
return parseModelRoute(trimmed) === undefined ? undefined : { kind: 'set', value: trimmed }
|
|
96
126
|
},
|
|
97
127
|
}
|
|
98
128
|
}
|
|
@@ -109,6 +139,12 @@ export interface TaskBoardSettings {
|
|
|
109
139
|
maxSubtaskDepth?: number
|
|
110
140
|
/** Roster-poll cadence in seconds while the board has work to reconcile. */
|
|
111
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
|
|
112
148
|
/**
|
|
113
149
|
* GLOBAL native /goal switch (default off): the master control over whether
|
|
114
150
|
* this board starts a run with dsh's built-in /goal at all. Frozen per
|
|
@@ -121,6 +157,20 @@ export interface TaskBoardSettings {
|
|
|
121
157
|
goalVerificationModel?: string
|
|
122
158
|
/** Judge reasoning effort; blank inherits the host default level. */
|
|
123
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
|
|
124
174
|
}
|
|
125
175
|
|
|
126
176
|
/** What the task-board card renders. */
|
|
@@ -135,6 +185,8 @@ export interface TaskBoardSettingsCardState extends CardShell {
|
|
|
135
185
|
maxSubtaskDepth: CardFieldState
|
|
136
186
|
/** Roster-poll cadence field. */
|
|
137
187
|
sessionPollSeconds: CardFieldState
|
|
188
|
+
/** Run-launch admission width field. */
|
|
189
|
+
runLaunchWidth: CardFieldState
|
|
138
190
|
/** GLOBAL native /goal switch. */
|
|
139
191
|
goalRunEnabled: CardFieldState
|
|
140
192
|
/** Goal-acceptance switch. */
|
|
@@ -143,6 +195,20 @@ export interface TaskBoardSettingsCardState extends CardShell {
|
|
|
143
195
|
goalVerificationModel: CardFieldState
|
|
144
196
|
/** Judge reasoning-effort field. */
|
|
145
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[]
|
|
146
212
|
}
|
|
147
213
|
|
|
148
214
|
/** The registration-side face the card's slot entry injects. */
|
|
@@ -159,6 +225,8 @@ export interface TaskBoardSettingsCardFace extends CardActions {
|
|
|
159
225
|
export class TaskBoardSettingsCardController {
|
|
160
226
|
private readonly form: CardForm<TaskBoardSettings>
|
|
161
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
|
|
162
230
|
|
|
163
231
|
/** @param scope - the bound configuration form for the `task-board` namespace. */
|
|
164
232
|
constructor(scope: ConfigForm<TaskBoardSettings>, private readonly dispatch: TaskBoardExtensionDispatch) {
|
|
@@ -168,10 +236,18 @@ export class TaskBoardSettingsCardController {
|
|
|
168
236
|
booleanField('preventIdleSleep'),
|
|
169
237
|
subtaskDepthField(),
|
|
170
238
|
sessionPollField(),
|
|
239
|
+
runLaunchWidthField(),
|
|
171
240
|
booleanField('goalRunEnabled'),
|
|
172
241
|
booleanField('goalVerification'),
|
|
173
|
-
|
|
174
|
-
|
|
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'),
|
|
175
251
|
])
|
|
176
252
|
this.store = this.form.bind(() => this.projection())
|
|
177
253
|
}
|
|
@@ -184,13 +260,33 @@ export class TaskBoardSettingsCardController {
|
|
|
184
260
|
preventIdleSleep: this.form.field('preventIdleSleep'),
|
|
185
261
|
maxSubtaskDepth: this.form.field('maxSubtaskDepth'),
|
|
186
262
|
sessionPollSeconds: this.form.field('sessionPollSeconds'),
|
|
263
|
+
runLaunchWidth: this.form.field('runLaunchWidth'),
|
|
187
264
|
goalRunEnabled: this.form.field('goalRunEnabled'),
|
|
188
265
|
goalVerification: this.form.field('goalVerification'),
|
|
189
266
|
goalVerificationModel: this.form.field('goalVerificationModel'),
|
|
190
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 }),
|
|
191
273
|
}
|
|
192
274
|
}
|
|
193
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
|
+
|
|
194
290
|
/**
|
|
195
291
|
* Build the face the card's slot registration injects.
|
|
196
292
|
* @returns the card's snapshot and its form actions.
|
|
@@ -304,6 +400,25 @@ export function TaskBoardSettingsCard(props: TaskBoardSettingsCardProps) {
|
|
|
304
400
|
invalidLabel: t('settings.invalidNumber'),
|
|
305
401
|
disabled,
|
|
306
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
|
+
}
|
|
307
422
|
// Chrome state of the nested disclosure cards below: they stage into this
|
|
308
423
|
// card's form and are written by its one save, so they carry no footer and no
|
|
309
424
|
// unsaved pill of their own (the outer header owns the pill).
|
|
@@ -405,6 +520,16 @@ export function TaskBoardSettingsCard(props: TaskBoardSettingsCardProps) {
|
|
|
405
520
|
onEdit={(text) => { props.edit('maxSubtaskDepth', text) }}
|
|
406
521
|
onReset={() => { props.resetField('maxSubtaskDepth') }}
|
|
407
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
|
+
/>
|
|
408
533
|
</PluginSettingsCard>
|
|
409
534
|
|
|
410
535
|
<PluginSettingsCard
|
|
@@ -511,6 +636,79 @@ export function TaskBoardSettingsCard(props: TaskBoardSettingsCardProps) {
|
|
|
511
636
|
)}
|
|
512
637
|
</PluginSettingsCard>
|
|
513
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
|
+
|
|
514
712
|
{/* Provider sections: a family extension contributes its own nested
|
|
515
713
|
settings card into this list through the seat this card declares —
|
|
516
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
|
: (
|