@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.
- package/README.md +36 -1
- package/docs/architecture.md +101 -1
- package/lib/agent-runtime.d.ts +11 -2
- package/lib/agent-runtime.d.ts.map +1 -1
- package/lib/agent-runtime.js +91 -66
- package/lib/agent-runtime.js.map +1 -1
- package/lib/api/features/workspace-api.d.ts +2 -0
- package/lib/api/features/workspace-api.d.ts.map +1 -1
- package/lib/api/features/workspace-api.js +49 -2
- package/lib/api/features/workspace-api.js.map +1 -1
- package/lib/api.d.ts +2 -0
- package/lib/api.d.ts.map +1 -1
- package/lib/api.js +13 -31
- package/lib/api.js.map +1 -1
- package/lib/channels/delivery-policy.d.ts +14 -0
- package/lib/channels/delivery-policy.d.ts.map +1 -0
- package/lib/channels/delivery-policy.js +82 -0
- package/lib/channels/delivery-policy.js.map +1 -0
- package/lib/channels/manager.d.ts +3 -2
- package/lib/channels/manager.d.ts.map +1 -1
- package/lib/channels/manager.js +40 -17
- package/lib/channels/manager.js.map +1 -1
- package/lib/channels/outbound-media.d.ts +6 -0
- package/lib/channels/outbound-media.d.ts.map +1 -0
- package/lib/channels/outbound-media.js +118 -0
- package/lib/channels/outbound-media.js.map +1 -0
- package/lib/client-api.d.ts +4 -0
- package/lib/client-api.d.ts.map +1 -1
- package/lib/client-api.js.map +1 -1
- package/lib/client.d.ts.map +1 -1
- package/lib/client.js +1572 -1109
- package/lib/client.js.map +4 -4
- package/lib/collaboration/composition.d.ts +3 -1
- package/lib/collaboration/composition.d.ts.map +1 -1
- package/lib/collaboration/composition.js +47 -14
- package/lib/collaboration/composition.js.map +1 -1
- package/lib/collaboration/service.d.ts +7 -0
- package/lib/collaboration/service.d.ts.map +1 -1
- package/lib/collaboration/service.js +154 -44
- package/lib/collaboration/service.js.map +1 -1
- package/lib/collaboration/task-dispatch.d.ts +18 -0
- package/lib/collaboration/task-dispatch.d.ts.map +1 -0
- package/lib/collaboration/task-dispatch.js +44 -0
- package/lib/collaboration/task-dispatch.js.map +1 -0
- package/lib/collaboration/task-recovery.d.ts +8 -0
- package/lib/collaboration/task-recovery.d.ts.map +1 -0
- package/lib/collaboration/task-recovery.js +30 -0
- package/lib/collaboration/task-recovery.js.map +1 -0
- package/lib/companions/directory-cleanup.d.ts +4 -0
- package/lib/companions/directory-cleanup.d.ts.map +1 -0
- package/lib/companions/directory-cleanup.js +77 -0
- package/lib/companions/directory-cleanup.js.map +1 -0
- package/lib/companions/removal.d.ts +20 -0
- package/lib/companions/removal.d.ts.map +1 -0
- package/lib/companions/removal.js +84 -0
- package/lib/companions/removal.js.map +1 -0
- package/lib/companions/service.d.ts +3 -0
- package/lib/companions/service.d.ts.map +1 -1
- package/lib/companions/service.js +4 -0
- package/lib/companions/service.js.map +1 -1
- package/lib/companions/session-configuration.js +1 -1
- package/lib/companions/session-configuration.js.map +1 -1
- package/lib/companions/workspace-cleanup.d.ts +4 -0
- package/lib/companions/workspace-cleanup.d.ts.map +1 -0
- package/lib/companions/workspace-cleanup.js +37 -0
- package/lib/companions/workspace-cleanup.js.map +1 -0
- package/lib/daily-review.d.ts +1 -0
- package/lib/daily-review.d.ts.map +1 -1
- package/lib/daily-review.js +3 -0
- package/lib/daily-review.js.map +1 -1
- package/lib/domain.d.ts +2 -0
- package/lib/domain.d.ts.map +1 -1
- package/lib/domain.js.map +1 -1
- package/lib/execution/agent-support.d.ts.map +1 -1
- package/lib/execution/agent-support.js +27 -3
- package/lib/execution/agent-support.js.map +1 -1
- package/lib/execution/service.d.ts +1 -0
- package/lib/execution/service.d.ts.map +1 -1
- package/lib/execution/service.js +10 -0
- package/lib/execution/service.js.map +1 -1
- package/lib/heartbeat.d.ts +1 -0
- package/lib/heartbeat.d.ts.map +1 -1
- package/lib/heartbeat.js +3 -0
- package/lib/heartbeat.js.map +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +22 -11
- package/lib/index.js.map +1 -1
- package/lib/pendant/renderer.d.ts +1 -0
- package/lib/pendant/renderer.d.ts.map +1 -1
- package/lib/pendant/renderer.js +4 -0
- package/lib/pendant/renderer.js.map +1 -1
- package/lib/pendant/widget.js +3 -1
- package/lib/pendant/widget.js.map +1 -1
- package/lib/pendant-renderer.js +6 -6
- package/lib/requirements/archive.d.ts +4 -0
- package/lib/requirements/archive.d.ts.map +1 -0
- package/lib/requirements/archive.js +14 -0
- package/lib/requirements/archive.js.map +1 -0
- package/lib/requirements/consolidation.d.ts +11 -0
- package/lib/requirements/consolidation.d.ts.map +1 -0
- package/lib/requirements/consolidation.js +50 -0
- package/lib/requirements/consolidation.js.map +1 -0
- package/lib/requirements/domain.d.ts +41 -0
- package/lib/requirements/domain.d.ts.map +1 -0
- package/lib/requirements/domain.js +2 -0
- package/lib/requirements/domain.js.map +1 -0
- package/lib/requirements/progress.d.ts +7 -0
- package/lib/requirements/progress.d.ts.map +1 -0
- package/lib/requirements/progress.js +17 -0
- package/lib/requirements/progress.js.map +1 -0
- package/lib/requirements/service.d.ts +23 -0
- package/lib/requirements/service.d.ts.map +1 -0
- package/lib/requirements/service.js +163 -0
- package/lib/requirements/service.js.map +1 -0
- package/lib/requirements/tool.d.ts +5 -0
- package/lib/requirements/tool.d.ts.map +1 -0
- package/lib/requirements/tool.js +49 -0
- package/lib/requirements/tool.js.map +1 -0
- package/lib/requirements/worker.d.ts +30 -0
- package/lib/requirements/worker.d.ts.map +1 -0
- package/lib/requirements/worker.js +164 -0
- package/lib/requirements/worker.js.map +1 -0
- package/lib/skills/builtin.d.ts.map +1 -1
- package/lib/skills/builtin.js +3 -2
- package/lib/skills/builtin.js.map +1 -1
- package/lib/skills/service.js +1 -1
- package/lib/skills/service.js.map +1 -1
- package/lib/skills/task-planning.d.ts +5 -0
- package/lib/skills/task-planning.d.ts.map +1 -0
- package/lib/skills/task-planning.js +47 -0
- package/lib/skills/task-planning.js.map +1 -0
- package/lib/store.d.ts +3 -0
- package/lib/store.d.ts.map +1 -1
- package/lib/store.js +10 -0
- package/lib/store.js.map +1 -1
- package/lib/tasks/context.d.ts +8 -0
- package/lib/tasks/context.d.ts.map +1 -0
- package/lib/tasks/context.js +48 -0
- package/lib/tasks/context.js.map +1 -0
- package/lib/tasks/domain.d.ts +12 -0
- package/lib/tasks/domain.d.ts.map +1 -1
- package/lib/tasks/progress-notifier.d.ts +4 -0
- package/lib/tasks/progress-notifier.d.ts.map +1 -0
- package/lib/tasks/progress-notifier.js +20 -0
- package/lib/tasks/progress-notifier.js.map +1 -0
- package/lib/tasks/removal.d.ts +5 -0
- package/lib/tasks/removal.d.ts.map +1 -0
- package/lib/tasks/removal.js +44 -0
- package/lib/tasks/removal.js.map +1 -0
- package/lib/tasks/requirement-link.d.ts +4 -0
- package/lib/tasks/requirement-link.d.ts.map +1 -0
- package/lib/tasks/requirement-link.js +11 -0
- package/lib/tasks/requirement-link.js.map +1 -0
- package/lib/tasks/service.d.ts +13 -5
- package/lib/tasks/service.d.ts.map +1 -1
- package/lib/tasks/service.js +113 -29
- package/lib/tasks/service.js.map +1 -1
- package/lib/ui/board-refresh.d.ts +3 -0
- package/lib/ui/board-refresh.d.ts.map +1 -0
- package/lib/ui/board-refresh.js +42 -0
- package/lib/ui/board-refresh.js.map +1 -0
- package/lib/ui/identity-editor.d.ts +11 -0
- package/lib/ui/identity-editor.d.ts.map +1 -0
- package/lib/ui/identity-editor.js +78 -0
- package/lib/ui/identity-editor.js.map +1 -0
- package/lib/ui/requirement-board.d.ts +5 -0
- package/lib/ui/requirement-board.d.ts.map +1 -0
- package/lib/ui/requirement-board.js +47 -0
- package/lib/ui/requirement-board.js.map +1 -0
- package/lib/ui/requirement-dialog.d.ts +15 -0
- package/lib/ui/requirement-dialog.d.ts.map +1 -0
- package/lib/ui/requirement-dialog.js +53 -0
- package/lib/ui/requirement-dialog.js.map +1 -0
- package/lib/ui/requirement-tasks-panel.d.ts +8 -0
- package/lib/ui/requirement-tasks-panel.d.ts.map +1 -0
- package/lib/ui/requirement-tasks-panel.js +269 -0
- package/lib/ui/requirement-tasks-panel.js.map +1 -0
- package/lib/ui/task-board-panel.d.ts +2 -4
- package/lib/ui/task-board-panel.d.ts.map +1 -1
- package/lib/ui/task-board-panel.js +2 -283
- package/lib/ui/task-board-panel.js.map +1 -1
- package/lib/ui/task-board-stage.d.ts +12 -0
- package/lib/ui/task-board-stage.d.ts.map +1 -0
- package/lib/ui/task-board-stage.js +13 -0
- package/lib/ui/task-board-stage.js.map +1 -0
- package/lib/ui/workspace-components.js +1 -1
- package/lib/ui/workspace-components.js.map +1 -1
- package/package.json +1 -1
package/lib/tasks/domain.d.ts
CHANGED
|
@@ -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"}
|
package/lib/tasks/service.d.ts
CHANGED
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/tasks/service.ts"],"names":[],"mappings":"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"}
|
package/lib/tasks/service.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
129
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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,
|