openmatrix 0.1.91 → 0.1.93
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 +4 -0
- package/dist/agents/agent-runner.js +3 -3
- package/dist/cli/commands/complete.js +2 -9
- package/dist/orchestrator/git-commit-manager.d.ts +25 -6
- package/dist/orchestrator/git-commit-manager.js +98 -17
- package/dist/storage/file-store.d.ts +5 -0
- package/dist/storage/file-store.js +11 -0
- package/dist/storage/state-manager.d.ts +6 -3
- package/dist/storage/state-manager.js +97 -72
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -493,6 +493,10 @@ cd openmatrix && npm install && npm run build && npm test
|
|
|
493
493
|
- [x] `/om:research` AI 驱动领域调研
|
|
494
494
|
- [x] Git 自动提交 (任务完成后自动 commit)
|
|
495
495
|
- [x] Brainstorm/Start 智能状态检测
|
|
496
|
+
- [x] AI 目标类型标注 (智能识别开发/测试/文档任务)
|
|
497
|
+
- [x] 系统集成任务 (多模块自动组装)
|
|
498
|
+
- [x] 智能项目检测 (gitignore 根据技术栈自动写入)
|
|
499
|
+
- [x] Git 父级目录支持 (子目录中正常执行 git 操作)
|
|
496
500
|
- [ ] VSCode 扩展
|
|
497
501
|
- [ ] CI/CD 集成
|
|
498
502
|
|
|
@@ -166,9 +166,9 @@ ${agentPrompt.instructions}
|
|
|
166
166
|
|
|
167
167
|
## 完成要求
|
|
168
168
|
|
|
169
|
-
1.
|
|
170
|
-
|
|
171
|
-
|
|
169
|
+
1. 将执行结果写入: \`.openmatrix/tasks/${task.id}/artifacts/result.md\`
|
|
170
|
+
(任务状态由 openmatrix complete 命令管理,请勿直接修改 task.json)
|
|
171
|
+
2. 如需审批,创建审批请求: \`.openmatrix/approvals/\` 目录
|
|
172
172
|
|
|
173
173
|
注意: 任务完成后,由 Skill 调用 \`openmatrix complete\` 并传入 --summary 参数,
|
|
174
174
|
该摘要会自动追加到全局 \`.openmatrix/context.md\` 供后续 Agent 参考。
|
|
@@ -115,15 +115,8 @@ exports.completeCommand = new commander_1.Command('complete')
|
|
|
115
115
|
|
|
116
116
|
`;
|
|
117
117
|
try {
|
|
118
|
-
//
|
|
119
|
-
|
|
120
|
-
try {
|
|
121
|
-
existingContent = await fs.readFile(contextFile, 'utf-8');
|
|
122
|
-
}
|
|
123
|
-
catch {
|
|
124
|
-
// 文件不存在,创建新文件
|
|
125
|
-
}
|
|
126
|
-
await fs.writeFile(contextFile, existingContent + contextEntry, 'utf-8');
|
|
118
|
+
// 原子追加写入全局 context.md(O_APPEND flag 保证并发安全)
|
|
119
|
+
await fs.appendFile(contextFile, contextEntry, 'utf-8');
|
|
127
120
|
}
|
|
128
121
|
catch {
|
|
129
122
|
// 忽略写入错误
|
|
@@ -46,6 +46,13 @@ export declare class GitCommitManager {
|
|
|
46
46
|
* 获取未提交的文件列表
|
|
47
47
|
*/
|
|
48
48
|
getUncommittedFiles(): Promise<string[]>;
|
|
49
|
+
/**
|
|
50
|
+
* 获取未提交的文件列表(带状态信息)
|
|
51
|
+
*/
|
|
52
|
+
getUncommittedFilesWithStatus(): Promise<{
|
|
53
|
+
status: 'new' | 'modified' | 'deleted';
|
|
54
|
+
path: string;
|
|
55
|
+
}[]>;
|
|
49
56
|
/**
|
|
50
57
|
* 获取已修改的文件差异统计
|
|
51
58
|
*/
|
|
@@ -61,17 +68,29 @@ export declare class GitCommitManager {
|
|
|
61
68
|
* 生成提交信息
|
|
62
69
|
*
|
|
63
70
|
* 格式规范:
|
|
64
|
-
*
|
|
71
|
+
* feat(TASK-001): 简短描述
|
|
72
|
+
*
|
|
73
|
+
* 实现内容:
|
|
74
|
+
* 模块A: 功能描述
|
|
75
|
+
* 模块B: 功能描述
|
|
65
76
|
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
77
|
+
* 新增文件:
|
|
78
|
+
* model/xxx.go
|
|
79
|
+
* service/xxx.go
|
|
68
80
|
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
81
|
+
* 修改文件:
|
|
82
|
+
* main.go: 路由注册和handler初始化
|
|
71
83
|
*
|
|
72
84
|
* Co-Authored-By: OpenMatrix https://github.com/bigfish1913/openmatrix
|
|
73
85
|
*/
|
|
74
|
-
generateCommitMessage(info: CommitInfo
|
|
86
|
+
generateCommitMessage(info: CommitInfo, filesWithStatus?: {
|
|
87
|
+
status: 'new' | 'modified' | 'deleted';
|
|
88
|
+
path: string;
|
|
89
|
+
}[]): string;
|
|
90
|
+
/**
|
|
91
|
+
* 按目录分组文件
|
|
92
|
+
*/
|
|
93
|
+
private groupFilesByDirectory;
|
|
75
94
|
/**
|
|
76
95
|
* 执行提交
|
|
77
96
|
*/
|
|
@@ -112,6 +112,32 @@ class GitCommitManager {
|
|
|
112
112
|
return [];
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* 获取未提交的文件列表(带状态信息)
|
|
117
|
+
*/
|
|
118
|
+
async getUncommittedFilesWithStatus() {
|
|
119
|
+
try {
|
|
120
|
+
const { stdout } = await execAsync('git status --porcelain', { cwd: this.repoPath });
|
|
121
|
+
return stdout
|
|
122
|
+
.split('\n')
|
|
123
|
+
.filter(line => line.trim())
|
|
124
|
+
.map(line => {
|
|
125
|
+
const statusCode = line.slice(0, 2);
|
|
126
|
+
const filePath = line.slice(3).trim();
|
|
127
|
+
let status = 'modified';
|
|
128
|
+
if (statusCode.includes('?') || statusCode.includes('A')) {
|
|
129
|
+
status = 'new';
|
|
130
|
+
}
|
|
131
|
+
else if (statusCode.includes('D')) {
|
|
132
|
+
status = 'deleted';
|
|
133
|
+
}
|
|
134
|
+
return { status, path: filePath };
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
140
|
+
}
|
|
115
141
|
/**
|
|
116
142
|
* 获取已修改的文件差异统计
|
|
117
143
|
*/
|
|
@@ -173,17 +199,22 @@ class GitCommitManager {
|
|
|
173
199
|
* 生成提交信息
|
|
174
200
|
*
|
|
175
201
|
* 格式规范:
|
|
176
|
-
*
|
|
202
|
+
* feat(TASK-001): 简短描述
|
|
177
203
|
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
204
|
+
* 实现内容:
|
|
205
|
+
* 模块A: 功能描述
|
|
206
|
+
* 模块B: 功能描述
|
|
180
207
|
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
208
|
+
* 新增文件:
|
|
209
|
+
* model/xxx.go
|
|
210
|
+
* service/xxx.go
|
|
211
|
+
*
|
|
212
|
+
* 修改文件:
|
|
213
|
+
* main.go: 路由注册和handler初始化
|
|
183
214
|
*
|
|
184
215
|
* Co-Authored-By: OpenMatrix https://github.com/bigfish1913/openmatrix
|
|
185
216
|
*/
|
|
186
|
-
generateCommitMessage(info) {
|
|
217
|
+
generateCommitMessage(info, filesWithStatus) {
|
|
187
218
|
const lines = [];
|
|
188
219
|
// 类型映射:根据 phase 确定提交类型
|
|
189
220
|
const phaseToType = {
|
|
@@ -201,7 +232,7 @@ class GitCommitManager {
|
|
|
201
232
|
// 格式: feat(TASK-001): 简短描述
|
|
202
233
|
lines.push(`${commitType}(${info.taskId}): ${title}`);
|
|
203
234
|
lines.push('');
|
|
204
|
-
// Phase
|
|
235
|
+
// Phase 描述
|
|
205
236
|
const phaseDescriptions = {
|
|
206
237
|
tdd: '编写测试用例',
|
|
207
238
|
develop: '实现功能代码',
|
|
@@ -214,21 +245,69 @@ class GitCommitManager {
|
|
|
214
245
|
lines.push('');
|
|
215
246
|
lines.push(`影响范围: ${info.impactScope.join('、')}`);
|
|
216
247
|
}
|
|
217
|
-
//
|
|
218
|
-
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
248
|
+
// 按新增/修改分类列出文件
|
|
249
|
+
if (filesWithStatus && filesWithStatus.length > 0) {
|
|
250
|
+
const newFiles = filesWithStatus.filter(f => f.status === 'new').map(f => f.path);
|
|
251
|
+
const modifiedFiles = filesWithStatus.filter(f => f.status === 'modified').map(f => f.path);
|
|
252
|
+
const deletedFiles = filesWithStatus.filter(f => f.status === 'deleted').map(f => f.path);
|
|
253
|
+
if (newFiles.length > 0) {
|
|
254
|
+
lines.push('');
|
|
255
|
+
lines.push('新增文件:');
|
|
256
|
+
// 按目录分组
|
|
257
|
+
const grouped = this.groupFilesByDirectory(newFiles);
|
|
258
|
+
for (const [dir, files] of grouped) {
|
|
259
|
+
lines.push(`${dir ? dir + '/' : ''}${files.join(', ')}`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (modifiedFiles.length > 0) {
|
|
263
|
+
lines.push('');
|
|
264
|
+
lines.push('修改文件:');
|
|
265
|
+
const grouped = this.groupFilesByDirectory(modifiedFiles);
|
|
266
|
+
for (const [dir, files] of grouped) {
|
|
267
|
+
lines.push(`${dir ? dir + '/' : ''}${files.join(', ')}`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
if (deletedFiles.length > 0) {
|
|
271
|
+
lines.push('');
|
|
272
|
+
lines.push('删除文件:');
|
|
273
|
+
for (const f of deletedFiles) {
|
|
274
|
+
lines.push(f);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
else if (info.changes.length > 0) {
|
|
279
|
+
// 回退:无状态信息时使用 changes 列表
|
|
280
|
+
const changedFiles = info.changes.slice(0, 10).map(f => {
|
|
281
|
+
const parts = f.split('/');
|
|
282
|
+
return parts.length > 2 ? parts.slice(-2).join('/') : f;
|
|
283
|
+
});
|
|
284
|
+
lines.push('');
|
|
285
|
+
lines.push(`文件改动: ${changedFiles.join(', ')}`);
|
|
286
|
+
if (info.changes.length > 10) {
|
|
287
|
+
lines.push(`...等 ${info.changes.length} 个文件`);
|
|
288
|
+
}
|
|
226
289
|
}
|
|
227
290
|
lines.push('');
|
|
228
291
|
// Co-Author
|
|
229
292
|
lines.push(`Co-Authored-By: OpenMatrix https://github.com/bigfish1913/openmatrix`);
|
|
230
293
|
return lines.join('\n');
|
|
231
294
|
}
|
|
295
|
+
/**
|
|
296
|
+
* 按目录分组文件
|
|
297
|
+
*/
|
|
298
|
+
groupFilesByDirectory(files) {
|
|
299
|
+
const groups = new Map();
|
|
300
|
+
for (const file of files) {
|
|
301
|
+
const parts = file.split('/');
|
|
302
|
+
const dir = parts.length > 1 ? parts.slice(0, -1).join('/') : '';
|
|
303
|
+
const fileName = parts[parts.length - 1];
|
|
304
|
+
if (!groups.has(dir)) {
|
|
305
|
+
groups.set(dir, []);
|
|
306
|
+
}
|
|
307
|
+
groups.get(dir).push(fileName);
|
|
308
|
+
}
|
|
309
|
+
return groups;
|
|
310
|
+
}
|
|
232
311
|
/**
|
|
233
312
|
* 执行提交
|
|
234
313
|
*/
|
|
@@ -251,6 +330,8 @@ class GitCommitManager {
|
|
|
251
330
|
}
|
|
252
331
|
// 分析影响范围
|
|
253
332
|
const impactScope = await this.analyzeImpactScope(files);
|
|
333
|
+
// 获取文件状态信息(新增/修改/删除)
|
|
334
|
+
const filesWithStatus = await this.getUncommittedFilesWithStatus();
|
|
254
335
|
// 更新 commit info
|
|
255
336
|
const fullInfo = {
|
|
256
337
|
...info,
|
|
@@ -258,7 +339,7 @@ class GitCommitManager {
|
|
|
258
339
|
impactScope: info.impactScope.length > 0 ? info.impactScope : impactScope
|
|
259
340
|
};
|
|
260
341
|
// 生成提交信息
|
|
261
|
-
const commitMessage = this.generateCommitMessage(fullInfo);
|
|
342
|
+
const commitMessage = this.generateCommitMessage(fullInfo, filesWithStatus);
|
|
262
343
|
// 添加文件 - 使用 git add . 而不是 git add -A
|
|
263
344
|
// git add . 只添加当前目录及子目录的文件,不会添加上级目录的文件
|
|
264
345
|
// 同时通过 .gitignore 排除不需要的文件
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
export declare class FileStore {
|
|
2
2
|
private basePath;
|
|
3
3
|
constructor(basePath: string);
|
|
4
|
+
getBasePath(): string;
|
|
4
5
|
ensureDir(path: string): Promise<void>;
|
|
5
6
|
writeJson<T>(path: string, data: T): Promise<void>;
|
|
6
7
|
writeMarkdown(path: string, content: string): Promise<void>;
|
|
8
|
+
/**
|
|
9
|
+
* 原子追加写入(使用 O_APPEND flag,内核保证追加原子性)
|
|
10
|
+
*/
|
|
11
|
+
appendFile(filePath: string, content: string): Promise<void>;
|
|
7
12
|
/**
|
|
8
13
|
* 读取 JSON 文件
|
|
9
14
|
* @returns 文件内容,如果文件不存在则返回 null;其他错误会抛出异常
|
|
@@ -9,6 +9,9 @@ class FileStore {
|
|
|
9
9
|
constructor(basePath) {
|
|
10
10
|
this.basePath = basePath;
|
|
11
11
|
}
|
|
12
|
+
getBasePath() {
|
|
13
|
+
return this.basePath;
|
|
14
|
+
}
|
|
12
15
|
async ensureDir(path) {
|
|
13
16
|
const fullPath = (0, path_1.join)(this.basePath, path);
|
|
14
17
|
await (0, promises_1.mkdir)(fullPath, { recursive: true });
|
|
@@ -23,6 +26,14 @@ class FileStore {
|
|
|
23
26
|
await (0, promises_1.mkdir)((0, path_1.dirname)(fullPath), { recursive: true });
|
|
24
27
|
await (0, promises_1.writeFile)(fullPath, content, 'utf-8');
|
|
25
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* 原子追加写入(使用 O_APPEND flag,内核保证追加原子性)
|
|
31
|
+
*/
|
|
32
|
+
async appendFile(filePath, content) {
|
|
33
|
+
const fullPath = (0, path_1.join)(this.basePath, filePath);
|
|
34
|
+
await (0, promises_1.mkdir)((0, path_1.dirname)(fullPath), { recursive: true });
|
|
35
|
+
await (0, promises_1.appendFile)(fullPath, content, 'utf-8');
|
|
36
|
+
}
|
|
26
37
|
/**
|
|
27
38
|
* 读取 JSON 文件
|
|
28
39
|
* @returns 文件内容,如果文件不存在则返回 null;其他错误会抛出异常
|
|
@@ -2,12 +2,15 @@ import type { GlobalState, Task, Approval, ApprovalStatus } from '../types/index
|
|
|
2
2
|
export declare class StateManager {
|
|
3
3
|
private store;
|
|
4
4
|
private stateCache;
|
|
5
|
-
private
|
|
5
|
+
private lockDepth;
|
|
6
6
|
constructor(basePath: string);
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
8
|
+
* 跨进程文件锁 — 防止多个 openmatrix CLI 进程同时读写 state.json
|
|
9
|
+
*
|
|
10
|
+
* 使用 O_EXCL | O_CREAT 原子创建锁文件,Windows/Linux/macOS 均支持
|
|
11
|
+
* 支持可重入:同进程内嵌套调用(如 updateTask → updateTaskStatistics)直接执行
|
|
9
12
|
*/
|
|
10
|
-
private
|
|
13
|
+
private withFileLock;
|
|
11
14
|
initialize(): Promise<void>;
|
|
12
15
|
getState(): Promise<GlobalState>;
|
|
13
16
|
updateState(updates: Partial<GlobalState>): Promise<void>;
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.StateManager = void 0;
|
|
4
4
|
const file_store_js_1 = require("./file-store.js");
|
|
5
|
+
const promises_1 = require("fs/promises");
|
|
6
|
+
const path_1 = require("path");
|
|
5
7
|
const DEFAULT_CONFIG = {
|
|
6
8
|
timeout: 120,
|
|
7
9
|
maxRetries: 3,
|
|
@@ -12,23 +14,44 @@ const DEFAULT_CONFIG = {
|
|
|
12
14
|
class StateManager {
|
|
13
15
|
store;
|
|
14
16
|
stateCache = null;
|
|
15
|
-
|
|
17
|
+
lockDepth = 0; // 可重入:同一进程内嵌套调用不阻塞
|
|
16
18
|
constructor(basePath) {
|
|
17
19
|
this.store = new file_store_js_1.FileStore(basePath);
|
|
18
20
|
}
|
|
19
21
|
/**
|
|
20
|
-
*
|
|
22
|
+
* 跨进程文件锁 — 防止多个 openmatrix CLI 进程同时读写 state.json
|
|
23
|
+
*
|
|
24
|
+
* 使用 O_EXCL | O_CREAT 原子创建锁文件,Windows/Linux/macOS 均支持
|
|
25
|
+
* 支持可重入:同进程内嵌套调用(如 updateTask → updateTaskStatistics)直接执行
|
|
21
26
|
*/
|
|
22
|
-
async
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
async withFileLock(fn) {
|
|
28
|
+
// 可重入:同一进程内嵌套调用直接执行
|
|
29
|
+
if (this.lockDepth > 0) {
|
|
30
|
+
return fn();
|
|
31
|
+
}
|
|
32
|
+
const lockPath = (0, path_1.join)(this.store.getBasePath(), '.lock');
|
|
33
|
+
const maxRetries = 50;
|
|
34
|
+
const retryDelay = 100;
|
|
35
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
36
|
+
try {
|
|
37
|
+
const fd = await (0, promises_1.open)(lockPath, 'wx');
|
|
38
|
+
await fd.write(`${process.pid}\n`);
|
|
39
|
+
await fd.close();
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
if (i === maxRetries - 1)
|
|
44
|
+
throw new Error('Cannot acquire state lock');
|
|
45
|
+
await new Promise(r => setTimeout(r, retryDelay));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
this.lockDepth++;
|
|
27
49
|
try {
|
|
28
50
|
return await fn();
|
|
29
51
|
}
|
|
30
52
|
finally {
|
|
31
|
-
|
|
53
|
+
this.lockDepth--;
|
|
54
|
+
await (0, promises_1.unlink)(lockPath).catch(() => { });
|
|
32
55
|
}
|
|
33
56
|
}
|
|
34
57
|
async initialize() {
|
|
@@ -83,7 +106,7 @@ class StateManager {
|
|
|
83
106
|
return this.stateCache;
|
|
84
107
|
}
|
|
85
108
|
async updateState(updates) {
|
|
86
|
-
await this.
|
|
109
|
+
await this.withFileLock(async () => {
|
|
87
110
|
const state = await this.getState();
|
|
88
111
|
// 确保 statistics 存在(兼容旧版本)
|
|
89
112
|
if (!state.statistics) {
|
|
@@ -112,7 +135,7 @@ class StateManager {
|
|
|
112
135
|
});
|
|
113
136
|
}
|
|
114
137
|
async createTask(input) {
|
|
115
|
-
return this.
|
|
138
|
+
return this.withFileLock(async () => {
|
|
116
139
|
const taskId = this.generateTaskId();
|
|
117
140
|
const now = new Date().toISOString();
|
|
118
141
|
const task = {
|
|
@@ -161,7 +184,7 @@ class StateManager {
|
|
|
161
184
|
return task;
|
|
162
185
|
}
|
|
163
186
|
async updateTask(taskId, updates) {
|
|
164
|
-
await this.
|
|
187
|
+
await this.withFileLock(async () => {
|
|
165
188
|
const task = await this.getTask(taskId);
|
|
166
189
|
if (!task)
|
|
167
190
|
throw new Error(`Task ${taskId} not found`);
|
|
@@ -234,67 +257,69 @@ class StateManager {
|
|
|
234
257
|
return await this.store.readMarkdown(`tasks/${taskId}/context.md`);
|
|
235
258
|
}
|
|
236
259
|
async updateTaskStatistics(oldStatus, newStatus) {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
260
|
+
await this.withFileLock(async () => {
|
|
261
|
+
// 从文件重新读取最新状态(不用缓存,避免 stale)
|
|
262
|
+
const state = await this.store.readJson('state.json') ?? await this.getState();
|
|
263
|
+
const stats = { ...state.statistics };
|
|
264
|
+
// 扩展统计字段(如果不存在则初始化)
|
|
265
|
+
if (!('scheduled' in stats))
|
|
266
|
+
stats.scheduled = 0;
|
|
267
|
+
if (!('blocked' in stats))
|
|
268
|
+
stats.blocked = 0;
|
|
269
|
+
if (!('waiting' in stats))
|
|
270
|
+
stats.waiting = 0;
|
|
271
|
+
if (!('verify' in stats))
|
|
272
|
+
stats.verify = 0;
|
|
273
|
+
if (!('accept' in stats))
|
|
274
|
+
stats.accept = 0;
|
|
275
|
+
if (!('retry_queue' in stats))
|
|
276
|
+
stats.retry_queue = 0;
|
|
277
|
+
// Decrement old status count
|
|
278
|
+
if (oldStatus === 'pending')
|
|
279
|
+
stats.pending--;
|
|
280
|
+
else if (oldStatus === 'scheduled')
|
|
281
|
+
stats.scheduled--;
|
|
282
|
+
else if (oldStatus === 'in_progress')
|
|
283
|
+
stats.inProgress--;
|
|
284
|
+
else if (oldStatus === 'blocked')
|
|
285
|
+
stats.blocked--;
|
|
286
|
+
else if (oldStatus === 'waiting')
|
|
287
|
+
stats.waiting--;
|
|
288
|
+
else if (oldStatus === 'verify')
|
|
289
|
+
stats.verify--;
|
|
290
|
+
else if (oldStatus === 'accept')
|
|
291
|
+
stats.accept--;
|
|
292
|
+
else if (oldStatus === 'completed')
|
|
293
|
+
stats.completed--;
|
|
294
|
+
else if (oldStatus === 'failed')
|
|
295
|
+
stats.failed--;
|
|
296
|
+
else if (oldStatus === 'retry_queue')
|
|
297
|
+
stats.retry_queue--;
|
|
298
|
+
// Increment new status count
|
|
299
|
+
if (newStatus === 'pending')
|
|
300
|
+
stats.pending++;
|
|
301
|
+
else if (newStatus === 'scheduled')
|
|
302
|
+
stats.scheduled++;
|
|
303
|
+
else if (newStatus === 'in_progress')
|
|
304
|
+
stats.inProgress++;
|
|
305
|
+
else if (newStatus === 'blocked')
|
|
306
|
+
stats.blocked++;
|
|
307
|
+
else if (newStatus === 'waiting')
|
|
308
|
+
stats.waiting++;
|
|
309
|
+
else if (newStatus === 'verify')
|
|
310
|
+
stats.verify++;
|
|
311
|
+
else if (newStatus === 'accept')
|
|
312
|
+
stats.accept++;
|
|
313
|
+
else if (newStatus === 'completed')
|
|
314
|
+
stats.completed++;
|
|
315
|
+
else if (newStatus === 'failed')
|
|
316
|
+
stats.failed++;
|
|
317
|
+
else if (newStatus === 'retry_queue')
|
|
318
|
+
stats.retry_queue++;
|
|
319
|
+
const newState = { ...state, statistics: stats };
|
|
320
|
+
await this.store.writeJson('state.json', newState);
|
|
321
|
+
this.stateCache = newState;
|
|
322
|
+
});
|
|
298
323
|
}
|
|
299
324
|
generateRunId() {
|
|
300
325
|
const date = new Date().toISOString().slice(0, 10).replace(/-/g, '');
|