@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.
Files changed (58) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +14 -6
  3. package/README.zh.md +14 -6
  4. package/lib/client.js +1502 -820
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +674 -75
  7. package/lib/types/client/CardDefaultPresetField.d.ts +35 -0
  8. package/lib/types/client/TaskBoardSettingsCard.d.ts +49 -0
  9. package/lib/types/client/board/ReasoningEffortField.d.ts +25 -0
  10. package/lib/types/client/board/model-options.d.ts +67 -0
  11. package/lib/types/client/board/preset-label.d.ts +21 -0
  12. package/lib/types/client/board/preset-roster.d.ts +45 -0
  13. package/lib/types/client/board/team-run-promotion.d.ts +26 -0
  14. package/lib/types/client/locales.d.ts +16 -0
  15. package/lib/types/core/card-defaults.d.ts +78 -0
  16. package/lib/types/core/controller.d.ts +32 -7
  17. package/lib/types/core/launch-width.d.ts +31 -0
  18. package/lib/types/core/reasoning-effort.d.ts +57 -0
  19. package/lib/types/core/subtask.d.ts +30 -8
  20. package/lib/types/core/tasks.d.ts +19 -0
  21. package/lib/types/core/use-cases/task-create.d.ts +10 -5
  22. package/lib/types/core/use-cases/task-update.d.ts +1 -1
  23. package/lib/types/host/launch-admission.d.ts +72 -0
  24. package/lib/types/host-ledger.d.ts +9 -4
  25. package/lib/types/host-runner.d.ts +79 -4
  26. package/lib/types/host-service.d.ts +85 -2
  27. package/lib/types/index.d.ts +87 -0
  28. package/lib/types/protocol.d.ts +13 -0
  29. package/package.json +28 -28
  30. package/src/client/CardDefaultPresetField.tsx +101 -0
  31. package/src/client/TaskBoardSettingsCard.tsx +214 -16
  32. package/src/client/board/LinkSubtaskModal.tsx +8 -0
  33. package/src/client/board/NewTaskModal.tsx +86 -40
  34. package/src/client/board/ReasoningEffortField.tsx +50 -0
  35. package/src/client/board/TaskDetail.tsx +42 -6
  36. package/src/client/board/model-options.ts +178 -0
  37. package/src/client/board/preset-label.ts +32 -0
  38. package/src/client/board/preset-roster.ts +58 -0
  39. package/src/client/board/team-run-promotion.ts +39 -0
  40. package/src/client/index.ts +98 -94
  41. package/src/client/locales.ts +34 -2
  42. package/src/client/settings-entry-form.ts +0 -1
  43. package/src/core/card-defaults.ts +132 -0
  44. package/src/core/controller.ts +44 -9
  45. package/src/core/launch-width.ts +38 -0
  46. package/src/core/reasoning-effort.ts +92 -0
  47. package/src/core/store.ts +5 -0
  48. package/src/core/subtask.ts +48 -13
  49. package/src/core/tasks.ts +20 -0
  50. package/src/core/use-cases/task-create.ts +27 -16
  51. package/src/core/use-cases/task-update.ts +6 -1
  52. package/src/host/agent-tools.ts +21 -8
  53. package/src/host/launch-admission.ts +140 -0
  54. package/src/host-ledger.ts +18 -6
  55. package/src/host-runner.ts +184 -18
  56. package/src/host-service.ts +247 -23
  57. package/src/index.ts +149 -2
  58. package/src/protocol.ts +28 -4
@@ -0,0 +1,35 @@
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 type { FieldProps } from './PluginSettingsCard.tsx';
21
+ /** Props of the default-preset field: the shared field contract plus the roster. */
22
+ export interface CardDefaultPresetFieldProps extends FieldProps {
23
+ /**
24
+ * The deployment's agent-preset roster, or undefined while its runtime read
25
+ * has not answered: an unread roster offers the inherit choice alone instead
26
+ * of calling every stored pin "removed".
27
+ */
28
+ presets: readonly ExecutionPresetOption[] | undefined;
29
+ }
30
+ /**
31
+ * Render the default-preset select.
32
+ * @param props - the field contract and the roster to list.
33
+ * @returns the field.
34
+ */
35
+ export declare function CardDefaultPresetField(props: CardDefaultPresetFieldProps): import("react").JSX.Element;
@@ -7,6 +7,7 @@ import type { InjectFace, PropsLocale, PropsRenderSlots, PropsRuntime } from '@d
7
7
  import type { SnapshotStore } from '@deepseek-ai/dsh-client-store';
8
8
  import type { ConfigForm } from '@deepseek-ai/dsh-client-ui-settings/client';
9
9
  import type { TaskBoardExtensionDispatch } from '../core/extension.ts';
10
+ import type { ExecutionPresetOption } from '../core/controller.ts';
10
11
  import { type CardActions, type CardShell, type FieldState as CardFieldState } from './settings-form.ts';
11
12
  /** The task-board fields this card edits (the namespace's full schema). */
12
13
  export interface TaskBoardSettings {
@@ -20,6 +21,12 @@ export interface TaskBoardSettings {
20
21
  maxSubtaskDepth?: number;
21
22
  /** Roster-poll cadence in seconds while the board has work to reconcile. */
22
23
  sessionPollSeconds?: number;
24
+ /**
25
+ * How many run launches may be in flight at once (1..16, default 3). One run
26
+ * opens its whole subtree, so this bounds the burst of sessions a single run
27
+ * action creates; members enter the running column in batches, none is dropped.
28
+ */
29
+ runLaunchWidth?: number;
23
30
  /**
24
31
  * GLOBAL native /goal switch (default off): the master control over whether
25
32
  * this board starts a run with dsh's built-in /goal at all. Frozen per
@@ -32,6 +39,20 @@ export interface TaskBoardSettings {
32
39
  goalVerificationModel?: string;
33
40
  /** Judge reasoning effort; blank inherits the host default level. */
34
41
  goalVerificationReasoningEffort?: string;
42
+ /**
43
+ * Agent preset the create dialog of this board opens with. Blank pins
44
+ * nothing, so a new card follows the deployment default as before.
45
+ */
46
+ cardDefaultMode?: string;
47
+ /** Permission the create dialog opens with; blank applies no default. */
48
+ cardDefaultPermission?: string;
49
+ /** Execution model route the create dialog opens with; blank applies none. */
50
+ cardDefaultModel?: string;
51
+ /**
52
+ * Reasoning level bound to `cardDefaultModel`; it applies only while a
53
+ * card's final model IS that configured route, and is inert without one.
54
+ */
55
+ cardDefaultReasoningEffort?: string;
35
56
  }
36
57
  /** What the task-board card renders. */
37
58
  export interface TaskBoardSettingsCardState extends CardShell {
@@ -45,6 +66,8 @@ export interface TaskBoardSettingsCardState extends CardShell {
45
66
  maxSubtaskDepth: CardFieldState;
46
67
  /** Roster-poll cadence field. */
47
68
  sessionPollSeconds: CardFieldState;
69
+ /** Run-launch admission width field. */
70
+ runLaunchWidth: CardFieldState;
48
71
  /** GLOBAL native /goal switch. */
49
72
  goalRunEnabled: CardFieldState;
50
73
  /** Goal-acceptance switch. */
@@ -53,6 +76,20 @@ export interface TaskBoardSettingsCardState extends CardShell {
53
76
  goalVerificationModel: CardFieldState;
54
77
  /** Judge reasoning-effort field. */
55
78
  goalVerificationReasoningEffort: CardFieldState;
79
+ /** New-card default agent preset. */
80
+ cardDefaultMode: CardFieldState;
81
+ /** New-card default permission. */
82
+ cardDefaultPermission: CardFieldState;
83
+ /** New-card default model route. */
84
+ cardDefaultModel: CardFieldState;
85
+ /** New-card default reasoning level. */
86
+ cardDefaultReasoningEffort: CardFieldState;
87
+ /**
88
+ * The deployment's agent-preset roster, published by the runtime read the
89
+ * plugin wires; undefined until it answers, which leaves the default-preset
90
+ * field offering the inherit choice alone.
91
+ */
92
+ presets?: readonly ExecutionPresetOption[];
56
93
  }
57
94
  /** The registration-side face the card's slot entry injects. */
58
95
  export interface TaskBoardSettingsCardFace extends CardActions {
@@ -68,9 +105,21 @@ export declare class TaskBoardSettingsCardController {
68
105
  private readonly dispatch;
69
106
  private readonly form;
70
107
  private readonly store;
108
+ /** The deployment's agent-preset roster, once its runtime read has answered. */
109
+ private presets;
71
110
  /** @param scope - the bound configuration form for the `task-board` namespace. */
72
111
  constructor(scope: ConfigForm<TaskBoardSettings>, dispatch: TaskBoardExtensionDispatch);
73
112
  private projection;
113
+ /**
114
+ * Publish the deployment's agent-preset roster to the default-preset field.
115
+ * The roster is a runtime fact read outside this card (src/client/index.ts
116
+ * wires it once for both the board's pickers and this topic); it can arrive
117
+ * late or not at all, and until it does the field lists the inherit choice
118
+ * alone. This never blocks a save: a deployment that serves no roster still
119
+ * has a writable settings document.
120
+ * @param presets - the roster rows.
121
+ */
122
+ setPresetRoster(presets: readonly ExecutionPresetOption[]): void;
74
123
  /**
75
124
  * Build the face the card's slot registration injects.
76
125
  * @returns the card's snapshot and its form actions.
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Reasoning-effort field: one picker shared by both execution-target editors
3
+ * (the create dialog and the detail page), so the two never describe the same
4
+ * capability differently. The levels are the ones the picked model's catalog
5
+ * entry declares; an empty value is a real choice — "follow the model or Host
6
+ * default" — and is the default state.
7
+ *
8
+ * A stored level the roster no longer lists (the model left the roster, or its
9
+ * adapter changed) stays shown and labelled, but is not offered again, so the
10
+ * field never silently shows a different value than the one the card will ask
11
+ * for while the choosable set stays exactly the levels the model declares.
12
+ */
13
+ import type { ExecutionModelOption } from '../../core/controller.ts';
14
+ /** The controlled reasoning-effort picker. */
15
+ export declare function ReasoningEffortField({ models, modelId, value, pending, onChange }: {
16
+ /** The roster the picked model is looked up in. */
17
+ models: readonly ExecutionModelOption[];
18
+ /** The model this card pins; '' means "no pin". */
19
+ modelId: string;
20
+ /** The selected effort; '' means "follow the model or Host default". */
21
+ value: string;
22
+ /** Whether a mutation is in flight (the field locks with the rest of the form). */
23
+ pending: boolean;
24
+ onChange: (next: string) => void;
25
+ }): import("react").JSX.Element;
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Execution model options: the picker rows built from the official
3
+ * `session/modelCatalog` reply, and the reasoning-effort derivation both
4
+ * execution-target editors read off them (the create dialog and the detail
5
+ * page share one contract here, so they cannot promise different levels).
6
+ *
7
+ * The pickers must offer each model's real reasoning levels, which only the
8
+ * catalog declares — so the reply is normalized once, here, instead of every
9
+ * picker re-reading the wire shape. The fallback discovery path carries no
10
+ * capability metadata at all; its rows stay silent about reasoning rather than
11
+ * claiming a model supports none.
12
+ */
13
+ import type { ExecutionModelOption } from '../../core/controller.ts';
14
+ import type { TaskBoardKey } from '../locales.ts';
15
+ /**
16
+ * Normalize a `session/modelCatalog` reply into picker rows: one row per model
17
+ * of every provider group, keyed by the qualified route an execution pins.
18
+ * @param value - the raw catalog reply.
19
+ * @returns the rows, empty when the reply is unreadable.
20
+ */
21
+ export declare function modelOptionsFromCatalog(value: unknown): ExecutionModelOption[];
22
+ /**
23
+ * Normalize the flat `llm/discoverModels` list into picker rows. That reply
24
+ * names models only, so every row leaves the reasoning capability unknown.
25
+ * @param value - the raw discovery list.
26
+ * @returns the rows, empty when the list is unreadable.
27
+ */
28
+ export declare function modelOptionsFromDiscoveredModels(value: unknown): ExecutionModelOption[];
29
+ /**
30
+ * The picker rows of a model roster: the qualified `provider/model` route as
31
+ * the value a pin stores, and the provider beside the model name so two
32
+ * providers offering one model name stay distinguishable.
33
+ * @param models - the roster rows.
34
+ * @returns the choice rows, in roster order.
35
+ */
36
+ export declare function modelChoiceRows(models: readonly ExecutionModelOption[]): Array<{
37
+ value: string;
38
+ label: string;
39
+ }>;
40
+ /**
41
+ * The capability row the roster publishes for one model route.
42
+ * @returns undefined when no model is pinned, or the roster does not describe
43
+ * the pinned one — an unknown model is not the same as a model without
44
+ * reasoning.
45
+ */
46
+ export declare function reasoningOfModel(models: readonly ExecutionModelOption[], modelId: string): ExecutionModelOption['reasoning'] | undefined;
47
+ /**
48
+ * The effort ids the picker offers for a model, in catalog order.
49
+ * @returns an empty list when the model is unpinned, unknown, declares no
50
+ * reasoning, or publishes no selectable level.
51
+ */
52
+ export declare function reasoningEffortsFor(models: readonly ExecutionModelOption[], modelId: string): string[];
53
+ /** Whether the effort picker may be operated: a pinned model with declared, selectable levels. */
54
+ export declare function reasoningPickerEnabled(models: readonly ExecutionModelOption[], modelId: string): boolean;
55
+ /**
56
+ * The effort to keep when the picked model changes. Only a model that declares
57
+ * its levels can invalidate one: a model the roster cannot describe keeps the
58
+ * stored value, because the roster has no grounds to call it invalid.
59
+ * @param effort - the effort currently stored/selected ('' = follow the default).
60
+ * @returns the effort to keep.
61
+ */
62
+ export declare function reconcileReasoningEffort(models: readonly ExecutionModelOption[], modelId: string, effort: string): string;
63
+ /**
64
+ * The copy explaining why the picker is in its current state. One lookup for
65
+ * both editors, so the create dialog and the detail page never drift apart.
66
+ */
67
+ export declare function reasoningHintKey(models: readonly ExecutionModelOption[], modelId: string): TaskBoardKey;
@@ -20,3 +20,24 @@ export declare function presetLabel(preset: Pick<ExecutionPresetOption, 'id' | '
20
20
  * marks with `isDefault`.
21
21
  */
22
22
  export declare function inheritPresetLabel(presets: readonly ExecutionPresetOption[]): string;
23
+ /** One preset row a picker renders: the stored id, its label, and whether it may be picked. */
24
+ export interface PresetChoiceRow {
25
+ /** The value a pin stores. */
26
+ id: string;
27
+ /** Display label, already carrying the default and unavailable suffixes. */
28
+ label: string;
29
+ /** True when the preset cannot compose a session: the row is shown but not selectable. */
30
+ disabled: boolean;
31
+ }
32
+ /**
33
+ * The picker rows of one roster, split into the built-in and the user presets.
34
+ * Both pickers render these rows — the create dialog's mode select and the
35
+ * settings card's default-preset field — so their wording and their
36
+ * unavailable rows cannot drift apart.
37
+ * @param presets - the roster options.
38
+ * @returns one row list per group, in roster order.
39
+ */
40
+ export declare function presetChoiceRows(presets: readonly ExecutionPresetOption[]): {
41
+ builtin: PresetChoiceRow[];
42
+ custom: PresetChoiceRow[];
43
+ };
@@ -0,0 +1,45 @@
1
+ /**
2
+ * The agent-preset roster the execution-target pickers read: one read of the
3
+ * generated api-remotes face, and its normalization into the option rows the
4
+ * board controller and the settings card's new-card-defaults topic both
5
+ * consume.
6
+ *
7
+ * The roster is a runtime fact of the deployment, not ledger state. The
8
+ * namespace mounts with the official api-remotes assembly, so the read itself
9
+ * is wired through a scoped inject at the call site (see src/client/index.ts);
10
+ * this module owns only the shape both consumers agree on, so neither has to
11
+ * know how the wire face answers.
12
+ */
13
+ import type { ClientRemote } from '@deepseek-ai/dsh-api-remotes/client';
14
+ import type { ExecutionPresetOption } from '../../core/controller.ts';
15
+ /** The generated Remote namespace the mode pickers read their roster from. */
16
+ export type PresetRemote = ClientRemote['agentPresets'];
17
+ /** One agent-preset row the wire face answers with. */
18
+ export interface PresetRosterRow {
19
+ id: string;
20
+ name?: string;
21
+ description?: string;
22
+ /** Why this preset cannot compose a session; absent when it can. */
23
+ broken?: string;
24
+ isDefault: boolean;
25
+ }
26
+ /**
27
+ * Read the agent-preset roster of one deployment. The caller passes the
28
+ * namespace resolved inside its scoped inject, so a host that serves no such
29
+ * namespace never reaches here; a host that serves it answers `ok: false`
30
+ * instead, which leaves the caller's options untouched.
31
+ * @param remote - the `remote.agentPresets` namespace.
32
+ * @returns the roster, or a failed read the caller ignores.
33
+ */
34
+ export declare function readPresetRoster(remote: PresetRemote): Promise<{
35
+ ok: boolean;
36
+ presets: readonly PresetRosterRow[];
37
+ }>;
38
+ /**
39
+ * Normalize one roster into the picker rows the UI consumes. A preset that
40
+ * cannot compose a session keeps its `broken` reason: the pickers show it and
41
+ * disable it, so a missing preset never looks like a missing roster.
42
+ * @param presets - the roster rows.
43
+ * @returns the picker options.
44
+ */
45
+ export declare function presetOptionsFromRoster(presets: readonly PresetRosterRow[]): ExecutionPresetOption[];
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Team-run promotion for a parent card that just gained a subtask. A card with
3
+ * subtasks is exactly what the Agent Team mode is for, so the two join actions
4
+ * (create a subtask, link an existing task) opt the parent in — but only after
5
+ * the Host confirmed the join, and only where the deployment serves Agent
6
+ * Teams. A refused join changes nothing, and a parent that already opted in is
7
+ * left alone rather than rewritten.
8
+ *
9
+ * Nothing here runs on mount or from a render effect: an existing card is only
10
+ * ever touched by the user's own join action, so switching the option off by
11
+ * hand stays off.
12
+ */
13
+ import type { BoardController } from '../../core/controller.ts';
14
+ import type { TaskRecord } from '../../core/tasks.ts';
15
+ /**
16
+ * Whether a join would promote this parent: the deployment serves Agent Teams
17
+ * and the parent has not opted in yet.
18
+ * @param controller - the board controller (the snapshot carries both facts).
19
+ * @param parent - the task that gained a subtask, if there is one.
20
+ */
21
+ export declare function parentAwaitsTeamRunPromotion(controller: BoardController, parent: TaskRecord | undefined): boolean;
22
+ /**
23
+ * Opt the parent in after the Host confirmed the join.
24
+ * @returns true when the opt-in was written; false when it did not apply.
25
+ */
26
+ export declare function promoteParentForTeamRun(controller: BoardController, parent: TaskRecord | undefined): Promise<boolean>;
@@ -244,6 +244,14 @@ export declare const zh: {
244
244
  'exec.permission.danger-full-access': string;
245
245
  'exec.model.default': string;
246
246
  'exec.model.unknown': string;
247
+ 'exec.reasoning': string;
248
+ 'exec.reasoning.inherit': string;
249
+ 'exec.reasoning.unknown': string;
250
+ 'exec.reasoning.hint.pickModel': string;
251
+ 'exec.reasoning.hint.unknownModel': string;
252
+ 'exec.reasoning.hint.noEfforts': string;
253
+ 'exec.reasoning.hint.inherit': string;
254
+ 'exec.teamRunAutoHint': string;
247
255
  'exec.reuseSession': string;
248
256
  'exec.teamRun': string;
249
257
  'exec.teamRunHint': string;
@@ -288,6 +296,8 @@ export declare const zh: {
288
296
  'settings.maxSubtaskDepth': string;
289
297
  'settings.maxSubtaskDepthHint': string;
290
298
  'settings.maxSubtaskDepthOption': string;
299
+ 'settings.runLaunchWidth': string;
300
+ 'settings.runLaunchWidthHint': string;
291
301
  'settings.runBehaviorTitle': string;
292
302
  'settings.runBehaviorCardHint': string;
293
303
  'settings.goalRunGlobal': string;
@@ -317,6 +327,11 @@ export declare const zh: {
317
327
  'settings.goalVerificationEffortFallback': string;
318
328
  'settings.goalVerificationEffortFallbackNone': string;
319
329
  'settings.goalVerificationModelInvalid': string;
330
+ 'settings.cardDefaultsTitle': string;
331
+ 'settings.cardDefaultsCardHint': string;
332
+ 'settings.cardDefaultModeHint': string;
333
+ 'settings.cardDefaultPermissionHint': string;
334
+ 'settings.cardDefaultModelHint': string;
320
335
  'running.executing': string;
321
336
  'running.verifying': string;
322
337
  'running.repairing': string;
@@ -391,6 +406,7 @@ export declare const zh: {
391
406
  'detail.subtasks.detach': string;
392
407
  'detail.subtasks.runningLock': string;
393
408
  'detail.subtasks.runHint': string;
409
+ 'detail.subtasks.runHintSettled': string;
394
410
  'detail.subtasks.waiting': string;
395
411
  'detail.subtasks.depthLimit': string;
396
412
  'detail.subtasks.hostOnly': string;
@@ -0,0 +1,78 @@
1
+ /**
2
+ * New-card defaults: the execution targets a freshly created card starts from,
3
+ * and how they combine with what the caller picked and with the card a subtask
4
+ * or a duplicate is created from.
5
+ *
6
+ * Framework-free (no cordis, no runtime imports) because BOTH halves compile
7
+ * it: the Host resolves the seed when it writes the card, and the browser
8
+ * resolves the same seed to open the new-task dialog on it. One pure function
9
+ * is the only place the rules live, so the dialog and the ledger cannot drift.
10
+ *
11
+ * Every target is resolved in ONE order: what the caller picked wins over the
12
+ * value carried over from the card this one is created from, which wins over
13
+ * the board's new-card defaults, and a target nothing supplies stays blank
14
+ * ("no pin, resolved at run time"). A duplicate is a copy of its source and a
15
+ * subtask is derived from its parent, so both keep the source's own pins and
16
+ * fill only the fields that source left unset.
17
+ *
18
+ * The permission of a PARENT is the one exception: it never participates. The
19
+ * permission is not copied onto a card at creation — doing so would also have
20
+ * to copy the parent's human confirmation to stay runnable, and a detached
21
+ * child would keep both, so one confirmed parent would mint unbounded confirmed
22
+ * elevated roots. It is resolved along the ancestor chain at launch instead
23
+ * (core/subtask.ts), which is what makes detaching remove the inheritance by
24
+ * construction. A duplicate is not a hierarchy link, so it copies the
25
+ * permission like every other target. The board's own default permission is not
26
+ * a parent binding either and does apply to a subtask; it only pre-fills the
27
+ * card, and a value above the session baseline still needs a human
28
+ * confirmation before the card may run.
29
+ *
30
+ * The reasoning level is bound to the model route it was chosen for and never
31
+ * crosses to another one:
32
+ *
33
+ * - a level the caller picked is used as it stands;
34
+ * - a caller that pinned a model of its own inherits no level at all, not
35
+ * even one the same model happens to carry on the source card;
36
+ * - otherwise the source card's level travels with the source value it was
37
+ * stored on, exactly as the card's own model/level pair does, and wins over
38
+ * the board default level, so a board that configures none behaves exactly
39
+ * as the create path always has;
40
+ * - a board default level applies only while the card's final model IS the
41
+ * configured default model, and a blank default model makes it inert —
42
+ * a level the resolved model does not declare could only be refused by
43
+ * model selection at run time.
44
+ */
45
+ import type { TaskRecord } from './tasks.ts';
46
+ /** The board's configured new-card defaults; a blank target means "not set". */
47
+ export interface CardDefaults {
48
+ /** Agent preset a new card starts from. */
49
+ mode?: string;
50
+ /** Permission a new card starts from. */
51
+ permission?: string;
52
+ /** Model a new card starts from. */
53
+ model?: string;
54
+ /** Reasoning level a new card starts from, for the model above. */
55
+ reasoningEffort?: string;
56
+ }
57
+ /** The card a new one is created from, and how it is derived from it. */
58
+ export interface CardTargetSource {
59
+ /** A copy of the source card, or a subtask of it. */
60
+ kind: 'duplicate' | 'parent';
61
+ /** The source card's execution targets. */
62
+ task: Pick<TaskRecord, 'mode' | 'permission' | 'model' | 'reasoningEffort'>;
63
+ }
64
+ /** The resolved targets of the card about to be created; blank means "unset". */
65
+ export interface CardTargetSeed {
66
+ mode: string;
67
+ permission: string;
68
+ model: string;
69
+ reasoningEffort: string;
70
+ }
71
+ /**
72
+ * Resolve the execution targets a new card starts from.
73
+ * @param defaults - the board's configured new-card defaults, if any.
74
+ * @param source - the card this one is created from (a duplicate or a parent), if any.
75
+ * @param picked - what the caller chose explicitly; a non-blank value wins.
76
+ * @returns the four targets, blank where nothing supplies a value.
77
+ */
78
+ export declare function resolveCardTargets(defaults: CardDefaults | undefined, source: CardTargetSource | undefined, picked?: Partial<CardTargetSeed>): CardTargetSeed;
@@ -100,6 +100,17 @@ export interface ExecutionModelOption {
100
100
  name?: string;
101
101
  label?: string;
102
102
  provider?: string;
103
+ /**
104
+ * Reasoning metadata the adapter declares for this exact route. An effort
105
+ * picker is offered only while `supported`, and its choices are `efforts` —
106
+ * the ids the target model really accepts, so the UI never offers (or keeps)
107
+ * a level the model would reject. Absent means the deployment published no
108
+ * capability for this model; the card value is then left untouched.
109
+ */
110
+ reasoning?: {
111
+ supported: boolean;
112
+ efforts?: readonly string[];
113
+ };
103
114
  }
104
115
  /** The execution-target option sets the UI feeds into the controller. */
105
116
  export interface ExecutionOptionsSnapshot {
@@ -112,7 +123,7 @@ export interface ExecutionOptionsSnapshot {
112
123
  * (revision/scheduler/power); a full snapshot also carries the deployment
113
124
  * constants the UI reads (session default permission, subtask depth).
114
125
  */
115
- export type HostMirror = Pick<TaskBoardSnapshot, 'revision' | 'scheduler' | 'power' | 'sessionDefaultPermission' | 'maxSubtaskDepth' | 'teamRunAvailable' | 'goalRunEnabled' | 'extensions'>;
126
+ export type HostMirror = Pick<TaskBoardSnapshot, 'revision' | 'scheduler' | 'power' | 'sessionDefaultPermission' | 'maxSubtaskDepth' | 'teamRunAvailable' | 'goalRunEnabled' | 'cardDefaults' | 'extensions'>;
116
127
  /** Immutable controller snapshot for UI subscriptions. */
117
128
  export interface ControllerSnapshot {
118
129
  tasks: readonly TaskRecord[];
@@ -187,6 +198,19 @@ export declare class BoardController {
187
198
  isHostBacked(): boolean;
188
199
  /** Retry initial migration/state synchronization after an explicit Host error. */
189
200
  retryHostSync(): Promise<boolean>;
201
+ /**
202
+ * Re-read the authoritative Host snapshot.
203
+ *
204
+ * The board's volatile deployment configuration — the new-card defaults among
205
+ * it — is republished the moment the settings document is written, but the SSE
206
+ * frame that republish emits carries the value as of BEFORE the write (the
207
+ * loader hands the update to the fiber while the new document is not yet
208
+ * visible to it), so a mirror that followed frames alone would stay one edit
209
+ * behind and the create dialog would prefill from the previous defaults for
210
+ * the rest of the session. The surface that owns the write asks for the
211
+ * snapshot again instead of trusting the frame.
212
+ */
213
+ refreshHostState(): void;
190
214
  /**
191
215
  * Show the board. The layout owns which panel the center column renders, so
192
216
  * the state flip and the panel selection travel together here; a composition
@@ -359,12 +383,13 @@ export declare class BoardController {
359
383
  */
360
384
  private mirrorDepth;
361
385
  /**
362
- * Project a Host snapshot onto the browser's mirror. SSE frames and partial
363
- * snapshots carry only the volatile subset (revision/scheduler/power), so the
364
- * deployment constants the UI reads — the session-default permission the
365
- * confirmation banner compares against and the subtask depth limit — are
366
- * carried over from the last full snapshot instead of being dropped by a
367
- * heartbeat frame.
386
+ * Project a Host snapshot onto the browser's mirror. Every snapshot the
387
+ * transport serves is a full document (bootstrap/state/action all answer
388
+ * `TaskBoardHostService.snapshot()`), so the deployment constants the UI reads
389
+ * — the session-default permission the confirmation banner compares against,
390
+ * the subtask depth limit, the platform switches and the extensions a provider
391
+ * published — are taken as published, carried over only where the snapshot
392
+ * itself omits the key.
368
393
  */
369
394
  private mirrorOf;
370
395
  private refreshRemote;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Run-launch admission width: the one bound on how many sessions this board
3
+ * starts at the same time.
4
+ *
5
+ * One board action opens a whole subtree, and a team run hands every member of
6
+ * it to the Lead at once, so without a bound the Host asks DSH for dozens of
7
+ * sessions in a single tick. The repository's own measured guidance caps heavy
8
+ * implementation subagents at three (docs/multi-agent-resources.md) and no
9
+ * runtime mechanism enforced it; this setting is that mechanism.
10
+ *
11
+ * The bounds and the default live here rather than in either half, exactly like
12
+ * the roster cadence: the settings card validates a draft against the same
13
+ * range the Host clamps a hand-edited configuration to, and a programmatic
14
+ * mount that names no width gets the default.
15
+ *
16
+ * @module dsh-task-board/core/launch-width
17
+ */
18
+ /** Width used when neither the settings document nor the mount names one. */
19
+ export declare const DEFAULT_RUN_LAUNCH_WIDTH = 3;
20
+ /** Narrowest accepted width: one launch at a time. */
21
+ export declare const RUN_LAUNCH_WIDTH_MIN = 1;
22
+ /** Widest accepted width. */
23
+ export declare const RUN_LAUNCH_WIDTH_MAX = 16;
24
+ /**
25
+ * Clamp a configured width into the supported range. A value the schema would
26
+ * refuse (non-finite, below the floor, above the ceiling) still leaves the
27
+ * board launching: an out-of-range setting never stalls a run.
28
+ * @param width - configured width, or undefined for the default.
29
+ * @returns the effective width.
30
+ */
31
+ export declare function normalizeRunLaunchWidth(width: number | undefined): number;
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Which reasoning effort one execution may actually send.
3
+ *
4
+ * The rule lives here, in one pure place, so the launch path cannot drift from
5
+ * what the picker promises: an effort is sent ONLY for the exact model route
6
+ * whose adapter declares that id. An unsupported value is dropped rather than
7
+ * passed through — the Host validates the selection against the same
8
+ * capability (`resolveCallConfig` rejects an unsupported effort) and the whole
9
+ * model selection would fail with it. An absent effort stays absent: the SDK
10
+ * then materializes the model's own default, which is exactly what "inherit
11
+ * the model" means.
12
+ *
13
+ * A card that pins a bare model id also gets its provider resolved here: the
14
+ * session wire contract requires `provider` (ModelSelection), so the catalog is
15
+ * the only place that can complete the documented `provider/model` or model-id
16
+ * pin.
17
+ */
18
+ import type { CatalogModel, ModelCatalogView } from './verification.ts';
19
+ /** One execution route an effort resolves against: `provider/model`, or a bare model id. */
20
+ export interface ReasoningRoute {
21
+ provider?: string;
22
+ model: string;
23
+ }
24
+ /** The catalog entry a route names, once the catalog can complete it. */
25
+ export interface DeclaredReasoningRoute {
26
+ provider: string;
27
+ model: string;
28
+ /** The adapter's reasoning metadata; absent when it exposes none. */
29
+ reasoning?: CatalogModel['reasoning'];
30
+ }
31
+ /** What one card's effort resolves to for one route. */
32
+ export interface ReasoningEffortDecision {
33
+ /** The effort to send; absent means "send none". */
34
+ effort?: string;
35
+ /** The card's own value when it will NOT be sent. */
36
+ dropped?: string;
37
+ }
38
+ /** Trim one optional effort: blank text means "not set", like every other pinned target. */
39
+ export declare function normalizeReasoningEffort(value: string | undefined): string | undefined;
40
+ /**
41
+ * Complete one route through the host catalog: a qualified route must match its
42
+ * provider group, and a bare model id matches the first group carrying it —
43
+ * which is the provider the request then names.
44
+ * @param catalog - the host model catalog, when this deployment serves one.
45
+ * @param route - the route as the card pins it.
46
+ * @returns the completed route with its reasoning metadata, or undefined when
47
+ * the catalog declares no such model.
48
+ */
49
+ export declare function declaredRoute(catalog: ModelCatalogView | undefined, route: ReasoningRoute): DeclaredReasoningRoute | undefined;
50
+ /**
51
+ * Resolve the effort one execution sends.
52
+ * @param requested - the card's stored effort (absent or blank = inherit).
53
+ * @param catalog - the host model catalog, when this deployment serves one.
54
+ * @param route - the route the execution will actually run at.
55
+ * @returns the effort to send, or the dropped value that will not be sent.
56
+ */
57
+ export declare function resolveReasoningEffort(requested: string | undefined, catalog: ModelCatalogView | undefined, route: ReasoningRoute | undefined): ReasoningEffortDecision;
@@ -76,11 +76,32 @@ export interface ParentLinkCheck {
76
76
  export declare function checkParentLink(tasks: readonly TaskRecord[], childId: string, parentId: string | null, maxSubtaskDepth: number, lineage?: LineageIndex): ParentLinkCheck;
77
77
  /**
78
78
  * The tasks one run request opens executions for: the requested task first,
79
- * then its on-board descendants within the configured depth. Archived tasks
80
- * never run; a participant that already has an open execution is skipped by
81
- * the caller, which cannot start it twice.
79
+ * then its on-board descendants within the configured depth. This is the
80
+ * SHAPE of a cascade — every walk that must see the whole tree (settlement,
81
+ * archive/restore, the running lock) reads it. The run path filters it with
82
+ * {@link joinsCascadeRun}: archived tasks never run, a participant that already
83
+ * has an open execution is skipped by the caller, which cannot start it twice,
84
+ * and a finished descendant is left alone.
82
85
  */
83
86
  export declare function cascadeTargets(tasks: readonly TaskRecord[], rootId: string, maxSubtaskDepth: number): TaskRecord[];
87
+ /**
88
+ * Whether a cascade member takes part in a new run.
89
+ *
90
+ * The task the user ran is always a member: the request names that card, and
91
+ * the operator asked for it to run whatever its column says. A descendant does
92
+ * not join once it sits in the `done` column — its work is finished, so running
93
+ * the parent again is a run of the parent, never a second execution of work
94
+ * that is already done. The rule keys on the column alone, so it covers both a
95
+ * subtask that ran and one an operator moved to done by hand, and it holds for
96
+ * every trigger: a manual run, a re-run, and a due occurrence of a schedule.
97
+ * A card whose own schedule is enabled settles back into `todo`, so a scheduled
98
+ * subtask stays a participant; to make a finished one run again, move it back
99
+ * to `todo` or run it on its own.
100
+ * @param task - the candidate member.
101
+ * @param rootId - the task the run request named.
102
+ * @returns false when this member is left alone by the run.
103
+ */
104
+ export declare function joinsCascadeRun(task: TaskRecord, rootId: string): boolean;
84
105
  /** One still-open execution belonging to a cascade run group. */
85
106
  export interface OpenCascadeChild {
86
107
  taskId: string;
@@ -114,11 +135,12 @@ export declare function effectiveTaskPermission(task: TaskRecord): TaskRecord['p
114
135
  /**
115
136
  * Resolve the execution targets a (sub)task actually runs with: its own value
116
137
  * wins, and an unset target inherits the nearest ancestor up to the root.
117
- * Workspace, agent preset and model inherit the ancestor's PIN only — a
118
- * handover bundle is a per-card continuation artefact and is never inherited.
119
- * The permission inherits the ancestor's EFFECTIVE binding, and its human
120
- * confirmation travels with it, so a subtask of an already-confirmed elevated
121
- * card is runnable while a subtask's own unconfirmed binding stays gated.
138
+ * Workspace, agent preset, model and reasoning effort inherit the ancestor's
139
+ * PIN only — a handover bundle is a per-card continuation artefact and is never
140
+ * inherited. The permission inherits the ancestor's EFFECTIVE binding, and its
141
+ * human confirmation travels with it, so a subtask of an already-confirmed
142
+ * elevated card is runnable while a subtask's own unconfirmed binding stays
143
+ * gated.
122
144
  */
123
145
  /**
124
146
  * The immutable teammate name for one team-mode member. Teammate names are