@lemoncat7/dsh-partner 1.8.3 → 1.10.2

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 (188) hide show
  1. package/README.md +36 -1
  2. package/docs/architecture.md +101 -1
  3. package/lib/agent-runtime.d.ts +11 -2
  4. package/lib/agent-runtime.d.ts.map +1 -1
  5. package/lib/agent-runtime.js +91 -66
  6. package/lib/agent-runtime.js.map +1 -1
  7. package/lib/api/features/workspace-api.d.ts +2 -0
  8. package/lib/api/features/workspace-api.d.ts.map +1 -1
  9. package/lib/api/features/workspace-api.js +49 -2
  10. package/lib/api/features/workspace-api.js.map +1 -1
  11. package/lib/api.d.ts +2 -0
  12. package/lib/api.d.ts.map +1 -1
  13. package/lib/api.js +13 -31
  14. package/lib/api.js.map +1 -1
  15. package/lib/channels/delivery-policy.d.ts +14 -0
  16. package/lib/channels/delivery-policy.d.ts.map +1 -0
  17. package/lib/channels/delivery-policy.js +82 -0
  18. package/lib/channels/delivery-policy.js.map +1 -0
  19. package/lib/channels/manager.d.ts +3 -2
  20. package/lib/channels/manager.d.ts.map +1 -1
  21. package/lib/channels/manager.js +40 -17
  22. package/lib/channels/manager.js.map +1 -1
  23. package/lib/channels/outbound-media.d.ts +6 -0
  24. package/lib/channels/outbound-media.d.ts.map +1 -0
  25. package/lib/channels/outbound-media.js +118 -0
  26. package/lib/channels/outbound-media.js.map +1 -0
  27. package/lib/client-api.d.ts +4 -0
  28. package/lib/client-api.d.ts.map +1 -1
  29. package/lib/client-api.js.map +1 -1
  30. package/lib/client.d.ts.map +1 -1
  31. package/lib/client.js +1572 -1109
  32. package/lib/client.js.map +4 -4
  33. package/lib/collaboration/composition.d.ts +3 -1
  34. package/lib/collaboration/composition.d.ts.map +1 -1
  35. package/lib/collaboration/composition.js +47 -14
  36. package/lib/collaboration/composition.js.map +1 -1
  37. package/lib/collaboration/service.d.ts +7 -0
  38. package/lib/collaboration/service.d.ts.map +1 -1
  39. package/lib/collaboration/service.js +154 -44
  40. package/lib/collaboration/service.js.map +1 -1
  41. package/lib/collaboration/task-dispatch.d.ts +18 -0
  42. package/lib/collaboration/task-dispatch.d.ts.map +1 -0
  43. package/lib/collaboration/task-dispatch.js +44 -0
  44. package/lib/collaboration/task-dispatch.js.map +1 -0
  45. package/lib/collaboration/task-recovery.d.ts +8 -0
  46. package/lib/collaboration/task-recovery.d.ts.map +1 -0
  47. package/lib/collaboration/task-recovery.js +30 -0
  48. package/lib/collaboration/task-recovery.js.map +1 -0
  49. package/lib/companions/directory-cleanup.d.ts +4 -0
  50. package/lib/companions/directory-cleanup.d.ts.map +1 -0
  51. package/lib/companions/directory-cleanup.js +77 -0
  52. package/lib/companions/directory-cleanup.js.map +1 -0
  53. package/lib/companions/removal.d.ts +20 -0
  54. package/lib/companions/removal.d.ts.map +1 -0
  55. package/lib/companions/removal.js +84 -0
  56. package/lib/companions/removal.js.map +1 -0
  57. package/lib/companions/service.d.ts +3 -0
  58. package/lib/companions/service.d.ts.map +1 -1
  59. package/lib/companions/service.js +4 -0
  60. package/lib/companions/service.js.map +1 -1
  61. package/lib/companions/session-configuration.js +1 -1
  62. package/lib/companions/session-configuration.js.map +1 -1
  63. package/lib/companions/workspace-cleanup.d.ts +4 -0
  64. package/lib/companions/workspace-cleanup.d.ts.map +1 -0
  65. package/lib/companions/workspace-cleanup.js +37 -0
  66. package/lib/companions/workspace-cleanup.js.map +1 -0
  67. package/lib/daily-review.d.ts +1 -0
  68. package/lib/daily-review.d.ts.map +1 -1
  69. package/lib/daily-review.js +3 -0
  70. package/lib/daily-review.js.map +1 -1
  71. package/lib/domain.d.ts +2 -0
  72. package/lib/domain.d.ts.map +1 -1
  73. package/lib/domain.js.map +1 -1
  74. package/lib/execution/agent-support.d.ts.map +1 -1
  75. package/lib/execution/agent-support.js +27 -3
  76. package/lib/execution/agent-support.js.map +1 -1
  77. package/lib/execution/service.d.ts +1 -0
  78. package/lib/execution/service.d.ts.map +1 -1
  79. package/lib/execution/service.js +10 -0
  80. package/lib/execution/service.js.map +1 -1
  81. package/lib/heartbeat.d.ts +1 -0
  82. package/lib/heartbeat.d.ts.map +1 -1
  83. package/lib/heartbeat.js +3 -0
  84. package/lib/heartbeat.js.map +1 -1
  85. package/lib/index.d.ts.map +1 -1
  86. package/lib/index.js +22 -11
  87. package/lib/index.js.map +1 -1
  88. package/lib/pendant/renderer.d.ts +1 -0
  89. package/lib/pendant/renderer.d.ts.map +1 -1
  90. package/lib/pendant/renderer.js +4 -0
  91. package/lib/pendant/renderer.js.map +1 -1
  92. package/lib/pendant/widget.js +3 -1
  93. package/lib/pendant/widget.js.map +1 -1
  94. package/lib/pendant-renderer.js +6 -6
  95. package/lib/requirements/archive.d.ts +4 -0
  96. package/lib/requirements/archive.d.ts.map +1 -0
  97. package/lib/requirements/archive.js +14 -0
  98. package/lib/requirements/archive.js.map +1 -0
  99. package/lib/requirements/consolidation.d.ts +11 -0
  100. package/lib/requirements/consolidation.d.ts.map +1 -0
  101. package/lib/requirements/consolidation.js +50 -0
  102. package/lib/requirements/consolidation.js.map +1 -0
  103. package/lib/requirements/domain.d.ts +41 -0
  104. package/lib/requirements/domain.d.ts.map +1 -0
  105. package/lib/requirements/domain.js +2 -0
  106. package/lib/requirements/domain.js.map +1 -0
  107. package/lib/requirements/progress.d.ts +7 -0
  108. package/lib/requirements/progress.d.ts.map +1 -0
  109. package/lib/requirements/progress.js +17 -0
  110. package/lib/requirements/progress.js.map +1 -0
  111. package/lib/requirements/service.d.ts +23 -0
  112. package/lib/requirements/service.d.ts.map +1 -0
  113. package/lib/requirements/service.js +163 -0
  114. package/lib/requirements/service.js.map +1 -0
  115. package/lib/requirements/tool.d.ts +5 -0
  116. package/lib/requirements/tool.d.ts.map +1 -0
  117. package/lib/requirements/tool.js +49 -0
  118. package/lib/requirements/tool.js.map +1 -0
  119. package/lib/requirements/worker.d.ts +30 -0
  120. package/lib/requirements/worker.d.ts.map +1 -0
  121. package/lib/requirements/worker.js +164 -0
  122. package/lib/requirements/worker.js.map +1 -0
  123. package/lib/skills/builtin.d.ts.map +1 -1
  124. package/lib/skills/builtin.js +3 -2
  125. package/lib/skills/builtin.js.map +1 -1
  126. package/lib/skills/service.js +1 -1
  127. package/lib/skills/service.js.map +1 -1
  128. package/lib/skills/task-planning.d.ts +5 -0
  129. package/lib/skills/task-planning.d.ts.map +1 -0
  130. package/lib/skills/task-planning.js +47 -0
  131. package/lib/skills/task-planning.js.map +1 -0
  132. package/lib/store.d.ts +3 -0
  133. package/lib/store.d.ts.map +1 -1
  134. package/lib/store.js +10 -0
  135. package/lib/store.js.map +1 -1
  136. package/lib/tasks/context.d.ts +8 -0
  137. package/lib/tasks/context.d.ts.map +1 -0
  138. package/lib/tasks/context.js +48 -0
  139. package/lib/tasks/context.js.map +1 -0
  140. package/lib/tasks/domain.d.ts +12 -0
  141. package/lib/tasks/domain.d.ts.map +1 -1
  142. package/lib/tasks/progress-notifier.d.ts +4 -0
  143. package/lib/tasks/progress-notifier.d.ts.map +1 -0
  144. package/lib/tasks/progress-notifier.js +20 -0
  145. package/lib/tasks/progress-notifier.js.map +1 -0
  146. package/lib/tasks/removal.d.ts +5 -0
  147. package/lib/tasks/removal.d.ts.map +1 -0
  148. package/lib/tasks/removal.js +44 -0
  149. package/lib/tasks/removal.js.map +1 -0
  150. package/lib/tasks/requirement-link.d.ts +4 -0
  151. package/lib/tasks/requirement-link.d.ts.map +1 -0
  152. package/lib/tasks/requirement-link.js +11 -0
  153. package/lib/tasks/requirement-link.js.map +1 -0
  154. package/lib/tasks/service.d.ts +13 -5
  155. package/lib/tasks/service.d.ts.map +1 -1
  156. package/lib/tasks/service.js +113 -29
  157. package/lib/tasks/service.js.map +1 -1
  158. package/lib/ui/board-refresh.d.ts +3 -0
  159. package/lib/ui/board-refresh.d.ts.map +1 -0
  160. package/lib/ui/board-refresh.js +42 -0
  161. package/lib/ui/board-refresh.js.map +1 -0
  162. package/lib/ui/identity-editor.d.ts +11 -0
  163. package/lib/ui/identity-editor.d.ts.map +1 -0
  164. package/lib/ui/identity-editor.js +78 -0
  165. package/lib/ui/identity-editor.js.map +1 -0
  166. package/lib/ui/requirement-board.d.ts +5 -0
  167. package/lib/ui/requirement-board.d.ts.map +1 -0
  168. package/lib/ui/requirement-board.js +47 -0
  169. package/lib/ui/requirement-board.js.map +1 -0
  170. package/lib/ui/requirement-dialog.d.ts +15 -0
  171. package/lib/ui/requirement-dialog.d.ts.map +1 -0
  172. package/lib/ui/requirement-dialog.js +53 -0
  173. package/lib/ui/requirement-dialog.js.map +1 -0
  174. package/lib/ui/requirement-tasks-panel.d.ts +8 -0
  175. package/lib/ui/requirement-tasks-panel.d.ts.map +1 -0
  176. package/lib/ui/requirement-tasks-panel.js +269 -0
  177. package/lib/ui/requirement-tasks-panel.js.map +1 -0
  178. package/lib/ui/task-board-panel.d.ts +2 -4
  179. package/lib/ui/task-board-panel.d.ts.map +1 -1
  180. package/lib/ui/task-board-panel.js +2 -283
  181. package/lib/ui/task-board-panel.js.map +1 -1
  182. package/lib/ui/task-board-stage.d.ts +12 -0
  183. package/lib/ui/task-board-stage.d.ts.map +1 -0
  184. package/lib/ui/task-board-stage.js +13 -0
  185. package/lib/ui/task-board-stage.js.map +1 -0
  186. package/lib/ui/workspace-components.js +1 -1
  187. package/lib/ui/workspace-components.js.map +1 -1
  188. package/package.json +1 -1
@@ -4,11 +4,14 @@ export declare const TASK_PRIORITIES: readonly ["low", "normal", "high", "urgent
4
4
  export type TaskPriority = typeof TASK_PRIORITIES[number];
5
5
  export interface BoardTask {
6
6
  id: string;
7
+ requirementId?: string;
7
8
  title: string;
8
9
  description: string;
9
10
  status: TaskStatus;
10
11
  priority: TaskPriority;
11
12
  assigneeCompanionId?: string;
13
+ /** Explicit durable execution intent. Missing on legacy/planning-only tasks. */
14
+ autoRun?: boolean;
12
15
  reviewerCompanionId?: string;
13
16
  createdBy: 'user' | 'companion' | 'schedule';
14
17
  creatorCompanionId?: string;
@@ -21,6 +24,15 @@ export interface BoardTask {
21
24
  /** Internal execution notes for the reviewer; never included in user-facing result delivery. */
22
25
  reviewHandoff?: string;
23
26
  reviewSummary?: string;
27
+ /** Changes only when the actual work specification changes, not on status updates. */
28
+ workRevision?: number;
29
+ rejectionReason?: string;
30
+ rejectedAt?: number;
31
+ previousAttempt?: {
32
+ resultSummary?: string;
33
+ reviewSummary?: string;
34
+ reviewHandoff?: string;
35
+ };
24
36
  dueAt?: number;
25
37
  revision: number;
26
38
  createdAt: number;
@@ -1 +1 @@
1
- {"version":3,"file":"domain.d.ts","sourceRoot":"","sources":["../../src/tasks/domain.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,aAAa,qEAAsE,CAAA;AAChG,MAAM,MAAM,UAAU,GAAG,OAAO,aAAa,CAAC,MAAM,CAAC,CAAA;AACrD,eAAO,MAAM,eAAe,8CAA+C,CAAA;AAC3E,MAAM,MAAM,YAAY,GAAG,OAAO,eAAe,CAAC,MAAM,CAAC,CAAA;AAEzD,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,UAAU,CAAA;IAClB,QAAQ,EAAE,YAAY,CAAA;IACtB,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,SAAS,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,CAAA;IAC5C,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,6DAA6D;IAC7D,iBAAiB,EAAE,MAAM,EAAE,CAAA;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,gGAAgG;IAChG,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AACD,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,GAAG,QAAQ,CAAA;IACnD,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,IAAI,EAAE,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,UAAU,GAAG,WAAW,GAAG,WAAW,GAAG,QAAQ,GAAG,UAAU,GAAG,UAAU,GAAG,WAAW,GAAG,QAAQ,GAAG,UAAU,GAAG,WAAW,CAAA;IACvK,OAAO,EAAE,MAAM,CAAA;IACf,EAAE,EAAE,MAAM,CAAA;CACX"}
1
+ {"version":3,"file":"domain.d.ts","sourceRoot":"","sources":["../../src/tasks/domain.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,aAAa,qEAAsE,CAAA;AAChG,MAAM,MAAM,UAAU,GAAG,OAAO,aAAa,CAAC,MAAM,CAAC,CAAA;AACrD,eAAO,MAAM,eAAe,8CAA+C,CAAA;AAC3E,MAAM,MAAM,YAAY,GAAG,OAAO,eAAe,CAAC,MAAM,CAAC,CAAA;AAEzD,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAA;IACV,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,UAAU,CAAA;IAClB,QAAQ,EAAE,YAAY,CAAA;IACtB,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,gFAAgF;IAChF,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,SAAS,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,CAAA;IAC5C,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,6DAA6D;IAC7D,iBAAiB,EAAE,MAAM,EAAE,CAAA;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,gGAAgG;IAChG,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,sFAAsF;IACtF,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,eAAe,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC5F,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AACD,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,GAAG,QAAQ,CAAA;IACnD,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,IAAI,EAAE,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,UAAU,GAAG,WAAW,GAAG,WAAW,GAAG,QAAQ,GAAG,UAAU,GAAG,UAAU,GAAG,WAAW,GAAG,QAAQ,GAAG,UAAU,GAAG,WAAW,CAAA;IACvK,OAAO,EAAE,MAAM,CAAA;IACf,EAAE,EAAE,MAAM,CAAA;CACX"}
@@ -0,0 +1,4 @@
1
+ import type { BoardTask } from './domain.js';
2
+ /** Channel results and internal follow-ups have independent failure boundaries. */
3
+ export declare function createTaskProgressNotifier(deliver: (task: BoardTask) => Promise<void>, followup: (task: BoardTask, previousStatus: BoardTask['status']) => Promise<void>, warn: (message: string) => void): (task: BoardTask, previousStatus: BoardTask['status']) => Promise<void>;
4
+ //# sourceMappingURL=progress-notifier.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"progress-notifier.d.ts","sourceRoot":"","sources":["../../src/tasks/progress-notifier.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAE5C,mFAAmF;AACnF,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,OAAO,CAAC,IAAI,CAAC,EAC3C,QAAQ,EAAE,CAAC,IAAI,EAAE,SAAS,EAAE,cAAc,EAAE,SAAS,CAAC,QAAQ,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,EACjF,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAC9B,CAAC,IAAI,EAAE,SAAS,EAAE,cAAc,EAAE,SAAS,CAAC,QAAQ,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CASzE"}
@@ -0,0 +1,20 @@
1
+ /** Channel results and internal follow-ups have independent failure boundaries. */
2
+ export function createTaskProgressNotifier(deliver, followup, warn) {
3
+ return async (task, previousStatus) => {
4
+ // The result is already persisted. Do not delay it behind agent creation,
5
+ // model work, or a knowledge extraction triggered by an internal follow-up.
6
+ try {
7
+ await deliver(task);
8
+ }
9
+ catch (error) {
10
+ warn(`dsh-partner task result delivery failed: ${error instanceof Error ? error.message : String(error)}`);
11
+ }
12
+ try {
13
+ await followup(task, previousStatus);
14
+ }
15
+ catch (error) {
16
+ warn(`dsh-partner task progress notification failed: ${error instanceof Error ? error.message : String(error)}`);
17
+ }
18
+ };
19
+ }
20
+ //# sourceMappingURL=progress-notifier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"progress-notifier.js","sourceRoot":"","sources":["../../src/tasks/progress-notifier.ts"],"names":[],"mappings":"AAEA,mFAAmF;AACnF,MAAM,UAAU,0BAA0B,CACxC,OAA2C,EAC3C,QAAiF,EACjF,IAA+B;IAE/B,OAAO,KAAK,EAAE,IAAI,EAAE,cAAc,EAAE,EAAE;QACpC,0EAA0E;QAC1E,4EAA4E;QAC5E,IAAI,CAAC;YAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;QAAC,CAAC;QAC3B,OAAO,KAAK,EAAE,CAAC;YAAC,IAAI,CAAC,4CAA4C,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;QAAC,CAAC;QAC5H,IAAI,CAAC;YAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC,CAAA;QAAC,CAAC;QAC5C,OAAO,KAAK,EAAE,CAAC;YAAC,IAAI,CAAC,kDAAkD,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;QAAC,CAAC;IACpI,CAAC,CAAA;AACH,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type { PartnerState } from '../domain.js';
2
+ /** Atomic removal. Missing prerequisites pause dependents instead of auto-unlocking. */
3
+ export declare function removeTaskRecords(state: PartnerState, ids: ReadonlySet<string>): string[];
4
+ export declare function touchTaskRequirement(state: PartnerState, requirementId?: string): void;
5
+ //# sourceMappingURL=removal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"removal.d.ts","sourceRoot":"","sources":["../../src/tasks/removal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAEhD,wFAAwF;AACxF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,CAsBzF;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,YAAY,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAMtF"}
@@ -0,0 +1,44 @@
1
+ /** Atomic removal. Missing prerequisites pause dependents instead of auto-unlocking. */
2
+ export function removeTaskRecords(state, ids) {
3
+ const affected = new Set(ids);
4
+ const requirementIds = new Set(state.tasks.filter(t => ids.has(t.id)).map(t => t.requirementId));
5
+ state.tasks = state.tasks.filter(t => !ids.has(t.id));
6
+ for (const task of state.tasks) {
7
+ if (!task.dependencyTaskIds.some(id => ids.has(id)))
8
+ continue;
9
+ task.dependencyTaskIds = task.dependencyTaskIds.filter(id => !ids.has(id));
10
+ if (task.status !== 'done') {
11
+ task.status = 'blocked';
12
+ task.autoRun = false;
13
+ task.resultSummary = '前置任务已取消或删除,请确认需求范围及依赖后再提交执行。';
14
+ affected.add(task.id);
15
+ }
16
+ task.revision++;
17
+ task.updatedAt = Date.now();
18
+ requirementIds.add(task.requirementId);
19
+ }
20
+ state.taskActivities = state.taskActivities.filter(item => !ids.has(item.taskId));
21
+ state.delegations = state.delegations.filter(item => !affected.has(item.taskId));
22
+ for (const item of state.requirements ?? []) {
23
+ if (!requirementIds.has(item.id) || item.status === 'done')
24
+ continue;
25
+ item.revision++;
26
+ item.updatedAt = Date.now();
27
+ item.status = 'planning';
28
+ delete item.nextAttemptAt;
29
+ delete item.lastError;
30
+ }
31
+ return [...affected];
32
+ }
33
+ export function touchTaskRequirement(state, requirementId) {
34
+ const item = state.requirements?.find(r => r.id === requirementId);
35
+ if (!item || item.status === 'done')
36
+ return;
37
+ item.revision++;
38
+ item.updatedAt = Date.now();
39
+ if (item.status === 'review')
40
+ item.status = 'active';
41
+ delete item.nextAttemptAt;
42
+ delete item.lastError;
43
+ }
44
+ //# sourceMappingURL=removal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"removal.js","sourceRoot":"","sources":["../../src/tasks/removal.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { BoardTask } from './domain.js';
2
+ /** Dependency links describe execution order, not ownership. Never silently guess a requirement. */
3
+ export declare function requireTaskRequirement(value: unknown, dependencyIds: readonly string[], tasks: readonly BoardTask[]): string;
4
+ //# sourceMappingURL=requirement-link.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"requirement-link.d.ts","sourceRoot":"","sources":["../../src/tasks/requirement-link.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAG5C,oGAAoG;AACpG,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,EAAE,SAAS,SAAS,EAAE,GAAG,MAAM,CAM5H"}
@@ -0,0 +1,11 @@
1
+ import { optionalText } from '../core/validation.js';
2
+ /** Dependency links describe execution order, not ownership. Never silently guess a requirement. */
3
+ export function requireTaskRequirement(value, dependencyIds, tasks) {
4
+ const id = optionalText(value, 'requirementId', 160);
5
+ if (id)
6
+ return id;
7
+ const dependencies = new Set(dependencyIds);
8
+ const candidates = [...new Set(tasks.filter(t => dependencies.has(t.id)).map(t => t.requirementId).filter(Boolean))];
9
+ throw new Error(`创建任务必须提供 requirementId,未创建任何任务或需求。先查询 partner_requirements list;续做同一目标请复用原需求,必要时 reopen 后追加,只有新的独立目标才 create 需求。dependencyTaskIds 不能替代 requirementId。${candidates.length ? `所选前置任务属于:${candidates.join('、')};请核对归属,不要直接另建需求。` : ''}`);
10
+ }
11
+ //# sourceMappingURL=requirement-link.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"requirement-link.js","sourceRoot":"","sources":["../../src/tasks/requirement-link.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAEpD,oGAAoG;AACpG,MAAM,UAAU,sBAAsB,CAAC,KAAc,EAAE,aAAgC,EAAE,KAA2B;IAClH,MAAM,EAAE,GAAG,YAAY,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,CAAC,CAAA;IACpD,IAAI,EAAE;QAAE,OAAO,EAAE,CAAA;IACjB,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,aAAa,CAAC,CAAA;IAC3C,MAAM,UAAU,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAA;IACpH,MAAM,IAAI,KAAK,CAAC,wJAAwJ,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,YAAY,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;AACxP,CAAC"}
@@ -4,23 +4,27 @@ import type { TaskExecutionOutput } from './result.js';
4
4
  export declare class TaskBoardService {
5
5
  private readonly store;
6
6
  private notifier?;
7
+ private removalNotifier?;
7
8
  constructor(store: PartnerStore);
9
+ setRemovalNotifier(notifier: (ids: string[]) => void): void;
8
10
  setProgressNotifier(notifier: (task: BoardTask, previousStatus: BoardTask['status']) => Promise<void>): void;
9
11
  snapshot(): {
10
12
  tasks: BoardTask[];
11
13
  activities: TaskActivity[];
14
+ requirements: import("../client-api.js").RequirementView[];
12
15
  };
13
- create(value: unknown, actor: TaskActor): Promise<BoardTask>;
16
+ create(value: unknown, actor: TaskActor, requireOwnership?: boolean): Promise<BoardTask>;
14
17
  update(taskId: string, value: unknown, actor: TaskActor): Promise<BoardTask>;
15
18
  comment(taskId: string, message: string, actor: TaskActor): Promise<void>;
16
19
  recordRecovery(taskId: string, message: string, retrying: boolean): Promise<void>;
17
20
  assertStartable(taskId: string): BoardTask;
18
- completeExecution(taskId: string, result: string | TaskExecutionOutput, actor: TaskActor): Promise<BoardTask>;
19
- recordReview(taskId: string, result: string, actor: TaskActor): Promise<BoardTask>;
21
+ completeExecution(taskId: string, result: string | TaskExecutionOutput, actor: TaskActor, workRevision?: number): Promise<BoardTask>;
22
+ recordReview(taskId: string, result: string, actor: TaskActor, expectedRevision?: number): Promise<BoardTask>;
20
23
  failExecution(taskId: string, error: string, actor: TaskActor): Promise<BoardTask>;
21
- accept(taskId: string, actor: TaskActor): Promise<BoardTask>;
22
- reject(taskId: string, reason: string, actor: TaskActor): Promise<BoardTask>;
24
+ accept(taskId: string, actor: TaskActor, expectedRevision?: number): Promise<BoardTask>;
25
+ reject(taskId: string, reason: string, actor: TaskActor, expectedRevision?: number): Promise<BoardTask>;
23
26
  remove(taskId: string): Promise<void>;
27
+ removeRequirement(id: string): Promise<void>;
24
28
  require(taskId: string): BoardTask;
25
29
  private assertCompanion;
26
30
  private assertDependencies;
@@ -36,4 +40,8 @@ export declare class TaskConflictError extends Error {
36
40
  readonly status = 409;
37
41
  constructor(current: BoardTask);
38
42
  }
43
+ export declare class TaskNotFoundError extends Error {
44
+ readonly status = 404;
45
+ constructor(message?: string);
46
+ }
39
47
  //# sourceMappingURL=service.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/tasks/service.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC/C,OAAO,EAAkC,KAAK,SAAS,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAA;AAC/F,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAKtD,qBAAa,gBAAgB;IAGf,OAAO,CAAC,QAAQ,CAAC,KAAK;IAFlC,OAAO,CAAC,QAAQ,CAAC,CAAyE;gBAE7D,KAAK,EAAE,YAAY;IAEhD,mBAAmB,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,SAAS,EAAE,cAAc,EAAE,SAAS,CAAC,QAAQ,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAI5G,QAAQ,IAAI;QAAE,KAAK,EAAE,SAAS,EAAE,CAAC;QAAC,UAAU,EAAE,YAAY,EAAE,CAAA;KAAE;IAKxD,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;IA8B5D,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;IAwD5E,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAOzE,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAOvF,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS;IAWpC,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,mBAAmB,EAAE,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;IA2B7G,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;IAgBlF,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;IAoBlF,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;IAM5D,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;IAkB5E,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAc3C,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS;IAMlC,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,kBAAkB;IAgB1B,OAAO,CAAC,0BAA0B;YAMpB,cAAc;CAO7B;AAED,MAAM,WAAW,SAAS;IAAG,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE;AAE5F,qBAAa,iBAAkB,SAAQ,KAAK;IAE9B,QAAQ,CAAC,OAAO,EAAE,SAAS;IADvC,QAAQ,CAAC,MAAM,OAAM;gBACA,OAAO,EAAE,SAAS;CACxC"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/tasks/service.ts"],"names":[],"mappings":"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"}
@@ -1,35 +1,47 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { appendBounded } from '../core/collections.js';
3
- import { oneOf, optionalText, record, requiredText, stringList } from '../core/validation.js';
3
+ import { oneOf, optionalBoolean, optionalText, record, requiredText, stringList } from '../core/validation.js';
4
4
  import { TASK_PRIORITIES, TASK_STATUSES } from './domain.js';
5
+ import { requirementDraft } from '../requirements/service.js';
6
+ import { removeTaskRecords, touchTaskRequirement } from './removal.js';
7
+ import { invalidateTaskWork, preserveTaskAttempt } from './context.js';
8
+ import { requireTaskRequirement } from './requirement-link.js';
5
9
  const MAX_TASKS = 500;
6
10
  const MAX_ACTIVITIES = 2000;
7
11
  export class TaskBoardService {
8
12
  store;
9
13
  notifier;
14
+ removalNotifier;
10
15
  constructor(store) {
11
16
  this.store = store;
12
17
  }
18
+ setRemovalNotifier(notifier) { this.removalNotifier = notifier; }
13
19
  setProgressNotifier(notifier) {
14
20
  this.notifier = notifier;
15
21
  }
16
22
  snapshot() {
17
23
  const state = this.store.snapshot();
18
- return { tasks: state.tasks, activities: state.taskActivities };
24
+ return { tasks: state.tasks, activities: state.taskActivities, requirements: state.requirements ?? [] };
19
25
  }
20
- async create(value, actor) {
26
+ async create(value, actor, requireOwnership = false) {
21
27
  const input = record(value, 'task');
22
28
  const now = Date.now();
23
29
  const assigneeCompanionId = optionalText(input.assigneeCompanionId, 'assigneeCompanionId', 120);
30
+ const autoRun = optionalBoolean(input.autoRun, false);
31
+ if (autoRun && !assigneeCompanionId)
32
+ throw new Error('提交执行需要指定负责人');
24
33
  const requestedReviewerCompanionId = optionalText(input.reviewerCompanionId, 'reviewerCompanionId', 120);
25
34
  const reviewerCompanionId = requestedReviewerCompanionId ?? actor.companionId;
26
35
  this.assertCompanion(assigneeCompanionId);
27
36
  this.assertCompanion(reviewerCompanionId);
28
37
  const dependencyTaskIds = stringList(input.dependencyTaskIds, 'dependencyTaskIds', 20, 120);
38
+ if (requireOwnership)
39
+ requireTaskRequirement(input.requirementId, dependencyTaskIds, this.store.snapshot().tasks);
29
40
  this.assertDependencies(undefined, dependencyTaskIds);
30
41
  const task = {
31
42
  id: `task-${randomUUID()}`, title: requiredText(input.title, 'title', 200), description: typeof input.description === 'string' ? input.description.trim().slice(0, 8000) : '',
32
- status: input.status === undefined ? 'backlog' : oneOf(input.status, TASK_STATUSES, 'status'),
43
+ status: input.status === undefined ? autoRun ? 'ready' : 'backlog' : oneOf(input.status, TASK_STATUSES, 'status'),
44
+ ...(autoRun ? { autoRun: true } : {}),
33
45
  priority: input.priority === undefined ? 'normal' : oneOf(input.priority, TASK_PRIORITIES, 'priority'),
34
46
  ...(assigneeCompanionId ? { assigneeCompanionId } : {}), createdBy: actor.kind,
35
47
  ...(reviewerCompanionId ? { reviewerCompanionId } : {}),
@@ -38,11 +50,33 @@ export class TaskBoardService {
38
50
  skillIds: stringList(input.skillIds, 'skillIds', 20, 120), dependencyTaskIds,
39
51
  ...(validTimestamp(input.dueAt) ? { dueAt: input.dueAt } : {}), revision: 1, createdAt: now, updatedAt: now,
40
52
  };
53
+ if (autoRun && task.status !== 'ready')
54
+ throw new Error('新提交的任务必须从待开始创建;收集箱请使用 autoRun=false,不能跳过执行和验收');
41
55
  if (started(task.status))
42
56
  this.assertDependenciesComplete(task, this.store.snapshot().tasks);
43
57
  await this.store.update(state => {
44
58
  if (state.tasks.length >= MAX_TASKS)
45
59
  throw new Error(`Task board reached its ${MAX_TASKS} task limit; archive or delete completed tasks first`);
60
+ this.assertDependencies(undefined, dependencyTaskIds, state.tasks);
61
+ state.requirements ??= [];
62
+ const requirementId = optionalText(input.requirementId, 'requirementId', 160);
63
+ if (requirementId) {
64
+ const requirement = state.requirements.find(r => r.id === requirementId);
65
+ if (!requirement)
66
+ throw new TaskNotFoundError('需求已删除或不存在');
67
+ if (requirement.status !== 'planning')
68
+ throw new Error('请先将需求重新打开为规划状态,再增加任务');
69
+ task.requirementId = requirementId;
70
+ }
71
+ else {
72
+ if (state.requirements.length >= 500)
73
+ throw new Error('需求数量已达上限');
74
+ const requirement = requirementDraft(task.title, task.description, task.creatorCompanionId, task.creatorSessionId);
75
+ requirement.status = 'active';
76
+ state.requirements.push(requirement);
77
+ task.requirementId = requirement.id;
78
+ }
79
+ touchTaskRequirement(state, task.requirementId);
46
80
  state.tasks.push(task);
47
81
  appendActivity(state.taskActivities, task.id, actor, 'created', `创建任务:${task.title}`, now);
48
82
  });
@@ -55,11 +89,14 @@ export class TaskBoardService {
55
89
  await this.store.update(state => {
56
90
  const task = state.tasks.find(item => item.id === taskId);
57
91
  if (!task)
58
- throw new Error('Task does not exist');
92
+ throw new TaskNotFoundError();
93
+ if (state.requirements?.some(r => r.id === task.requirementId && r.status === 'done'))
94
+ throw new Error('已归档需求中的任务不能修改;同一目标续做请 reopen 原需求后追加新任务,不要重跑已验收任务');
59
95
  const expected = input.expectedRevision;
60
96
  if (!Number.isInteger(expected) || expected !== task.revision)
61
97
  throw new TaskConflictError(task);
62
98
  previousStatus = task.status;
99
+ const previousSpec = JSON.stringify([task.title, task.description, task.assigneeCompanionId, task.skillIds, task.dependencyTaskIds]);
63
100
  if (input.title !== undefined)
64
101
  task.title = requiredText(input.title, 'title', 200);
65
102
  if (input.description !== undefined)
@@ -68,6 +105,18 @@ export class TaskBoardService {
68
105
  task.status = oneOf(input.status, TASK_STATUSES, 'status');
69
106
  if (input.priority !== undefined)
70
107
  task.priority = oneOf(input.priority, TASK_PRIORITIES, 'priority');
108
+ if (input.autoRun !== undefined) {
109
+ task.autoRun = optionalBoolean(input.autoRun, false);
110
+ if (!task.autoRun)
111
+ for (const item of state.delegations) {
112
+ if (item.taskId !== task.id || item.status !== 'queued' || item.kind === 'review')
113
+ continue;
114
+ item.status = 'canceled';
115
+ item.completedAt = Date.now();
116
+ item.error = '任务已改为仅规划,取消尚未开始的执行';
117
+ delete item.nextAttemptAt;
118
+ }
119
+ }
71
120
  if ('assigneeCompanionId' in input) {
72
121
  const assignee = optionalText(input.assigneeCompanionId, 'assigneeCompanionId', 120);
73
122
  this.assertCompanion(assignee);
@@ -100,17 +149,24 @@ export class TaskBoardService {
100
149
  else
101
150
  delete task.dueAt;
102
151
  }
152
+ if (previousSpec !== JSON.stringify([task.title, task.description, task.assigneeCompanionId, task.skillIds, task.dependencyTaskIds])) {
153
+ invalidateTaskWork(state, task, '任务要求或分配已更新,旧执行/验收失效');
154
+ }
155
+ if (input.autoRun === true && !task.assigneeCompanionId)
156
+ throw new Error('提交执行需要指定负责人');
103
157
  if (task.status === 'done' && previousStatus !== 'done' && previousStatus !== 'review')
104
158
  throw new Error('任务必须先进入待验收,才能标记为已完成');
105
159
  if (started(task.status))
106
160
  this.assertDependenciesComplete(task, state.tasks);
107
161
  task.revision += 1;
108
162
  task.updatedAt = Date.now();
163
+ touchTaskRequirement(state, task.requirementId);
109
164
  if (task.status === 'done' && previousStatus !== 'done')
110
165
  task.completedAt = task.updatedAt;
111
166
  if (task.status !== 'done')
112
167
  delete task.completedAt;
113
168
  if (task.status === 'doing' && previousStatus !== 'doing') {
169
+ preserveTaskAttempt(task);
114
170
  delete task.resultAbstract;
115
171
  delete task.resultSummary;
116
172
  delete task.reviewHandoff;
@@ -124,11 +180,19 @@ export class TaskBoardService {
124
180
  return output;
125
181
  }
126
182
  async comment(taskId, message, actor) {
183
+ let task;
127
184
  await this.store.update(state => {
128
- if (!state.tasks.some(item => item.id === taskId))
129
- throw new Error('Task does not exist');
185
+ const current = state.tasks.find(item => item.id === taskId);
186
+ if (!current)
187
+ throw new TaskNotFoundError();
130
188
  appendActivity(state.taskActivities, taskId, actor, 'commented', requiredText(message, 'message', 1200), Date.now());
189
+ current.revision++;
190
+ current.updatedAt = Date.now();
191
+ touchTaskRequirement(state, current.requirementId);
192
+ task = structuredClone(current);
131
193
  });
194
+ if (task.status === 'review')
195
+ await this.notifyProgress(task, 'review', true);
132
196
  }
133
197
  async recordRecovery(taskId, message, retrying) {
134
198
  await this.store.update(state => {
@@ -141,7 +205,7 @@ export class TaskBoardService {
141
205
  const state = this.store.snapshot();
142
206
  const task = state.tasks.find(item => item.id === taskId);
143
207
  if (!task)
144
- throw new Error('Task does not exist');
208
+ throw new TaskNotFoundError();
145
209
  if (task.status === 'doing')
146
210
  throw new Error('任务已经在执行中');
147
211
  if (task.status === 'review')
@@ -151,15 +215,17 @@ export class TaskBoardService {
151
215
  this.assertDependenciesComplete(task, state.tasks);
152
216
  return task;
153
217
  }
154
- async completeExecution(taskId, result, actor) {
218
+ async completeExecution(taskId, result, actor, workRevision) {
155
219
  let output;
156
220
  let previousStatus;
157
221
  await this.store.update(state => {
158
222
  const task = state.tasks.find(item => item.id === taskId);
159
223
  if (!task)
160
- throw new Error('Task does not exist');
224
+ throw new TaskNotFoundError();
161
225
  if (task.status !== 'doing' && task.status !== 'review')
162
226
  throw new Error('只有进行中或待验收的任务可以提交执行结果');
227
+ if (workRevision !== undefined && workRevision !== (task.workRevision ?? 1))
228
+ throw new TaskConflictError(task);
163
229
  previousStatus = task.status;
164
230
  if (!task.reviewerCompanionId && task.creatorCompanionId)
165
231
  task.reviewerCompanionId = task.creatorCompanionId;
@@ -179,25 +245,29 @@ export class TaskBoardService {
179
245
  task.status = 'review';
180
246
  task.revision += 1;
181
247
  task.updatedAt = Date.now();
248
+ touchTaskRequirement(state, task.requirementId);
182
249
  appendActivity(state.taskActivities, task.id, actor, 'result', movedToReview ? '执行结果已提交,等待验收' : '执行结果已补充到待验收任务', task.updatedAt);
183
250
  output = structuredClone(task);
184
251
  });
185
252
  await this.notifyProgress(output, previousStatus, true);
186
253
  return output;
187
254
  }
188
- async recordReview(taskId, result, actor) {
255
+ async recordReview(taskId, result, actor, expectedRevision) {
189
256
  let output;
190
257
  await this.store.update(state => {
191
258
  const task = state.tasks.find(item => item.id === taskId);
192
259
  if (!task)
193
- throw new Error('Task does not exist');
260
+ throw new TaskNotFoundError();
194
261
  if (task.status !== 'review')
195
262
  throw new Error('只有待验收任务可以提交核验结果');
263
+ if (expectedRevision !== undefined && expectedRevision !== task.revision)
264
+ throw new TaskConflictError(task);
196
265
  if (task.reviewerCompanionId && actor.companionId !== task.reviewerCompanionId)
197
266
  throw new Error('核验结果必须由任务指定的验收伙伴提交');
198
267
  task.reviewSummary = boundedText(result, 'reviewResult', 12_000);
199
268
  task.revision += 1;
200
269
  task.updatedAt = Date.now();
270
+ touchTaskRequirement(state, task.requirementId);
201
271
  appendActivity(state.taskActivities, task.id, actor, 'reviewed', '验收伙伴已提交核验结果', task.updatedAt);
202
272
  output = structuredClone(task);
203
273
  });
@@ -209,63 +279,73 @@ export class TaskBoardService {
209
279
  await this.store.update(state => {
210
280
  const task = state.tasks.find(item => item.id === taskId);
211
281
  if (!task)
212
- throw new Error('Task does not exist');
282
+ throw new TaskNotFoundError();
213
283
  previousStatus = task.status;
284
+ if (task.status !== 'doing')
285
+ throw new Error('只有进行中的任务可以报告执行失败');
214
286
  task.resultSummary = `执行受阻:${boundedText(error, 'error', 4000)}`;
215
287
  delete task.resultAbstract;
216
288
  delete task.reviewHandoff;
217
289
  task.status = 'blocked';
218
290
  task.revision += 1;
219
291
  task.updatedAt = Date.now();
292
+ touchTaskRequirement(state, task.requirementId);
220
293
  appendActivity(state.taskActivities, task.id, actor, 'failed', task.resultSummary, task.updatedAt);
221
294
  output = structuredClone(task);
222
295
  });
223
296
  await this.notifyProgress(output, previousStatus);
224
297
  return output;
225
298
  }
226
- async accept(taskId, actor) {
299
+ async accept(taskId, actor, expectedRevision) {
227
300
  const current = this.require(taskId);
228
301
  if (current.status !== 'review')
229
302
  throw new Error('只有待验收任务可以通过验收');
230
- return this.update(taskId, { expectedRevision: current.revision, status: 'done' }, actor);
303
+ return this.update(taskId, { expectedRevision: expectedRevision ?? current.revision, status: 'done' }, actor);
231
304
  }
232
- async reject(taskId, reason, actor) {
305
+ async reject(taskId, reason, actor, expectedRevision) {
233
306
  let output;
234
307
  await this.store.update(state => {
235
308
  const task = state.tasks.find(item => item.id === taskId);
236
309
  if (!task)
237
- throw new Error('Task does not exist');
310
+ throw new TaskNotFoundError();
238
311
  if (task.status !== 'review')
239
312
  throw new Error('只有待验收任务可以打回');
313
+ if (expectedRevision !== undefined && expectedRevision !== task.revision)
314
+ throw new TaskConflictError(task);
240
315
  const message = boundedText(reason, 'reason', 1200);
316
+ task.rejectionReason = message;
317
+ task.rejectedAt = Date.now();
318
+ invalidateTaskWork(state, task, '验收打回,旧执行/验收已失效');
241
319
  task.status = 'ready';
242
320
  delete task.reviewSummary;
243
321
  delete task.completedAt;
244
322
  task.revision += 1;
245
323
  task.updatedAt = Date.now();
324
+ touchTaskRequirement(state, task.requirementId);
246
325
  appendActivity(state.taskActivities, task.id, actor, 'reopened', `验收打回:${message}`, task.updatedAt);
247
326
  output = structuredClone(task);
248
327
  });
249
328
  return output;
250
329
  }
251
330
  async remove(taskId) {
331
+ let affected = [];
252
332
  await this.store.update(state => {
253
- state.tasks = state.tasks.filter(item => item.id !== taskId);
254
- for (const task of state.tasks) {
255
- if (!task.dependencyTaskIds.includes(taskId))
256
- continue;
257
- task.dependencyTaskIds = task.dependencyTaskIds.filter(id => id !== taskId);
258
- task.revision += 1;
259
- task.updatedAt = Date.now();
260
- }
261
- state.taskActivities = state.taskActivities.filter(item => item.taskId !== taskId);
262
- state.delegations = state.delegations.filter(item => item.taskId !== taskId);
333
+ affected = removeTaskRecords(state, new Set([taskId]));
263
334
  });
335
+ this.removalNotifier?.(affected);
336
+ }
337
+ async removeRequirement(id) {
338
+ let affected = [];
339
+ await this.store.update(state => {
340
+ affected = removeTaskRecords(state, new Set(state.tasks.filter(t => t.requirementId === id).map(t => t.id)));
341
+ state.requirements = (state.requirements ?? []).filter(r => r.id !== id);
342
+ });
343
+ this.removalNotifier?.(affected);
264
344
  }
265
345
  require(taskId) {
266
346
  const task = this.store.snapshot().tasks.find(item => item.id === taskId);
267
347
  if (!task)
268
- throw new Error('Task does not exist');
348
+ throw new TaskNotFoundError();
269
349
  return task;
270
350
  }
271
351
  assertCompanion(id) {
@@ -315,10 +395,14 @@ export class TaskConflictError extends Error {
315
395
  current;
316
396
  status = 409;
317
397
  constructor(current) {
318
- super('Task changed; refresh it before updating');
398
+ super('任务状态已更新,请刷新后重试(Task changed)');
319
399
  this.current = current;
320
400
  }
321
401
  }
402
+ export class TaskNotFoundError extends Error {
403
+ status = 404;
404
+ constructor(message = '任务已删除或不存在,无需继续处理') { super(message); }
405
+ }
322
406
  function appendActivity(items, taskId, actor, kind, message, at) {
323
407
  appendBounded(items, {
324
408
  id: `activity-${randomUUID()}`, taskId, actor: actor.kind, ...('companionId' in actor && actor.companionId ? { actorCompanionId: actor.companionId } : {}), kind, message, at,