@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
@@ -159,12 +159,12 @@ export interface PromptContext {
159
159
  /** True when those members run as teammates inside this session (Team Lead). */
160
160
  team?: boolean
161
161
  /**
162
- * Scheduled-run provenance: the instant the cron rule actually fired and the
163
- * IANA zone its wall clock was read in. Present only for a cron-triggered
164
- * run, so the model can resolve an unqualified date in the run's own terms
165
- * instead of guessing from the Host clock (issue #1722).
162
+ * Scheduled-run provenance: the instant the rule actually fired, its kind,
163
+ * and the IANA zone its wall clock was read in. Present only for a
164
+ * timer-triggered run, so the model can resolve an unqualified date in the
165
+ * run's own terms instead of guessing from the Host clock (issue #1722).
166
166
  */
167
- schedule?: { triggeredAt: number; timeZone: string; cron: string }
167
+ schedule?: { triggeredAt: number; timeZone: string; mode: 'cron' | 'once'; cron?: string }
168
168
  }
169
169
 
170
170
  /**
@@ -184,16 +184,20 @@ function peerPromptPreamble(context: PromptContext): string | undefined {
184
184
  }
185
185
 
186
186
  /**
187
- * The scheduled-run section: this execution was triggered by the card's cron
188
- * rule rather than by a person. It states the firing instant, the rule's zone,
189
- * and the rule text, so the run's own clock is unambiguous — the board's
187
+ * The scheduled-run section: this execution was triggered by the card's
188
+ * schedule rather than by a person. It states the firing instant, the rule's
189
+ * zone, and the rule text, so the run's own clock is unambiguous — the board's
190
190
  * scheduler fires on a wall clock in a specific IANA zone, which is not
191
- * necessarily the zone of whatever session the run lands in.
191
+ * necessarily the zone of whatever session the run lands in. A one-shot says
192
+ * so explicitly, so the run does not expect a recurrence.
192
193
  */
193
194
  function schedulePromptPreamble(context: PromptContext): string | undefined {
194
195
  const schedule = context.schedule
195
196
  if (schedule === undefined) return undefined
196
197
  const stamp = new Date(schedule.triggeredAt).toISOString()
198
+ if (schedule.mode === 'once') {
199
+ return `本次执行由任务看板的单次定时自动触发:计划时间 ${stamp}(UTC),时区 ${schedule.timeZone}。这是一次性计划,本次执行之后该卡片不会再自动执行。需要判断「今天」「现在」或计算时间窗口时,以该时区的触发时间为准。`
200
+ }
197
201
  return `本次执行由任务看板的定时规则自动触发:触发时间 ${stamp}(UTC),规则时区 ${schedule.timeZone},cron 表达式 ${schedule.cron}。需要判断「今天」「现在」或计算时间窗口时,以该时区的触发时间为准。`
198
202
  }
199
203
 
@@ -356,7 +360,24 @@ export class HostExecutionRunner {
356
360
  * @param options - optional session to continue in.
357
361
  * @returns the session id the execution runs in.
358
362
  */
359
- async launch(task: TaskRecord, options: { reuseSessionId?: string; promptContext?: PromptContext; onSession?: (sessionId: string) => void; onGoalArmed?: (armed: boolean) => void } = {}): Promise<string> {
363
+ async launch(task: TaskRecord, options: {
364
+ reuseSessionId?: string
365
+ promptContext?: PromptContext
366
+ onSession?: (sessionId: string) => void
367
+ onGoalArmed?: (armed: boolean) => void
368
+ /**
369
+ * GLOBAL native /goal switch, resolved by the caller ONCE per execution and
370
+ * carried here so the decision is frozen for this launch.
371
+ *
372
+ * Frozen deliberately: a settings edit made while the run is in flight must
373
+ * not arm, disarm or otherwise change the goal/acceptance contract of a run
374
+ * already running. Absent means off — the default for every deployment that
375
+ * never touched the switch, including cards written before the option
376
+ * existed. A plain single turn is what an off switch produces; the task-level
377
+ * `goalRun` still applies on top of it (both must ASK for a goal).
378
+ */
379
+ goalEnabled?: boolean
380
+ } = {}): Promise<string> {
360
381
  // A handover bundle overrides the legacy pin fields: the bundle is the
361
382
  // authoritative execution triplet for a continuation card (issue #5). A
362
383
  // card that pins nothing is resolved to the deployment's most recently used
@@ -396,7 +417,7 @@ export class HostExecutionRunner {
396
417
  // at the gate without its binding.
397
418
  options.onSession?.(reused)
398
419
  await this.assertReusedPreset(reused, mode)
399
- await this.pinAndPrompt(reused, task, permission, options.promptContext, options.onGoalArmed)
420
+ await this.pinAndPrompt(reused, task, permission, options.promptContext, options.onGoalArmed, options.goalEnabled === true)
400
421
  } catch (error) {
401
422
  throw new SessionLaunchError(reused, error)
402
423
  }
@@ -410,7 +431,7 @@ export class HostExecutionRunner {
410
431
  options.onSession?.(sessionId)
411
432
  try {
412
433
  await this.invoke('session', 'rename', { sessionId, title: task.title })
413
- await this.pinAndPrompt(sessionId, task, permission, options.promptContext, options.onGoalArmed)
434
+ await this.pinAndPrompt(sessionId, task, permission, options.promptContext, options.onGoalArmed, options.goalEnabled === true)
414
435
  } catch (error) {
415
436
  throw new SessionLaunchError(sessionId, error)
416
437
  }
@@ -456,6 +477,7 @@ export class HostExecutionRunner {
456
477
  permission: TaskPermission | undefined,
457
478
  context: PromptContext = {},
458
479
  onGoalArmed?: (armed: boolean) => void,
480
+ goalEnabled = false,
459
481
  ): Promise<void> {
460
482
  if (permission !== undefined) {
461
483
  if (this.commands === undefined) throw new Error('permission command dispatcher is unavailable')
@@ -484,7 +506,7 @@ export class HostExecutionRunner {
484
506
  mode: 'queue' as const,
485
507
  content: [{ type: 'text' as const, text: promptText(task, context) }],
486
508
  })
487
- const armed = await this.armGoal(sessionId, task, context)
509
+ const armed = await this.armGoal(sessionId, task, context, goalEnabled)
488
510
  onGoalArmed?.(armed)
489
511
  }
490
512
 
@@ -512,14 +534,25 @@ export class HostExecutionRunner {
512
534
  * dsh's goal-round driver keeps starting continuation rounds until the agent
513
535
  * marks it complete (see {@link goalVerdict} for how the run settles).
514
536
  *
515
- * Opt-out: a task whose `goalRun` is an explicit false runs one plain turn.
537
+ * Two switches gate the arming, and BOTH must ask for it:
538
+ * - the GLOBAL native-/goal switch (`goalEnabled`, default OFF), frozen when
539
+ * this execution started; and
540
+ * - the task's own `goalRun` (absent means off, only an explicit true opts
541
+ * the card into a goal run).
542
+ *
516
543
  * A refusal (no command dispatcher, no `/goal` command in this cohort, an
517
- * objective the command rejects) is reported and the run continues as that
518
- * plain turn: the task was asked to run, and a missing goal mode must not
519
- * lose the work.
544
+ * objective the command rejects) is reported and the run continues as a plain
545
+ * turn: the task was asked to run, and a missing goal mode must not lose the
546
+ * work.
547
+ * @param sessionId - the session to arm in.
548
+ * @param task - the task being run.
549
+ * @param context - the run's prompt context.
550
+ * @param goalEnabled - the GLOBAL switch, frozen for this execution.
551
+ * @returns whether `/goal` was really armed.
520
552
  */
521
- private async armGoal(sessionId: ExecutionSessionId, task: TaskRecord, context: PromptContext): Promise<boolean> {
522
- if (task.goalRun === false) return false
553
+ private async armGoal(sessionId: ExecutionSessionId, task: TaskRecord, context: PromptContext, goalEnabled: boolean): Promise<boolean> {
554
+ if (!goalEnabled) return false
555
+ if (task.goalRun !== true) return false
523
556
  if (this.commands === undefined) {
524
557
  console.warn('[dsh-task-board] no command dispatcher is available; task ' + task.id + ' runs without /goal')
525
558
  return false