@linxin666/dsh-client-ui-task-board 0.4.4 → 0.4.6

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 (120) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +59 -20
  3. package/README.zh.md +59 -20
  4. package/lib/client.js +6208 -624
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +4774 -493
  7. package/lib/types/client/PluginSettingsCard.d.ts +7 -0
  8. package/lib/types/client/TaskBoardSettingsCard.d.ts +31 -3
  9. package/lib/types/client/board/ClampedMarkdown.d.ts +3 -0
  10. package/lib/types/client/board/ConfirmDialog.d.ts +4 -1
  11. package/lib/types/client/board/EditTaskModal.d.ts +5 -2
  12. package/lib/types/client/board/LinkSubtaskModal.d.ts +5 -2
  13. package/lib/types/client/board/NewTaskModal.d.ts +4 -1
  14. package/lib/types/client/board/TagManagerModal.d.ts +26 -0
  15. package/lib/types/client/board/TaskBoard.d.ts +10 -1
  16. package/lib/types/client/board/TaskCard.d.ts +14 -1
  17. package/lib/types/client/board/TaskDetail.d.ts +3 -1
  18. package/lib/types/client/board/TaskForm.d.ts +4 -1
  19. package/lib/types/client/board/VerificationReport.d.ts +23 -0
  20. package/lib/types/client/board/card-view.d.ts +113 -0
  21. package/lib/types/client/board/icons.d.ts +32 -0
  22. package/lib/types/client/board/overlay.d.ts +69 -0
  23. package/lib/types/client/board/status-key.d.ts +15 -0
  24. package/lib/types/client/board/task-markdown.d.ts +19 -0
  25. package/lib/types/client/index.d.ts +23 -5
  26. package/lib/types/client/locales.d.ts +168 -1
  27. package/lib/types/client/native-panel.d.ts +6 -20
  28. package/lib/types/client/plugin-card-seat.d.ts +5 -0
  29. package/lib/types/client/schedule-zone.d.ts +50 -0
  30. package/lib/types/client/seats.d.ts +18 -0
  31. package/lib/types/client/service.d.ts +32 -0
  32. package/lib/types/client/settings-form.d.ts +16 -0
  33. package/lib/types/core/controller.d.ts +46 -11
  34. package/lib/types/core/extension.d.ts +283 -0
  35. package/lib/types/core/handover.d.ts +13 -1
  36. package/lib/types/core/issue-form.d.ts +16 -0
  37. package/lib/types/core/poll-cadence.d.ts +31 -0
  38. package/lib/types/core/tasks.d.ts +228 -17
  39. package/lib/types/core/use-cases/task-schedule.d.ts +68 -10
  40. package/lib/types/core/use-cases/task-tag.d.ts +48 -0
  41. package/lib/types/core/use-cases/task-update.d.ts +5 -3
  42. package/lib/types/core/verification-budget.d.ts +52 -0
  43. package/lib/types/core/verification.d.ts +753 -0
  44. package/lib/types/core/workspace-target.d.ts +41 -0
  45. package/lib/types/host/agent-tools.d.ts +1 -1
  46. package/lib/types/host/extension-registry.d.ts +111 -0
  47. package/lib/types/host/llm-dispatch.d.ts +63 -0
  48. package/lib/types/host/verification-gate.d.ts +95 -0
  49. package/lib/types/host/verification-runner.d.ts +139 -0
  50. package/lib/types/host/workspace-evidence.d.ts +53 -0
  51. package/lib/types/host-ledger.d.ts +133 -14
  52. package/lib/types/host-runner.d.ts +55 -10
  53. package/lib/types/host-service.d.ts +205 -11
  54. package/lib/types/index.d.ts +134 -26
  55. package/lib/types/protocol.d.ts +92 -10
  56. package/lib/types/tool-surface.d.ts +71 -0
  57. package/package.json +28 -31
  58. package/src/client/PluginSettingsCard.tsx +9 -0
  59. package/src/client/TaskBoardSettingsCard.tsx +378 -66
  60. package/src/client/board/ClampedMarkdown.tsx +69 -0
  61. package/src/client/board/ConfirmDialog.tsx +7 -3
  62. package/src/client/board/EditTaskModal.tsx +5 -2
  63. package/src/client/board/LinkSubtaskModal.tsx +12 -7
  64. package/src/client/board/NewTaskModal.tsx +168 -21
  65. package/src/client/board/TagManagerModal.tsx +235 -0
  66. package/src/client/board/TaskBoard.tsx +259 -103
  67. package/src/client/board/TaskCard.tsx +214 -81
  68. package/src/client/board/TaskDetail.tsx +289 -43
  69. package/src/client/board/TaskForm.tsx +13 -3
  70. package/src/client/board/VerificationReport.tsx +322 -0
  71. package/src/client/board/card-view.ts +220 -0
  72. package/src/client/board/icons.tsx +89 -0
  73. package/src/client/board/overlay.tsx +189 -0
  74. package/src/client/board/status-key.ts +30 -0
  75. package/src/client/board/task-markdown.module.css +137 -0
  76. package/src/client/board/task-markdown.tsx +106 -0
  77. package/src/client/board-settings.module.css +66 -0
  78. package/src/client/board.module.css +488 -63
  79. package/src/client/index.ts +82 -51
  80. package/src/client/locales.ts +343 -6
  81. package/src/client/native-panel.tsx +18 -2
  82. package/src/client/plugin-card-seat.ts +7 -0
  83. package/src/client/schedule-zone.ts +100 -2
  84. package/src/client/seats.tsx +58 -0
  85. package/src/client/service.ts +94 -0
  86. package/src/client/settings-card.module.css +31 -5
  87. package/src/client/settings-form.ts +27 -1
  88. package/src/core/controller.ts +105 -10
  89. package/src/core/extension.ts +376 -0
  90. package/src/core/freeze-snapshot.ts +2 -2
  91. package/src/core/handover.ts +16 -1
  92. package/src/core/issue-form.ts +19 -0
  93. package/src/core/poll-cadence.ts +41 -0
  94. package/src/core/store.ts +80 -15
  95. package/src/core/tasks.ts +293 -45
  96. package/src/core/use-cases/task-create.ts +30 -9
  97. package/src/core/use-cases/task-parent.ts +3 -2
  98. package/src/core/use-cases/task-schedule.ts +190 -25
  99. package/src/core/use-cases/task-tag.ts +110 -0
  100. package/src/core/use-cases/task-update.ts +13 -7
  101. package/src/core/verification-budget.ts +115 -0
  102. package/src/core/verification.ts +1594 -0
  103. package/src/core/workspace-target.ts +59 -0
  104. package/src/host/agent-tools.ts +125 -38
  105. package/src/host/extension-registry.ts +509 -0
  106. package/src/host/llm-dispatch.ts +87 -0
  107. package/src/host/verification-gate.ts +487 -0
  108. package/src/host/verification-runner.ts +702 -0
  109. package/src/host/workspace-evidence.ts +176 -0
  110. package/src/host-ai.ts +15 -9
  111. package/src/host-ledger.ts +468 -71
  112. package/src/host-routes.ts +15 -2
  113. package/src/host-runner.ts +106 -24
  114. package/src/host-service.ts +546 -40
  115. package/src/index.ts +305 -21
  116. package/src/protocol.ts +226 -28
  117. package/src/tool-surface.ts +95 -0
  118. package/lib/invariant.js +0 -6
  119. package/lib/types/invariant.d.ts +0 -3
  120. package/src/invariant.ts +0 -4
@@ -5,7 +5,9 @@
5
5
  * unit-testable in isolation.
6
6
  */
7
7
  import type { FreezeSnapshot } from './freeze-snapshot.ts';
8
+ import type { ExecutionVerification } from './verification.ts';
8
9
  import type { TaskHandover, TaskHandoverInput } from './handover.ts';
10
+ import { type TaskBoardExtensionPayload } from './extension.ts';
9
11
  /** Task lifecycle status, one per kanban column. */
10
12
  export type TaskStatus = 'backlog' | 'todo' | 'running' | 'done' | 'failed';
11
13
  /** Settled outcome of one execution attempt. */
@@ -32,8 +34,23 @@ export interface ExecutionRecord {
32
34
  * Session id of the DSH session that issued the run/rerun action (issue #6
33
35
  * audit origin). Client-asserted, not a trust boundary; absent when the run
34
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.
35
40
  */
36
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;
37
54
  /** Freeze instant captured from the card snapshot when the run opened. */
38
55
  frozenAt?: number;
39
56
  /** Freeze source session captured from the card snapshot when the run opened. */
@@ -52,6 +69,14 @@ export interface ExecutionRecord {
52
69
  ownResult?: ExecutionOutcome;
53
70
  /** Human failure text that arrived with {@link ownResult}. */
54
71
  ownError?: string;
72
+ /**
73
+ * Goal acceptance state of this execution (task-board goal verification).
74
+ * Fixed when the execution starts and never rewritten from later settings:
75
+ * the contract is frozen here, the attempts accrue here, and the settlement
76
+ * rule reads the pass record here. Absent on an execution that started
77
+ * before the feature existed, which keeps it on the historical verdict.
78
+ */
79
+ verification?: ExecutionVerification;
55
80
  }
56
81
  /**
57
82
  * Maximum number of execution records retained per task. Older settled runs
@@ -66,17 +91,69 @@ export declare const EXECUTION_HISTORY_LIMIT = 20;
66
91
  * cannot start a new run while one is still open.
67
92
  */
68
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[];
69
126
  /**
70
127
  * A scheduled-run rule attached to a task. The Host scheduler triggers the
71
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.
72
136
  */
73
137
  export interface ScheduleRule {
74
138
  /** Whether the schedule is armed. */
75
139
  enabled: boolean;
76
- /** 5-field cron expression: `分 时 日 月 周`. */
77
- cron: string;
78
140
  /**
79
- * 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
80
157
  * Host process zone, which is what a rule written before zones were
81
158
  * persisted keeps using; the Host stamps its own zone at ledger migration so
82
159
  * a later `TZ` change cannot silently move an existing rule.
@@ -86,7 +163,51 @@ export interface ScheduleRule {
86
163
  nextRunAt: number | undefined;
87
164
  /** Instant of the latest scheduled trigger (ms epoch). */
88
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;
89
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;
90
211
  /**
91
212
  * Frozen context snapshot carried by a continuation card (issue #4): the
92
213
  * goal/progress/next text (already sanitized by the freeze gates) plus the
@@ -226,11 +347,27 @@ export interface TaskRecord {
226
347
  * armed with the composed execution prompt as its objective. The session then
227
348
  * keeps working automatic continuation rounds until the agent marks the goal
228
349
  * complete, and this board settles the execution on the goal's own end
229
- * instead of at the first turn end. Absent means ON — the option is checked
230
- * by default, so only an explicit `false` runs the task as a single plain
231
- * turn. A session whose `/goal` command is refused still runs the prompt.
350
+ * instead of at the first turn end. Absent means OFF — the option is
351
+ * unchecked by default, so only an explicit `true` joins the goal run and
352
+ * every card that never touched it (including every card written before the
353
+ * field existed) runs one plain turn. A session whose `/goal` command is
354
+ * refused still runs the prompt.
232
355
  */
233
356
  goalRun?: boolean;
357
+ /**
358
+ * Run this task's goal executions WITHOUT the board's acceptance gate.
359
+ *
360
+ * Absent means INHERIT: the execution's applicability is decided by the
361
+ * board-wide `goalVerification` switch frozen at start, exactly as a card
362
+ * that never touched this option behaves. Only an explicit `true` opts the
363
+ * card out, and the execution records `applicability: 'disabled'` so the
364
+ * report says plainly that nothing certified this run.
365
+ *
366
+ * The opt-out is per execution, not per run: flipping it never rewrites the
367
+ * verdict rule of an execution already open, and a rerun or a scheduled
368
+ * occurrence reads the card's current value when it freezes its contract.
369
+ */
370
+ skipVerification?: boolean;
234
371
  /**
235
372
  * Frozen context snapshot for a continuation card; absent on plain tasks.
236
373
  * Sanitized before it enters the ledger (redaction, slash-command taint,
@@ -262,6 +399,20 @@ export interface TaskRecord {
262
399
  * absent means on-board.
263
400
  */
264
401
  archivedAt?: number;
402
+ /**
403
+ * Opaque external-provider metadata, keyed by extension id; optional and
404
+ * additive so existing tasks require no ledger migration. The board stores
405
+ * and forwards it without interpreting the keys — each provider owns the
406
+ * shape and validation of its own entry.
407
+ */
408
+ integrations?: Record<string, TaskBoardExtensionPayload>;
409
+ /**
410
+ * Whether the card is held off the board: it exists in the ledger (and its
411
+ * execution history is intact) but no column ever renders it. Providers
412
+ * create hidden cards for work they materialize but do not want displayed;
413
+ * absent means on-board.
414
+ */
415
+ hidden?: boolean;
265
416
  }
266
417
  /**
267
418
  * Statuses a task may be archived from: every status but `running`, whose
@@ -287,7 +438,7 @@ export interface NewTaskInput {
287
438
  * inherits the parent's unset execution targets.
288
439
  */
289
440
  parentId?: string;
290
- /** Workspace the execution must run in; empty/absent = the recent workspace. */
441
+ /** Workspace the execution must run in; empty/absent = the creating session's workspace for a root task, else the recent workspace at execution time. */
291
442
  workspaceId?: string;
292
443
  /** Agent preset the execution session must be composed from; empty/absent = deployment default. */
293
444
  mode?: string;
@@ -300,20 +451,31 @@ export interface NewTaskInput {
300
451
  /** Run the subtree as an Agent Team (Team Lead session plus one teammate per direct subtask). */
301
452
  teamRun?: boolean;
302
453
  /**
303
- * Start the execution with dsh's built-in `/goal`. Absent/true keeps the
304
- * default (goal run); an explicit false requests a single plain turn.
454
+ * Start the execution with dsh's built-in `/goal`. Absent means the default
455
+ * (one plain turn); only an explicit true joins the goal run.
305
456
  */
306
457
  goalRun?: boolean;
307
458
  /**
308
- * Optional scheduled-run rule requested at creation time (the new-task
309
- * dialog): an enable flag, a 5-field cron expression, and the IANA zone its
310
- * wall clock is read in (absent means the Host zone). The create use case
311
- * arms it only when enabled and the expression is valid.
459
+ * Run this task's goal executions without the acceptance gate; absent
460
+ * inherits the board-wide `goalVerification` switch.
461
+ */
462
+ skipVerification?: boolean;
463
+ /**
464
+ * Optional scheduling rule requested at creation time (the new-task
465
+ * dialog): an enable flag, the kind of plan (a recurring 5-field cron
466
+ * expression, or one planned instant for a one-shot), an optional run cap,
467
+ * and the IANA zone its wall clock is read in (absent means the Host zone).
468
+ * The create use case arms it only when enabled and the plan is valid.
312
469
  */
313
470
  schedule?: {
314
471
  enabled: boolean;
315
- cron: string;
472
+ mode?: ScheduleMode;
473
+ cron?: string;
474
+ /** Planned instant of a one-shot rule (ms epoch). */
475
+ at?: number;
316
476
  timeZone?: string;
477
+ /** Run cap of a recurring rule; absent means unlimited. */
478
+ maxRuns?: number;
317
479
  };
318
480
  /**
319
481
  * Optional frozen context snapshot (goal/progress/next, sanitized by the
@@ -333,13 +495,31 @@ export interface NewTaskInput {
333
495
  * ahead of the execution prompt; a bare name changes nothing at run time.
334
496
  */
335
497
  tags?: TaskTag[];
498
+ /** Opaque external-provider metadata keyed by extension id. */
499
+ integrations?: Record<string, TaskBoardExtensionPayload>;
500
+ /** Column the created card opens in; absent means the board default (todo). */
501
+ status?: TaskStatus;
502
+ /** Create the card off-board (held in the ledger, never rendered). */
503
+ hidden?: boolean;
336
504
  }
337
505
  /** The five kanban columns, in display order. */
338
506
  export declare const COLUMNS: readonly {
339
507
  status: TaskStatus;
340
508
  label: string;
341
509
  }[];
342
- /** Statuses a user may move a card to manually (execution states are owned by the runner). */
510
+ /**
511
+ * Statuses a card may be moved to by hand: every column. The board UI and the
512
+ * agent tool surface share this one list, so no column can be manual on one
513
+ * surface and locked on the other.
514
+ *
515
+ * The column is a board statement, not a claim about a run. A hand-written
516
+ * `done`/`failed` declares that the work finished (or failed) outside a
517
+ * Host-run execution — human hands, an external system — and a hand-written
518
+ * `running` says the work is under way without a tracked session. None of them
519
+ * creates an {@link ExecutionRecord}, which is what keeps a declaration
520
+ * distinguishable from a recorded outcome in the card's execution history; the
521
+ * next settled run overwrites the column with the real one.
522
+ */
343
523
  export declare const MANUAL_STATUSES: readonly TaskStatus[];
344
524
  /** Statuses the runner may move a card to from 'running'. */
345
525
  export declare const RUNNER_SETTLE_STATUSES: readonly TaskStatus[];
@@ -347,8 +527,28 @@ export declare const RUNNER_SETTLE_STATUSES: readonly TaskStatus[];
347
527
  export declare const ALL_STATUSES: readonly TaskStatus[];
348
528
  /** Brand an unknown string as a status; undefined when it is not one. */
349
529
  export declare function isTaskStatus(value: unknown): value is TaskStatus;
350
- /** Whether a manual move target is allowed from the given status. */
530
+ /**
531
+ * Whether the manual move `from` -> `to` is a legal *column* change: any
532
+ * column, and never the one the card already shows.
533
+ *
534
+ * Whether the card may be moved at all is a separate question. The lock an
535
+ * executing card carries belongs to its open execution, never to the column
536
+ * text, so it is {@link canMoveTask} — not this predicate — that expresses it.
537
+ */
351
538
  export declare function canMoveManually(from: TaskStatus, to: TaskStatus): boolean;
539
+ /**
540
+ * Whether the board may move `task` to `to` by hand: the card is on-board,
541
+ * holds no open execution, and the target is a legal column change. The ledger,
542
+ * the detail view's status buttons and the board's drop handler all decide
543
+ * through this one predicate, so no two surfaces can disagree about a card.
544
+ */
545
+ export declare function canMoveTask(task: TaskRecord, to: TaskStatus): boolean;
546
+ /**
547
+ * Whether the task still has an execution the runner has not settled. This —
548
+ * not the `running` column — is what "the runner owns this card" means: a card
549
+ * parked in `running` by hand has no session and stays fully movable.
550
+ */
551
+ export declare function hasOpenExecution(task: TaskRecord): boolean;
352
552
  /** Normalize one optional execution-target string: trim; blank collapses to undefined. */
353
553
  export declare function normalizeTargetId(value: string | undefined): string | undefined;
354
554
  /**
@@ -367,7 +567,8 @@ export declare function withStatus(task: TaskRecord, status: TaskStatus, now: nu
367
567
  * Merge a schedule patch into a task's schedule rule (creating it when
368
568
  * absent), with a fresh updatedAt. Keys present in the patch overwrite the
369
569
  * current value — including explicit `undefined`, which clears a field (used
370
- * to disarm `nextRunAt`); absent keys keep their current value.
570
+ * to disarm `nextRunAt` or to drop an ended/skip record); absent keys keep
571
+ * their current value.
371
572
  */
372
573
  export declare function withSchedule(task: TaskRecord, patch: Partial<ScheduleRule>, now: number): TaskRecord;
373
574
  /**
@@ -380,6 +581,16 @@ export declare function startExecution(task: TaskRecord, now: number, executionI
380
581
  task: TaskRecord;
381
582
  execution: ExecutionRecord;
382
583
  };
584
+ /**
585
+ * The column a settled outcome puts a card in. The ONE derivation shared by
586
+ * every settlement path (issue #1826): a Host-run execution and an outcome an
587
+ * outside agent reported both land in the same column from the same rule, so
588
+ * the column never becomes a second, independent piece of state.
589
+ @param task - the card being settled.
590
+ @param outcome - the settled outcome.
591
+ @returns the status the card shows afterwards.
592
+ */
593
+ export declare function settledStatus(task: TaskRecord, outcome: ExecutionOutcome): TaskStatus;
383
594
  /**
384
595
  * Settle a running execution: record the outcome and move the task into the
385
596
  * matching column. No-op (returns the input task) when the execution is not
@@ -1,23 +1,37 @@
1
- import { type TaskRecord } from '../tasks.ts';
1
+ import { type ScheduleMode, type ScheduleStopReason, type TaskRecord } from '../tasks.ts';
2
2
  /** Fields the schedule use case may change on a rule. */
3
3
  export interface SetSchedulePatch {
4
4
  enabled?: boolean;
5
+ /** Which kind of plan to write; an absent key keeps the current mode. */
6
+ mode?: ScheduleMode;
7
+ /** 5-field cron expression of a recurring rule. */
5
8
  cron?: string;
6
- /** IANA zone for the cron wall clock; `null` clears it back to the Host zone. */
9
+ /** Planned instant of a one-shot rule (ms epoch). */
10
+ at?: number;
11
+ /** IANA zone for the wall clock; `null` clears it back to the Host zone. */
7
12
  timeZone?: string | null;
13
+ /** Run cap of a recurring rule; `null` clears it back to unlimited. */
14
+ maxRuns?: number | null;
8
15
  }
9
16
  /** Result of arming/disarming a rule. */
10
17
  export interface SetScheduleResult {
11
18
  /** The next ledger; unchanged reference when rejected. */
12
19
  tasks: readonly TaskRecord[];
13
- /** Whether the rule was applied (false on unknown task / invalid cron). */
20
+ /** Whether the rule was applied (false on unknown task / invalid plan). */
14
21
  applied: boolean;
15
22
  }
16
23
  /**
17
- * Set an on-board task's schedule rule. A blank or invalid cron, an unknown or
18
- * archived task, or an unusable zone is rejected (state untouched); an enabled
19
- * rule computes the next run instant immediately in the rule's own zone, a
20
- * disabled one carries no next-run instant.
24
+ * Set an on-board task's scheduling rule. An unknown or archived task, an
25
+ * unusable zone, a blank or invalid cron, a one-shot with no usable instant,
26
+ * a one-shot armed in the past, and a run cap that is not a positive integer
27
+ * are all rejected (state untouched). An enabled rule computes its next run
28
+ * instant immediately — a one-shot's next instant IS its planned instant — and
29
+ * a disabled rule carries none.
30
+ *
31
+ * Arming a rule that was not enabled, or switching its kind, starts a fresh
32
+ * budget: the run counter and any previous stop/skip record are cleared. Edits
33
+ * to a live rule (expression, zone, cap) keep the consumed count, so an
34
+ * already-spent budget can never silently reset and overrun.
21
35
  * @param tasks - current ledger.
22
36
  * @param id - the task to schedule.
23
37
  * @param patch - rule fields to change (absent fields keep their current value).
@@ -26,9 +40,8 @@ export interface SetScheduleResult {
26
40
  */
27
41
  export declare function applySetSchedule(tasks: readonly TaskRecord[], id: string, patch: SetSchedulePatch, now: number, hostTimeZone: string): SetScheduleResult;
28
42
  /**
29
- * Roll a task's schedule rule forward (scheduler callback): persist the next
30
- * due instant and the trigger instant. No-op for tasks without a rule (deleted
31
- * mid-tick, for example).
43
+ * Roll a task's schedule rule forward (legacy pure-controller seam): persist
44
+ * the next due instant and the trigger instant. No-op for tasks without a rule.
32
45
  * @param tasks - current ledger.
33
46
  * @param id - the task to roll forward.
34
47
  * @param nextRunAt - next due instant (may be undefined to clear).
@@ -36,3 +49,48 @@ export declare function applySetSchedule(tasks: readonly TaskRecord[], id: strin
36
49
  * @param now - clock instant (ms epoch).
37
50
  */
38
51
  export declare function applyScheduleNextRun(tasks: readonly TaskRecord[], id: string, nextRunAt: number | undefined, lastTriggeredAt: number | undefined, now: number): readonly TaskRecord[];
52
+ /**
53
+ * The effect one due occurrence has on its rule, applied by the Host ledger.
54
+ */
55
+ export interface ScheduleProgress {
56
+ /** Executions this occurrence actually opened (0 or 1), added to the counter. */
57
+ consumed: number;
58
+ /** Next due instant; absent stops the rule. */
59
+ nextRunAt?: number;
60
+ /** Trigger instant recorded as `lastTriggeredAt` (a consumed run only). */
61
+ lastTriggeredAt?: number;
62
+ /** Terminal reason when the rule stops itself (disarms it). */
63
+ endedReason?: ScheduleStopReason;
64
+ /** Why this occurrence produced no run, when it did not. */
65
+ skippedReason?: ScheduleStopReason;
66
+ }
67
+ /**
68
+ * Refund the one scheduled run an occurrence consumed before its launch failed
69
+ * without ever creating a session. Nothing ran, so the run budget is restored
70
+ * and the rule is left able to serve its next occurrence — a launch failure
71
+ * must never silently spend the user's limited budget.
72
+ *
73
+ * A recurring rule keeps its already-rolled-forward target; a fire that ended
74
+ * the rule at its cap (no target left) is restored and re-armed at the next
75
+ * occurrence, so a failed launch consumes nothing. A one-shot has no later
76
+ * occurrence, so it is left stopped with `launch-failed` and its run returned.
77
+ * A rule the user disabled in the meantime is not resurrected.
78
+ * @param tasks - current ledger.
79
+ * @param id - the task whose occurrence is being refunded.
80
+ * @param now - clock instant (ms epoch).
81
+ * @param hostTimeZone - zone a rule with no stored zone follows.
82
+ * @returns the next ledger.
83
+ */
84
+ export declare function applyScheduleRefund(tasks: readonly TaskRecord[], id: string, now: number, hostTimeZone: string): readonly TaskRecord[];
85
+ /**
86
+ * Record what a due occurrence did to a task's rule: advance the run counter
87
+ * by the executions it opened, set the next target (or stop the rule), and
88
+ * note a terminal reason or a skipped occurrence. Nothing is written for a
89
+ * task that has no rule or has been archived mid-tick.
90
+ * @param tasks - current ledger.
91
+ * @param id - the task whose rule fired.
92
+ * @param progress - the occurrence's effect.
93
+ * @param now - clock instant (ms epoch).
94
+ * @returns the next ledger.
95
+ */
96
+ export declare function applyScheduleProgress(tasks: readonly TaskRecord[], id: string, progress: ScheduleProgress, now: number): readonly TaskRecord[];
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Label-management use cases (issue: labels could not be cleaned up).
3
+ *
4
+ * A label lives on every task that carries it, so renaming or removing one is a
5
+ * ledger-wide edit, not a task edit: the board's label row is the union of the
6
+ * labels in use, and without these two transitions a label created by an
7
+ * afternoon experiment stayed on the board forever.
8
+ *
9
+ * Both transitions are pure ledger rewrites (no persistence, no notify — the
10
+ * controller and the Host ledger orchestrate those), and both are idempotent:
11
+ * a label that no task carries changes nothing and reports no error for the
12
+ * delete path (the desired end state already holds), while a rename from an
13
+ * unknown label is refused so a stale tab cannot silently do nothing.
14
+ *
15
+ * Renaming onto a name that is already in use MERGES the two: a task carrying
16
+ * both keeps the row it already had (with its execution hint) and loses the
17
+ * duplicate, which is the only reading that cannot lose a hint the user typed.
18
+ */
19
+ import { type TaskRecord } from '../tasks.ts';
20
+ /** Outcome of one ledger-wide label edit. */
21
+ export interface TagEditResult {
22
+ /** The ledger after the edit, or the input unchanged when nothing applied. */
23
+ tasks: readonly TaskRecord[];
24
+ /** Whether any task actually changed (drives whether the caller persists). */
25
+ changed: boolean;
26
+ /** Refusal reason; the ledger is unchanged when set. */
27
+ error?: string;
28
+ }
29
+ /**
30
+ * Rename one label everywhere, merging into an existing label of the target
31
+ * name. Every touched task gets a fresh updatedAt; untouched tasks keep theirs.
32
+ * @param tasks - current ledger.
33
+ * @param from - the label to rename.
34
+ * @param to - the new name (an existing name merges the two labels).
35
+ * @param now - clock instant (ms epoch).
36
+ * @returns the rewritten ledger, or a refusal.
37
+ */
38
+ export declare function applyRenameTag(tasks: readonly TaskRecord[], from: string, to: string, now: number): TagEditResult;
39
+ /**
40
+ * Remove one label from every task that carries it. Archived cards are included:
41
+ * the board's label row draws from the whole ledger, so a label left on an
42
+ * archived card would come straight back into the filter.
43
+ * @param tasks - current ledger.
44
+ * @param name - the label to remove.
45
+ * @param now - clock instant (ms epoch).
46
+ * @returns the rewritten ledger.
47
+ */
48
+ export declare function applyDeleteTag(tasks: readonly TaskRecord[], name: string, now: number): TagEditResult;
@@ -15,7 +15,7 @@ import type { TaskHandoverInput } from '../handover.ts';
15
15
  * Editable fields on a task (the update patch surface). `freeze` replaces the
16
16
  * continuation-card snapshot (restamping frozenAt); an explicit null clears it.
17
17
  */
18
- export type TaskUpdatePatch = Partial<Pick<TaskRecord, 'title' | 'description' | 'prompt' | 'workspaceId' | 'mode' | 'permission' | 'model' | 'reuseSession' | 'teamRun' | 'goalRun'>> & {
18
+ export type TaskUpdatePatch = Partial<Pick<TaskRecord, 'title' | 'description' | 'prompt' | 'workspaceId' | 'mode' | 'permission' | 'model' | 'reuseSession' | 'teamRun' | 'goalRun' | 'skipVerification'>> & {
19
19
  freeze?: FreezeSnapshot & {
20
20
  redacted?: boolean;
21
21
  } | null;
@@ -39,8 +39,10 @@ export declare const TASK_CONTENT_FIELDS: readonly ["title", "description", "pro
39
39
  export declare function hasContentPatch(patch: TaskUpdatePatch): boolean;
40
40
  /**
41
41
  * Whether a task's content may still be edited: the task must be on-board
42
- * (not archived) and must never have started executing. Fail-closed: a
43
- * running, settled, or cancelled-before-launch task keeps its content fixed.
42
+ * (not archived) and must never have started executing. Fail-closed: a task
43
+ * with any execution record — running, settled, or cancelled before launch —
44
+ * keeps its content fixed. The run, not the column, is the trigger: a card
45
+ * parked in 'running' by hand has nothing recorded yet and stays editable.
44
46
  */
45
47
  export declare function canEditTaskContent(task: TaskRecord): boolean;
46
48
  /**
@@ -0,0 +1,52 @@
1
+ /** Judge calls one acceptance issues: every criterion, every round. */
2
+ export declare const ACCEPTANCE_JUDGE_CALLS: number;
3
+ /**
4
+ * Per-judge-call ceiling used when the row names none: two and a half minutes
5
+ * for a reasoning model reading an 80,000 character trace with a 16,384 token
6
+ * output cap. The former 120s constant was tight enough that a slow but healthy
7
+ * route produced a wall of per-call timeouts, and every one of them was charged
8
+ * as an acceptance anomaly against a budget that then failed the card.
9
+ */
10
+ export declare const DEFAULT_VERIFICATION_CALL_TIMEOUT_SECONDS = 150;
11
+ /** Shortest accepted per-call ceiling: below this no useful judge answers. */
12
+ export declare const VERIFICATION_CALL_TIMEOUT_MIN_SECONDS = 30;
13
+ /** Longest accepted per-call ceiling, ten minutes. */
14
+ export declare const VERIFICATION_CALL_TIMEOUT_MAX_SECONDS = 600;
15
+ /**
16
+ * Total ceiling of one acceptance attempt, covering evidence rendering and
17
+ * every judge call including retries. Derived from the per-call default rather
18
+ * than pinned, because a total budget smaller than
19
+ * {@link ACCEPTANCE_JUDGE_CALLS} times the per-call ceiling guarantees that no
20
+ * acceptance can ever finish.
21
+ */
22
+ export declare const DEFAULT_VERIFICATION_BUDGET_SECONDS: number;
23
+ /** Shortest accepted total budget: long enough for at least one judge call. */
24
+ export declare const VERIFICATION_BUDGET_MIN_SECONDS = 120;
25
+ /** Longest accepted total budget, thirty minutes. */
26
+ export declare const VERIFICATION_BUDGET_MAX_SECONDS = 1800;
27
+ /** The default total budget in milliseconds, the face the abort timer takes. */
28
+ export declare const DEFAULT_VERIFICATION_BUDGET_MS: number;
29
+ /**
30
+ * The effective per-judge-call ceiling.
31
+ * @param seconds - configured seconds, or undefined for the default.
32
+ * @returns the effective ceiling in seconds.
33
+ */
34
+ export declare function normalizeVerificationCallTimeoutSeconds(seconds: number | undefined): number;
35
+ /**
36
+ * The effective per-judge-call ceiling the timer face takes.
37
+ * @param seconds - configured seconds, or undefined for the default.
38
+ * @returns the effective ceiling in milliseconds.
39
+ */
40
+ export declare function verificationCallTimeoutMs(seconds: number | undefined): number;
41
+ /**
42
+ * The effective total budget of one acceptance attempt.
43
+ * @param seconds - configured seconds, or undefined for the default.
44
+ * @returns the effective budget in seconds.
45
+ */
46
+ export declare function normalizeVerificationBudgetSeconds(seconds: number | undefined): number;
47
+ /**
48
+ * The effective total budget the abort face takes.
49
+ * @param seconds - configured seconds, or undefined for the default.
50
+ * @returns the effective budget in milliseconds.
51
+ */
52
+ export declare function verificationBudgetMs(seconds: number | undefined): number;