@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
@@ -208,7 +208,7 @@ export function makeTaskBoardRoutes(
208
208
  if (parsed.action.kind !== 'import' && Buffer.byteLength(body.raw) > ACTION_LIMIT) {
209
209
  return writeJson(res, 413, { ok: false, error: 'body-too-large' }, { 'cache-control': 'no-store' })
210
210
  }
211
- writeJson(res, 200, service.apply(parsed.requestId, parsed.action, parsed.initiator), { 'cache-control': 'no-store' })
211
+ writeJson(res, 200, await service.apply(parsed.requestId, parsed.action, parsed.initiator), { 'cache-control': 'no-store' })
212
212
  } catch (error) {
213
213
  const message = error instanceof Error ? error.message : String(error)
214
214
  writeJson(res, message === 'body-too-large' ? 413 : 400, { ok: false, error: message }, { 'cache-control': 'no-store' })
@@ -289,5 +289,18 @@ export function makeTaskBoardRoutes(
289
289
  }
290
290
  },
291
291
  }
292
- return [state, action, events, parse]
292
+ const verification: WebRoute = {
293
+ kind: 'exact',
294
+ path: `${TASK_BOARD_API_PREFIX}/verification`,
295
+ handler: async (req, res): Promise<void> => {
296
+ if (req.method !== 'GET') return writeJson(res, 405, { ok: false, error: 'method-not-allowed' }, { 'cache-control': 'no-store' })
297
+ if (!guard(req, res)) return
298
+ try {
299
+ writeJson(res, 200, await service.verificationOptions(), { 'cache-control': 'no-store' })
300
+ } catch (error) {
301
+ writeJson(res, 500, { ok: false, error: error instanceof Error ? error.message : String(error) }, { 'cache-control': 'no-store' })
302
+ }
303
+ },
304
+ }
305
+ return [state, action, events, parse, verification]
293
306
  }
@@ -3,9 +3,13 @@ import type { SessionAddress, SessionHistoryRecord, SessionListValue, SessionPag
3
3
  import type { CommandResult } from '@deepseek-ai/dsh-commands/types'
4
4
  import type { Workspace } from '@deepseek-ai/dsh-workspace/types'
5
5
  import { teammateName } from './core/subtask.ts'
6
+ import { mostRecentWorkspaceId } from './core/workspace-target.ts'
6
7
  import type { TaskPermission, TaskRecord } from './core/tasks.ts'
7
8
 
8
- /** Host services needed to validate a task's workspace before creating a session. */
9
+ /**
10
+ * Host services needed to validate a task's workspace before creating a
11
+ * session, and to resolve the workspace an unpinned card runs in.
12
+ */
9
13
  export interface TaskBoardWorkspaceRegistry {
10
14
  list(): readonly Workspace[]
11
15
  }
@@ -155,12 +159,12 @@ export interface PromptContext {
155
159
  /** True when those members run as teammates inside this session (Team Lead). */
156
160
  team?: boolean
157
161
  /**
158
- * Scheduled-run provenance: the instant the cron rule actually fired and the
159
- * IANA zone its wall clock was read in. Present only for a cron-triggered
160
- * run, so the model can resolve an unqualified date in the run's own terms
161
- * 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).
162
166
  */
163
- schedule?: { triggeredAt: number; timeZone: string; cron: string }
167
+ schedule?: { triggeredAt: number; timeZone: string; mode: 'cron' | 'once'; cron?: string }
164
168
  }
165
169
 
166
170
  /**
@@ -180,16 +184,20 @@ function peerPromptPreamble(context: PromptContext): string | undefined {
180
184
  }
181
185
 
182
186
  /**
183
- * The scheduled-run section: this execution was triggered by the card's cron
184
- * rule rather than by a person. It states the firing instant, the rule's zone,
185
- * 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
186
190
  * scheduler fires on a wall clock in a specific IANA zone, which is not
187
- * 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.
188
193
  */
189
194
  function schedulePromptPreamble(context: PromptContext): string | undefined {
190
195
  const schedule = context.schedule
191
196
  if (schedule === undefined) return undefined
192
197
  const stamp = new Date(schedule.triggeredAt).toISOString()
198
+ if (schedule.mode === 'once') {
199
+ return `本次执行由任务看板的单次定时自动触发:计划时间 ${stamp}(UTC),时区 ${schedule.timeZone}。这是一次性计划,本次执行之后该卡片不会再自动执行。需要判断「今天」「现在」或计算时间窗口时,以该时区的触发时间为准。`
200
+ }
193
201
  return `本次执行由任务看板的定时规则自动触发:触发时间 ${stamp}(UTC),规则时区 ${schedule.timeZone},cron 表达式 ${schedule.cron}。需要判断「今天」「现在」或计算时间窗口时,以该时区的触发时间为准。`
194
202
  }
195
203
 
@@ -298,6 +306,8 @@ function nonCompletedTurnEnd(data: unknown): string | undefined {
298
306
  */
299
307
  function invokeWireArgs(namespace: string, method: string, request: Record<string, unknown>): Record<string, unknown> {
300
308
  if (namespace === 'agentPresets' && method === 'list') return {}
309
+ // session/modelCatalog declares zero parameters, so its args must be {}.
310
+ if (namespace === 'session' && method === 'modelCatalog') return {}
301
311
  if (namespace === 'session' && method === 'list') return { _request: request }
302
312
  return { request }
303
313
  }
@@ -328,6 +338,18 @@ export class HostExecutionRunner {
328
338
  return this.gateway.stream({ namespace, method, args: { request }, ...(signal === undefined ? {} : { signal }) })
329
339
  }
330
340
 
341
+ /**
342
+ * The workspace an unpinned run lands in: the deployment's most recently used
343
+ * one. Undefined when the deployment serves no workspace registry or has
344
+ * registered none yet; the launch then keeps the "let the Host decide" shape,
345
+ * which is the only remaining path to the Host's own working directory.
346
+ * @returns the workspace id to create the session in, when one is known.
347
+ */
348
+ private recentWorkspaceId(): string | undefined {
349
+ if (this.workspaceRegistry === undefined) return undefined
350
+ return mostRecentWorkspaceId(this.workspaceRegistry.list())
351
+ }
352
+
331
353
  /**
332
354
  * Launch one execution. Without `options.reuseSessionId` a fresh session is
333
355
  * created, renamed, pinned, and prompted (the historical contract). With it,
@@ -338,10 +360,31 @@ export class HostExecutionRunner {
338
360
  * @param options - optional session to continue in.
339
361
  * @returns the session id the execution runs in.
340
362
  */
341
- async launch(task: TaskRecord, options: { reuseSessionId?: string; promptContext?: PromptContext } = {}): 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> {
342
381
  // A handover bundle overrides the legacy pin fields: the bundle is the
343
- // authoritative execution triplet for a continuation card (issue #5).
344
- const workspaceId = task.handover?.workspaceId ?? task.workspaceId
382
+ // authoritative execution triplet for a continuation card (issue #5). A
383
+ // card that pins nothing is resolved to the deployment's most recently used
384
+ // workspace instead of being left to `session.create`, whose own fallback is
385
+ // the Host process working directory (the desktop app's profile directory) —
386
+ // never the project the user was in when the card was written.
387
+ const workspaceId = task.handover?.workspaceId ?? task.workspaceId ?? this.recentWorkspaceId()
345
388
  const mode = task.handover?.mode ?? task.mode
346
389
  const permission = task.handover?.permission ?? task.permission
347
390
 
@@ -369,8 +412,12 @@ export class HostExecutionRunner {
369
412
  // whose session was composed from a different preset now fails closed
370
413
  // instead of silently running under the wrong composition, matching
371
414
  // the fresh branch's `agentPreset` assertion (issue #1708).
415
+ // Report the session before anything is queued into it, so the board
416
+ // can bind the execution first and a completion claim can never arrive
417
+ // at the gate without its binding.
418
+ options.onSession?.(reused)
372
419
  await this.assertReusedPreset(reused, mode)
373
- await this.pinAndPrompt(reused, task, permission, options.promptContext)
420
+ await this.pinAndPrompt(reused, task, permission, options.promptContext, options.onGoalArmed, options.goalEnabled === true)
374
421
  } catch (error) {
375
422
  throw new SessionLaunchError(reused, error)
376
423
  }
@@ -381,9 +428,10 @@ export class HostExecutionRunner {
381
428
  ...(mode === undefined ? {} : { agentPreset: mode }),
382
429
  }) as { sessionId: ExecutionSessionId }
383
430
  const sessionId = created.sessionId
431
+ options.onSession?.(sessionId)
384
432
  try {
385
433
  await this.invoke('session', 'rename', { sessionId, title: task.title })
386
- await this.pinAndPrompt(sessionId, task, permission, options.promptContext)
434
+ await this.pinAndPrompt(sessionId, task, permission, options.promptContext, options.onGoalArmed, options.goalEnabled === true)
387
435
  } catch (error) {
388
436
  throw new SessionLaunchError(sessionId, error)
389
437
  }
@@ -428,6 +476,8 @@ export class HostExecutionRunner {
428
476
  task: TaskRecord,
429
477
  permission: TaskPermission | undefined,
430
478
  context: PromptContext = {},
479
+ onGoalArmed?: (armed: boolean) => void,
480
+ goalEnabled = false,
431
481
  ): Promise<void> {
432
482
  if (permission !== undefined) {
433
483
  if (this.commands === undefined) throw new Error('permission command dispatcher is unavailable')
@@ -456,7 +506,25 @@ export class HostExecutionRunner {
456
506
  mode: 'queue' as const,
457
507
  content: [{ type: 'text' as const, text: promptText(task, context) }],
458
508
  })
459
- await this.armGoal(sessionId, task, context)
509
+ const armed = await this.armGoal(sessionId, task, context, goalEnabled)
510
+ onGoalArmed?.(armed)
511
+ }
512
+
513
+ /**
514
+ * Read the host's model catalog: the default route an unconfigured session
515
+ * starts at, and each provider's models with the reasoning metadata its
516
+ * adapter exposes. The acceptance settings card and the frozen contract both
517
+ * resolve against it, so "inherit host" means the host's own configuration
518
+ * rather than a session's or a card's pinned model.
519
+ * @returns the raw catalog value, or undefined when this cohort serves none.
520
+ */
521
+ async modelCatalog(): Promise<unknown> {
522
+ try {
523
+ return await this.invoke('session', 'modelCatalog', {})
524
+ } catch (error) {
525
+ console.warn('[dsh-task-board] session/modelCatalog failed; the acceptance settings fall back to the configured route', error)
526
+ return undefined
527
+ }
460
528
  }
461
529
 
462
530
  /**
@@ -466,17 +534,28 @@ export class HostExecutionRunner {
466
534
  * dsh's goal-round driver keeps starting continuation rounds until the agent
467
535
  * marks it complete (see {@link goalVerdict} for how the run settles).
468
536
  *
469
- * 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
+ *
470
543
  * A refusal (no command dispatcher, no `/goal` command in this cohort, an
471
- * objective the command rejects) is reported and the run continues as that
472
- * plain turn: the task was asked to run, and a missing goal mode must not
473
- * 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.
474
552
  */
475
- private async armGoal(sessionId: ExecutionSessionId, task: TaskRecord, context: PromptContext): Promise<void> {
476
- if (task.goalRun === false) return
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
477
556
  if (this.commands === undefined) {
478
557
  console.warn('[dsh-task-board] no command dispatcher is available; task ' + task.id + ' runs without /goal')
479
- return
558
+ return false
480
559
  }
481
560
  try {
482
561
  const command = await this.commands.execute(
@@ -486,13 +565,16 @@ export class HostExecutionRunner {
486
565
  )
487
566
  if (command === undefined) {
488
567
  console.warn('[dsh-task-board] /goal was not acknowledged for session ' + sessionId + '; the run continues as a plain turn')
489
- return
568
+ return false
490
569
  }
491
570
  if (command.kind !== 'success') {
492
571
  console.warn('[dsh-task-board] /goal was refused for session ' + sessionId + ': ' + (command.text ?? 'no reason reported') + '; the run continues as a plain turn')
572
+ return false
493
573
  }
574
+ return true
494
575
  } catch (error) {
495
576
  console.warn('[dsh-task-board] could not arm /goal on session ' + sessionId + '; the run continues as a plain turn', error)
577
+ return false
496
578
  }
497
579
  }
498
580