@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.
Files changed (83) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +34 -18
  3. package/README.zh.md +34 -18
  4. package/lib/client.js +2804 -1024
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +2576 -416
  7. package/lib/types/client/CardDefaultPresetField.d.ts +35 -0
  8. package/lib/types/client/TaskBoardSettingsCard.d.ts +61 -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/index.d.ts +4 -5
  15. package/lib/types/client/locales.d.ts +81 -0
  16. package/lib/types/client/plugin-card-seat.d.ts +5 -0
  17. package/lib/types/client/schedule-zone.d.ts +50 -0
  18. package/lib/types/core/card-defaults.d.ts +78 -0
  19. package/lib/types/core/controller.d.ts +41 -17
  20. package/lib/types/core/launch-width.d.ts +31 -0
  21. package/lib/types/core/poll-cadence.d.ts +31 -0
  22. package/lib/types/core/reasoning-effort.d.ts +57 -0
  23. package/lib/types/core/subtask.d.ts +30 -8
  24. package/lib/types/core/tasks.d.ts +163 -14
  25. package/lib/types/core/use-cases/task-create.d.ts +10 -5
  26. package/lib/types/core/use-cases/task-schedule.d.ts +68 -10
  27. package/lib/types/core/use-cases/task-update.d.ts +1 -1
  28. package/lib/types/core/verification-budget.d.ts +52 -0
  29. package/lib/types/core/verification.d.ts +376 -9
  30. package/lib/types/host/launch-admission.d.ts +72 -0
  31. package/lib/types/host/verification-gate.d.ts +24 -0
  32. package/lib/types/host/verification-runner.d.ts +38 -7
  33. package/lib/types/host-ledger.d.ts +91 -11
  34. package/lib/types/host-runner.d.ts +111 -13
  35. package/lib/types/host-service.d.ts +199 -8
  36. package/lib/types/index.d.ts +141 -0
  37. package/lib/types/protocol.d.ts +63 -12
  38. package/package.json +29 -33
  39. package/src/client/CardDefaultPresetField.tsx +101 -0
  40. package/src/client/TaskBoardSettingsCard.tsx +288 -17
  41. package/src/client/board/LinkSubtaskModal.tsx +8 -0
  42. package/src/client/board/NewTaskModal.tsx +237 -60
  43. package/src/client/board/ReasoningEffortField.tsx +50 -0
  44. package/src/client/board/TaskCard.tsx +3 -3
  45. package/src/client/board/TaskDetail.tsx +261 -24
  46. package/src/client/board/VerificationReport.tsx +132 -13
  47. package/src/client/board/model-options.ts +178 -0
  48. package/src/client/board/preset-label.ts +32 -0
  49. package/src/client/board/preset-roster.ts +58 -0
  50. package/src/client/board/status-key.ts +3 -0
  51. package/src/client/board/team-run-promotion.ts +39 -0
  52. package/src/client/index.ts +105 -108
  53. package/src/client/locales.ts +171 -6
  54. package/src/client/plugin-card-seat.ts +7 -0
  55. package/src/client/schedule-zone.ts +100 -2
  56. package/src/client/settings-entry-form.ts +0 -1
  57. package/src/core/card-defaults.ts +132 -0
  58. package/src/core/controller.ts +55 -16
  59. package/src/core/launch-width.ts +38 -0
  60. package/src/core/poll-cadence.ts +41 -0
  61. package/src/core/reasoning-effort.ts +92 -0
  62. package/src/core/store.ts +69 -15
  63. package/src/core/subtask.ts +48 -13
  64. package/src/core/tasks.ts +213 -39
  65. package/src/core/use-cases/task-create.ts +57 -25
  66. package/src/core/use-cases/task-schedule.ts +190 -25
  67. package/src/core/use-cases/task-update.ts +9 -4
  68. package/src/core/verification-budget.ts +115 -0
  69. package/src/core/verification.ts +785 -17
  70. package/src/host/agent-tools.ts +127 -29
  71. package/src/host/launch-admission.ts +140 -0
  72. package/src/host/verification-gate.ts +172 -16
  73. package/src/host/verification-runner.ts +207 -40
  74. package/src/host-ledger.ts +346 -48
  75. package/src/host-runner.ts +234 -35
  76. package/src/host-service.ts +539 -52
  77. package/src/index.ts +250 -7
  78. package/src/protocol.ts +160 -32
  79. package/lib/invariant.js +0 -6
  80. package/lib/types/client/board-card-seat.d.ts +0 -34
  81. package/lib/types/invariant.d.ts +0 -3
  82. package/src/client/board-card-seat.ts +0 -102
  83. package/src/invariant.ts +0 -4
@@ -13,6 +13,7 @@
13
13
  */
14
14
  import type { TaskStore } from './store.ts';
15
15
  import { type NewTaskInput, type TaskRecord, type TaskStatus } from './tasks.ts';
16
+ import { type SetSchedulePatch } from './use-cases/task-schedule.ts';
16
17
  import { type TaskUpdatePatch } from './use-cases/task-update.ts';
17
18
  import type { TaskBoardAction, TaskBoardEventPayload, TaskBoardParseDraft, TaskBoardParseRequest, TaskBoardSnapshot } from '../protocol.ts';
18
19
  import type { TaskBoardExtensionActionRequest, TaskBoardVisibilityPredicate } from './extension.ts';
@@ -99,6 +100,17 @@ export interface ExecutionModelOption {
99
100
  name?: string;
100
101
  label?: string;
101
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
+ };
102
114
  }
103
115
  /** The execution-target option sets the UI feeds into the controller. */
104
116
  export interface ExecutionOptionsSnapshot {
@@ -111,7 +123,7 @@ export interface ExecutionOptionsSnapshot {
111
123
  * (revision/scheduler/power); a full snapshot also carries the deployment
112
124
  * constants the UI reads (session default permission, subtask depth).
113
125
  */
114
- export type HostMirror = Pick<TaskBoardSnapshot, 'revision' | 'scheduler' | 'power' | 'sessionDefaultPermission' | 'maxSubtaskDepth' | 'teamRunAvailable' | 'extensions'>;
126
+ export type HostMirror = Pick<TaskBoardSnapshot, 'revision' | 'scheduler' | 'power' | 'sessionDefaultPermission' | 'maxSubtaskDepth' | 'teamRunAvailable' | 'goalRunEnabled' | 'cardDefaults' | 'extensions'>;
115
127
  /** Immutable controller snapshot for UI subscriptions. */
116
128
  export interface ControllerSnapshot {
117
129
  tasks: readonly TaskRecord[];
@@ -186,6 +198,19 @@ export declare class BoardController {
186
198
  isHostBacked(): boolean;
187
199
  /** Retry initial migration/state synchronization after an explicit Host error. */
188
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;
189
214
  /**
190
215
  * Show the board. The layout owns which panel the center column renders, so
191
216
  * the state flip and the panel selection travel together here; a composition
@@ -287,19 +312,17 @@ export declare class BoardController {
287
312
  */
288
313
  deleteTag(name: string): Promise<boolean>;
289
314
  /**
290
- * Update a task's schedule rule. A blank or invalid cron expression is
291
- * rejected (returns false, state untouched). When the rule ends up enabled
292
- * the next run instant is computed immediately; a disabled rule carries no
293
- * next-run instant. Delegates the domain transition to the schedule use case.
315
+ * Update a task's schedule rule. An unusable plan (blank or invalid cron, a
316
+ * one-shot with no future instant, a non-positive run cap) is rejected
317
+ * (returns false, state untouched). When the rule ends up enabled the next
318
+ * run instant is computed immediately — a one-shot's next instant IS its
319
+ * planned instant — and a disabled rule carries none. Delegates the domain
320
+ * transition to the schedule use case.
294
321
  * @param id - the task to schedule.
295
322
  * @param patch - fields to change (absent fields keep their current value).
296
- * @returns true when applied, false when rejected (invalid cron / unknown task).
323
+ * @returns true when applied, false when rejected (unknown task / unusable plan).
297
324
  */
298
- setSchedule(id: string, patch: {
299
- enabled?: boolean;
300
- cron?: string;
301
- timeZone?: string | null;
302
- }): boolean;
325
+ setSchedule(id: string, patch: SetSchedulePatch): boolean;
303
326
  /**
304
327
  * Legacy pure-controller seam retained for migration-focused tests. The
305
328
  * production browser never rolls schedules; the Host ledger owns them.
@@ -360,12 +383,13 @@ export declare class BoardController {
360
383
  */
361
384
  private mirrorDepth;
362
385
  /**
363
- * Project a Host snapshot onto the browser's mirror. SSE frames and partial
364
- * snapshots carry only the volatile subset (revision/scheduler/power), so the
365
- * deployment constants the UI reads — the session-default permission the
366
- * confirmation banner compares against and the subtask depth limit — are
367
- * carried over from the last full snapshot instead of being dropped by a
368
- * 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.
369
393
  */
370
394
  private mirrorOf;
371
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,31 @@
1
+ /**
2
+ * Roster-poll cadence: the one recurring Host timer the board holds.
3
+ *
4
+ * The cadence is a user setting (seconds), so the bounds and the seconds to
5
+ * milliseconds conversion live here rather than in either half: the settings
6
+ * card validates a draft against the same range the Host clamps a hand-edited
7
+ * configuration to, and a programmatic mount that passes no cadence gets the
8
+ * default.
9
+ *
10
+ * @module dsh-task-board/core/poll-cadence
11
+ */
12
+ /** Cadence used when neither the settings document nor the mount names one. */
13
+ export declare const DEFAULT_SESSION_POLL_SECONDS = 5;
14
+ /** Shortest accepted cadence: a faster poll only burns CPU on the Host. */
15
+ export declare const SESSION_POLL_MIN_SECONDS = 1;
16
+ /** Longest accepted cadence, five minutes. */
17
+ export declare const SESSION_POLL_MAX_SECONDS = 300;
18
+ /**
19
+ * Clamp a configured cadence into the supported range. A value the schema
20
+ * would refuse (non-finite, below the floor, above the ceiling) still leaves
21
+ * the board polling: an out-of-range setting never disables the timer.
22
+ * @param seconds - configured cadence in seconds, or undefined for the default.
23
+ * @returns the effective cadence in seconds.
24
+ */
25
+ export declare function normalizeSessionPollSeconds(seconds: number | undefined): number;
26
+ /**
27
+ * The cadence in the milliseconds the Host timer face takes.
28
+ * @param seconds - configured cadence in seconds, or undefined for the default.
29
+ * @returns the effective cadence in milliseconds.
30
+ */
31
+ export declare function sessionPollMs(seconds: 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
@@ -34,8 +34,23 @@ export interface ExecutionRecord {
34
34
  * Session id of the DSH session that issued the run/rerun action (issue #6
35
35
  * audit origin). Client-asserted, not a trust boundary; absent when the run
36
36
  * was triggered by cron (source unknown).
37
+ *
38
+ * On an EXTERNAL record (issue #1826) this names the agent that completed the
39
+ * work outside the Host instead, which is what makes the history readable.
37
40
  */
38
41
  initiatedBy?: string;
42
+ /**
43
+ * True when this outcome was recorded from outside the Host by
44
+ * `record-external-outcome` rather than observed from a DSH session
45
+ * (issue #1826).
46
+ *
47
+ * The board never manufactures one of these itself: every Host-side path
48
+ * (`run`, `settle`, cascade folding, restart recovery) writes an execution
49
+ * without it, so `external` is the single field separating work this Host ran
50
+ * from work an outside agent reported. Such a record never carries a
51
+ * `sessionId` and never claims a verdict the Host observed.
52
+ */
53
+ external?: boolean;
39
54
  /** Freeze instant captured from the card snapshot when the run opened. */
40
55
  frozenAt?: number;
41
56
  /** Freeze source session captured from the card snapshot when the run opened. */
@@ -76,17 +91,69 @@ export declare const EXECUTION_HISTORY_LIMIT = 20;
76
91
  * cannot start a new run while one is still open.
77
92
  */
78
93
  export declare function retainRecentExecutions(executions: readonly ExecutionRecord[]): ExecutionRecord[];
94
+ /**
95
+ * How a rule produces its triggers: a recurring cron rule, or one single
96
+ * planned instant. The distinction is stored rather than inferred so a
97
+ * one-shot is never represented as a date-restricted cron expression.
98
+ */
99
+ export type ScheduleMode = 'cron' | 'once';
100
+ /**
101
+ * Why a due occurrence produced no run, or why a rule stopped on its own.
102
+ *
103
+ * These are stable codes, not display text: the board renders them through its
104
+ * locale dictionary, so the ledger never carries a translated string.
105
+ */
106
+ export type ScheduleStopReason =
107
+ /** The single planned instant produced its run. */
108
+ 'fired'
109
+ /** The recurring rule reached its run budget. */
110
+ | 'limit'
111
+ /** The plan has no reachable instant any more (an impossible cron). */
112
+ | 'no-target'
113
+ /** The instant passed while the board was not running through it. */
114
+ | 'missed'
115
+ /** The task already had an open execution, so this occurrence was skipped. */
116
+ | 'busy'
117
+ /** An above-default permission was still unconfirmed, so this occurrence was skipped. */
118
+ | 'permission'
119
+ /**
120
+ * A scheduled launch failed before any session existed, so the occurrence
121
+ * created no execution and its run was refunded rather than spent.
122
+ */
123
+ | 'launch-failed';
124
+ /** Every stop reason this build understands; the repair and wire gates share it. */
125
+ export declare const SCHEDULE_STOP_REASONS: readonly ScheduleStopReason[];
79
126
  /**
80
127
  * A scheduled-run rule attached to a task. The Host scheduler triggers the
81
128
  * task when `nextRunAt` is due and persists the rule in the Host ledger.
129
+ *
130
+ * A rule is either `cron` (the historical shape: a 5-field expression with an
131
+ * explicit IANA zone, optionally capped by `maxRuns`) or `once` (one planned
132
+ * instant, `at`, with an implicit budget of one run). Both modes count the
133
+ * executions the scheduler actually started in `runCount`; a rule that reaches
134
+ * its budget stops itself in the ledger (`enabled: false` + `endedReason`)
135
+ * instead of relying on the execution agent to disable it.
82
136
  */
83
137
  export interface ScheduleRule {
84
138
  /** Whether the schedule is armed. */
85
139
  enabled: boolean;
86
- /** 5-field cron expression: `分 时 日 月 周`. */
87
- cron: string;
88
140
  /**
89
- * IANA zone the cron wall clock is read in (issue #1722). Absent means the
141
+ * Which kind of plan this rule carries. A rule loaded from a ledger written
142
+ * before the field existed normalizes to `cron` (its `cron` is the plan).
143
+ */
144
+ mode: ScheduleMode;
145
+ /**
146
+ * 5-field cron expression: `分 时 日 月 周`. Present on a `cron` rule and
147
+ * absent on a `once` rule.
148
+ */
149
+ cron?: string;
150
+ /**
151
+ * The instant a `once` rule executes (ms epoch). Kept after the run so the
152
+ * detail view can still show the planned time once the rule has ended.
153
+ */
154
+ at?: number;
155
+ /**
156
+ * IANA zone the wall clock is read in (issue #1722). Absent means the
90
157
  * Host process zone, which is what a rule written before zones were
91
158
  * persisted keeps using; the Host stamps its own zone at ledger migration so
92
159
  * a later `TZ` change cannot silently move an existing rule.
@@ -96,7 +163,51 @@ export interface ScheduleRule {
96
163
  nextRunAt: number | undefined;
97
164
  /** Instant of the latest scheduled trigger (ms epoch). */
98
165
  lastTriggeredAt: number | undefined;
166
+ /**
167
+ * Scheduled executions the Host has created under this rule. This is the
168
+ * run budget's counter: it counts executions that were successfully opened
169
+ * (whatever their later outcome), never completions, and a manual run never
170
+ * touches it. A skipped occurrence (busy / permission / missed / no target)
171
+ * creates no execution and therefore consumes nothing.
172
+ */
173
+ runCount: number;
174
+ /**
175
+ * Maximum scheduled executions for a `cron` rule; absent means unlimited
176
+ * (the historical behavior). A `once` rule has an implicit budget of one and
177
+ * stores no `maxRuns`.
178
+ */
179
+ maxRuns?: number;
180
+ /** Instant the rule stopped itself (ms epoch); absent while it may still fire. */
181
+ endedAt?: number;
182
+ /** Why the rule stopped itself; present with `endedAt`. */
183
+ endedReason?: ScheduleStopReason;
184
+ /** Instant of the latest occurrence that produced no run. */
185
+ skippedAt?: number;
186
+ /** Why that occurrence produced no run; present with `skippedAt`. */
187
+ skippedReason?: ScheduleStopReason;
99
188
  }
189
+ /**
190
+ * The rule's total run budget: one for a one-shot, the stored cap for a
191
+ * recurring rule, and undefined when the rule is unlimited.
192
+ * @param rule - the schedule rule.
193
+ * @returns the budget, or undefined for an uncapped rule.
194
+ */
195
+ export declare function scheduleRunBudget(rule: ScheduleRule): number | undefined;
196
+ /**
197
+ * Whether a rule has no runs left. The scheduler refuses to open another
198
+ * execution for an exhausted rule, which is what makes the budget terminal
199
+ * regardless of restarts or repeated timer fires.
200
+ * @param rule - the schedule rule.
201
+ * @returns true when the run budget is spent.
202
+ */
203
+ export declare function scheduleExhausted(rule: ScheduleRule): boolean;
204
+ /**
205
+ * The instant a rule plans to fire next, for display, whatever its mode: the
206
+ * planned `at` of a one-shot, or the stored `nextRunAt` of a recurring rule.
207
+ * @param rule - the schedule rule.
208
+ * @returns the planned instant, or undefined when the rule carries none.
209
+ */
210
+ export declare function schedulePlannedAt(rule: ScheduleRule): number | undefined;
100
211
  /**
101
212
  * Frozen context snapshot carried by a continuation card (issue #4): the
102
213
  * goal/progress/next text (already sanitized by the freeze gates) plus the
@@ -214,6 +325,15 @@ export interface TaskRecord {
214
325
  * absent falls back to the host default (agent-default-model).
215
326
  */
216
327
  model?: string;
328
+ /**
329
+ * Reasoning effort pinned for that execution route (an adapter-owned id such
330
+ * as "high"). Absent (or blank) means INHERIT: the session runs at the
331
+ * model's own default level, exactly as a card written before the field
332
+ * existed does. The launch sends the value only when the target model's
333
+ * adapter declares that exact id, so switching a card to a model without
334
+ * that level never pushes an invalid selection (see core/reasoning-effort.ts).
335
+ */
336
+ reasoningEffort?: string;
217
337
  /**
218
338
  * Whether later executions continue in the previous execution's session
219
339
  * (issue #1419) instead of minting a fresh conversation per run. Absent or
@@ -236,9 +356,11 @@ export interface TaskRecord {
236
356
  * armed with the composed execution prompt as its objective. The session then
237
357
  * keeps working automatic continuation rounds until the agent marks the goal
238
358
  * complete, and this board settles the execution on the goal's own end
239
- * instead of at the first turn end. Absent means ON — the option is checked
240
- * by default, so only an explicit `false` runs the task as a single plain
241
- * turn. A session whose `/goal` command is refused still runs the prompt.
359
+ * instead of at the first turn end. Absent means OFF — the option is
360
+ * unchecked by default, so only an explicit `true` joins the goal run and
361
+ * every card that never touched it (including every card written before the
362
+ * field existed) runs one plain turn. A session whose `/goal` command is
363
+ * refused still runs the prompt.
242
364
  */
243
365
  goalRun?: boolean;
244
366
  /**
@@ -333,13 +455,23 @@ export interface NewTaskInput {
333
455
  permission?: TaskPermission;
334
456
  /** Optional pinned model for the execution session; absent = host default. */
335
457
  model?: string;
458
+ /** Optional reasoning effort for that model route; empty/absent = the model's own default. */
459
+ reasoningEffort?: string;
460
+ /**
461
+ * Opt in to the deployment's new-card defaults: a target this input leaves
462
+ * out is filled from the configured default (agent preset, permission, model
463
+ * and the level bound to that model). Absent means the create stores exactly
464
+ * what it was given, which is what the extension provider's own create path
465
+ * and the legacy import rely on.
466
+ */
467
+ applyCardDefaults?: boolean;
336
468
  /** Reuse the previous execution's session for later runs (issue #1419). */
337
469
  reuseSession?: boolean;
338
470
  /** Run the subtree as an Agent Team (Team Lead session plus one teammate per direct subtask). */
339
471
  teamRun?: boolean;
340
472
  /**
341
- * Start the execution with dsh's built-in `/goal`. Absent/true keeps the
342
- * default (goal run); an explicit false requests a single plain turn.
473
+ * Start the execution with dsh's built-in `/goal`. Absent means the default
474
+ * (one plain turn); only an explicit true joins the goal run.
343
475
  */
344
476
  goalRun?: boolean;
345
477
  /**
@@ -348,15 +480,21 @@ export interface NewTaskInput {
348
480
  */
349
481
  skipVerification?: boolean;
350
482
  /**
351
- * Optional scheduled-run rule requested at creation time (the new-task
352
- * dialog): an enable flag, a 5-field cron expression, and the IANA zone its
353
- * wall clock is read in (absent means the Host zone). The create use case
354
- * arms it only when enabled and the expression is valid.
483
+ * Optional scheduling rule requested at creation time (the new-task
484
+ * dialog): an enable flag, the kind of plan (a recurring 5-field cron
485
+ * expression, or one planned instant for a one-shot), an optional run cap,
486
+ * and the IANA zone its wall clock is read in (absent means the Host zone).
487
+ * The create use case arms it only when enabled and the plan is valid.
355
488
  */
356
489
  schedule?: {
357
490
  enabled: boolean;
358
- cron: string;
491
+ mode?: ScheduleMode;
492
+ cron?: string;
493
+ /** Planned instant of a one-shot rule (ms epoch). */
494
+ at?: number;
359
495
  timeZone?: string;
496
+ /** Run cap of a recurring rule; absent means unlimited. */
497
+ maxRuns?: number;
360
498
  };
361
499
  /**
362
500
  * Optional frozen context snapshot (goal/progress/next, sanitized by the
@@ -448,7 +586,8 @@ export declare function withStatus(task: TaskRecord, status: TaskStatus, now: nu
448
586
  * Merge a schedule patch into a task's schedule rule (creating it when
449
587
  * absent), with a fresh updatedAt. Keys present in the patch overwrite the
450
588
  * current value — including explicit `undefined`, which clears a field (used
451
- * to disarm `nextRunAt`); absent keys keep their current value.
589
+ * to disarm `nextRunAt` or to drop an ended/skip record); absent keys keep
590
+ * their current value.
452
591
  */
453
592
  export declare function withSchedule(task: TaskRecord, patch: Partial<ScheduleRule>, now: number): TaskRecord;
454
593
  /**
@@ -461,6 +600,16 @@ export declare function startExecution(task: TaskRecord, now: number, executionI
461
600
  task: TaskRecord;
462
601
  execution: ExecutionRecord;
463
602
  };
603
+ /**
604
+ * The column a settled outcome puts a card in. The ONE derivation shared by
605
+ * every settlement path (issue #1826): a Host-run execution and an outcome an
606
+ * outside agent reported both land in the same column from the same rule, so
607
+ * the column never becomes a second, independent piece of state.
608
+ @param task - the card being settled.
609
+ @param outcome - the settled outcome.
610
+ @returns the status the card shows afterwards.
611
+ */
612
+ export declare function settledStatus(task: TaskRecord, outcome: ExecutionOutcome): TaskStatus;
464
613
  /**
465
614
  * Settle a running execution: record the outcome and move the task into the
466
615
  * matching column. No-op (returns the input task) when the execution is not
@@ -1,3 +1,4 @@
1
+ import { type CardDefaults } from '../card-defaults.ts';
1
2
  import { type NewTaskInput, type TaskRecord } from '../tasks.ts';
2
3
  /** Result of a create transition: the new task (when accepted) + the next ledger. */
3
4
  export interface CreateTaskResult {
@@ -13,15 +14,19 @@ export interface CreateTaskResult {
13
14
  * appended ledger, or the unchanged ledger when the title is blank or the
14
15
  * requested parent link is refused.
15
16
  *
16
- * A subtask inherits the execution targets its parent already pins (workspace,
17
- * agent preset, permission, model) unless the form supplied its own; the
18
- * parent human permission confirmation travels with an inherited permission,
19
- * so a subtask of an already-confirmed elevated card is runnable at once.
17
+ * The execution targets resolve through {@link resolveCardTargets}: what the
18
+ * caller sent wins, then the parent's own pins, then - only when the caller
19
+ * opted in with `applyCardDefaults` - the deployment's configured new-card
20
+ * defaults. Without the flag the resolution sees no defaults at all, which is
21
+ * exactly the inline parent inheritance this path has always applied. The
22
+ * workspace is not part of that resolution: a subtask still takes the parent's
23
+ * workspace, and the defaults never name one.
20
24
  * @param tasks - current ledger.
21
25
  * @param input - raw user input (title/description/prompt + optional schedule/parent).
22
26
  * @param now - clock instant (ms epoch).
23
27
  * @param id - minted task id.
24
28
  * @param maxSubtaskDepth - deployment subtask depth limit.
25
29
  * @param hostTimeZone - zone a rule that stores no zone of its own follows.
30
+ * @param cardDefaults - the deployment's new-card defaults, read live at creation.
26
31
  */
27
- export declare function applyCreateTask(tasks: readonly TaskRecord[], input: NewTaskInput, now: number, id: string, maxSubtaskDepth?: number, hostTimeZone?: string): CreateTaskResult;
32
+ export declare function applyCreateTask(tasks: readonly TaskRecord[], input: NewTaskInput, now: number, id: string, maxSubtaskDepth?: number, hostTimeZone?: string, cardDefaults?: CardDefaults): CreateTaskResult;