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

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 (108) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +42 -11
  3. package/README.zh.md +42 -11
  4. package/lib/client.js +4958 -472
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +2841 -121
  7. package/lib/types/client/PluginSettingsCard.d.ts +7 -0
  8. package/lib/types/client/TaskBoardSettingsCard.d.ts +19 -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/board-card-seat.d.ts +34 -0
  26. package/lib/types/client/index.d.ts +19 -0
  27. package/lib/types/client/locales.d.ts +103 -1
  28. package/lib/types/client/native-panel.d.ts +6 -20
  29. package/lib/types/client/seats.d.ts +18 -0
  30. package/lib/types/client/service.d.ts +32 -0
  31. package/lib/types/client/settings-form.d.ts +16 -0
  32. package/lib/types/core/controller.d.ts +37 -1
  33. package/lib/types/core/extension.d.ts +283 -0
  34. package/lib/types/core/handover.d.ts +13 -1
  35. package/lib/types/core/issue-form.d.ts +16 -0
  36. package/lib/types/core/tasks.d.ts +84 -3
  37. package/lib/types/core/use-cases/task-tag.d.ts +48 -0
  38. package/lib/types/core/use-cases/task-update.d.ts +5 -3
  39. package/lib/types/core/verification.d.ts +386 -0
  40. package/lib/types/core/workspace-target.d.ts +41 -0
  41. package/lib/types/host/agent-tools.d.ts +1 -1
  42. package/lib/types/host/extension-registry.d.ts +111 -0
  43. package/lib/types/host/llm-dispatch.d.ts +63 -0
  44. package/lib/types/host/verification-gate.d.ts +71 -0
  45. package/lib/types/host/verification-runner.d.ts +108 -0
  46. package/lib/types/host/workspace-evidence.d.ts +53 -0
  47. package/lib/types/host-ledger.d.ts +52 -8
  48. package/lib/types/host-runner.d.ts +23 -1
  49. package/lib/types/host-service.d.ts +88 -2
  50. package/lib/types/index.d.ts +80 -26
  51. package/lib/types/protocol.d.ts +49 -5
  52. package/lib/types/tool-surface.d.ts +71 -0
  53. package/package.json +27 -26
  54. package/src/client/PluginSettingsCard.tsx +9 -0
  55. package/src/client/TaskBoardSettingsCard.tsx +304 -65
  56. package/src/client/board/ClampedMarkdown.tsx +69 -0
  57. package/src/client/board/ConfirmDialog.tsx +7 -3
  58. package/src/client/board/EditTaskModal.tsx +5 -2
  59. package/src/client/board/LinkSubtaskModal.tsx +12 -7
  60. package/src/client/board/NewTaskModal.tsx +17 -1
  61. package/src/client/board/TagManagerModal.tsx +235 -0
  62. package/src/client/board/TaskBoard.tsx +259 -103
  63. package/src/client/board/TaskCard.tsx +214 -81
  64. package/src/client/board/TaskDetail.tsx +71 -26
  65. package/src/client/board/TaskForm.tsx +13 -3
  66. package/src/client/board/VerificationReport.tsx +203 -0
  67. package/src/client/board/card-view.ts +220 -0
  68. package/src/client/board/icons.tsx +89 -0
  69. package/src/client/board/overlay.tsx +189 -0
  70. package/src/client/board/status-key.ts +27 -0
  71. package/src/client/board/task-markdown.module.css +137 -0
  72. package/src/client/board/task-markdown.tsx +106 -0
  73. package/src/client/board-card-seat.ts +102 -0
  74. package/src/client/board-settings.module.css +66 -0
  75. package/src/client/board.module.css +488 -63
  76. package/src/client/index.ts +43 -5
  77. package/src/client/locales.ts +208 -4
  78. package/src/client/native-panel.tsx +18 -2
  79. package/src/client/seats.tsx +58 -0
  80. package/src/client/service.ts +94 -0
  81. package/src/client/settings-card.module.css +31 -5
  82. package/src/client/settings-form.ts +27 -1
  83. package/src/core/controller.ts +94 -3
  84. package/src/core/extension.ts +376 -0
  85. package/src/core/freeze-snapshot.ts +2 -2
  86. package/src/core/handover.ts +16 -1
  87. package/src/core/issue-form.ts +19 -0
  88. package/src/core/store.ts +16 -0
  89. package/src/core/tasks.ts +100 -6
  90. package/src/core/use-cases/task-parent.ts +3 -2
  91. package/src/core/use-cases/task-tag.ts +110 -0
  92. package/src/core/use-cases/task-update.ts +10 -4
  93. package/src/core/verification.ts +826 -0
  94. package/src/core/workspace-target.ts +59 -0
  95. package/src/host/agent-tools.ts +20 -18
  96. package/src/host/extension-registry.ts +509 -0
  97. package/src/host/llm-dispatch.ts +87 -0
  98. package/src/host/verification-gate.ts +331 -0
  99. package/src/host/verification-runner.ts +535 -0
  100. package/src/host/workspace-evidence.ts +176 -0
  101. package/src/host-ai.ts +15 -9
  102. package/src/host-ledger.ts +143 -32
  103. package/src/host-routes.ts +15 -2
  104. package/src/host-runner.ts +60 -11
  105. package/src/host-service.ts +253 -10
  106. package/src/index.ts +204 -16
  107. package/src/protocol.ts +102 -8
  108. package/src/tool-surface.ts +95 -0
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Workspace targeting for cards that pin none.
3
+ *
4
+ * The Host answers two questions here instead of leaving them to
5
+ * `session.create`, whose own fallback is the Host process working directory
6
+ * (the desktop app's profile directory): which workspace a root card inherits
7
+ * from the session that created it, and which workspace an unpinned run lands
8
+ * in when nothing could be inherited.
9
+ *
10
+ * Structural types only — the Host's `Workspace` entity satisfies them — so the
11
+ * browser program compiles this module without importing an SDK value.
12
+ *
13
+ * @module dsh-task-board/core/workspace-target
14
+ */
15
+
16
+ /** The workspace facts these rules read (a `Workspace` entity subset). */
17
+ export interface WorkspaceCandidate {
18
+ /** Stable workspace record id. */
19
+ readonly id: string
20
+ /** ISO-8601 instant of the workspace's last durable mutation. */
21
+ readonly updatedAt: string
22
+ /** Header-validated session ids the workspace owns, most recent first. */
23
+ readonly sessionIds: readonly string[]
24
+ }
25
+
26
+ /**
27
+ * The workspace that owns one session.
28
+ * @param items - the deployment's workspaces.
29
+ * @param sessionId - the session whose workspace is requested.
30
+ * @returns the owning workspace id, or undefined when none claims the session.
31
+ */
32
+ export function workspaceOwningSession(
33
+ items: readonly WorkspaceCandidate[],
34
+ sessionId: string,
35
+ ): string | undefined {
36
+ return items.find(item => item.sessionIds.includes(sessionId))?.id
37
+ }
38
+
39
+ /**
40
+ * The workspace the board treats as most recently used: the one whose record
41
+ * changed last. Attaching a session stamps that instant, so the newest stamp
42
+ * tracks where the user most recently opened or resumed a session. The
43
+ * deployment's own list order breaks ties, and a stamp that does not parse
44
+ * never outranks one that does; a registry whose stamps are all unreadable
45
+ * falls back to list order rather than to "no workspace at all".
46
+ * @param items - the deployment's workspaces.
47
+ * @returns the most recently used workspace id, or undefined when there is none.
48
+ */
49
+ export function mostRecentWorkspaceId(items: readonly WorkspaceCandidate[]): string | undefined {
50
+ let best: WorkspaceCandidate | undefined
51
+ let bestAt = Number.NEGATIVE_INFINITY
52
+ for (const item of items) {
53
+ const at = Date.parse(item.updatedAt)
54
+ if (!Number.isFinite(at) || at <= bestAt) continue
55
+ best = item
56
+ bestAt = at
57
+ }
58
+ return best?.id ?? items[0]?.id
59
+ }
@@ -39,7 +39,7 @@ export interface TaskBoardToolHost {
39
39
  /** Current board snapshot (tasks + scheduler + power + deployment constants). */
40
40
  snapshot(): TaskBoardSnapshot
41
41
  /** Submit one confirmed Host action; returns the resulting snapshot. */
42
- apply(requestId: string, action: TaskBoardAction, initiator?: string): TaskBoardSnapshot
42
+ apply(requestId: string, action: TaskBoardAction, initiator?: string): TaskBoardSnapshot | Promise<TaskBoardSnapshot>
43
43
  }
44
44
 
45
45
  /** Registered tool names, in registration order. */
@@ -154,6 +154,8 @@ function taskSummary(
154
154
  ...(task.teamRun === true ? { teamRun: true } : {}),
155
155
  // Default-ON switch: only the opt-out is a deviation worth reporting.
156
156
  ...(task.goalRun === false ? { goalRun: false } : {}),
157
+ // Default-OFF opt-out: the deviation is what a reader must be told.
158
+ ...(task.skipVerification === true ? { skipVerification: true } : {}),
157
159
  ...(task.schedule === undefined ? {} : { schedule: scheduleView(task.schedule) }),
158
160
  ...(task.freeze === undefined ? {} : { continuationCard: true }),
159
161
  ...(task.permissionConfirmedAt === undefined ? {} : { permissionConfirmedAt: task.permissionConfirmedAt }),
@@ -260,7 +262,6 @@ function buildSetParentTool(host: TaskBoardToolHost): ToolDefinition {
260
262
  'Pass an empty parentId to detach.',
261
263
  'The Host refuses the link when the parent is unknown or archived, when the moved task is one of its own descendants, when the resulting depth exceeds the deployment subtask limit (default one level, maximum three), or while the task has an unsettled execution (its link may still be a running cascade parent pointer).',
262
264
  'Running a task runs its whole subtree concurrently; a parent card settles only after its own turn and every direct subtask settle.',
263
- 'Triggers: 任务看板, task board, 看板, 子任务, subtask, 关联任务, link task, 解除关联, detach.',
264
265
  ].join(' '),
265
266
  parameters: {
266
267
  taskId: { type: 'string', required: true, description: 'The task whose parent link changes.' },
@@ -269,7 +270,7 @@ function buildSetParentTool(host: TaskBoardToolHost): ToolDefinition {
269
270
  output: { schema: { type: 'json' }, render: renderJson },
270
271
  async execute(args, exec) {
271
272
  try {
272
- const snapshot = host.apply(
273
+ const snapshot = await host.apply(
273
274
  crypto.randomUUID(),
274
275
  { kind: 'set-parent', taskId: args.taskId, parentId: args.parentId === '' ? null : args.parentId },
275
276
  callingSessionId(exec),
@@ -294,7 +295,6 @@ function buildRunTool(host: TaskBoardToolHost): ToolDefinition {
294
295
  'A card whose effective permission is above the deployment session default is refused with confirmation-required until a HUMAN confirms the binding in the board UI; this tool cannot confirm it.',
295
296
  'A card whose task carries teamRun instead starts ONE Team Lead session and the Host spawns a teammate per subtask inside it, so those cards run as an Agent Team rather than as independent sessions; that mode needs the deployment Agent Teams service and is refused when it is missing.',
296
297
  'Executions consume real API quota. Sessions are created asynchronously, so read their ids and outcomes back with task_board_get.',
297
- 'Triggers: 任务看板, task board, 看板, 执行任务, run task, 重新执行, rerun, 子任务, subtask.',
298
298
  ].join(' '),
299
299
  parameters: {
300
300
  taskId: { type: 'string', required: true, description: 'The task to run; running a root task runs its whole subtree.' },
@@ -304,7 +304,7 @@ function buildRunTool(host: TaskBoardToolHost): ToolDefinition {
304
304
  async execute(args, exec) {
305
305
  const before = host.snapshot()
306
306
  try {
307
- const snapshot = host.apply(
307
+ const snapshot = await host.apply(
308
308
  crypto.randomUUID(),
309
309
  { kind: args.rerun === true ? 'rerun' : 'run', taskId: args.taskId },
310
310
  callingSessionId(exec),
@@ -339,18 +339,18 @@ function buildManageTool(host: TaskBoardToolHost): ToolDefinition {
339
339
  name: 'task_board_manage',
340
340
  description: [
341
341
  'Move, archive, restore, delete, or settle one task board card.',
342
- 'move-todo and move-backlog are the manual column moves; a running card cannot be moved.',
342
+ 'move-backlog, move-todo, move-running, move-done and move-failed are the manual column moves and cover every column; a card the runner is executing (one with an open execution) cannot be moved.',
343
+ 'A manual move writes the card column only and never fabricates an execution record, so the card reports a declaration rather than evidence of a run: done/failed declare work finished (or failed) outside a Host-run execution — human work, an external system, a decision made elsewhere — and running says the work is under way without a tracked session. Use task_board_run when the work should actually run in a session here.',
343
344
  'settle force-closes the open execution of a card the board can no longer observe (a stuck running card) and records it cancelled with the caller as the reason, so the card returns to the todo column and can be run again.',
344
345
  'archive takes the whole subtask tree off the board and is refused while any member has an unsettled execution; restore brings the task, its ancestors and its subtree back; delete removes one card and is refused while it still has subtasks (detach or delete them first) or while it runs.',
345
346
  'It cannot confirm a permission binding: the confirmation gate is a human act performed in the board UI.',
346
- 'Triggers: 任务看板, task board, 看板, 归档, archive, 删除任务, delete task, 移动任务, move task, 卡住, stuck, 强制结算, settle.',
347
347
  ].join(' '),
348
348
  parameters: {
349
349
  taskId: { type: 'string', required: true, description: 'The task to act on.' },
350
350
  action: {
351
351
  type: 'string',
352
352
  required: true,
353
- enum: ['move-todo', 'move-backlog', 'archive', 'restore', 'delete', 'settle'],
353
+ enum: ['move-todo', 'move-backlog', 'move-running', 'move-done', 'move-failed', 'archive', 'restore', 'delete', 'settle'],
354
354
  description: 'The lifecycle operation to perform.',
355
355
  },
356
356
  },
@@ -360,6 +360,9 @@ function buildManageTool(host: TaskBoardToolHost): ToolDefinition {
360
360
  const actions: Record<string, TaskBoardAction> = {
361
361
  'move-todo': { kind: 'move', taskId: args.taskId, status: 'todo' },
362
362
  'move-backlog': { kind: 'move', taskId: args.taskId, status: 'backlog' },
363
+ 'move-running': { kind: 'move', taskId: args.taskId, status: 'running' },
364
+ 'move-done': { kind: 'move', taskId: args.taskId, status: 'done' },
365
+ 'move-failed': { kind: 'move', taskId: args.taskId, status: 'failed' },
363
366
  archive: { kind: 'archive', taskId: args.taskId },
364
367
  settle: { kind: 'settle', taskId: args.taskId },
365
368
  restore: { kind: 'restore', taskId: args.taskId },
@@ -371,7 +374,7 @@ function buildManageTool(host: TaskBoardToolHost): ToolDefinition {
371
374
  return refused('task-not-found', 'no task with id ' + args.taskId)
372
375
  }
373
376
  try {
374
- const snapshot = host.apply(crypto.randomUUID(), action, initiator)
377
+ const snapshot = await host.apply(crypto.randomUUID(), action, initiator)
375
378
  if (args.action === 'delete') return json({ ok: true, action: args.action, taskId: args.taskId })
376
379
  const task = snapshot.tasks.find(item => item.id === args.taskId)
377
380
  if (task === undefined) return refused('task-not-found', 'no task with id ' + args.taskId)
@@ -392,7 +395,6 @@ function buildScheduleTool(host: TaskBoardToolHost): ToolDefinition {
392
395
  'A due scheduled task runs the same cascade a manual run does, so a root task runs its whole subtask tree.',
393
396
  'A schedule whose tree contains an unconfirmed above-default permission is refused and rolls to its next occurrence; the reason is reported in the list board summary as schedulerError.',
394
397
  'Missed occurrences during Host downtime are skipped, never queued; a task that is already running skips its occurrence.',
395
- 'Triggers: 任务看板, task board, 看板, 定时任务, schedule, cron, 每天执行.',
396
398
  ].join(' '),
397
399
  parameters: {
398
400
  taskId: { type: 'string', required: true, description: 'The task whose schedule changes.' },
@@ -421,7 +423,7 @@ function buildScheduleTool(host: TaskBoardToolHost): ToolDefinition {
421
423
  ...(args.timeZone === undefined ? {} : { timeZone: args.timeZone === '' ? null : args.timeZone }),
422
424
  }
423
425
  try {
424
- const snapshot = host.apply(crypto.randomUUID(), { kind: 'set-schedule', taskId: args.taskId, patch }, callingSessionId(exec))
426
+ const snapshot = await host.apply(crypto.randomUUID(), { kind: 'set-schedule', taskId: args.taskId, patch }, callingSessionId(exec))
425
427
  const task = snapshot.tasks.find(item => item.id === args.taskId)
426
428
  if (task === undefined) return refused('task-not-found', 'no task with id ' + args.taskId)
427
429
  return json({
@@ -445,7 +447,6 @@ function buildListTool(host: TaskBoardToolHost): ToolDefinition {
445
447
  'Filters: status column, parentId (direct subtasks), rootsOnly, tag, free-text query, and archived inclusion.',
446
448
  'Returns each task id, title, column, parent/subtask link, execution targets, schedule and latest run outcome, plus the board summary (revision, time zone, subtask depth limit, session-default permission).',
447
449
  'Use task_board_get for one task in full, task_board_create to add work, and task_board_run to execute it.',
448
- 'Triggers: 任务看板, task board, 看板, 子任务, subtask, task list, 定时任务, scheduled task.',
449
450
  ].join(' '),
450
451
  parameters: {
451
452
  status: { type: 'string', enum: ['backlog', 'todo', 'running', 'done', 'failed'], description: 'Only cards in this column.' },
@@ -491,7 +492,6 @@ function buildGetTool(host: TaskBoardToolHost): ToolDefinition {
491
492
  description: [
492
493
  'Read one task board card in full: prompt, description, labels, column, parent link, direct subtasks, execution targets, schedule, permission gate state, and the last ten execution attempts with their session ids and outcomes.',
493
494
  'Use it before running or editing a card, and after a run to read the settled outcome.',
494
- 'Triggers: 任务看板, task board, 看板, 子任务, subtask, task detail.',
495
495
  ].join(' '),
496
496
  parameters: {
497
497
  taskId: { type: 'string', required: true, description: 'Task id, as reported by task_board_list or task_board_create.' },
@@ -515,20 +515,20 @@ function buildCreateTool(host: TaskBoardToolHost): ToolDefinition {
515
515
  'A subtask inherits every execution target it does not override (workspace, agent preset, permission, model) from its parent, and inherits the parent human permission confirmation together with an inherited binding.',
516
516
  'The deployment caps subtask depth (default one level, maximum three): a subtask of a subtask is refused unless the board is configured deeper.',
517
517
  'The task lands in the todo column and does NOT run; call task_board_run to execute it.',
518
- 'Triggers: 任务看板, task board, 看板, 新建任务, create task, 子任务, subtask, 定时任务.',
519
518
  ].join(' '),
520
519
  parameters: {
521
520
  title: { type: 'string', required: true, description: 'Short display title (one line).' },
522
521
  description: { type: 'string', description: 'Longer human description shown in the detail view.' },
523
522
  prompt: { type: 'string', description: 'The instruction sent to the execution agent; the title is used when blank.' },
524
523
  parentId: { type: 'string', description: 'Parent task id, making this a subtask. Omit for a root task.' },
525
- workspaceId: { type: 'string', description: 'Workspace id the execution must run in; omit to inherit the parent value or use the most recent workspace.' },
524
+ workspaceId: { type: 'string', description: 'Workspace id the execution must run in; omit to inherit the workspace this session is in (root task) or the parent value (subtask).' },
526
525
  mode: { type: 'string', description: 'Agent preset id the execution session is composed from; omit for the deployment default or the parent value.' },
527
526
  permission: { type: 'string', enum: [...TASK_PERMISSIONS], description: 'Permission preset for the execution session. Omit to inherit the parent binding; a value above the session default needs a human confirmation in the board UI before the card can run.' },
528
527
  model: { type: 'string', description: 'Pinned model as provider/model (or a model id); omit for the host default or the parent value.' },
529
528
  reuseSession: { type: 'boolean', description: 'Continue later runs in the previous execution session instead of a fresh conversation.' },
530
529
  teamRun: { type: 'boolean', description: 'Run this task as an Agent Team: running it starts one Team Lead session and the Host spawns a teammate per subtask inside it. Omit or false for one independent session per member. Refused when the deployment serves no Agent Teams service.' },
531
530
  goalRun: { type: 'boolean', description: 'Start each run with dsh built-in /goal so the session keeps working continuation rounds until the goal completes. Default true (omit to keep it); pass false for a single plain turn.' },
531
+ skipVerification: { type: 'boolean', description: 'Run this task without the board acceptance gate: update_goal may mark the goal complete without a passing acceptance, and the execution record is marked skipped. Default false (omit to inherit the board-wide acceptance switch). Only affects executions started afterwards.' },
532
532
  tags: {
533
533
  type: 'array',
534
534
  description: 'Labels: the name renders as a badge and drives the board filter; promptPrefix is injected ahead of the execution prompt on every run.',
@@ -571,11 +571,12 @@ function buildCreateTool(host: TaskBoardToolHost): ToolDefinition {
571
571
  ...(args.reuseSession === true ? { reuseSession: true } : {}),
572
572
  ...(args.teamRun === true ? { teamRun: true } : {}),
573
573
  ...(args.goalRun === false ? { goalRun: false } : {}),
574
+ ...(args.skipVerification === true ? { skipVerification: true } : {}),
574
575
  ...(tags.length === 0 ? {} : { tags }),
575
576
  ...(args.schedule === undefined ? {} : { schedule: { enabled: args.schedule.enabled, cron: args.schedule.cron } }),
576
577
  }
577
578
  try {
578
- const snapshot = host.apply(crypto.randomUUID(), { kind: 'create', id, input }, callingSessionId(exec))
579
+ const snapshot = await host.apply(crypto.randomUUID(), { kind: 'create', id, input }, callingSessionId(exec))
579
580
  const task = snapshot.tasks.find(item => item.id === id)
580
581
  if (task === undefined) return refused('create-failed', 'the Host did not confirm the new task')
581
582
  return json({ ok: true, task: taskDetail(task, snapshot.tasks, snapshot.sessionDefaultPermission) })
@@ -594,7 +595,6 @@ function buildUpdateTool(host: TaskBoardToolHost): ToolDefinition {
594
595
  'Only the fields you pass change; pass an empty string to clear a target, or an empty labels array to clear the labels.',
595
596
  'Title, description and prompt freeze once the task has started executing; the execution targets stay editable because they only affect future runs.',
596
597
  'Changing the pinned permission re-arms the permission confirmation gate.',
597
- 'Triggers: 任务看板, task board, 看板, 修改任务, edit task, 子任务, 标签, tags.',
598
598
  ].join(' '),
599
599
  parameters: {
600
600
  taskId: { type: 'string', required: true, description: 'Task id to edit.' },
@@ -622,6 +622,7 @@ function buildUpdateTool(host: TaskBoardToolHost): ToolDefinition {
622
622
  reuseSession: { type: 'boolean', description: 'Continue later runs in the previous execution session.' },
623
623
  teamRun: { type: 'boolean', description: 'Switches this task between a plain cascade (one session per member) and an Agent Team run (Lead session plus a teammate per subtask).' },
624
624
  goalRun: { type: 'boolean', description: 'Whether each run starts with dsh built-in /goal (default true). Pass false to run one plain turn instead.' },
625
+ skipVerification: { type: 'boolean', description: 'Whether this task runs without the board acceptance gate (default false, inherit the board-wide switch). Pass true to opt out; pass false to opt back in. Only affects executions started afterwards.' },
625
626
  tags: {
626
627
  type: 'array',
627
628
  description: 'Replacement label set; an empty array clears all labels.',
@@ -651,6 +652,7 @@ function buildUpdateTool(host: TaskBoardToolHost): ToolDefinition {
651
652
  if (has(args, 'reuseSession')) patch.reuseSession = args.reuseSession === true
652
653
  if (has(args, 'teamRun')) patch.teamRun = args.teamRun === true
653
654
  if (has(args, 'goalRun')) patch.goalRun = args.goalRun === false ? false : true
655
+ if (has(args, 'skipVerification')) patch.skipVerification = args.skipVerification === true ? true : false
654
656
  if (has(args, 'tags')) {
655
657
  const tags = args.tags ?? []
656
658
  patch.tags = tags.length === 0
@@ -662,7 +664,7 @@ function buildUpdateTool(host: TaskBoardToolHost): ToolDefinition {
662
664
  }
663
665
  if (Object.keys(patch).length === 0) return refused('nothing-to-change', 'pass at least one field to update')
664
666
  try {
665
- const snapshot = host.apply(crypto.randomUUID(), { kind: 'update', taskId: args.taskId, patch }, callingSessionId(exec))
667
+ const snapshot = await host.apply(crypto.randomUUID(), { kind: 'update', taskId: args.taskId, patch }, callingSessionId(exec))
666
668
  const task = snapshot.tasks.find(item => item.id === args.taskId)
667
669
  if (task === undefined) return refused('task-not-found', 'no task with id ' + args.taskId)
668
670
  return json({ ok: true, task: taskDetail(task, snapshot.tasks, snapshot.sessionDefaultPermission) })