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

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 (83) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +34 -18
  3. package/README.zh.md +34 -18
  4. package/lib/client.js +2804 -1024
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +2576 -416
  7. package/lib/types/client/CardDefaultPresetField.d.ts +35 -0
  8. package/lib/types/client/TaskBoardSettingsCard.d.ts +61 -0
  9. package/lib/types/client/board/ReasoningEffortField.d.ts +25 -0
  10. package/lib/types/client/board/model-options.d.ts +67 -0
  11. package/lib/types/client/board/preset-label.d.ts +21 -0
  12. package/lib/types/client/board/preset-roster.d.ts +45 -0
  13. package/lib/types/client/board/team-run-promotion.d.ts +26 -0
  14. package/lib/types/client/index.d.ts +4 -5
  15. package/lib/types/client/locales.d.ts +81 -0
  16. package/lib/types/client/plugin-card-seat.d.ts +5 -0
  17. package/lib/types/client/schedule-zone.d.ts +50 -0
  18. package/lib/types/core/card-defaults.d.ts +78 -0
  19. package/lib/types/core/controller.d.ts +41 -17
  20. package/lib/types/core/launch-width.d.ts +31 -0
  21. package/lib/types/core/poll-cadence.d.ts +31 -0
  22. package/lib/types/core/reasoning-effort.d.ts +57 -0
  23. package/lib/types/core/subtask.d.ts +30 -8
  24. package/lib/types/core/tasks.d.ts +163 -14
  25. package/lib/types/core/use-cases/task-create.d.ts +10 -5
  26. package/lib/types/core/use-cases/task-schedule.d.ts +68 -10
  27. package/lib/types/core/use-cases/task-update.d.ts +1 -1
  28. package/lib/types/core/verification-budget.d.ts +52 -0
  29. package/lib/types/core/verification.d.ts +376 -9
  30. package/lib/types/host/launch-admission.d.ts +72 -0
  31. package/lib/types/host/verification-gate.d.ts +24 -0
  32. package/lib/types/host/verification-runner.d.ts +38 -7
  33. package/lib/types/host-ledger.d.ts +91 -11
  34. package/lib/types/host-runner.d.ts +111 -13
  35. package/lib/types/host-service.d.ts +199 -8
  36. package/lib/types/index.d.ts +141 -0
  37. package/lib/types/protocol.d.ts +63 -12
  38. package/package.json +29 -33
  39. package/src/client/CardDefaultPresetField.tsx +101 -0
  40. package/src/client/TaskBoardSettingsCard.tsx +288 -17
  41. package/src/client/board/LinkSubtaskModal.tsx +8 -0
  42. package/src/client/board/NewTaskModal.tsx +237 -60
  43. package/src/client/board/ReasoningEffortField.tsx +50 -0
  44. package/src/client/board/TaskCard.tsx +3 -3
  45. package/src/client/board/TaskDetail.tsx +261 -24
  46. package/src/client/board/VerificationReport.tsx +132 -13
  47. package/src/client/board/model-options.ts +178 -0
  48. package/src/client/board/preset-label.ts +32 -0
  49. package/src/client/board/preset-roster.ts +58 -0
  50. package/src/client/board/status-key.ts +3 -0
  51. package/src/client/board/team-run-promotion.ts +39 -0
  52. package/src/client/index.ts +105 -108
  53. package/src/client/locales.ts +171 -6
  54. package/src/client/plugin-card-seat.ts +7 -0
  55. package/src/client/schedule-zone.ts +100 -2
  56. package/src/client/settings-entry-form.ts +0 -1
  57. package/src/core/card-defaults.ts +132 -0
  58. package/src/core/controller.ts +55 -16
  59. package/src/core/launch-width.ts +38 -0
  60. package/src/core/poll-cadence.ts +41 -0
  61. package/src/core/reasoning-effort.ts +92 -0
  62. package/src/core/store.ts +69 -15
  63. package/src/core/subtask.ts +48 -13
  64. package/src/core/tasks.ts +213 -39
  65. package/src/core/use-cases/task-create.ts +57 -25
  66. package/src/core/use-cases/task-schedule.ts +190 -25
  67. package/src/core/use-cases/task-update.ts +9 -4
  68. package/src/core/verification-budget.ts +115 -0
  69. package/src/core/verification.ts +785 -17
  70. package/src/host/agent-tools.ts +127 -29
  71. package/src/host/launch-admission.ts +140 -0
  72. package/src/host/verification-gate.ts +172 -16
  73. package/src/host/verification-runner.ts +207 -40
  74. package/src/host-ledger.ts +346 -48
  75. package/src/host-runner.ts +234 -35
  76. package/src/host-service.ts +539 -52
  77. package/src/index.ts +250 -7
  78. package/src/protocol.ts +160 -32
  79. package/lib/invariant.js +0 -6
  80. package/lib/types/client/board-card-seat.d.ts +0 -34
  81. package/lib/types/invariant.d.ts +0 -3
  82. package/src/client/board-card-seat.ts +0 -102
  83. package/src/invariant.ts +0 -4
@@ -18,17 +18,36 @@
18
18
  * - an anomaly (timeout, authentication, unparseable answer, an unusable judge
19
19
  * route) is recorded separately from a quality verdict and is never treated
20
20
  * as one;
21
+ * - exhausting the anomaly budget does NOT judge the card: an environment that
22
+ * cannot answer is not the work failing, so the cycle is held open with no
23
+ * judge call until a user clears the recorded anomalies (issue #1828);
24
+ * - an attempt the time budget ends before any verdict is recorded as its own
25
+ * stage and charged to no budget at all (issue #1828);
26
+ * - an INVALID acceptance (a veto whose required evidence is missing,
27
+ * unlocatable, or aimed at the empty-work baseline) is recorded in its own
28
+ * stage, spends its OWN bounded budget, and never consumes a quality attempt
29
+ * or fails the card; when that budget is spent the cycle is held for a human
30
+ * exactly like a spent anomaly budget;
21
31
  * - concurrent completion calls for one execution share one acceptance.
32
+
33
+ * The validity check itself is program-level (`assessAcceptanceValidity` in
34
+ * `core/verification.ts`), applied after the judge answers and BEFORE any
35
+ * quality verdict is written: a stronger prompt alone could not enforce it.
22
36
  */
23
37
  import type { PreToolDecision } from '@deepseek-ai/dsh-tools'
24
38
  import type { LlmRuntime } from '@deepseek-ai/dsh-llm'
25
39
  import {
26
40
  MAX_EXCEPTION_ATTEMPTS,
41
+ MAX_INVALID_ATTEMPTS,
27
42
  MAX_QUALITY_ATTEMPTS,
28
43
  exceptionAttempts,
29
44
  hasExceptionBudget,
30
45
  finalFailureReason,
46
+ hasInvalidBudget,
31
47
  hasQualityBudget,
48
+ invalidAttempts,
49
+ invalidAcceptanceFeedback,
50
+ invalidHoldReason,
32
51
  passedAttempt,
33
52
  qualityAttempts,
34
53
  qualityFeedback,
@@ -37,6 +56,7 @@ import {
37
56
  } from '../core/verification.ts'
38
57
  import { collectEvidence, evidenceEvents, runAcceptance } from './verification-runner.ts'
39
58
  import { renderWorkspaceEvidence, type WorkspaceChangeSource } from './workspace-evidence.ts'
59
+ import { DEFAULT_VERIFICATION_BUDGET_MS, verificationBudgetMs, verificationCallTimeoutMs } from '../core/verification-budget.ts'
40
60
  import type { HostTaskLedger } from '../host-ledger.ts'
41
61
  import type { ExecutionRecord, TaskRecord } from '../core/tasks.ts'
42
62
 
@@ -58,6 +78,16 @@ export interface GoalVerificationGateDeps {
58
78
  * that serves none) simply means the judge sees the trajectory alone.
59
79
  */
60
80
  workspaceChanges?: () => WorkspaceChangeSource | undefined
81
+ /**
82
+ * Ceiling of one judge request, read per acceptance (a row setting). Absent
83
+ * keeps the default, so a programmatic mount still judges.
84
+ */
85
+ callTimeoutMs?: () => number
86
+ /**
87
+ * Total ceiling of one acceptance attempt, read per acceptance (a row
88
+ * setting). Absent keeps the default.
89
+ */
90
+ budgetMs?: () => number
61
91
  logger: { warn(message: string, ...rest: unknown[]): void }
62
92
  now?: () => number
63
93
  }
@@ -140,6 +170,80 @@ export function createGoalVerificationGate(deps: GoalVerificationGateDeps): (exe
140
170
  )
141
171
  }
142
172
 
173
+ /**
174
+ * Hold the cycle open after its non-quality budget ran out.
175
+ *
176
+ * A judge route that times out, cannot authenticate or cannot be resolved is
177
+ * the ENVIRONMENT failing, not the work, so this records the attempt and
178
+ * refuses the completion claim without writing a `failedReason` and without
179
+ * blocking the goal (issue #1828). The execution therefore survives for a
180
+ * user who fixes the environment and clears the recorded anomalies, instead
181
+ * of carrying a verdict that only the clock produced.
182
+ */
183
+ const hold = (
184
+ taskId: string,
185
+ executionId: string,
186
+ verification: ExecutionVerification,
187
+ message: string,
188
+ detail: string,
189
+ code: string,
190
+ ): PreToolDecision => {
191
+ write(taskId, executionId, { ...verification, inFlight: undefined })
192
+ return deny(bounded(message), code, detail)
193
+ }
194
+
195
+ /** The refusal one recorded anomaly returns to the fixing agent. */
196
+ const anomalyRefusal = (spend: ExecutionVerification, error: string | undefined): PreToolDecision =>
197
+ deny(
198
+ bounded('[任务看板 · 验收异常] ' + (error ?? 'unknown') + '\n这是验收异常而非质量判定,本次未消耗质量验收额度(已用 ' + exceptionAttempts(spend).length + '/' + MAX_EXCEPTION_ATTEMPTS + ')。请修复环境后再次调用 update_goal(action: complete)。'),
199
+ 'TASK_BOARD_VERIFICATION_ANOMALY',
200
+ error,
201
+ )
202
+
203
+ /**
204
+ * The refusal one recorded INVALID acceptance returns.
205
+ *
206
+ * The claim is still refused (no pass record exists), but the agent is told
207
+ * the veto itself was unusable and is explicitly NOT asked to repair anything
208
+ * on that basis. The reason and scores stay recorded for review; the quality
209
+ * budget is untouched.
210
+ */
211
+ const invalidRefusal = (spend: ExecutionVerification, attempt: VerificationAttempt): PreToolDecision =>
212
+ deny(
213
+ bounded(invalidAcceptanceFeedback(attempt, MAX_INVALID_ATTEMPTS - invalidAttempts(spend).length)),
214
+ 'TASK_BOARD_VERIFICATION_INVALID',
215
+ attempt.error,
216
+ )
217
+
218
+ /**
219
+ * Hold a cycle whose invalid-acceptance budget is spent.
220
+ *
221
+ * No usable veto was ever produced, so the board neither passes nor fails the
222
+ * card: it stops calling the judge and leaves the execution for a human. Only
223
+ * an explicit user action (or a rerun) resumes it, which is what bounds the
224
+ * retries.
225
+ */
226
+ const invalidHeld = (taskId: string, executionId: string, spend: ExecutionVerification): PreToolDecision =>
227
+ hold(
228
+ taskId,
229
+ executionId,
230
+ spend,
231
+ '[任务看板 · 验收无效] ' + invalidHoldReason(invalidAttempts(spend).length),
232
+ 'acceptance produced no usable veto; a user action must clear the recorded invalid acceptances',
233
+ 'TASK_BOARD_VERIFICATION_INVALID',
234
+ )
235
+
236
+ /** The refusal a spent anomaly budget returns: no verdict, and no card failure. */
237
+ const anomalyHeld = (taskId: string, executionId: string, spend: ExecutionVerification): PreToolDecision =>
238
+ hold(
239
+ taskId,
240
+ executionId,
241
+ spend,
242
+ '[任务看板 · 验收异常] 验收异常额度已用尽(' + MAX_EXCEPTION_ATTEMPTS + '/' + MAX_EXCEPTION_ATTEMPTS + '):本执行周期不再调用裁判模型,也不把这次执行判为失败。\n这是环境异常而非质量判定:请如实告诉用户验收环境当前不可用,由用户修复环境后用 task_board_manage(action=reset-verification) 清除已记录的异常,再重新完成目标。',
243
+ 'acceptance anomaly budget exhausted; a user action must clear the recorded anomalies',
244
+ 'TASK_BOARD_VERIFICATION_ANOMALY',
245
+ )
246
+
143
247
  /** Record one attempt and decide whether the completion claim may proceed. */
144
248
  const settle = async (input: {
145
249
  task: TaskRecord
@@ -171,14 +275,10 @@ export function createGoalVerificationGate(deps: GoalVerificationGateDeps): (exe
171
275
  const attempts = [...verification.attempts, attempt]
172
276
  const spend = { ...verification, attempts, inFlight: undefined }
173
277
  if (exceptionAttempts(spend).length >= MAX_EXCEPTION_ATTEMPTS) {
174
- return finalize(task.id, execution.id, spend, '验收异常达到上限:' + attempt.error, agent)
278
+ return anomalyHeld(task.id, execution.id, spend)
175
279
  }
176
280
  write(task.id, execution.id, spend)
177
- return deny(
178
- bounded('[任务看板 · 验收异常] ' + attempt.error + '\n这是验收异常而非质量判定,本次未消耗质量验收额度(已用 ' + exceptionAttempts(spend).length + '/' + MAX_EXCEPTION_ATTEMPTS + ')。请修复环境后再次调用 update_goal(action: complete)。'),
179
- 'TASK_BOARD_VERIFICATION_ANOMALY',
180
- attempt.error,
181
- )
281
+ return anomalyRefusal(spend, attempt.error)
182
282
  }
183
283
  write(task.id, execution.id, baselines)
184
284
  const llm = deps.llm?.()
@@ -201,14 +301,19 @@ export function createGoalVerificationGate(deps: GoalVerificationGateDeps): (exe
201
301
  }
202
302
  const spend = { ...verification, attempts: [...verification.attempts, attempt], inFlight: undefined }
203
303
  if (exceptionAttempts(spend).length >= MAX_EXCEPTION_ATTEMPTS) {
204
- return finalize(task.id, execution.id, spend, '验收异常达到上限:' + attempt.error, agent)
304
+ return anomalyHeld(task.id, execution.id, spend)
205
305
  }
206
306
  write(task.id, execution.id, spend)
207
307
  return deny(bounded('[任务看板 · 验收异常] ' + attempt.error), 'TASK_BOARD_VERIFICATION_ANOMALY', attempt.error)
208
308
  }
209
309
  const session = (agent as { session?: unknown }).session
210
310
  const evidence = collectEvidence(session, objective, execution.startedAt)
211
- const acceptance = AbortSignal.timeout(600_000)
311
+ // Both ceilings are row settings read live: raising one unblocks a card
312
+ // whose acceptance is stuck on a slow route without a plugin reload.
313
+ const callTimeout = deps.callTimeoutMs?.() ?? verificationCallTimeoutMs(undefined)
314
+ const budget = deps.budgetMs?.() ?? DEFAULT_VERIFICATION_BUDGET_MS
315
+ const deadline = now() + budget
316
+ const acceptance = AbortSignal.timeout(budget)
212
317
  // The host's own record of what changed on disk is offered next to the
213
318
  // trajectory; an unavailable service or a failed read degrades to the
214
319
  // trajectory alone instead of failing an acceptance already under way.
@@ -230,21 +335,42 @@ export function createGoalVerificationGate(deps: GoalVerificationGateDeps): (exe
230
335
  context: workspace.text === '' ? undefined : workspace.text,
231
336
  workspaceFiles: workspace.files,
232
337
  signal: acceptance,
338
+ deadline,
339
+ callTimeoutMs: callTimeout,
233
340
  now,
234
341
  })
235
342
  const attempt = result.attempt
236
343
  const attempts = [...verification.attempts, attempt]
237
344
  const spend: ExecutionVerification = { ...verification, attempts, inFlight: undefined }
345
+ // A budget stop is charged to no budget at all (issue #1828): the judge route
346
+ // may be healthy and the machine simply out of time, so it is neither an
347
+ // anomaly nor a verdict, and it must never close the card.
348
+ if (attempt.stage === 'budget') {
349
+ return hold(
350
+ task.id,
351
+ execution.id,
352
+ spend,
353
+ '[任务看板 · 验收未完成] ' + (attempt.error ?? '时间预算耗尽') + '\n本次没有产生任何质量判定,也不消耗质量或异常额度。请稍后重试;若裁判路由确实很慢,可调大验收的单次调用上限或总时间预算。',
354
+ attempt.error ?? 'the acceptance time budget ran out before a verdict',
355
+ 'TASK_BOARD_VERIFICATION_BUDGET',
356
+ )
357
+ }
238
358
  if (attempt.stage === 'exception') {
239
359
  if (exceptionAttempts(spend).length >= MAX_EXCEPTION_ATTEMPTS) {
240
- return finalize(task.id, execution.id, spend, '验收异常达到上限:' + (attempt.error ?? 'unknown'), agent)
360
+ return anomalyHeld(task.id, execution.id, spend)
241
361
  }
242
362
  write(task.id, execution.id, spend)
243
- return deny(
244
- bounded('[任务看板 · 验收异常] ' + (attempt.error ?? 'unknown') + '\n这是验收异常而非质量判定,本次未消耗质量验收额度(已用 ' + exceptionAttempts(spend).length + '/' + MAX_EXCEPTION_ATTEMPTS + ')。请修复环境后再次调用 update_goal(action: complete)。'),
245
- 'TASK_BOARD_VERIFICATION_ANOMALY',
246
- attempt.error,
247
- )
363
+ return anomalyRefusal(spend, attempt.error)
364
+ }
365
+ // An invalid acceptance is recorded and returned to the agent, but it is
366
+ // NOT a quality verdict: it spends its own bounded budget, never the
367
+ // quality one, and it never fails the card.
368
+ if (attempt.stage === 'invalid') {
369
+ if (invalidAttempts(spend).length >= MAX_INVALID_ATTEMPTS) {
370
+ return invalidHeld(task.id, execution.id, spend)
371
+ }
372
+ write(task.id, execution.id, spend)
373
+ return invalidRefusal(spend, attempt)
248
374
  }
249
375
  if (attempt.passed) {
250
376
  write(task.id, execution.id, spend)
@@ -255,7 +381,11 @@ export function createGoalVerificationGate(deps: GoalVerificationGateDeps): (exe
255
381
  return finalize(task.id, execution.id, spend, finalFailureReason(attempt, used, verification.contract.threshold), agent)
256
382
  }
257
383
  write(task.id, execution.id, spend)
258
- return deny(bounded(qualityFeedback(attempt, MAX_QUALITY_ATTEMPTS - used)), 'TASK_BOARD_VERIFICATION_FAILED', attempt.findings.join('\n'))
384
+ return deny(
385
+ bounded(qualityFeedback(attempt, MAX_QUALITY_ATTEMPTS - used)),
386
+ 'TASK_BOARD_VERIFICATION_FAILED',
387
+ locatedFindingDetail(attempt),
388
+ )
259
389
  }
260
390
 
261
391
  /** Evaluate one tool call; undefined means "not this gate's business". */
@@ -279,9 +409,21 @@ export function createGoalVerificationGate(deps: GoalVerificationGateDeps): (exe
279
409
  verification.failedReason,
280
410
  )
281
411
  }
282
- if (!hasQualityBudget(verification) && !hasExceptionBudget(verification)) {
412
+ // A spent INVALID budget comes first: the judge produced no usable veto, so
413
+ // calling it again would only burn quota against a budget that determines
414
+ // nothing, and the card must stay open for a human rather than be failed.
415
+ if (!hasInvalidBudget(verification)) {
416
+ return invalidHeld(binding.task.id, binding.execution.id, verification)
417
+ }
418
+ if (!hasQualityBudget(verification)) {
283
419
  return finalize(binding.task.id, binding.execution.id, verification, 'goal 验收额度已耗尽,本次 execution 判失败。', exec.agent)
284
420
  }
421
+ // A spent anomaly budget is an environment that could not answer, not a
422
+ // judged work: the claim is refused and NO judge call is spent until a user
423
+ // clears the recorded anomalies (issue #1828).
424
+ if (!hasExceptionBudget(verification)) {
425
+ return anomalyHeld(binding.task.id, binding.execution.id, verification)
426
+ }
285
427
  const key = binding.execution.id
286
428
  const running = inFlight.get(key)
287
429
  if (running !== undefined) return await running
@@ -325,6 +467,20 @@ export function createGoalVerificationGate(deps: GoalVerificationGateDeps): (exe
325
467
  }
326
468
  }
327
469
 
470
+ /**
471
+ * The structured detail of one quality refusal: the judge's own findings, in
472
+ * the order the acceptance recorded them, so the denial carries exactly the
473
+ * located evidence the report shows.
474
+ */
475
+ function locatedFindingDetail(attempt: VerificationAttempt): string {
476
+ const lines: string[] = []
477
+ for (const finding of attempt.criterionFindings ?? []) {
478
+ lines.push('[' + finding.criterionId + ' ' + finding.location + '] "' + finding.quote + '"')
479
+ }
480
+ if (lines.length === 0) lines.push(...attempt.findings)
481
+ return lines.join('\n')
482
+ }
483
+
328
484
  /** The bare execution prompt, used when the goal service carries no objective. */
329
485
  function taskPrompt(task: TaskRecord): string {
330
486
  return task.prompt.trim() !== '' ? task.prompt : task.title