@linxin666/dsh-client-ui-task-board 0.4.5 → 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 (57) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +20 -12
  3. package/README.zh.md +20 -12
  4. package/lib/client.js +1327 -229
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +1909 -348
  7. package/lib/types/client/TaskBoardSettingsCard.d.ts +12 -0
  8. package/lib/types/client/index.d.ts +4 -5
  9. package/lib/types/client/locales.d.ts +65 -0
  10. package/lib/types/client/plugin-card-seat.d.ts +5 -0
  11. package/lib/types/client/schedule-zone.d.ts +50 -0
  12. package/lib/types/core/controller.d.ts +10 -11
  13. package/lib/types/core/poll-cadence.d.ts +31 -0
  14. package/lib/types/core/tasks.d.ts +144 -14
  15. package/lib/types/core/use-cases/task-schedule.d.ts +68 -10
  16. package/lib/types/core/verification-budget.d.ts +52 -0
  17. package/lib/types/core/verification.d.ts +376 -9
  18. package/lib/types/host/verification-gate.d.ts +24 -0
  19. package/lib/types/host/verification-runner.d.ts +38 -7
  20. package/lib/types/host-ledger.d.ts +82 -7
  21. package/lib/types/host-runner.d.ts +32 -9
  22. package/lib/types/host-service.d.ts +117 -9
  23. package/lib/types/index.d.ts +54 -0
  24. package/lib/types/protocol.d.ts +50 -12
  25. package/package.json +2 -6
  26. package/src/client/TaskBoardSettingsCard.tsx +74 -1
  27. package/src/client/board/NewTaskModal.tsx +151 -20
  28. package/src/client/board/TaskCard.tsx +3 -3
  29. package/src/client/board/TaskDetail.tsx +219 -18
  30. package/src/client/board/VerificationReport.tsx +132 -13
  31. package/src/client/board/status-key.ts +3 -0
  32. package/src/client/index.ts +43 -50
  33. package/src/client/locales.ts +137 -4
  34. package/src/client/plugin-card-seat.ts +7 -0
  35. package/src/client/schedule-zone.ts +100 -2
  36. package/src/core/controller.ts +12 -8
  37. package/src/core/poll-cadence.ts +41 -0
  38. package/src/core/store.ts +64 -15
  39. package/src/core/tasks.ts +193 -39
  40. package/src/core/use-cases/task-create.ts +30 -9
  41. package/src/core/use-cases/task-schedule.ts +190 -25
  42. package/src/core/use-cases/task-update.ts +3 -3
  43. package/src/core/verification-budget.ts +115 -0
  44. package/src/core/verification.ts +785 -17
  45. package/src/host/agent-tools.ts +106 -21
  46. package/src/host/verification-gate.ts +172 -16
  47. package/src/host/verification-runner.ts +207 -40
  48. package/src/host-ledger.ts +328 -42
  49. package/src/host-runner.ts +52 -19
  50. package/src/host-service.ts +299 -36
  51. package/src/index.ts +101 -5
  52. package/src/protocol.ts +132 -28
  53. package/lib/invariant.js +0 -6
  54. package/lib/types/client/board-card-seat.d.ts +0 -34
  55. package/lib/types/invariant.d.ts +0 -3
  56. package/src/client/board-card-seat.ts +0 -102
  57. package/src/invariant.ts +0 -4
@@ -1,6 +1,6 @@
1
- import { type ExecutionOutcome, type ExecutionRecord, type TaskRecord } from './core/tasks.ts';
1
+ import { type ExecutionOutcome, type ExecutionRecord, type ScheduleMode, type TaskRecord } from './core/tasks.ts';
2
2
  import { type TaskBoardAction, type TaskBoardSchedulerSnapshot } from './protocol.ts';
3
- import type { ExecutionVerification } from './core/verification.ts';
3
+ import { type ExecutionVerification } from './core/verification.ts';
4
4
  import { type TaskPermission } from './core/handover.ts';
5
5
  export interface LedgerState {
6
6
  revision: number;
@@ -35,7 +35,10 @@ export interface OpenExecutionReference {
35
35
  /** Minimal value copy used by the Host scheduler. */
36
36
  export interface DueScheduleReference {
37
37
  readonly taskId: string;
38
- readonly cron: string;
38
+ /** Whether this due rule is a recurring cron rule or a single planned instant. */
39
+ readonly mode: ScheduleMode;
40
+ /** Cron expression of a recurring rule; absent on a one-shot. */
41
+ readonly cron?: string;
39
42
  /** Zone this rule's wall clock is read in. */
40
43
  readonly timeZone: string;
41
44
  readonly nextRunAt: number;
@@ -44,6 +47,24 @@ export interface DueScheduleReference {
44
47
  export interface LedgerRuntimeView {
45
48
  readonly armedSchedules: number;
46
49
  readonly openExecutions: readonly OpenExecutionReference[];
50
+ /**
51
+ * Session ids of every open execution, including the ones
52
+ * {@link openExecutions} skips because their own outcome is already
53
+ * recorded. A caller that asks whether the board still observes any session
54
+ * must use this list: a deferred cascade parent is settled by its members,
55
+ * but it is still an open execution.
56
+ */
57
+ readonly openSessionIds: readonly string[];
58
+ /**
59
+ * Whether this document holds anything the session roster can still decide:
60
+ * an open execution that needs inspecting, or a card in the running column
61
+ * whose verdict may arrive from a settle this process never saw.
62
+ *
63
+ * A board with no running card, no open execution and no armed schedule has
64
+ * no use for the roster at all, which is what lets the Host poll stand down
65
+ * instead of re-reading every persisted session forever.
66
+ */
67
+ readonly needsSessionState: boolean;
47
68
  }
48
69
  /**
49
70
  * Best-effort single-letter process state ('R','S','D','Z',...) or undefined
@@ -125,7 +146,7 @@ export declare class HostTaskLedger {
125
146
  };
126
147
  state(): LedgerState;
127
148
  /**
128
- * Runtime-only projection for the 5 s Host poll. It copies just primitive
149
+ * Runtime-only projection for the Host poll. It copies just primitive
129
150
  * identifiers and timestamps, never the complete task/execution history or
130
151
  * an authoritative mutable object from the ledger.
131
152
  */
@@ -152,9 +173,45 @@ export declare class HostTaskLedger {
152
173
  /**
153
174
  * Open the cascade one due schedule triggers: an empty array means nothing
154
175
  * ran (already running, or a participant whose elevated permission is still
155
- * unconfirmed), and the rule rolls to its next occurrence either way.
176
+ * unconfirmed, a one-shot whose instant was missed, or an exhausted budget),
177
+ * and the rule's own state says which.
178
+ *
179
+ * The run budget is enforced HERE, inside the single ledger writer: a rule
180
+ * whose counter already reached its cap is stopped instead of opened, so no
181
+ * restart, repeated timer fire or re-arm can overrun it.
182
+ * @param taskId - the task whose rule came due.
183
+ * @param triggeredAt - the instant the occurrence came due.
184
+ * @returns the executions this occurrence opened.
185
+ */
186
+ openScheduled(taskId: string, triggeredAt: number): OpenedRun[];
187
+ /**
188
+ * Record a due occurrence that opened no execution: roll a recurring rule to
189
+ * its next occurrence (budget untouched), or stop it when there is none. A
190
+ * one-shot has no second occurrence, so it always stops here with the reason.
191
+ */
192
+ private skipOccurrence;
193
+ /** Apply a schedule-occurrence transition and persist it atomically. */
194
+ private progressSchedule;
195
+ /**
196
+ * Refund the one scheduled run an occurrence consumed before its launch
197
+ * failed without creating a session (see
198
+ * {@link applyScheduleRefund}). Called by the service on the failed-launch
199
+ * path only; a manual run and a launch that did reach a session are never
200
+ * refunded. A no-op for a task with no rule, so a failed cascade member does
201
+ * not touch anything.
202
+ * @param taskId - the task whose occurrence is being refunded.
203
+ * @param now - clock instant (ms epoch).
204
+ */
205
+ refundScheduledOccurrence(taskId: string, now: number): void;
206
+ /**
207
+ * Boot / resume recovery: skip every occurrence that came due while the board
208
+ * was not running through it. A recurring rule rolls to its next future
209
+ * target; a one-shot whose instant has passed is stopped as missed, because
210
+ * there is no later occurrence left to run. Rendering the missed occurrence
211
+ * is deliberately not attempted: the ACL of a card that fired hours ago is
212
+ * stale, and the board's own recovery contract is "missed triggers are
213
+ * skipped, never replayed".
156
214
  */
157
- openScheduled(taskId: string, nextRunAt: number | undefined, triggeredAt: number): OpenedRun[];
158
215
  skipMissed(now: number): void;
159
216
  setScheduler(patch: Partial<TaskBoardSchedulerSnapshot>): void;
160
217
  /**
@@ -169,6 +226,14 @@ export declare class HostTaskLedger {
169
226
  getTask(id: string): TaskRecord | undefined;
170
227
  saveTaskRecord(task: TaskRecord): void;
171
228
  private apply;
229
+ /**
230
+ * Whether a creation-time schedule request is usable. A one-shot must carry
231
+ * a whole-millisecond instant in the future; a recurring rule must carry a
232
+ * valid expression with a reachable occurrence and, when capped, a positive
233
+ * whole run budget. An unusable zone is refused rather than reinterpreted as
234
+ * the Host zone.
235
+ */
236
+ private validScheduleRequest;
172
237
  /**
173
238
  * The binding that makes a run illegal, if any.
174
239
  *
@@ -202,7 +267,9 @@ export declare class HostTaskLedger {
202
267
  * @param now - clock instant (ms epoch).
203
268
  * @param initiator - the DSH session that asked for the run (audit only).
204
269
  * @param rerun - true to reset the root to 'todo' before starting it.
205
- * @param nextRunAt - when set, the root's schedule also rolls forward.
270
+ * @param progress - when set, this run was a due occurrence and the root's
271
+ * rule is advanced (counter, next target, stop/skip record) in the same
272
+ * atomic commit that opened the executions.
206
273
  */
207
274
  private startCascade;
208
275
  /**
@@ -248,6 +315,14 @@ export declare class HostTaskLedger {
248
315
  * @returns whether the document changed.
249
316
  */
250
317
  private closeTeamMembers;
318
+ /**
319
+ * Re-align every armed rule at load/import. A recurring rule is recomputed
320
+ * from the current instant; a one-shot is armed at its planned instant when
321
+ * that is still in the future, and stopped as missed once it has passed
322
+ * (there is no later occurrence to run). A recurring rule recomputed past an
323
+ * occurrence records the skip, so the board can show that a fire was passed
324
+ * over rather than silently rewinding.
325
+ */
251
326
  private repairSchedules;
252
327
  private reconcileInterruptedStarts;
253
328
  /**
@@ -78,15 +78,16 @@ export interface PromptContext {
78
78
  /** True when those members run as teammates inside this session (Team Lead). */
79
79
  team?: boolean;
80
80
  /**
81
- * Scheduled-run provenance: the instant the cron rule actually fired and the
82
- * IANA zone its wall clock was read in. Present only for a cron-triggered
83
- * run, so the model can resolve an unqualified date in the run's own terms
84
- * instead of guessing from the Host clock (issue #1722).
81
+ * Scheduled-run provenance: the instant the rule actually fired, its kind,
82
+ * and the IANA zone its wall clock was read in. Present only for a
83
+ * timer-triggered run, so the model can resolve an unqualified date in the
84
+ * run's own terms instead of guessing from the Host clock (issue #1722).
85
85
  */
86
86
  schedule?: {
87
87
  triggeredAt: number;
88
88
  timeZone: string;
89
- cron: string;
89
+ mode: 'cron' | 'once';
90
+ cron?: string;
90
91
  };
91
92
  }
92
93
  /**
@@ -144,6 +145,18 @@ export declare class HostExecutionRunner {
144
145
  promptContext?: PromptContext;
145
146
  onSession?: (sessionId: string) => void;
146
147
  onGoalArmed?: (armed: boolean) => void;
148
+ /**
149
+ * GLOBAL native /goal switch, resolved by the caller ONCE per execution and
150
+ * carried here so the decision is frozen for this launch.
151
+ *
152
+ * Frozen deliberately: a settings edit made while the run is in flight must
153
+ * not arm, disarm or otherwise change the goal/acceptance contract of a run
154
+ * already running. Absent means off — the default for every deployment that
155
+ * never touched the switch, including cards written before the option
156
+ * existed. A plain single turn is what an off switch produces; the task-level
157
+ * `goalRun` still applies on top of it (both must ASK for a goal).
158
+ */
159
+ goalEnabled?: boolean;
147
160
  }): Promise<string>;
148
161
  /**
149
162
  * Fail closed when a reused session records a different Agent preset than
@@ -182,11 +195,21 @@ export declare class HostExecutionRunner {
182
195
  * dsh's goal-round driver keeps starting continuation rounds until the agent
183
196
  * marks it complete (see {@link goalVerdict} for how the run settles).
184
197
  *
185
- * Opt-out: a task whose `goalRun` is an explicit false runs one plain turn.
198
+ * Two switches gate the arming, and BOTH must ask for it:
199
+ * - the GLOBAL native-/goal switch (`goalEnabled`, default OFF), frozen when
200
+ * this execution started; and
201
+ * - the task's own `goalRun` (absent means off, only an explicit true opts
202
+ * the card into a goal run).
203
+ *
186
204
  * A refusal (no command dispatcher, no `/goal` command in this cohort, an
187
- * objective the command rejects) is reported and the run continues as that
188
- * plain turn: the task was asked to run, and a missing goal mode must not
189
- * lose the work.
205
+ * objective the command rejects) is reported and the run continues as a plain
206
+ * turn: the task was asked to run, and a missing goal mode must not lose the
207
+ * work.
208
+ * @param sessionId - the session to arm in.
209
+ * @param task - the task being run.
210
+ * @param context - the run's prompt context.
211
+ * @param goalEnabled - the GLOBAL switch, frozen for this execution.
212
+ * @returns whether `/goal` was really armed.
190
213
  */
191
214
  private armGoal;
192
215
  /**
@@ -5,6 +5,7 @@ import { PowerInhibitor } from './power-inhibitor.ts';
5
5
  import { type TaskBoardAction, type TaskBoardEventPayload, type TaskBoardSnapshot } from './protocol.ts';
6
6
  import { TaskBoardExtensionRegistry } from './host/extension-registry.ts';
7
7
  import type { TaskBoardExtension } from './core/extension.ts';
8
+ import type { ScheduleMode } from './core/tasks.ts';
8
9
  import { type ModelCatalogView, type VerificationContract, type VerificationSettings } from './core/verification.ts';
9
10
  import type { TaskPermission } from './core/handover.ts';
10
11
  /** One teammate the Host asks the Agent Teams service to spawn for a team run. */
@@ -34,6 +35,30 @@ export interface TeamSpawnResult {
34
35
  export interface TaskBoardTeamDispatcher {
35
36
  spawn(input: TeamSpawnInput): Promise<TeamSpawnResult>;
36
37
  }
38
+ /**
39
+ * Why an enforced goal execution settled failed while its acceptance DID run:
40
+ * attempts were recorded and none of them passed. The judge answered about the
41
+ * work and the work did not pass.
42
+ */
43
+ export declare const NO_MATCHING_PASS_VERIFICATION_REASON = "goal \u9A8C\u6536\uFF1A\u672C\u6B21\u6267\u884C\u6CA1\u6709\u5339\u914D\u7684\u9A8C\u6536\u901A\u8FC7\u8BB0\u5F55\uFF08\u9A8C\u6536\u672A\u8FD0\u884C\u3001\u672A\u901A\u8FC7\u6216\u62A5\u544A\u6765\u81EA\u5176\u4ED6\u6267\u884C\uFF09\uFF0C\u6309\u672A\u9A8C\u6536\u5224\u5931\u8D25\u3002";
44
+ /**
45
+ * Why an enforced goal execution settled failed with ZERO acceptance records:
46
+ * the gate was never opened, so the judge never ran and nothing was ever judged
47
+ * (issue #1837). The root cause is a session that declared completion in prose
48
+ * instead of calling `update_goal(action: complete)` — a tool-adherence problem,
49
+ * NOT a quality verdict on the delivery — so this reason says so and keeps the
50
+ * two investigation directions apart.
51
+ */
52
+ export declare const NEVER_INVOKED_VERIFICATION_REASON = "goal \u9A8C\u6536\u672A\u89E6\u53D1\uFF1A\u672C\u6B21\u6267\u884C\u6CA1\u6709\u4EFB\u4F55\u9A8C\u6536\u8BB0\u5F55\uFF0C\u9A8C\u6536\u95E8\u4ECE\u672A\u6253\u5F00\u2014\u2014\u6267\u884C\u4F1A\u8BDD\u6CA1\u6709\u8C03\u7528 update_goal(action: complete) \u6765\u5B8C\u6210\u76EE\u6807\uFF08\u5E38\u89C1\u4E8E\u53EA\u5728\u56DE\u590D\u91CC\u5BA3\u544A\u5B8C\u6210\uFF09\u3002\u8FD9\u4E0D\u662F\u8D28\u91CF\u5224\u8D1F\uFF1A\u4EA4\u4ED8\u4ECE\u672A\u88AB\u88C1\u5224\u8BC4\u4F30\u3002";
53
+ /**
54
+ * Why an enforced goal execution settled failed while its ONLY acceptance
55
+ * records are INVALID: the judge answered but could not locate its own
56
+ * rejection, so the board stopped calling it. This is NOT an evidence-backed
57
+ * quality veto — the requirement is explicit that such a run must not be
58
+ * described as one — but the execution still has no matching pass, so it ends
59
+ * for lack of one, and the reason names that precisely.
60
+ */
61
+ export declare const INVALID_ONLY_VERIFICATION_REASON = "goal \u9A8C\u6536\u65E0\u6548\uFF1A\u672C\u6B21\u6267\u884C\u53EA\u8BB0\u5F55\u4E86\u65E0\u6548\u9A8C\u6536\uFF08\u88C1\u5224\u672A\u80FD\u7ED9\u51FA\u4E0E\u4EFB\u52A1\u8981\u6C42\u5BF9\u5E94\u3001\u4E14\u53EF\u5B9A\u4F4D\u5230\u672C\u6B21\u8BC1\u636E\u7684\u5426\u51B3\u4F9D\u636E\uFF09\uFF0C\u770B\u677F\u5DF2\u505C\u6B62\u8C03\u7528\u88C1\u5224\u3002\u8FD9**\u4E0D\u662F**\u6709\u8BC1\u636E\u7684\u8D28\u91CF\u5224\u8D1F\uFF1A\u9A8C\u6536\u672C\u8EAB\u4E0D\u53EF\u7528\uFF0C\u672C\u6B21\u8FD0\u884C\u56E0\u6CA1\u6709\u5339\u914D\u7684\u901A\u8FC7\u8BB0\u5F55\u800C\u7ED3\u675F\u3002\u8BF7\u68C0\u67E5\u9A8C\u6536\u73AF\u5883\u4E0E\u8BC1\u636E\u53EF\u89C1\u6027\uFF0C\u6216\u7528 task_board_manage(action=reset-verification) \u6E05\u9664\u8BB0\u5F55\u7684\u65E0\u6548\u9A8C\u6536\u540E\u91CD\u65B0\u5B8C\u6210\u76EE\u6807\u3002";
37
62
  /**
38
63
  * Provenance of one cron-triggered cascade: when the rule fired and the zone
39
64
  * its wall clock was read in. Passed into every launched prompt of that run so
@@ -42,7 +67,10 @@ export interface TaskBoardTeamDispatcher {
42
67
  export interface ScheduledRunContext {
43
68
  triggeredAt: number;
44
69
  timeZone: string;
45
- cron: string;
70
+ /** Whether this fire came from a recurring rule or a single planned instant. */
71
+ mode: ScheduleMode;
72
+ /** Cron expression of a recurring rule; absent on a one-shot. */
73
+ cron?: string;
46
74
  }
47
75
  /**
48
76
  * The native timer face the Host arms through. The cordis `timer` service
@@ -62,6 +90,12 @@ export declare class TaskBoardHostService {
62
90
  private readonly listeners;
63
91
  /** The one recurring timer: the session-roster poll. */
64
92
  private pollTimer;
93
+ /** Cadence of the roster poll, in seconds (a settings field; see the config schema). */
94
+ private sessionPollSeconds;
95
+ /** Pending delay that follows a failed poll pass, if one is armed. */
96
+ private pollBackoffTimer;
97
+ /** Consecutive failed poll passes; doubles the next retry delay. */
98
+ private consecutivePollFailures;
65
99
  /** The armed schedule timer, if a trigger is pending. */
66
100
  private scheduleTimer;
67
101
  /** The instant the armed schedule timer targets (ms epoch), for resume detection. */
@@ -69,13 +103,6 @@ export declare class TaskBoardHostService {
69
103
  private disposed;
70
104
  private pollInFlight;
71
105
  private active;
72
- /**
73
- * Ids the last roster poll saw as present and idle; undefined while the
74
- * roster is unknown. Session reuse (issue #1419) requires this positive
75
- * evidence, so a launch before the first successful poll mints a fresh
76
- * conversation instead of prompting into a session it cannot see.
77
- */
78
- private idleSessionIds;
79
106
  /**
80
107
  * Consecutive unreadable-history polls per open execution (see
81
108
  * {@link noteUnreadableInspection}). Cleared as soon as an inspection
@@ -87,6 +114,8 @@ export declare class TaskBoardHostService {
87
114
  private readonly verificationCatalog;
88
115
  private readonly team;
89
116
  private readonly timers;
117
+ /** Live reader of the global native-/goal switch; frozen per execution. */
118
+ private readonly goalRunEnabled;
90
119
  private lastPowerJson;
91
120
  private readonly now;
92
121
  /** The deployment's workspaces, consulted to resolve a card that pins none. */
@@ -111,6 +140,14 @@ export declare class TaskBoardHostService {
111
140
  maxSubtaskDepth?: number;
112
141
  team?: TaskBoardTeamDispatcher;
113
142
  timers?: HostTimerFace;
143
+ /**
144
+ * Live GLOBAL native /goal switch (a volatile config read). Read ONCE per
145
+ * execution, at launch, and carried by value from then on: a settings edit
146
+ * made while a run is in flight must not arm, disarm or otherwise change the
147
+ * goal/acceptance contract of that run. Absent means OFF, which is the
148
+ * default for every deployment that never configured it.
149
+ */
150
+ goalRunEnabled?: () => boolean;
114
151
  /**
115
152
  * Live acceptance settings (volatile config reads). Absent keeps goal
116
153
  * acceptance OFF for every execution this service opens, which is what a
@@ -121,7 +158,16 @@ export declare class TaskBoardHostService {
121
158
  verificationCatalog?: () => Promise<ModelCatalogView | undefined>;
122
159
  });
123
160
  start(): void;
124
- setConfiguration(active: boolean, preventIdleSleep: boolean): void;
161
+ /**
162
+ * Apply the live configuration. The roster cadence is re-read on every
163
+ * commit, so a settings edit takes effect without a restart; the poll is
164
+ * scheduled at the new cadence immediately rather than waiting out the old
165
+ * interval.
166
+ * @param active - the board's master switch.
167
+ * @param preventIdleSleep - whether the board holds an idle-sleep assertion.
168
+ * @param sessionPollSeconds - roster-poll cadence in seconds (clamped).
169
+ */
170
+ setConfiguration(active: boolean, preventIdleSleep: boolean, sessionPollSeconds?: number): void;
125
171
  snapshot(): TaskBoardSnapshot;
126
172
  /**
127
173
  * Admit one external provider. Idempotent by extension id; the returned
@@ -193,7 +239,32 @@ export declare class TaskBoardHostService {
193
239
  * Lead's cascade verdict.
194
240
  */
195
241
  private spawnTeammate;
242
+ /**
243
+ * One roster pass, and whether it could read the session tree.
244
+ *
245
+ * The board reads the DSH session roster only while it has something the
246
+ * roster can decide: an execution to inspect, or a card parked in the running
247
+ * column whose verdict may arrive from a settle this process never saw. That
248
+ * gate is what removes the cost the reporter measured — `session/list`
249
+ * rebuilds every persisted session row (string conversion, object allocation,
250
+ * a stat per record), and an idle board with no tasks was paying it every few
251
+ * seconds forever.
252
+ * @returns whether the roster was readable (an idle pass counts as readable).
253
+ */
196
254
  private pollSessions;
255
+ /**
256
+ * The session a run may continue in, for a card that opted into reuse
257
+ * (issue #1419). Reuse requires positive evidence that the previous session
258
+ * is present and idle, and that evidence must be read for THIS launch: an
259
+ * idle board no longer polls, so a roster cached from an earlier pass could
260
+ * be hours old — either refusing a session that has been idle all along, or
261
+ * prompting into one that started running since. A card that did not opt in
262
+ * reads no roster at all, and a read that fails mints a fresh conversation
263
+ * exactly as an unknown roster always did.
264
+ * @param task - the task about to run.
265
+ * @returns the session id to continue in, or undefined for a fresh session.
266
+ */
267
+ private reuseSessionFor;
197
268
  /** Reuse the session list this poll already fetched: one list RPC per tick, not 1 + E. */
198
269
  private reconcileExecutions;
199
270
  /**
@@ -206,6 +277,30 @@ export declare class TaskBoardHostService {
206
277
  * each streak is logged, so the Host log names the session.
207
278
  */
208
279
  private settleAndNotify;
280
+ /**
281
+ * Remove one execution's acceptance-only detail after it PASSED and settled.
282
+ *
283
+ * Deliberately narrow: only the acceptance mechanism's own per-attempt
284
+ * material (findings, invalid diagnostics, the bulky judge side) is removed,
285
+ * and only for THIS execution, keyed by its own id in the ledger it belongs
286
+ * to. No user file, task artifact, raw session history or other execution's
287
+ * record is read or written. Idempotent by the record's own state, and it
288
+ * clearly never touches a run that did not pass (the caller's guard) — a
289
+ * failed or invalid execution keeps everything a repair or a review needs.
290
+ * @param taskId - the task whose execution settled.
291
+ * @param executionId - the settled execution.
292
+ */
293
+ private cleanupAcceptanceDetail;
294
+ /**
295
+ * Catch-up pass: clean the acceptance detail of every execution that PASSED
296
+ * and settled but whose cleanup did not finish (a crash, a failed write, a
297
+ * Host killed mid-cleanup).
298
+ *
299
+ * The same bounds as any other cleanup apply — only a real pass record on a
300
+ * settled execution is touched — so this can never delete material belonging
301
+ * to an execution that did not pass, and never a user file.
302
+ */
303
+ private cleanupPendingAcceptanceDetail;
209
304
  private noteUnreadableInspection;
210
305
  /** Drop the armed schedule timer and forget its target. */
211
306
  private clearScheduleTimer;
@@ -249,6 +344,19 @@ export declare class TaskBoardHostService {
249
344
  private setApplicability;
250
345
  private scheduleLaunch;
251
346
  private schedulePoll;
347
+ /**
348
+ * Count one failed pass and arm the retry that follows it.
349
+ *
350
+ * A session tree that is down is retried on a doubling delay instead of
351
+ * hammering it on the fixed cadence: the heartbeat would otherwise repeat
352
+ * every failure — and every retry inside {@link HostExecutionRunner.listRunning}
353
+ * — at exactly the interval the reporter measured.
354
+ */
355
+ private notePollFailure;
356
+ /** Arm the retry that follows a failed pass, doubling up to the ceiling. */
357
+ private armPollBackoff;
358
+ /** Drop a pending retry; the next regular tick takes over. */
359
+ private clearPollBackoff;
252
360
  /**
253
361
  * Re-arm from the ledger's current targets. Callers that just changed a
254
362
  * schedule (the host routes, the agent tools) invoke this after the write
@@ -62,12 +62,35 @@ export interface Config {
62
62
  * of the root task opens, so the ceiling is deliberately small.
63
63
  */
64
64
  maxSubtaskDepth?: Volatile<number>;
65
+ /**
66
+ * How often, in seconds, the Host re-reads the DSH session roster while the
67
+ * board has something to reconcile: a running card, an open execution, or an
68
+ * active provider extension. An idle board with none of those does not poll
69
+ * at all, so this cadence only bounds how quickly a running card settles.
70
+ */
71
+ sessionPollSeconds?: Volatile<number>;
65
72
  /**
66
73
  * Continuable-subagent provider the Agent Teams service uses to compose a
67
74
  * teammate. Matches the Agent Teams tool plugin's `freshProvider` default;
68
75
  * only team-mode runs use it.
69
76
  */
70
77
  teamProvider?: string;
78
+ /**
79
+ * GLOBAL native /goal switch (default OFF), the master control over whether
80
+ * this board starts a run with dsh's built-in /goal at all. A run uses native
81
+ * /goal only when THIS is on AND the task's own `goalRun` is not off; while
82
+ * it is off every task runs one plain turn.
83
+ *
84
+ * The value is frozen when an execution starts, so toggling it only affects
85
+ * executions that begin afterwards and never re-writes a task's stored
86
+ * `goalRun` preference. A run that consequently does not execute native
87
+ * /goal is marked as such (its acceptance applicability is `goal-disabled`)
88
+ * and is never reported as a passed goal acceptance or failed for lacking a
89
+ * completion call. Independent of `goalVerification`; acceptance governs the
90
+ * completing call of a goal-form run, this switch governs whether the run
91
+ * becomes a goal run.
92
+ */
93
+ goalRunEnabled?: Volatile<boolean>;
71
94
  /**
72
95
  * Goal acceptance for executions this board starts (default ON). When on,
73
96
  * update_goal(action: complete) inside a task execution is refused until an
@@ -93,6 +116,23 @@ export interface Config {
93
116
  * sent, and the fallback to the model's own default is reported.
94
117
  */
95
118
  goalVerificationReasoningEffort?: Volatile<string>;
119
+ /**
120
+ * Ceiling of ONE judge request of a goal acceptance, in seconds
121
+ * (30..600, default 150). One acceptance issues six judge requests (three
122
+ * criteria times two rounds), so a ceiling that is too small turns a slow but
123
+ * healthy judge route into a wall of timeouts, and every timeout was charged
124
+ * as an acceptance anomaly. Raise it for a reasoning model reading a long
125
+ * trace.
126
+ */
127
+ goalVerificationCallTimeoutSeconds?: Volatile<number>;
128
+ /**
129
+ * Total ceiling of ONE goal acceptance attempt, in seconds (120..1800,
130
+ * default eight times the per-call default). Keep it at or above
131
+ * `goalVerificationCallTimeoutSeconds` times six or an acceptance can never
132
+ * finish: the attempt then ends early with a budget stop that judges nothing
133
+ * and spends no acceptance budget.
134
+ */
135
+ goalVerificationBudgetSeconds?: Volatile<number>;
96
136
  }
97
137
  /**
98
138
  * Continuable-subagent provider for fresh teammates, matching the Agent Teams
@@ -124,18 +164,32 @@ export interface ConfigInput {
124
164
  sessionDefaultPermission?: TaskPermission;
125
165
  /** Subtask depth limit, 1..3. */
126
166
  maxSubtaskDepth?: number;
167
+ /** Roster-poll cadence in seconds while the board has work to reconcile. */
168
+ sessionPollSeconds?: number;
127
169
  /** Continuable-subagent provider the Agent Teams service composes a teammate from. */
128
170
  teamProvider?: string;
171
+ /** Global native /goal switch; default off, frozen per execution. */
172
+ goalRunEnabled?: boolean;
129
173
  /** Goal acceptance switch. */
130
174
  goalVerification?: boolean;
131
175
  /** Judge model route for goal acceptance; blank inherits the host default. */
132
176
  goalVerificationModel?: string;
133
177
  /** Judge reasoning effort; blank inherits the host default. */
134
178
  goalVerificationReasoningEffort?: string;
179
+ /** Ceiling of one judge request of a goal acceptance, in seconds. */
180
+ goalVerificationCallTimeoutSeconds?: number;
181
+ /** Total ceiling of one goal acceptance attempt, in seconds. */
182
+ goalVerificationBudgetSeconds?: number;
135
183
  }
136
184
  export declare const Config: z<ConfigInput, Config>;
137
185
  /** Schema default for the goal-acceptance switch, re-read for hand-built contexts. */
138
186
  export declare const DEFAULT_GOAL_VERIFICATION = true;
187
+ /**
188
+ * Schema default for the GLOBAL native-/goal switch, re-read for hand-built
189
+ * contexts. Deliberately false: a deployment that never configured it (and
190
+ * every card written before the option existed) runs plain single turns.
191
+ */
192
+ export declare const DEFAULT_GOAL_RUN = false;
139
193
  declare module '@deepseek-ai/cordis' {
140
194
  interface Events {
141
195
  /**
@@ -1,22 +1,36 @@
1
1
  import type { TaskUpdatePatch } from './core/use-cases/task-update.ts';
2
+ import type { SetSchedulePatch } from './core/use-cases/task-schedule.ts';
2
3
  import { type NewTaskInput, type TaskPermission, type TaskRecord, type TaskStatus } from './core/tasks.ts';
3
4
  import { type TaskBoardExtensionPayload } from './core/extension.ts';
4
5
  import { type ModelCatalogView, type VerificationContract, type VerificationSettings } from './core/verification.ts';
5
- export declare const TASK_BOARD_SCHEMA_VERSION: 5;
6
- /** Ledger documents written before v5; loaded once and migrated on startup. */
7
- export declare const TASK_BOARD_LEGACY_SCHEMA_VERSION: 4;
6
+ export declare const TASK_BOARD_SCHEMA_VERSION: 6;
7
+ /** Ledger documents written before v6; loaded once and migrated on startup. */
8
+ export declare const TASK_BOARD_LEGACY_SCHEMA_VERSION: 5;
9
+ /** Ledger documents written before v5; migrated through the v5 normalization too. */
10
+ export declare const TASK_BOARD_PRE_ACCEPTANCE_SCHEMA_VERSION: 4;
8
11
  /** Ledger documents written before v4; migrated through the v4 normalization too. */
9
12
  export declare const TASK_BOARD_OLDER_SCHEMA_VERSION: 3;
10
13
  /** Ledger documents written before v3; migrated through every later normalization too. */
11
14
  export declare const TASK_BOARD_OLDEST_SCHEMA_VERSION: 2;
12
15
  /**
13
- * Every older generation the loader migrates in place. v5 only ADDS the
14
- * per-execution acceptance block, so the migration is the normalization pass
15
- * that re-validates each row; a document from any listed generation upgrades
16
- * losslessly.
16
+ * The last generation whose rules still need the Host zone stamped on them at
17
+ * migration: v4 introduced the stored zone but a rule written without one must
18
+ * stop following a later TZ change. A v5 rule already carries its zone (or
19
+ * deliberately follows the Host), so it is NOT stamped again.
20
+ */
21
+ export declare const TASK_BOARD_ZONE_STAMP_SCHEMA_VERSION: 4;
22
+ /**
23
+ * Every older generation the loader migrates in place. v5 only added the
24
+ * per-execution acceptance block and v6 only added the scheduling mode and run
25
+ * budget, so the migration is the normalization pass that re-validates each
26
+ * row; a document from any listed generation upgrades losslessly.
17
27
  */
18
28
  export declare const TASK_BOARD_MIGRATABLE_SCHEMA_VERSIONS: readonly number[];
19
29
  export declare const TASK_BOARD_API_PREFIX = "/api/task-board";
30
+ /** Longest accepted caller identity on an external outcome record. */
31
+ export declare const EXTERNAL_INITIATOR_MAX_LENGTH = 200;
32
+ /** Longest accepted free-text summary on an external outcome record. */
33
+ export declare const EXTERNAL_SUMMARY_MAX_LENGTH = 1000;
20
34
  export type PowerPhase = 'disabled' | 'idle' | 'acquiring' | 'active' | 'error' | 'unsupported';
21
35
  export interface TaskBoardPowerSnapshot {
22
36
  platform: string;
@@ -52,6 +66,14 @@ export interface TaskBoardSnapshot {
52
66
  * into team execution (Team Lead session plus one teammate per subtask).
53
67
  */
54
68
  teamRunAvailable?: boolean;
69
+ /**
70
+ * Live GLOBAL native-/goal switch. The browser uses it to disable the
71
+ * task-level option (and explain that the global switch must be turned on
72
+ * first) WITHOUT rewriting anyone's stored per-task preference; the Host
73
+ * freezes its own read per execution, so this mirror never governs a run in
74
+ * flight.
75
+ */
76
+ goalRunEnabled?: boolean;
55
77
  /**
56
78
  * Read-only summaries published by running extensions, keyed by extension id.
57
79
  * The board forwards them verbatim; it never interprets their shape.
@@ -124,14 +146,30 @@ export type TaskBoardAction = {
124
146
  } | {
125
147
  kind: 'settle';
126
148
  taskId: string;
149
+ }
150
+ /** Clear the acceptance anomalies recorded on a card's open execution (issue #1828). */
151
+ | {
152
+ kind: 'reset-verification';
153
+ taskId: string;
154
+ }
155
+ /**
156
+ * Record an outcome an agent OUTSIDE the Host produced (issue #1826).
157
+ *
158
+ * It writes a real execution record, so the terminal column and the run
159
+ * history both derive from that one record instead of a bare column edit.
160
+ * `cancelled` is deliberately absent: nothing outside a DSH session may
161
+ * claim the Host closed a run, and `running` must stay Host-only.
162
+ */
163
+ | {
164
+ kind: 'record-external-outcome';
165
+ taskId: string;
166
+ result: 'succeeded' | 'failed';
167
+ initiatedBy: string;
168
+ summary?: string;
127
169
  } | {
128
170
  kind: 'set-schedule';
129
171
  taskId: string;
130
- patch: {
131
- enabled?: boolean;
132
- cron?: string;
133
- timeZone?: string | null;
134
- };
172
+ patch: SetSchedulePatch;
135
173
  } | {
136
174
  kind: 'run';
137
175
  taskId: string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@linxin666/dsh-client-ui-task-board",
3
3
  "description": "Host-authoritative task board for the DSH Web GUI with real session execution, Host cron scheduling, and optional cross-platform idle-sleep protection; mounted without DSH source changes.",
4
- "version": "0.4.5",
4
+ "version": "0.4.6",
5
5
  "icon": "icon.svg",
6
6
  "type": "module",
7
7
  "main": "lib/index.js",
@@ -11,10 +11,6 @@
11
11
  "types": "./lib/types/index.d.ts",
12
12
  "default": "./lib/index.js"
13
13
  },
14
- "./invariant": {
15
- "types": "./lib/types/invariant.d.ts",
16
- "default": "./lib/invariant.js"
17
- },
18
14
  "./client": {
19
15
  "types": "./lib/types/client/index.d.ts",
20
16
  "default": "./lib/client.js"
@@ -101,7 +97,7 @@
101
97
  "url": "https://github.com/zhu1090093659/dsh-web.git"
102
98
  },
103
99
  "scripts": {
104
- "build": "tsc -p tsconfig.build.json && tsdown",
100
+ "build": "tsc -b tsconfig.build.json && tsdown",
105
101
  "bundle": "tsdown",
106
102
  "watch": "tsdown --watch",
107
103
  "typecheck": "tsc --noEmit",