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.
@@ -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) Copy team config and agent prompts
40
+ ### 1) Install the team
40
41
 
41
42
  ```bash
42
- # Create team directory
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
- ### 3) Configure OpenCode plugin
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
- ### 4) Start the team
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, serve process, and enters the PM (leader) session.
54
+ This launches the daemon, server, and agent panes in a tmux/zellij session.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openteam",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "description": "Agent-centric team collaboration framework",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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 覆盖冲突
@@ -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`);
@@ -1,10 +1,9 @@
1
1
  /**
2
2
  * 日志系统
3
3
  *
4
- * error 级别始终写入日志文件(无需配置)
5
- * debug/info/warn 需要启用:
6
- * OPENTEAM_LOG=1 启用日志
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 isEnabled = false;
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
- const envLog = process.env.OPENTEAM_LOG;
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
- // error 始终记录,其余需要 OPENTEAM_LOG=1
75
- if (!isEnabled && level !== 'error') return;
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) {
@@ -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
- for (const msg of messages) {
127
- injectMessage(ptyProcess, msg.message);
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
- * 使用 bracketed paste 模式,防止多行消息被逐行执行
174
+ * 返回 Promise,resolve 后才能注入下一条
166
175
  */
167
176
  function injectMessage(ptyProcess, message) {
168
- try {
169
- // 消息中的 \n 保持原样 — claude code 中 \n(0x0a) = 换行,\r(0x0d) = 提交
170
- ptyProcess.write(message);
171
- // 延迟发送 Enter 提交 — 确保 TUI 完成文本渲染
172
- setTimeout(() => ptyProcess.write('\r'), 100);
173
- log.info('wrapper.inject.ok', { preview: message.slice(0, 50) });
174
- } catch (err) {
175
- log.warn('wrapper.inject.failed', { error: err.message, preview: message.slice(0, 50) });
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 => {