@longzai-intelligence-workflow/core 0.0.1
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/CHANGELOG.md +20 -0
- package/README.md +14 -0
- package/coverage/.lcov.info.0d8fc8c4f6acaa77.tmp +159 -0
- package/coverage/.lcov.info.1ca0aee1c681607a.tmp +61 -0
- package/coverage/.lcov.info.5a9690152fb62c73.tmp +159 -0
- package/coverage/.lcov.info.984d9ad89a4b1598.tmp +12 -0
- package/coverage/.lcov.info.a607374a9831c63e.tmp +61 -0
- package/coverage/.lcov.info.b58246c9a6df7fc5.tmp +167 -0
- package/coverage/.lcov.info.b5f068b7c1d63f36.tmp +12 -0
- package/coverage/.lcov.info.bbac83a4ec8a1294.tmp +159 -0
- package/coverage/.lcov.info.bf3e6cf9d98abfc0.tmp +159 -0
- package/coverage/.lcov.info.fb2d882b78dc47d4.tmp +169 -0
- package/coverage/lcov.info +169 -0
- package/dist/index.d.ts +577 -0
- package/dist/index.js +8 -0
- package/dist/schemas-D0Zuq8_5.js +1 -0
- package/dist/schemas.d.ts +684 -0
- package/dist/schemas.js +1 -0
- package/docs/overview.md +17 -0
- package/lzi-builder.config.ts +13 -0
- package/lzi-dev.config.ts +7 -0
- package/oxlint.config.ts +6 -0
- package/package.json +38 -0
- package/src/__tests__/helpers/setup.ts +9 -0
- package/src/__tests__/unit/constants.test.ts +129 -0
- package/src/__tests__/unit/utils.test.ts +156 -0
- package/src/__tests__/work-item-rules.test.ts +90 -0
- package/src/constants/index.ts +1 -0
- package/src/constants/workflow.ts +114 -0
- package/src/index.ts +16 -0
- package/src/ports/action-executor.port.ts +73 -0
- package/src/ports/feed-event.port.ts +74 -0
- package/src/ports/index.ts +9 -0
- package/src/schemas/action-executor.schema.ts +209 -0
- package/src/schemas/api.schema.ts +11 -0
- package/src/schemas/definition.schema.ts +53 -0
- package/src/schemas/feed-event-trigger.schema.ts +102 -0
- package/src/schemas/index.ts +8 -0
- package/src/schemas/instance.schema.ts +47 -0
- package/src/schemas/monitoring.schema.ts +53 -0
- package/src/schemas/trigger.schema.ts +26 -0
- package/src/schemas/workflow-doc.schema.ts +365 -0
- package/src/types/action-executor.ts +64 -0
- package/src/types/api.ts +10 -0
- package/src/types/definition.ts +49 -0
- package/src/types/feed-event-trigger.ts +38 -0
- package/src/types/index.ts +6 -0
- package/src/types/instance.ts +49 -0
- package/src/types/monitoring.ts +54 -0
- package/src/types/trigger.ts +39 -0
- package/src/utils/dag.utils.ts +95 -0
- package/src/utils/declaration-frontmatter.utils.ts +138 -0
- package/src/utils/index.ts +19 -0
- package/src/utils/work-item-rules.utils.ts +194 -0
- package/tsconfig/.cache/build.tsbuildinfo +1 -0
- package/tsconfig/.cache/node.tsbuildinfo +1 -0
- package/tsconfig/.cache/test.tsbuildinfo +1 -0
- package/tsconfig/app.json +12 -0
- package/tsconfig/build.json +15 -0
- package/tsconfig/node.json +12 -0
- package/tsconfig/test.json +15 -0
- package/tsconfig.json +15 -0
package/oxlint.config.ts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@longzai-intelligence-workflow/core",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"license": "UNLICENSED",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./schemas": {
|
|
14
|
+
"types": "./dist/schemas.d.ts",
|
|
15
|
+
"import": "./dist/schemas.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "lzi-builder",
|
|
20
|
+
"build:prod": "NODE_ENV=production bun run build",
|
|
21
|
+
"prepublishOnly": "bun run build:prod",
|
|
22
|
+
"typecheck": "bun run typecheck:app && bun run typecheck:node && bun run typecheck:test",
|
|
23
|
+
"typecheck:app": "lzi-tsgo typecheck tsconfig/app.json",
|
|
24
|
+
"typecheck:node": "lzi-tsgo typecheck tsconfig/node.json",
|
|
25
|
+
"typecheck:test": "lzi-tsgo typecheck tsconfig/test.json",
|
|
26
|
+
"lint": "oxlint src && oxfmt --check src",
|
|
27
|
+
"lint:fix": "oxlint --fix src && oxfmt src",
|
|
28
|
+
"test": "bun test",
|
|
29
|
+
"test:watch": "bun test --watch",
|
|
30
|
+
"test:coverage": "bun test --coverage",
|
|
31
|
+
"clean": "lzi-dev-cli clean"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@longzai-intelligence-shared-kernel/core": "^0.1.4",
|
|
35
|
+
"zod": "^4.4.3"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {}
|
|
38
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
ALERT_TYPES,
|
|
5
|
+
DEFAULT_PAGE_SIZE,
|
|
6
|
+
INSTANCE_STATUSES,
|
|
7
|
+
MAX_PAGE_SIZE,
|
|
8
|
+
METRIC_TYPES,
|
|
9
|
+
NODE_EXECUTION_STATUSES,
|
|
10
|
+
NODE_TYPES,
|
|
11
|
+
TRIGGER_TYPES,
|
|
12
|
+
} from '../../constants/workflow';
|
|
13
|
+
|
|
14
|
+
describe('DEFAULT_PAGE_SIZE', () => {
|
|
15
|
+
it('默认分页大小为 20', () => {
|
|
16
|
+
// Arrange & Act & Assert
|
|
17
|
+
expect(DEFAULT_PAGE_SIZE).toBe(20);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('MAX_PAGE_SIZE', () => {
|
|
22
|
+
it('最大分页大小为 100', () => {
|
|
23
|
+
// Arrange & Act & Assert
|
|
24
|
+
expect(MAX_PAGE_SIZE).toBe(100);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe('NODE_TYPES', () => {
|
|
29
|
+
it('包含预期的节点类型', () => {
|
|
30
|
+
// Arrange
|
|
31
|
+
const expectedTypes = [
|
|
32
|
+
'StartEvent',
|
|
33
|
+
'EndEvent',
|
|
34
|
+
'UserTask',
|
|
35
|
+
'ServiceTask',
|
|
36
|
+
'ExclusiveGateway',
|
|
37
|
+
'ParallelGateway',
|
|
38
|
+
'InclusiveGateway',
|
|
39
|
+
'SubProcess',
|
|
40
|
+
'TimerEvent',
|
|
41
|
+
'ErrorEvent',
|
|
42
|
+
'SignalEvent',
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
// Act & Assert
|
|
46
|
+
expect(NODE_TYPES).toEqual(expectedTypes);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('包含 StartEvent', () => {
|
|
50
|
+
// Arrange & Act & Assert
|
|
51
|
+
expect(NODE_TYPES).toContain('StartEvent');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('包含 EndEvent', () => {
|
|
55
|
+
// Arrange & Act & Assert
|
|
56
|
+
expect(NODE_TYPES).toContain('EndEvent');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('为只读数组', () => {
|
|
60
|
+
// Arrange & Act & Assert
|
|
61
|
+
expect(Array.isArray(NODE_TYPES)).toBe(true);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe('INSTANCE_STATUSES', () => {
|
|
66
|
+
it('包含预期的实例状态', () => {
|
|
67
|
+
// Arrange
|
|
68
|
+
const expectedStatuses = ['pending', 'running', 'paused', 'completed', 'cancelled', 'failed'];
|
|
69
|
+
|
|
70
|
+
// Act & Assert
|
|
71
|
+
expect(INSTANCE_STATUSES).toEqual(expectedStatuses);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('包含 pending 状态', () => {
|
|
75
|
+
// Arrange & Act & Assert
|
|
76
|
+
expect(INSTANCE_STATUSES).toContain('pending');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('包含 failed 状态', () => {
|
|
80
|
+
// Arrange & Act & Assert
|
|
81
|
+
expect(INSTANCE_STATUSES).toContain('failed');
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('NODE_EXECUTION_STATUSES', () => {
|
|
86
|
+
it('包含预期的节点执行状态', () => {
|
|
87
|
+
// Arrange
|
|
88
|
+
const expectedStatuses = ['pending', 'running', 'completed', 'failed', 'skipped'];
|
|
89
|
+
|
|
90
|
+
// Act & Assert
|
|
91
|
+
expect(NODE_EXECUTION_STATUSES).toEqual(expectedStatuses);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe('TRIGGER_TYPES', () => {
|
|
96
|
+
it('包含预期的触发器类型', () => {
|
|
97
|
+
// Arrange
|
|
98
|
+
const expectedTypes = ['timer', 'webhook', 'event'];
|
|
99
|
+
|
|
100
|
+
// Act & Assert
|
|
101
|
+
expect(TRIGGER_TYPES).toEqual(expectedTypes);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe('ALERT_TYPES', () => {
|
|
106
|
+
it('包含预期的告警类型', () => {
|
|
107
|
+
// Arrange
|
|
108
|
+
const expectedTypes = ['timeout', 'failure_rate', 'slow_execution'];
|
|
109
|
+
|
|
110
|
+
// Act & Assert
|
|
111
|
+
expect(ALERT_TYPES).toEqual(expectedTypes);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('METRIC_TYPES', () => {
|
|
116
|
+
it('包含预期的指标类型', () => {
|
|
117
|
+
// Arrange
|
|
118
|
+
const expectedTypes = [
|
|
119
|
+
'avg_duration',
|
|
120
|
+
'success_rate',
|
|
121
|
+
'failure_rate',
|
|
122
|
+
'timeout_rate',
|
|
123
|
+
'instance_count',
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
// Act & Assert
|
|
127
|
+
expect(METRIC_TYPES).toEqual(expectedTypes);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test';
|
|
2
|
+
|
|
3
|
+
import { DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE } from '../../constants/workflow';
|
|
4
|
+
import { formatDate, getPagination, getTotalPages } from '../../utils/index';
|
|
5
|
+
|
|
6
|
+
describe('getPagination', () => {
|
|
7
|
+
it('默认参数返回 offset=0, limit=DEFAULT_PAGE_SIZE', () => {
|
|
8
|
+
// Arrange & Act
|
|
9
|
+
const result = getPagination();
|
|
10
|
+
|
|
11
|
+
// Assert
|
|
12
|
+
expect(result.offset).toBe(0);
|
|
13
|
+
expect(result.limit).toBe(DEFAULT_PAGE_SIZE);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('page=2, pageSize=10 返回 offset=10, limit=10', () => {
|
|
17
|
+
// Arrange & Act
|
|
18
|
+
const result = getPagination(2, 10);
|
|
19
|
+
|
|
20
|
+
// Assert
|
|
21
|
+
expect(result.offset).toBe(10);
|
|
22
|
+
expect(result.limit).toBe(10);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('pageSize 超过 MAX_PAGE_SIZE 时限制为 MAX_PAGE_SIZE', () => {
|
|
26
|
+
// Arrange
|
|
27
|
+
const oversizedPageSize = MAX_PAGE_SIZE + 50;
|
|
28
|
+
|
|
29
|
+
// Act
|
|
30
|
+
const result = getPagination(1, oversizedPageSize);
|
|
31
|
+
|
|
32
|
+
// Assert
|
|
33
|
+
expect(result.limit).toBe(MAX_PAGE_SIZE);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('page=0 时默认为 page=1', () => {
|
|
37
|
+
// Arrange & Act
|
|
38
|
+
const result = getPagination(0, 10);
|
|
39
|
+
|
|
40
|
+
// Assert
|
|
41
|
+
expect(result.offset).toBe(0);
|
|
42
|
+
expect(result.limit).toBe(10);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('page 为负数时默认为 page=1', () => {
|
|
46
|
+
// Arrange & Act
|
|
47
|
+
const result = getPagination(-1, 10);
|
|
48
|
+
|
|
49
|
+
// Assert
|
|
50
|
+
expect(result.offset).toBe(0);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('仅传入 page 时 pageSize 使用默认值', () => {
|
|
54
|
+
// Arrange & Act
|
|
55
|
+
const result = getPagination(3);
|
|
56
|
+
|
|
57
|
+
// Assert
|
|
58
|
+
expect(result.offset).toBe((3 - 1) * DEFAULT_PAGE_SIZE);
|
|
59
|
+
expect(result.limit).toBe(DEFAULT_PAGE_SIZE);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('仅传入 pageSize 时 page 默认为 1', () => {
|
|
63
|
+
// Arrange & Act
|
|
64
|
+
const result = getPagination(undefined, 15);
|
|
65
|
+
|
|
66
|
+
// Assert
|
|
67
|
+
expect(result.offset).toBe(0);
|
|
68
|
+
expect(result.limit).toBe(15);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe('getTotalPages', () => {
|
|
73
|
+
it('正确计算总页数', () => {
|
|
74
|
+
// Arrange
|
|
75
|
+
const total = 45;
|
|
76
|
+
const pageSize = 10;
|
|
77
|
+
|
|
78
|
+
// Act
|
|
79
|
+
const result = getTotalPages(total, pageSize);
|
|
80
|
+
|
|
81
|
+
// Assert
|
|
82
|
+
expect(result).toBe(5);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('总数恰好整除时返回精确页数', () => {
|
|
86
|
+
// Arrange
|
|
87
|
+
const total = 40;
|
|
88
|
+
const pageSize = 10;
|
|
89
|
+
|
|
90
|
+
// Act
|
|
91
|
+
const result = getTotalPages(total, pageSize);
|
|
92
|
+
|
|
93
|
+
// Assert
|
|
94
|
+
expect(result).toBe(4);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('总数为 0 时返回 0', () => {
|
|
98
|
+
// Arrange
|
|
99
|
+
const total = 0;
|
|
100
|
+
const pageSize = 10;
|
|
101
|
+
|
|
102
|
+
// Act
|
|
103
|
+
const result = getTotalPages(total, pageSize);
|
|
104
|
+
|
|
105
|
+
// Assert
|
|
106
|
+
expect(result).toBe(0);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('总数为 1 时返回 1', () => {
|
|
110
|
+
// Arrange
|
|
111
|
+
const total = 1;
|
|
112
|
+
const pageSize = 10;
|
|
113
|
+
|
|
114
|
+
// Act
|
|
115
|
+
const result = getTotalPages(total, pageSize);
|
|
116
|
+
|
|
117
|
+
// Assert
|
|
118
|
+
expect(result).toBe(1);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe('formatDate', () => {
|
|
123
|
+
it('将 Date 对象转换为 ISO 字符串', () => {
|
|
124
|
+
// Arrange
|
|
125
|
+
const date = new Date('2025-01-15T10:30:00.000Z');
|
|
126
|
+
|
|
127
|
+
// Act
|
|
128
|
+
const result = formatDate(date);
|
|
129
|
+
|
|
130
|
+
// Assert
|
|
131
|
+
expect(result).toBe('2025-01-15T10:30:00.000Z');
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('将日期字符串转换为 ISO 字符串', () => {
|
|
135
|
+
// Arrange
|
|
136
|
+
const dateStr = '2025-06-01';
|
|
137
|
+
|
|
138
|
+
// Act
|
|
139
|
+
const result = formatDate(dateStr);
|
|
140
|
+
|
|
141
|
+
// Assert
|
|
142
|
+
expect(result).toContain('2025-06-01');
|
|
143
|
+
expect(typeof result).toBe('string');
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('返回值为有效的 ISO 格式', () => {
|
|
147
|
+
// Arrange
|
|
148
|
+
const date = new Date();
|
|
149
|
+
|
|
150
|
+
// Act
|
|
151
|
+
const result = formatDate(date);
|
|
152
|
+
|
|
153
|
+
// Assert
|
|
154
|
+
expect(new Date(result).toISOString()).toBe(result);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 工作项判定核测试:四段制解析 + 转移表执法 + 视图派生
|
|
3
|
+
*/
|
|
4
|
+
import { describe, expect, test } from 'bun:test';
|
|
5
|
+
|
|
6
|
+
import { parseGateLine, parseItemLine, type WorkflowEdge, type WorkflowGate, type WorkflowItem } from '../schemas/workflow-doc.schema';
|
|
7
|
+
import { checkStatusTransition, deriveWorkflowView } from '../utils/work-item-rules.utils';
|
|
8
|
+
|
|
9
|
+
describe('声明方言四段制解析', () => {
|
|
10
|
+
test('工作项行:状态段执法', () => {
|
|
11
|
+
expect(parseItemLine('t-011 | 生成器补全 | ref:demo/docs/tasks/a.md | todo')).toEqual({
|
|
12
|
+
id: 't-011',
|
|
13
|
+
title: '生成器补全',
|
|
14
|
+
ref: 'ref:demo/docs/tasks/a.md',
|
|
15
|
+
status: 'todo',
|
|
16
|
+
});
|
|
17
|
+
expect(parseItemLine('t-011 | 生成器补全 | ref:demo/docs/tasks/a.md')).toBeNull();
|
|
18
|
+
expect(parseItemLine('t-011 | 生成器补全 | ref:demo/docs/tasks/a.md | invalid')).toBeNull();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('裁定门行:裁定段四形态执法', () => {
|
|
22
|
+
expect(parseGateLine('t-013 | ruling | 载体指示待裁 | pending')?.ruling).toEqual({ status: 'pending' });
|
|
23
|
+
expect(parseGateLine('workflow | ruling | TE 悬置 | pending')?.at).toBe('workflow');
|
|
24
|
+
expect(parseGateLine('t-013 | ruling | 载体指示待裁 | approved:user:decisions/019.md')?.ruling).toEqual({
|
|
25
|
+
status: 'approved',
|
|
26
|
+
decidedBy: 'user',
|
|
27
|
+
evidence: 'decisions/019.md',
|
|
28
|
+
});
|
|
29
|
+
expect(parseGateLine('t-013 | ruling | 载体指示待裁 | rejected:user')?.ruling).toEqual({
|
|
30
|
+
status: 'rejected',
|
|
31
|
+
decidedBy: 'user',
|
|
32
|
+
});
|
|
33
|
+
expect(parseGateLine('t-013 | ruling | 载体指示待裁 | approved')).toBeNull();
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('状态转移表执法', () => {
|
|
38
|
+
test('合法/非法转移判定', () => {
|
|
39
|
+
expect(checkStatusTransition('todo', 'doing')).toBeNull();
|
|
40
|
+
expect(checkStatusTransition('doing', 'blocked')).toBeNull();
|
|
41
|
+
expect(checkStatusTransition('done', 'todo')).toBeNull();
|
|
42
|
+
const error = checkStatusTransition('todo', 'done');
|
|
43
|
+
expect(error?.code).toBe('INVALID_TRANSITION');
|
|
44
|
+
expect(error?.message).toContain('todo → done');
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe('视图派生(依赖门控与未决裁定)', () => {
|
|
49
|
+
/**
|
|
50
|
+
* 拓扑:a -> b(b 挂门);c 独立;workflow 级门一枚
|
|
51
|
+
*/
|
|
52
|
+
const items: readonly WorkflowItem[] = [
|
|
53
|
+
{ id: 'a', title: '甲', ref: 'ref:demo/a.md', status: 'todo' },
|
|
54
|
+
{ id: 'b', title: '乙', ref: 'ref:demo/b.md', status: 'todo' },
|
|
55
|
+
{ id: 'c', title: '丙', ref: 'task:demo/c-item', status: 'doing' },
|
|
56
|
+
];
|
|
57
|
+
const edges: readonly WorkflowEdge[] = [{ from: 'a', to: 'b' }];
|
|
58
|
+
const gates: readonly WorkflowGate[] = [
|
|
59
|
+
{ at: 'b', kind: 'ruling', note: '乙项须裁定', ruling: { status: 'pending' } },
|
|
60
|
+
{ at: 'workflow', kind: 'ruling', note: '工作流级悬置', ruling: { status: 'pending' } },
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
test('依赖未满足派生阻塞;门未批不可启动;未决裁定含工作流级', () => {
|
|
64
|
+
const view = deriveWorkflowView(items, edges, gates);
|
|
65
|
+
const byId = new Map(view.items.map((entry) => [entry.id, entry]));
|
|
66
|
+
|
|
67
|
+
expect(byId.get('a')?.canStart).toBe(true);
|
|
68
|
+
expect(byId.get('b')?.blockedOnPrerequisite).toBe(true);
|
|
69
|
+
expect(byId.get('b')?.canStart).toBe(false);
|
|
70
|
+
expect(byId.get('c')?.canStart).toBe(false);
|
|
71
|
+
expect(view.pendingRulings.map((ruling) => ruling.key)).toEqual(['b:ruling', 'workflow:ruling']);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('前置闭环解除阻塞;门批准解除门控;escalated 仍属未决', () => {
|
|
75
|
+
const settledItems: readonly WorkflowItem[] = [
|
|
76
|
+
{ id: 'a', title: '甲', ref: 'ref:demo/a.md', status: 'done' },
|
|
77
|
+
{ id: 'b', title: '乙', ref: 'ref:demo/b.md', status: 'todo' },
|
|
78
|
+
];
|
|
79
|
+
const approvedGates: readonly WorkflowGate[] = [
|
|
80
|
+
{ at: 'b', kind: 'ruling', note: '已裁', ruling: { status: 'approved', decidedBy: 'user' } },
|
|
81
|
+
{ at: 'workflow', kind: 'ruling', note: '升级待裁', ruling: { status: 'escalated' } },
|
|
82
|
+
];
|
|
83
|
+
const view = deriveWorkflowView(settledItems, edges, approvedGates);
|
|
84
|
+
const byId = new Map(view.items.map((entry) => [entry.id, entry]));
|
|
85
|
+
|
|
86
|
+
expect(byId.get('b')?.blockedOnPrerequisite).toBe(false);
|
|
87
|
+
expect(byId.get('b')?.canStart).toBe(true);
|
|
88
|
+
expect(view.pendingRulings.map((ruling) => ruling.rulingStatus)).toEqual(['escalated']);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './workflow';
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 工作流节点类型枚举
|
|
3
|
+
*/
|
|
4
|
+
export const NODE_TYPES = [
|
|
5
|
+
'StartEvent',
|
|
6
|
+
'EndEvent',
|
|
7
|
+
'UserTask',
|
|
8
|
+
'ServiceTask',
|
|
9
|
+
'ExclusiveGateway',
|
|
10
|
+
'ParallelGateway',
|
|
11
|
+
'InclusiveGateway',
|
|
12
|
+
'SubProcess',
|
|
13
|
+
'TimerEvent',
|
|
14
|
+
'ErrorEvent',
|
|
15
|
+
'SignalEvent',
|
|
16
|
+
] as const;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 工作流实例状态枚举
|
|
20
|
+
*/
|
|
21
|
+
export const INSTANCE_STATUSES = [
|
|
22
|
+
'pending',
|
|
23
|
+
'running',
|
|
24
|
+
'paused',
|
|
25
|
+
'completed',
|
|
26
|
+
'cancelled',
|
|
27
|
+
'failed',
|
|
28
|
+
] as const;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 节点执行状态枚举
|
|
32
|
+
*/
|
|
33
|
+
export const NODE_EXECUTION_STATUSES = [
|
|
34
|
+
'pending',
|
|
35
|
+
'running',
|
|
36
|
+
'completed',
|
|
37
|
+
'failed',
|
|
38
|
+
'skipped',
|
|
39
|
+
] as const;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 触发器类型枚举
|
|
43
|
+
*/
|
|
44
|
+
export const TRIGGER_TYPES = ['timer', 'webhook', 'event', 'feed.event'] as const;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* feed 事件名称枚举(W-2,Phase 2)
|
|
48
|
+
*
|
|
49
|
+
* 用于 feed.event 触发器订阅 feed 项目暴露的事件总线。
|
|
50
|
+
*/
|
|
51
|
+
export const FEED_EVENT_NAMES = [
|
|
52
|
+
/**
|
|
53
|
+
* 新条目入库事件
|
|
54
|
+
*/
|
|
55
|
+
'item.ingested',
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 信息源抓取完成事件
|
|
59
|
+
*/
|
|
60
|
+
'source.fetched',
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 条目过滤事件
|
|
64
|
+
*/
|
|
65
|
+
'item.filtered',
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 行业识别事件
|
|
69
|
+
*/
|
|
70
|
+
'industry.detected',
|
|
71
|
+
] as const;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* 告警类型枚举
|
|
75
|
+
*/
|
|
76
|
+
export const ALERT_TYPES = ['timeout', 'failure_rate', 'slow_execution'] as const;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 工作流内置 action 类型枚举(W-3,Phase 2)
|
|
80
|
+
*
|
|
81
|
+
* - feed.applyTags:调用 feed F-2 applyTags 为条目打标签
|
|
82
|
+
* - feed.summarize:调用 feed F-3 summarize 生成摘要
|
|
83
|
+
* - llm.complete:调用 agent LlmCompletePort 完成文本
|
|
84
|
+
* - webhook.send:调用 webhook 项目发送 webhook
|
|
85
|
+
*
|
|
86
|
+
* D-08 决策:不暴露 DSL 编辑 UI,action 通过自然语言编译的 DSL 执行。
|
|
87
|
+
*/
|
|
88
|
+
export const ACTION_TYPES = [
|
|
89
|
+
'feed.applyTags',
|
|
90
|
+
'feed.summarize',
|
|
91
|
+
'llm.complete',
|
|
92
|
+
'webhook.send',
|
|
93
|
+
] as const;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 监控指标类型枚举
|
|
97
|
+
*/
|
|
98
|
+
export const METRIC_TYPES = [
|
|
99
|
+
'avg_duration',
|
|
100
|
+
'success_rate',
|
|
101
|
+
'failure_rate',
|
|
102
|
+
'timeout_rate',
|
|
103
|
+
'instance_count',
|
|
104
|
+
] as const;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* 分页查询默认每页条数
|
|
108
|
+
*/
|
|
109
|
+
export const DEFAULT_PAGE_SIZE = 20;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* 分页查询每页最大条数上限
|
|
113
|
+
*/
|
|
114
|
+
export const MAX_PAGE_SIZE = 100;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 工作流核心包
|
|
3
|
+
*
|
|
4
|
+
* 提供共享基础类型、Schema、常量和工具函数。
|
|
5
|
+
* 不依赖任何 domain 包。
|
|
6
|
+
*/
|
|
7
|
+
// 类型导出
|
|
8
|
+
export * from './types/index';
|
|
9
|
+
// 常量导出
|
|
10
|
+
export * from './constants/index';
|
|
11
|
+
// 工具函数导出
|
|
12
|
+
export * from './utils/index';
|
|
13
|
+
// zod schema 导出
|
|
14
|
+
export * from './schemas/index';
|
|
15
|
+
// 端口契约导出(C1 合规,零框架依赖)
|
|
16
|
+
export * from './ports/index';
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* action 执行器端口契约(W-3,Phase 2)
|
|
3
|
+
*
|
|
4
|
+
* 定义工作流内置 action 执行器的端口契约。
|
|
5
|
+
* execution 子插件通过此端口注册与调度 action handler,
|
|
6
|
+
* 当工作流执行到 action 节点时,根据 action.type 调用对应 handler。
|
|
7
|
+
*
|
|
8
|
+
* 支持的 action 类型:
|
|
9
|
+
* - feed.applyTags:调用 feed F-2 applyTags(Phase 2)
|
|
10
|
+
* - feed.summarize:调用 feed F-3 summarize(Phase 2)
|
|
11
|
+
* - llm.complete:调用 agent LlmCompletePort
|
|
12
|
+
* - webhook.send:调用 webhook 项目
|
|
13
|
+
*
|
|
14
|
+
* D-08 决策:不暴露 DSL 编辑 UI,action 通过自然语言编译的 DSL 执行。
|
|
15
|
+
*
|
|
16
|
+
* C1 合规:本端口为零框架依赖的纯类型契约,由具体适配器层提供实现。
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type { Action, ActionContext, ActionResult, ActionType } from '../types';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 单个 action handler 函数签名
|
|
23
|
+
*
|
|
24
|
+
* 注册到 ActionExecutorPort 中,按 action.type 路由调用。
|
|
25
|
+
*
|
|
26
|
+
* @param action - 待执行的 action(具体子类型由注册时的 actionType 决定)
|
|
27
|
+
* @param context - action 执行上下文
|
|
28
|
+
* @returns 执行结果
|
|
29
|
+
*/
|
|
30
|
+
export type ActionHandler = (action: Action, context: ActionContext) => Promise<ActionResult>;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* action 执行器端口
|
|
34
|
+
*
|
|
35
|
+
* 工作流引擎通过此端口调度内置 action。
|
|
36
|
+
* execution 子插件在初始化时注册各 actionType 的 handler,
|
|
37
|
+
* 工作流执行到 action 节点时调用 execute 方法。
|
|
38
|
+
*/
|
|
39
|
+
export type ActionExecutorPort = {
|
|
40
|
+
/**
|
|
41
|
+
* 执行指定 action
|
|
42
|
+
*
|
|
43
|
+
* 根据 action.type 路由到注册的 handler 执行。
|
|
44
|
+
* 未注册的 actionType 返回失败结果。
|
|
45
|
+
*
|
|
46
|
+
* @param action - 待执行的 action
|
|
47
|
+
* @param context - action 执行上下文
|
|
48
|
+
* @returns 执行结果
|
|
49
|
+
*/
|
|
50
|
+
execute: (action: Action, context: ActionContext) => Promise<ActionResult>;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 注册 action handler
|
|
54
|
+
*
|
|
55
|
+
* 为指定 actionType 注册 handler,重复注册将覆盖旧 handler。
|
|
56
|
+
*
|
|
57
|
+
* @param actionType - action 类型
|
|
58
|
+
* @param handler - action 处理器
|
|
59
|
+
*/
|
|
60
|
+
registerHandler: (actionType: ActionType, handler: ActionHandler) => void;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 检查 action 执行器是否可用
|
|
64
|
+
*
|
|
65
|
+
* @returns 端口是否可用
|
|
66
|
+
*/
|
|
67
|
+
isAvailable: () => Promise<boolean>;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* action 执行器端口 DI 令牌
|
|
72
|
+
*/
|
|
73
|
+
export const ACTION_EXECUTOR_PORT_TOKEN = Symbol('ACTION_EXECUTOR_PORT_TOKEN');
|