openteam 1.0.7 → 1.0.9
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/examples/dev-team/readme.md +6 -28
- package/package.json +1 -1
- package/src/adapters/base.js +9 -0
- package/src/adapters/claude-code.js +4 -0
- package/src/adapters/opencode.js +5 -0
- package/src/foundation/logger.js +7 -22
- package/src/interfaces/daemon/index.js +0 -3
- package/src/wrapper/index.js +36 -17
|
@@ -32,45 +32,23 @@ Each role has skills that guide its workflow stages:
|
|
|
32
32
|
|
|
33
33
|
- **PM**: `requirement-clarification`, `prd-generation`, `system-discovery`
|
|
34
34
|
- **Architect**: `codebase-mapping`, `implementation-planning`, `architecture-review`
|
|
35
|
+
- **Developer**: `incremental-implementation`, `self-verification`
|
|
35
36
|
- **QA**: `test-plan-design`, `acceptance-testing`, `bug-reporting`
|
|
36
37
|
|
|
37
38
|
## Deployment
|
|
38
39
|
|
|
39
|
-
### 1)
|
|
40
|
+
### 1) Install the team
|
|
40
41
|
|
|
41
42
|
```bash
|
|
42
|
-
|
|
43
|
-
mkdir -p ~/.opencode/agents/<team-name>
|
|
44
|
-
|
|
45
|
-
# Copy team config (edit "name" field to match your team name)
|
|
46
|
-
cp team.json ~/.opencode/agents/<team-name>/
|
|
47
|
-
|
|
48
|
-
# Copy agent prompts into the team directory
|
|
49
|
-
cp pm.md architect.md developer.md qa.md ~/.opencode/agents/<team-name>/
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
Agent prompts live in the team directory alongside `team.json`.
|
|
53
|
-
|
|
54
|
-
### 2) Install skills
|
|
55
|
-
|
|
56
|
-
```bash
|
|
57
|
-
cp -r skills/* ~/.opencode/skills/
|
|
43
|
+
openteam setup dev-team
|
|
58
44
|
```
|
|
59
45
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
Add to `~/.opencode/opencode.json`:
|
|
63
|
-
|
|
64
|
-
```json
|
|
65
|
-
{
|
|
66
|
-
"plugin": ["openteam"]
|
|
67
|
-
}
|
|
68
|
-
```
|
|
46
|
+
This copies agent prompts to `~/.openteam/agents/`, skills to `~/.openteam/skills/`, and team config to `~/.openteam/teams/<team-name>/team.json`.
|
|
69
47
|
|
|
70
|
-
###
|
|
48
|
+
### 2) Start the team
|
|
71
49
|
|
|
72
50
|
```bash
|
|
73
51
|
openteam start <team-name>
|
|
74
52
|
```
|
|
75
53
|
|
|
76
|
-
This launches the daemon,
|
|
54
|
+
This launches the daemon, server, and agent panes in a tmux/zellij session.
|
package/package.json
CHANGED
package/src/adapters/base.js
CHANGED
|
@@ -42,6 +42,15 @@ export class BaseAdapter {
|
|
|
42
42
|
throw new Error('subclass must implement buildMcpConfig');
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* 返回 CLI 特定的工具调用指引,追加到系统提示词中。
|
|
47
|
+
* 不同 CLI 对 MCP 工具有不同的命名/调用约定,需要显式告知 agent。
|
|
48
|
+
* @returns {string} 工具调用指引文本
|
|
49
|
+
*/
|
|
50
|
+
buildToolGuide() {
|
|
51
|
+
throw new Error('subclass must implement buildToolGuide');
|
|
52
|
+
}
|
|
53
|
+
|
|
45
54
|
/**
|
|
46
55
|
* MCP 配置文件路径(每个 agent 独立,避免多 agent 覆盖冲突)
|
|
47
56
|
* @param {string} projectDir - 项目目录
|
|
@@ -45,6 +45,10 @@ export class ClaudeCodeAdapter extends BaseAdapter {
|
|
|
45
45
|
};
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
buildToolGuide() {
|
|
49
|
+
return 'Use the openteam MCP server tools: msg (messaging) and taskboard (task management).';
|
|
50
|
+
}
|
|
51
|
+
|
|
48
52
|
getMcpConfigPath(projectDir, agent) {
|
|
49
53
|
// 用临时目录避免污染项目的 .mcp.json(可能有用户自己的 MCP 配置)
|
|
50
54
|
// 每个 agent 独立文件,避免多 agent 覆盖冲突
|
package/src/adapters/opencode.js
CHANGED
|
@@ -30,6 +30,11 @@ export class OpenCodeAdapter extends BaseAdapter {
|
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
buildToolGuide() {
|
|
34
|
+
// TODO: opencode 的 MCP 工具命名规则待确认
|
|
35
|
+
return 'Use the openteam MCP server tools: msg (messaging) and taskboard (task management).';
|
|
36
|
+
}
|
|
37
|
+
|
|
33
38
|
getMcpConfigPath(projectDir, agent) {
|
|
34
39
|
// TODO: opencode 的 MCP 配置路径待确认
|
|
35
40
|
return path.join(os.tmpdir(), `openteam-mcp-opencode-${agent}-${encodeURIComponent(path.basename(projectDir))}.json`);
|
package/src/foundation/logger.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 日志系统
|
|
3
3
|
*
|
|
4
|
-
* error
|
|
5
|
-
* debug
|
|
6
|
-
*
|
|
7
|
-
* OPENTEAM_LOG_LEVEL=debug|info|warn|error
|
|
4
|
+
* info/warn/error 默认写入日志文件,无需配置。
|
|
5
|
+
* debug 需要启用:
|
|
6
|
+
* OPENTEAM_DEBUG=1 启用 debug 级别日志
|
|
8
7
|
* OPENTEAM_LOG_STDERR=1 调试时镜像到 stderr(便于 daemon/serve 捕获)
|
|
9
8
|
*
|
|
10
9
|
* 日志文件: ~/.openteam/openteam.log
|
|
@@ -14,29 +13,16 @@ import fs from 'fs';
|
|
|
14
13
|
import path from 'path';
|
|
15
14
|
import { PATHS } from './constants.js';
|
|
16
15
|
|
|
17
|
-
const LOG_LEVELS = {
|
|
18
|
-
debug: 0,
|
|
19
|
-
info: 1,
|
|
20
|
-
warn: 2,
|
|
21
|
-
error: 3,
|
|
22
|
-
};
|
|
23
|
-
|
|
24
16
|
// 延迟求值:首次写日志时才读配置,避免循环依赖
|
|
25
17
|
let resolved = false;
|
|
26
|
-
let
|
|
27
|
-
let minLevel = LOG_LEVELS.info;
|
|
18
|
+
let debugEnabled = false;
|
|
28
19
|
let mirrorToStderr = false;
|
|
29
20
|
|
|
30
21
|
function resolve() {
|
|
31
22
|
if (resolved) return;
|
|
32
23
|
resolved = true;
|
|
33
24
|
|
|
34
|
-
|
|
35
|
-
const envLevel = process.env.OPENTEAM_LOG_LEVEL;
|
|
36
|
-
|
|
37
|
-
isEnabled = !!envLog && envLog !== '';
|
|
38
|
-
const levelStr = envLevel || 'info';
|
|
39
|
-
minLevel = LOG_LEVELS[levelStr] ?? LOG_LEVELS.info;
|
|
25
|
+
debugEnabled = process.env.OPENTEAM_DEBUG === '1';
|
|
40
26
|
mirrorToStderr = process.env.OPENTEAM_LOG_STDERR === '1';
|
|
41
27
|
}
|
|
42
28
|
|
|
@@ -71,9 +57,8 @@ function writeToFile(formatted) {
|
|
|
71
57
|
|
|
72
58
|
function log(level, module, message, data = null) {
|
|
73
59
|
resolve();
|
|
74
|
-
//
|
|
75
|
-
if (
|
|
76
|
-
if (LOG_LEVELS[level] < minLevel) return;
|
|
60
|
+
// debug 需要 OPENTEAM_DEBUG=1,其余默认写入
|
|
61
|
+
if (level === 'debug' && !debugEnabled) return;
|
|
77
62
|
|
|
78
63
|
const formatted = formatMessage(level, module, message, data);
|
|
79
64
|
writeToFile(formatted);
|
|
@@ -26,9 +26,6 @@ const HEALTH_CHECK_INTERVAL = 10000;
|
|
|
26
26
|
export async function runDaemon(teamName, projectDir, options = {}) {
|
|
27
27
|
// 标记 daemon 进程,logger 据此跳过 console.error(避免破坏 blessed TUI)
|
|
28
28
|
process.env.OPENTEAM_DAEMON = '1';
|
|
29
|
-
// daemon 及其子进程(wrapper)默认启用日志
|
|
30
|
-
if (!process.env.OPENTEAM_LOG) process.env.OPENTEAM_LOG = '1';
|
|
31
|
-
|
|
32
29
|
// ── 校验 ──
|
|
33
30
|
const validation = validateTeamConfig(teamName);
|
|
34
31
|
if (!validation.valid) {
|
package/src/wrapper/index.js
CHANGED
|
@@ -63,7 +63,7 @@ async function main() {
|
|
|
63
63
|
const mcpConfigPath = writeMcpConfig(adapter, serverUrl, agent, projectDir);
|
|
64
64
|
|
|
65
65
|
// ── 3. 构建系统提示词 + 注入策略 ──
|
|
66
|
-
const systemPrompt = buildSystemPrompt(agent, team, agents, cliType);
|
|
66
|
+
const systemPrompt = buildSystemPrompt(agent, team, agents, cliType, adapter);
|
|
67
67
|
let systemPromptFile = null;
|
|
68
68
|
|
|
69
69
|
if (keepDefaultSP) {
|
|
@@ -115,18 +115,24 @@ async function main() {
|
|
|
115
115
|
|
|
116
116
|
// ── 5. 启动消息轮询 ──
|
|
117
117
|
let polling = true;
|
|
118
|
+
let injecting = false; // 注入锁,防止轮询重入导致消息交叉
|
|
118
119
|
const pollTimer = setInterval(async () => {
|
|
119
|
-
if (!polling) return;
|
|
120
|
+
if (!polling || injecting) return;
|
|
120
121
|
try {
|
|
121
122
|
// 上报活动状态
|
|
122
123
|
const active = (Date.now() - lastPtyOutput) < ACTIVE_THRESHOLD;
|
|
123
124
|
heartbeat(serverUrl, agent, active).catch(() => {});
|
|
124
125
|
|
|
125
126
|
const messages = await pullMessages(serverUrl, agent);
|
|
126
|
-
|
|
127
|
-
|
|
127
|
+
if (messages.length > 0) {
|
|
128
|
+
injecting = true;
|
|
129
|
+
for (const msg of messages) {
|
|
130
|
+
await injectMessage(ptyProcess, msg.message);
|
|
131
|
+
}
|
|
132
|
+
injecting = false;
|
|
128
133
|
}
|
|
129
134
|
} catch (err) {
|
|
135
|
+
injecting = false;
|
|
130
136
|
log.warn('wrapper.poll.error', { error: err.message });
|
|
131
137
|
}
|
|
132
138
|
}, POLL_INTERVAL);
|
|
@@ -160,20 +166,30 @@ async function main() {
|
|
|
160
166
|
|
|
161
167
|
// ── 消息注入 ──
|
|
162
168
|
|
|
169
|
+
const INJECT_ENTER_DELAY = 200; // 写入文本后等待 TUI 渲染再发 Enter
|
|
170
|
+
const INJECT_BETWEEN_DELAY = 300; // 两条消息之间的间隔,确保前一条已提交
|
|
171
|
+
|
|
163
172
|
/**
|
|
164
173
|
* 通过 PTY master 注入消息到 CLI
|
|
165
|
-
*
|
|
174
|
+
* 返回 Promise,resolve 后才能注入下一条
|
|
166
175
|
*/
|
|
167
176
|
function injectMessage(ptyProcess, message) {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
+
return new Promise((resolve) => {
|
|
178
|
+
try {
|
|
179
|
+
// 消息中的 \n 保持原样 — claude code 中 \n(0x0a) = 换行,\r(0x0d) = 提交
|
|
180
|
+
ptyProcess.write(message);
|
|
181
|
+
// 延迟发送 Enter 提交 — 确保 TUI 完成文本渲染
|
|
182
|
+
setTimeout(() => {
|
|
183
|
+
ptyProcess.write('\r');
|
|
184
|
+
log.info('wrapper.inject.ok', { preview: message.slice(0, 50) });
|
|
185
|
+
// 再等一段时间让 TUI 处理提交,然后才允许下一条
|
|
186
|
+
setTimeout(resolve, INJECT_BETWEEN_DELAY);
|
|
187
|
+
}, INJECT_ENTER_DELAY);
|
|
188
|
+
} catch (err) {
|
|
189
|
+
log.warn('wrapper.inject.failed', { error: err.message, preview: message.slice(0, 50) });
|
|
190
|
+
resolve();
|
|
191
|
+
}
|
|
192
|
+
});
|
|
177
193
|
}
|
|
178
194
|
|
|
179
195
|
// ── HTTP 辅助函数 ──
|
|
@@ -286,7 +302,7 @@ function cleanupTempFile(filePath) {
|
|
|
286
302
|
|
|
287
303
|
// ── 系统提示词 ──
|
|
288
304
|
|
|
289
|
-
function buildSystemPrompt(agent, team, agents, cliType) {
|
|
305
|
+
function buildSystemPrompt(agent, team, agents, cliType, adapter) {
|
|
290
306
|
const teammates = agents.filter(a => a !== agent);
|
|
291
307
|
const lines = [
|
|
292
308
|
`You are agent "${agent}" in team "${team}".`,
|
|
@@ -295,13 +311,16 @@ function buildSystemPrompt(agent, team, agents, cliType) {
|
|
|
295
311
|
lines.push(`Team members: ${agents.join(', ')}. Your teammates: ${teammates.join(', ')}.`);
|
|
296
312
|
}
|
|
297
313
|
lines.push(
|
|
298
|
-
'Use the msg tool to communicate with other agents.',
|
|
314
|
+
'Use the msg tool to communicate with other agents. You MUST actually invoke the tool — never just claim you sent a message.',
|
|
299
315
|
'Messages without [from xxx] prefix are from the boss — reply directly in your output.',
|
|
300
316
|
'Messages with [from <agent>] are from team agents — reply using msg tool.',
|
|
301
317
|
'NEVER use msg(who="boss") — boss is in your session, not an agent.',
|
|
302
318
|
'Use taskboard tool to manage tasks: create (leader only), done, list.',
|
|
303
319
|
);
|
|
304
320
|
|
|
321
|
+
// adapter 提供 CLI 特定的工具调用指引
|
|
322
|
+
lines.push('', adapter.buildToolGuide());
|
|
323
|
+
|
|
305
324
|
// Claude Code 特有:记忆目录隔离
|
|
306
325
|
if (cliType === 'claude-code') {
|
|
307
326
|
lines.push(
|
|
@@ -315,7 +334,7 @@ function buildSystemPrompt(agent, team, agents, cliType) {
|
|
|
315
334
|
);
|
|
316
335
|
}
|
|
317
336
|
|
|
318
|
-
return lines.join('
|
|
337
|
+
return lines.join('\n');
|
|
319
338
|
}
|
|
320
339
|
|
|
321
340
|
main().catch(err => {
|