openmatrix 0.2.25 → 0.2.26
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 +155 -595
- package/dist/agents/agent-runner.d.ts +5 -1
- package/dist/agents/agent-runner.js +53 -3
- package/dist/cli/commands/complete.js +13 -1
- package/dist/cli/commands/start.js +4 -1
- package/dist/storage/file-store.d.ts +8 -0
- package/dist/storage/file-store.js +30 -0
- package/dist/storage/state-manager.d.ts +7 -0
- package/dist/storage/state-manager.js +101 -46
- package/package.json +1 -1
- package/skills/om.md +3 -3
- package/skills/start.md +103 -34
|
@@ -72,9 +72,13 @@ export declare class AgentRunner {
|
|
|
72
72
|
*/
|
|
73
73
|
private buildAmbiguityDetectionInstruction;
|
|
74
74
|
/**
|
|
75
|
-
* 构建累积上下文 -
|
|
75
|
+
* 构建累积上下文 - 从当前运行的 context.md 读取前序 Agent 的决策和知识
|
|
76
76
|
*/
|
|
77
77
|
private buildAccumulatedContext;
|
|
78
|
+
/**
|
|
79
|
+
* 构建测试项目上下文 — 注入原始扫描数据供 AI 自行分析
|
|
80
|
+
*/
|
|
81
|
+
private buildTestContext;
|
|
78
82
|
/**
|
|
79
83
|
* 构建阶段上下文
|
|
80
84
|
*/
|
|
@@ -137,6 +137,9 @@ class AgentRunner {
|
|
|
137
137
|
const phaseContext = this.buildPhaseContext(task);
|
|
138
138
|
const accumulatedContext = await this.buildAccumulatedContext(task);
|
|
139
139
|
const ambiguityInstruction = this.buildAmbiguityDetectionInstruction(task);
|
|
140
|
+
const testContext = task.assignedAgent === 'tester' ? await this.buildTestContext() : '';
|
|
141
|
+
const state = await this.stateManager.getState();
|
|
142
|
+
const runId = state.runId;
|
|
140
143
|
return `# 任务执行
|
|
141
144
|
|
|
142
145
|
${ambiguityInstruction}
|
|
@@ -148,6 +151,15 @@ ${ambiguityInstruction}
|
|
|
148
151
|
- 优先级: ${task.priority}
|
|
149
152
|
- 超时: ${task.timeout / 1000} 秒
|
|
150
153
|
|
|
154
|
+
## 工作目录隔离
|
|
155
|
+
|
|
156
|
+
本次运行 ID: ${runId}
|
|
157
|
+
所有中间产物必须写入运行专属目录: \`.openmatrix/${runId}/tasks/${task.id}/artifacts/\`
|
|
158
|
+
- 中间产物: \`.openmatrix/${runId}/tasks/${task.id}/artifacts/\`
|
|
159
|
+
- 结果报告: \`.openmatrix/${runId}/tasks/${task.id}/artifacts/result.md\`
|
|
160
|
+
|
|
161
|
+
**注意**: 业务代码(src/、tests/ 等)写入项目正常目录,仅中间产物和报告写入运行目录。
|
|
162
|
+
|
|
151
163
|
## 当前阶段
|
|
152
164
|
${phaseContext}
|
|
153
165
|
|
|
@@ -157,6 +169,7 @@ ${task.dependencies.length > 0
|
|
|
157
169
|
: '无依赖'}
|
|
158
170
|
|
|
159
171
|
${accumulatedContext}
|
|
172
|
+
${testContext}
|
|
160
173
|
|
|
161
174
|
---
|
|
162
175
|
|
|
@@ -168,7 +181,7 @@ ${agentPrompt.instructions}
|
|
|
168
181
|
|
|
169
182
|
## 完成要求
|
|
170
183
|
|
|
171
|
-
1. 将执行结果写入: \`.openmatrix/tasks/${task.id}/artifacts/result.md\`
|
|
184
|
+
1. 将执行结果写入: \`.openmatrix/${runId}/tasks/${task.id}/artifacts/result.md\`
|
|
172
185
|
(任务状态由 openmatrix complete 命令管理,请勿直接修改 task.json)
|
|
173
186
|
2. 如需审批,创建审批请求: \`.openmatrix/approvals/\` 目录
|
|
174
187
|
|
|
@@ -260,11 +273,21 @@ ${agentPrompt.instructions}
|
|
|
260
273
|
**重要**: 如果无法解析歧义检测结果,视为无歧义继续执行。`;
|
|
261
274
|
}
|
|
262
275
|
/**
|
|
263
|
-
* 构建累积上下文 -
|
|
276
|
+
* 构建累积上下文 - 从当前运行的 context.md 读取前序 Agent 的决策和知识
|
|
264
277
|
*/
|
|
265
278
|
async buildAccumulatedContext(currentTask) {
|
|
266
279
|
const omPath = path.join(process.cwd(), '.openmatrix');
|
|
267
|
-
|
|
280
|
+
// 读取 current.json 获取当前 runId
|
|
281
|
+
let contextFile = path.join(omPath, 'context.md');
|
|
282
|
+
try {
|
|
283
|
+
const currentJson = await fs.readFile(path.join(omPath, 'current.json'), 'utf-8');
|
|
284
|
+
const { runId } = JSON.parse(currentJson);
|
|
285
|
+
if (runId)
|
|
286
|
+
contextFile = path.join(omPath, runId, 'context.md');
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
// fallback to root context.md
|
|
290
|
+
}
|
|
268
291
|
try {
|
|
269
292
|
const content = await fs.readFile(contextFile, 'utf-8');
|
|
270
293
|
const trimmed = content.trim();
|
|
@@ -277,6 +300,33 @@ ${agentPrompt.instructions}
|
|
|
277
300
|
你应该基于这些信息来工作,避免重复犯错或与已有决策冲突。
|
|
278
301
|
|
|
279
302
|
${trimmed}
|
|
303
|
+
`;
|
|
304
|
+
}
|
|
305
|
+
catch {
|
|
306
|
+
return '';
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* 构建测试项目上下文 — 注入原始扫描数据供 AI 自行分析
|
|
311
|
+
*/
|
|
312
|
+
async buildTestContext() {
|
|
313
|
+
try {
|
|
314
|
+
const { performFullScan } = await import('../test/context-analyzer.js');
|
|
315
|
+
const scanResult = performFullScan(process.cwd());
|
|
316
|
+
return `
|
|
317
|
+
## 项目测试扫描数据(原始数据,由 AI 自行分析决策)
|
|
318
|
+
|
|
319
|
+
\`\`\`json
|
|
320
|
+
${JSON.stringify({
|
|
321
|
+
frameworks: scanResult.frameworks,
|
|
322
|
+
testStyle: scanResult.testStyle,
|
|
323
|
+
existingTests: scanResult.existingTests.slice(0, 10),
|
|
324
|
+
projectType: scanResult.projectType,
|
|
325
|
+
isFrontend: scanResult.isFrontend,
|
|
326
|
+
hasUIComponents: scanResult.hasUIComponents,
|
|
327
|
+
summary: scanResult.summary
|
|
328
|
+
}, null, 2)}
|
|
329
|
+
\`\`\`
|
|
280
330
|
`;
|
|
281
331
|
}
|
|
282
332
|
catch {
|
|
@@ -106,7 +106,18 @@ exports.completeCommand = new commander_1.Command('complete')
|
|
|
106
106
|
});
|
|
107
107
|
// 5. 追加写入全局 context.md (Agent Memory)
|
|
108
108
|
if (isSuccess) {
|
|
109
|
-
|
|
109
|
+
// 读取 current.json 获取当前 runId,context.md 写入运行目录
|
|
110
|
+
let contextDir = omPath;
|
|
111
|
+
try {
|
|
112
|
+
const currentJson = await fs.readFile(path.join(omPath, 'current.json'), 'utf-8');
|
|
113
|
+
const { runId } = JSON.parse(currentJson);
|
|
114
|
+
if (runId)
|
|
115
|
+
contextDir = path.join(omPath, runId);
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
// fallback to omPath
|
|
119
|
+
}
|
|
120
|
+
const contextFile = path.join(contextDir, 'context.md');
|
|
110
121
|
const timestamp = new Date().toISOString();
|
|
111
122
|
// 构建上下文内容
|
|
112
123
|
const summary = options.summary || '任务已完成';
|
|
@@ -116,6 +127,7 @@ exports.completeCommand = new commander_1.Command('complete')
|
|
|
116
127
|
|
|
117
128
|
`;
|
|
118
129
|
try {
|
|
130
|
+
await fs.mkdir(contextDir, { recursive: true });
|
|
119
131
|
// 原子追加写入全局 context.md(O_APPEND flag 保证并发安全)
|
|
120
132
|
await fs.appendFile(contextFile, contextEntry, 'utf-8');
|
|
121
133
|
}
|
|
@@ -66,7 +66,6 @@ exports.startCommand = new commander_1.Command('start')
|
|
|
66
66
|
const omPath = path.join(basePath, '.openmatrix');
|
|
67
67
|
// 确保目录存在
|
|
68
68
|
await fs.mkdir(omPath, { recursive: true });
|
|
69
|
-
await fs.mkdir(path.join(omPath, 'tasks'), { recursive: true });
|
|
70
69
|
await fs.mkdir(path.join(omPath, 'approvals'), { recursive: true });
|
|
71
70
|
// 确保 .openmatrix 被 git 忽略
|
|
72
71
|
await (0, gitignore_js_1.ensureOpenmatrixGitignore)(basePath);
|
|
@@ -88,6 +87,10 @@ exports.startCommand = new commander_1.Command('start')
|
|
|
88
87
|
const stateManager = new state_manager_js_1.StateManager(omPath);
|
|
89
88
|
await stateManager.initialize();
|
|
90
89
|
const state = await stateManager.getState();
|
|
90
|
+
// 如果上次运行已结束(completed/failed),自动清理旧数据开始新运行
|
|
91
|
+
if (state.status === 'completed' || state.status === 'failed') {
|
|
92
|
+
await stateManager.reset();
|
|
93
|
+
}
|
|
91
94
|
// 检查是否已有运行中的任务
|
|
92
95
|
if (state.status === 'running') {
|
|
93
96
|
if (options.json) {
|
|
@@ -22,6 +22,14 @@ export declare class FileStore {
|
|
|
22
22
|
exists(path: string): Promise<boolean>;
|
|
23
23
|
listFiles(dir: string): Promise<string[]>;
|
|
24
24
|
listDirs(dir: string): Promise<string[]>;
|
|
25
|
+
/**
|
|
26
|
+
* 递归删除目录
|
|
27
|
+
*/
|
|
28
|
+
removeDir(dir: string): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* 删除文件
|
|
31
|
+
*/
|
|
32
|
+
removeFile(filePath: string): Promise<void>;
|
|
25
33
|
/**
|
|
26
34
|
* 同步写入 JSON 文件
|
|
27
35
|
*/
|
|
@@ -108,6 +108,36 @@ class FileStore {
|
|
|
108
108
|
return [];
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* 递归删除目录
|
|
113
|
+
*/
|
|
114
|
+
async removeDir(dir) {
|
|
115
|
+
const fullPath = (0, path_1.join)(this.basePath, dir);
|
|
116
|
+
try {
|
|
117
|
+
await (0, promises_1.rm)(fullPath, { recursive: true, force: true });
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
const nodeError = error;
|
|
121
|
+
if (nodeError.code !== 'ENOENT') {
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* 删除文件
|
|
128
|
+
*/
|
|
129
|
+
async removeFile(filePath) {
|
|
130
|
+
const fullPath = (0, path_1.join)(this.basePath, filePath);
|
|
131
|
+
try {
|
|
132
|
+
await (0, promises_1.unlink)(fullPath);
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
const nodeError = error;
|
|
136
|
+
if (nodeError.code !== 'ENOENT') {
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
111
141
|
// ============ 同步方法(用于构造器等场景)============
|
|
112
142
|
/**
|
|
113
143
|
* 同步写入 JSON 文件
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { GlobalState, Task, Approval, ApprovalStatus, Meeting, MeetingStatus } from '../types/index.js';
|
|
2
2
|
export declare class StateManager {
|
|
3
|
+
private rootStore;
|
|
3
4
|
private store;
|
|
5
|
+
private basePath;
|
|
4
6
|
private stateCache;
|
|
5
7
|
private lockDepth;
|
|
6
8
|
constructor(basePath: string);
|
|
@@ -18,6 +20,11 @@ export declare class StateManager {
|
|
|
18
20
|
*/
|
|
19
21
|
private tryCleanStaleLock;
|
|
20
22
|
initialize(): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* 重置状态 — 清理旧运行数据,为新运行做准备
|
|
25
|
+
* 删除旧 runId 目录,生成新 runId,更新 current.json
|
|
26
|
+
*/
|
|
27
|
+
reset(): Promise<void>;
|
|
21
28
|
getState(): Promise<GlobalState>;
|
|
22
29
|
updateState(updates: Partial<GlobalState>): Promise<void>;
|
|
23
30
|
createTask(input: {
|
|
@@ -13,11 +13,16 @@ const DEFAULT_CONFIG = {
|
|
|
13
13
|
model: 'claude-sonnet-4-6'
|
|
14
14
|
};
|
|
15
15
|
class StateManager {
|
|
16
|
+
rootStore;
|
|
16
17
|
store;
|
|
18
|
+
basePath;
|
|
17
19
|
stateCache = null;
|
|
18
|
-
lockDepth = 0;
|
|
20
|
+
lockDepth = 0;
|
|
19
21
|
constructor(basePath) {
|
|
20
|
-
this.
|
|
22
|
+
this.basePath = basePath;
|
|
23
|
+
this.rootStore = new file_store_js_1.FileStore(basePath);
|
|
24
|
+
// store will be set in initialize() or reset() to point to {basePath}/{runId}/
|
|
25
|
+
this.store = this.rootStore;
|
|
21
26
|
}
|
|
22
27
|
/**
|
|
23
28
|
* 跨进程文件锁 — 防止多个 openmatrix CLI 进程同时读写 state.json
|
|
@@ -84,49 +89,101 @@ class StateManager {
|
|
|
84
89
|
}
|
|
85
90
|
}
|
|
86
91
|
async initialize() {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
await this.store.writeJson('state.json', initialState);
|
|
111
|
-
this.stateCache = initialState;
|
|
92
|
+
// 读取根索引,确定当前 runId
|
|
93
|
+
const current = await this.rootStore.readJson('current.json');
|
|
94
|
+
if (current?.runId) {
|
|
95
|
+
// 切换 store 到当前运行目录
|
|
96
|
+
this.store = new file_store_js_1.FileStore((0, path_1.join)(this.basePath, current.runId));
|
|
97
|
+
const existing = await this.store.readJson('state.json');
|
|
98
|
+
if (existing) {
|
|
99
|
+
existing.statistics = {
|
|
100
|
+
totalTasks: existing.statistics?.totalTasks ?? 0,
|
|
101
|
+
completed: existing.statistics?.completed ?? 0,
|
|
102
|
+
inProgress: existing.statistics?.inProgress ?? 0,
|
|
103
|
+
failed: existing.statistics?.failed ?? 0,
|
|
104
|
+
pending: existing.statistics?.pending ?? 0,
|
|
105
|
+
scheduled: existing.statistics?.scheduled ?? 0,
|
|
106
|
+
blocked: existing.statistics?.blocked ?? 0,
|
|
107
|
+
waiting: existing.statistics?.waiting ?? 0,
|
|
108
|
+
verify: existing.statistics?.verify ?? 0,
|
|
109
|
+
accept: existing.statistics?.accept ?? 0,
|
|
110
|
+
retry_queue: existing.statistics?.retry_queue ?? 0
|
|
111
|
+
};
|
|
112
|
+
this.stateCache = existing;
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
112
115
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
116
|
+
// 没有 current.json 或 state.json 不存在 — 创建新运行
|
|
117
|
+
const runId = this.generateRunId();
|
|
118
|
+
this.store = new file_store_js_1.FileStore((0, path_1.join)(this.basePath, runId));
|
|
119
|
+
await this.rootStore.writeJson('current.json', { runId });
|
|
120
|
+
const initialState = {
|
|
121
|
+
version: '1.0',
|
|
122
|
+
runId,
|
|
123
|
+
status: 'initialized',
|
|
124
|
+
currentPhase: 'planning',
|
|
125
|
+
startedAt: new Date().toISOString(),
|
|
126
|
+
config: DEFAULT_CONFIG,
|
|
127
|
+
statistics: {
|
|
128
|
+
totalTasks: 0,
|
|
129
|
+
completed: 0,
|
|
130
|
+
inProgress: 0,
|
|
131
|
+
failed: 0,
|
|
132
|
+
pending: 0,
|
|
133
|
+
scheduled: 0,
|
|
134
|
+
blocked: 0,
|
|
135
|
+
waiting: 0,
|
|
136
|
+
verify: 0,
|
|
137
|
+
accept: 0,
|
|
138
|
+
retry_queue: 0
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
await this.store.writeJson('state.json', initialState);
|
|
142
|
+
this.stateCache = initialState;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* 重置状态 — 清理旧运行数据,为新运行做准备
|
|
146
|
+
* 删除旧 runId 目录,生成新 runId,更新 current.json
|
|
147
|
+
*/
|
|
148
|
+
async reset() {
|
|
149
|
+
// 清理旧 runId 目录
|
|
150
|
+
const oldRunId = this.stateCache?.runId;
|
|
151
|
+
if (oldRunId) {
|
|
152
|
+
await this.rootStore.removeDir(oldRunId);
|
|
129
153
|
}
|
|
154
|
+
// 兼容旧版本的目录
|
|
155
|
+
await this.rootStore.removeDir('tasks');
|
|
156
|
+
await this.rootStore.removeDir('approvals');
|
|
157
|
+
await this.rootStore.removeDir('meetings');
|
|
158
|
+
await this.rootStore.removeDir('runs');
|
|
159
|
+
await this.rootStore.removeFile('context.md');
|
|
160
|
+
// 创建新运行
|
|
161
|
+
const runId = this.generateRunId();
|
|
162
|
+
this.store = new file_store_js_1.FileStore((0, path_1.join)(this.basePath, runId));
|
|
163
|
+
await this.rootStore.writeJson('current.json', { runId });
|
|
164
|
+
const freshState = {
|
|
165
|
+
version: '1.0',
|
|
166
|
+
runId,
|
|
167
|
+
status: 'initialized',
|
|
168
|
+
currentPhase: 'planning',
|
|
169
|
+
startedAt: new Date().toISOString(),
|
|
170
|
+
config: DEFAULT_CONFIG,
|
|
171
|
+
statistics: {
|
|
172
|
+
totalTasks: 0,
|
|
173
|
+
completed: 0,
|
|
174
|
+
inProgress: 0,
|
|
175
|
+
failed: 0,
|
|
176
|
+
pending: 0,
|
|
177
|
+
scheduled: 0,
|
|
178
|
+
blocked: 0,
|
|
179
|
+
waiting: 0,
|
|
180
|
+
verify: 0,
|
|
181
|
+
accept: 0,
|
|
182
|
+
retry_queue: 0
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
await this.store.writeJson('state.json', freshState);
|
|
186
|
+
this.stateCache = freshState;
|
|
130
187
|
}
|
|
131
188
|
async getState() {
|
|
132
189
|
if (!this.stateCache) {
|
|
@@ -207,7 +264,6 @@ class StateManager {
|
|
|
207
264
|
});
|
|
208
265
|
}
|
|
209
266
|
async getTask(taskId) {
|
|
210
|
-
// Try subdirectory structure first, fall back to flat file
|
|
211
267
|
let task = await this.store.readJson(`tasks/${taskId}/task.json`);
|
|
212
268
|
if (!task) {
|
|
213
269
|
task = await this.store.readJson(`tasks/${taskId}.json`);
|
|
@@ -225,7 +281,7 @@ class StateManager {
|
|
|
225
281
|
...updates,
|
|
226
282
|
updatedAt: new Date().toISOString()
|
|
227
283
|
};
|
|
228
|
-
// Always write to
|
|
284
|
+
// Always write to current run directory
|
|
229
285
|
await this.store.writeJson(`tasks/${taskId}/task.json`, updatedTask);
|
|
230
286
|
// Update statistics if status changed
|
|
231
287
|
if (updates.status && updates.status !== oldStatus) {
|
|
@@ -249,7 +305,6 @@ class StateManager {
|
|
|
249
305
|
continue;
|
|
250
306
|
const task = await this.store.readJson(`tasks/${file}`);
|
|
251
307
|
if (task) {
|
|
252
|
-
// Avoid duplicate if already found in subdirectory
|
|
253
308
|
if (!tasks.some(t => t.id === task.id)) {
|
|
254
309
|
tasks.push(task);
|
|
255
310
|
}
|
package/package.json
CHANGED
package/skills/om.md
CHANGED
|
@@ -154,9 +154,9 @@ Examples:
|
|
|
154
154
|
/om 从零搭建后台系统 → AI 推荐设计流程
|
|
155
155
|
|
|
156
156
|
Quality:
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
157
|
+
严格模式 - TDD + 80% coverage
|
|
158
|
+
平衡模式 - 60% coverage
|
|
159
|
+
快速模式 - No gates
|
|
160
160
|
|
|
161
161
|
Commands:
|
|
162
162
|
/om:status - 查看执行状态
|