openteam 0.7.0 → 0.7.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openteam",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
4
4
  "description": "Agent-centric team collaboration for OpenCode",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -212,31 +212,31 @@ function formatTeamPrompt(teamConfig, currentAgentName) {
212
212
  */
213
213
  function getCollaborationRules() {
214
214
  return `<collaboration-rules>
215
- ## 团队协作规则
216
-
217
- ### 消息来源识别
218
- - \`[from xxx]\` 前缀表示消息来源
219
- - \`[from boss]\` = 老板直接指示,优先级最高
220
- - \`[from <agent>]\` = 来自其他 agent(如 architectdeveloper 等)
221
-
222
- ### 通信方式
223
- - **直接输出文字对方看不到**,必须用 \`msg\` 工具
224
- - 收到 \`[from agent]\` 消息后,必须用 \`msg\` 回复对方才能看到
225
-
226
- ### Boss 消息(重要)
227
- - 收到 \`[from boss]\` 时**直接回复**即可(boss 在同一会话中,不是 agent
228
- - **绝对禁止** \`msg(who="boss", ...)\`,msg 只能发给团队 agent
229
- - boss 能直接看到你的输出,不需要任何工具
230
-
231
- ### 任务汇报(重要)
232
- - **任务完成后必须用 \`msg\` 向任务分配者汇报结果**
233
- - 汇报内容:完成了什么、关键产出、是否有遗留问题
234
- - 不汇报 = 对方不知道你完成了,协作链断裂
235
-
236
- ### 任务系统
237
- - 收到 \`[task #N]\` 开头的消息表示你有新任务已就绪,按任务要求开始工作
238
- - 完成后调 \`taskboard(action="done", id=N)\` 标记完成,系统会自动通知下游
239
- - \`taskboard(action="list")\` 查看所有任务及状态
240
- - 任务完成标记后不需要额外用 msg 汇报(系统自动处理流转)
215
+ ## Team Collaboration Rules
216
+
217
+ ### Message Sources
218
+ - \`[from xxx]\` prefix identifies the sender
219
+ - \`[from boss]\` = direct instruction from the boss, highest priority
220
+ - \`[from <agent>]\` = message from another agent (e.g., architect, developer)
221
+
222
+ ### Communication
223
+ - **Your text output is invisible to other agents** — use the \`msg\` tool to communicate
224
+ - When you receive \`[from <agent>]\`, reply with \`msg\` so they can see your response
225
+
226
+ ### Boss Messages (IMPORTANT)
227
+ - When you receive \`[from boss]\`, **reply directly in your output** — boss is in your session, not an agent
228
+ - **NEVER** use \`msg(who="boss", ...)\` — msg is only for team agents
229
+ - Boss can see your output directly, no tool needed
230
+
231
+ ### Task Reporting (IMPORTANT)
232
+ - **After completing a task, use \`msg\` to report results to whoever assigned it**
233
+ - Include: what was done, key outputs, any remaining issues
234
+ - No report = the assigner doesn't know you finished, collaboration breaks
235
+
236
+ ### Task System
237
+ - A message starting with \`[task #N]\` means you have a new task ready — start working on it
238
+ - When done, call \`taskboard(action="done", id=N)\` to mark complete — the system auto-notifies downstream
239
+ - Use \`taskboard(action="list")\` to view all tasks and their status
240
+ - After marking a task done, no extra msg report is needed (system handles flow automatically)
241
241
  </collaboration-rules>`;
242
242
  }
@@ -17,17 +17,26 @@ function createTraceID() {
17
17
  return `msg-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
18
18
  }
19
19
 
20
+ /**
21
+ * 记录工具错误并返回错误字符串
22
+ * 基础设施错误(agent 识别失败、serve 不可用等)用 error 级别,始终写入日志文件
23
+ */
24
+ function toolError(toolName, message, data = {}) {
25
+ log.error(`${toolName}: ${message}`, data);
26
+ return `Error: ${message}`;
27
+ }
28
+
20
29
  export function createToolDefs() {
21
30
  return {
22
31
  msg: {
23
32
  description:
24
- '给团队 agent 发消息(异步,像发微信)。直接输出文字对方看不到,必须用 msg。收到 [from agent] 消息后需用 msg 回复对方才能看到。注意:收到 [from boss] 时直接回复即可,不要用 msg——boss 在同一会话中,不是 agentLeader 可广播。',
33
+ 'Send a message to a team agent (async). Your text output is invisible to other agents — use this tool to communicate. When you receive [from <agent>], reply with msg. IMPORTANT: When you receive [from boss], reply directly in your output — boss is in your session, not an agent. Leader can broadcast by omitting "who".',
25
34
  args: {
26
35
  who: tool.schema
27
36
  .string()
28
37
  .optional()
29
- .describe('发给谁。不填或填 "all" 表示广播(仅 leader'),
30
- message: tool.schema.string().describe('消息内容'),
38
+ .describe('Target agent name. Omit or set to "all" to broadcast (leader only).'),
39
+ message: tool.schema.string().describe('Message content'),
31
40
  },
32
41
  execute: async (args, ctx) => {
33
42
  const trace = createTraceID();
@@ -39,20 +48,20 @@ export function createToolDefs() {
39
48
  });
40
49
 
41
50
  const currentAgent = await getCurrentAgent(ctx.sessionID, 2000, { trace, reason: 'msg.execute' });
42
- if (!currentAgent) return 'Error: 无法确定当前 agent';
51
+ if (!currentAgent) return toolError('msg', 'unable to identify current agent', { trace, sessionID: ctx.sessionID });
43
52
 
44
53
  const teamConfig = loadTeamConfig(currentAgent.team);
45
- if (!teamConfig) return 'Error: 团队配置不存在';
54
+ if (!teamConfig) return toolError('msg', 'team config not found', { trace, team: currentAgent.team });
46
55
 
47
56
  const projectDir = currentAgent.projectDir;
48
- if (!projectDir) return 'Error: 无法确定项目目录';
57
+ if (!projectDir) return toolError('msg', 'unable to determine project directory', { trace, agent: currentAgent.full });
49
58
  const serveUrl = getServeUrl(currentAgent.team, projectDir, { trace, reason: 'msg.execute' });
50
- if (!serveUrl) return 'Error: 团队 serve 未启动';
59
+ if (!serveUrl) return toolError('msg', 'team serve is not running', { trace, agent: currentAgent.full, team: currentAgent.team, projectDir });
51
60
 
52
61
  const isLeader = currentAgent.name === teamConfig.leader;
53
62
  const isBroadcast = !args.who || args.who === 'all';
54
63
 
55
- if (isBroadcast && !isLeader) return 'Error: 只有 leader 才能广播';
64
+ if (isBroadcast && !isLeader) return 'Error: only the leader can broadcast';
56
65
 
57
66
  if (isBroadcast) {
58
67
  // 广播
@@ -63,11 +72,11 @@ export function createToolDefs() {
63
72
 
64
73
  // 单点发送
65
74
  if (args.who === 'boss') {
66
- return 'Error: boss 在同一会话中,直接回复即可,不需要用 msg';
75
+ return 'Error: boss is in your session — reply directly in your output, no need to use msg.';
67
76
  }
68
77
 
69
78
  if (!isAgentInTeam(currentAgent.team, args.who)) {
70
- return `Error: 团队里没有 "${args.who}",可选: ${teamConfig.agents.join(', ')}`;
79
+ return `Error: "${args.who}" is not in the team. Available agents: ${teamConfig.agents.join(', ')}`;
71
80
  }
72
81
 
73
82
  const msgPreview = args.message.slice(0, 30) + (args.message.length > 30 ? '...' : '');
@@ -87,28 +96,29 @@ export function createToolDefs() {
87
96
 
88
97
  command: {
89
98
  description:
90
- 'Leader 专用指令。action: status(查看状态)、free(让人休息)、redirect(切换目录)',
99
+ 'Leader-only commands for team management. Actions: status (view status), free (release agent), redirect (change working directory).',
91
100
  args: {
92
- action: tool.schema.string().describe('指令:statusfreeredirect'),
93
- who: tool.schema.string().optional().describe('目标成员(status 时可选)'),
94
- cwd: tool.schema.string().optional().describe('工作目录(redirect 时用)'),
95
- alias: tool.schema.string().optional().describe('实例别名'),
101
+ action: tool.schema.string().describe('Action: status, free, or redirect'),
102
+ who: tool.schema.string().optional().describe('Target agent (optional for status)'),
103
+ cwd: tool.schema.string().optional().describe('Working directory (for redirect)'),
104
+ alias: tool.schema.string().optional().describe('Instance alias'),
96
105
  },
97
106
  execute: async (args, ctx) => {
107
+ const trace = createTraceID();
98
108
  const currentAgent = await getCurrentAgent(ctx.sessionID);
99
- if (!currentAgent) return 'Error: 无法确定当前 agent';
109
+ if (!currentAgent) return toolError('command', 'unable to identify current agent', { trace, sessionID: ctx.sessionID });
100
110
 
101
111
  const teamConfig = loadTeamConfig(currentAgent.team);
102
- if (!teamConfig) return 'Error: 团队配置不存在';
112
+ if (!teamConfig) return toolError('command', 'team config not found', { trace, team: currentAgent.team });
103
113
 
104
114
  if (currentAgent.name !== teamConfig.leader) {
105
- return `Error: 只有 ${teamConfig.leader} 才能使用 command`;
115
+ return `Error: only ${teamConfig.leader} can use command`;
106
116
  }
107
117
 
108
118
  const projectDir = currentAgent.projectDir;
109
- if (!projectDir) return 'Error: 无法确定项目目录';
110
- const serveUrl = getServeUrl(currentAgent.team, projectDir);
111
- if (!serveUrl) return 'Error: 团队 serve 未启动';
119
+ if (!projectDir) return toolError('command', 'unable to determine project directory', { trace, agent: currentAgent.full });
120
+ const serveUrl = getServeUrl(currentAgent.team, projectDir, { trace, reason: 'command.execute' });
121
+ if (!serveUrl) return toolError('command', 'team serve is not running', { trace, agent: currentAgent.full, team: currentAgent.team, projectDir });
112
122
 
113
123
  let who = args.who;
114
124
  let alias = args.alias;
@@ -123,10 +133,10 @@ export function createToolDefs() {
123
133
  return getStatus(currentAgent.team, projectDir, serveUrl, who);
124
134
  }
125
135
 
126
- if (!who) return 'Error: 请指定 who 参数';
136
+ if (!who) return 'Error: "who" parameter is required';
127
137
 
128
138
  if (!isAgentInTeam(currentAgent.team, who)) {
129
- return `Error: 团队里没有 "${who}",可选: ${teamConfig.agents.join(', ')}`;
139
+ return `Error: "${who}" is not in the team. Available agents: ${teamConfig.agents.join(', ')}`;
130
140
  }
131
141
 
132
142
  // FREE
@@ -136,46 +146,46 @@ export function createToolDefs() {
136
146
 
137
147
  // REDIRECT
138
148
  if (args.action === 'redirect') {
139
- if (!args.cwd) return 'Error: redirect 需要 cwd 参数';
140
- if (!fs.existsSync(args.cwd)) return `Error: 目录不存在 - ${args.cwd}`;
149
+ if (!args.cwd) return 'Error: redirect requires "cwd" parameter';
150
+ if (!fs.existsSync(args.cwd)) return `Error: directory not found - ${args.cwd}`;
141
151
  return redirectAgent(currentAgent.team, projectDir, who, args.cwd, serveUrl, { alias });
142
152
  }
143
153
 
144
- return `Error: 未知指令 "${args.action}",可用: status, free, redirect`;
154
+ return `Error: unknown action "${args.action}". Available: status, free, redirect`;
145
155
  },
146
156
  },
147
157
 
148
158
  taskboard: {
149
159
  description:
150
- '任务管理。create(创建任务,仅 leader)、done(完成任务)、list(查看列表)',
160
+ 'Task management. Actions: create (leader only), done (mark complete), list (view all tasks).',
151
161
  args: {
152
- action: tool.schema.string().describe('指令:createdonelist'),
153
- title: tool.schema.string().optional().describe('任务标题(create 时必填)'),
154
- description: tool.schema.string().optional().describe('任务描述(create 时可选)'),
155
- assignee: tool.schema.string().optional().describe('分配给谁(create 时必填)'),
156
- depends_on: tool.schema.array(tool.schema.number()).optional().describe('依赖任务 ID 数组(create 时可选)'),
157
- id: tool.schema.number().optional().describe('任务 IDdone 时必填)'),
162
+ action: tool.schema.string().describe('Action: create, done, or list'),
163
+ title: tool.schema.string().optional().describe('Task title (required for create)'),
164
+ description: tool.schema.string().optional().describe('Task description (optional for create)'),
165
+ assignee: tool.schema.string().optional().describe('Assignee agent name (required for create)'),
166
+ depends_on: tool.schema.array(tool.schema.number()).optional().describe('Dependency task IDs (optional for create)'),
167
+ id: tool.schema.number().optional().describe('Task ID (required for done)'),
158
168
  },
159
169
  execute: async (args, ctx) => {
160
170
  const trace = createTraceID();
161
171
  const currentAgent = await getCurrentAgent(ctx.sessionID, 2000, { trace, reason: 'task.execute' });
162
- if (!currentAgent) return 'Error: 无法确定当前 agent';
172
+ if (!currentAgent) return toolError('taskboard', 'unable to identify current agent', { trace, sessionID: ctx.sessionID });
163
173
 
164
174
  const teamConfig = loadTeamConfig(currentAgent.team);
165
- if (!teamConfig) return 'Error: 团队配置不存在';
175
+ if (!teamConfig) return toolError('taskboard', 'team config not found', { trace, team: currentAgent.team });
166
176
 
167
177
  const projectDir = currentAgent.projectDir;
168
- if (!projectDir) return 'Error: 无法确定项目目录';
178
+ if (!projectDir) return toolError('taskboard', 'unable to determine project directory', { trace, agent: currentAgent.full });
169
179
  const serveUrl = getServeUrl(currentAgent.team, projectDir, { trace, reason: 'task.execute' });
170
- if (!serveUrl) return 'Error: 团队 serve 未启动';
180
+ if (!serveUrl) return toolError('taskboard', 'team serve is not running', { trace, agent: currentAgent.full, team: currentAgent.team, projectDir });
171
181
 
172
182
  // CREATE
173
183
  if (args.action === 'create') {
174
184
  if (currentAgent.name !== teamConfig.leader) {
175
- return `Error: 只有 ${teamConfig.leader} 才能创建任务`;
185
+ return `Error: only ${teamConfig.leader} can create tasks`;
176
186
  }
177
- if (!args.title) return 'Error: create 需要 title 参数';
178
- if (!args.assignee) return 'Error: create 需要 assignee 参数';
187
+ if (!args.title) return 'Error: create requires "title" parameter';
188
+ if (!args.assignee) return 'Error: create requires "assignee" parameter';
179
189
 
180
190
  const result = await createTask({
181
191
  teamName: currentAgent.team, projectDir, serveUrl,
@@ -188,18 +198,18 @@ export function createToolDefs() {
188
198
 
189
199
  if (!result.ok) return `Error: ${result.error}`;
190
200
 
191
- let response = `任务 #${result.task.id} 已创建:「${result.task.title}」→ ${result.task.assignee}`;
201
+ let response = `Task #${result.task.id} created: "${result.task.title}" ${result.task.assignee}`;
192
202
  if (result.triggered && result.triggered.length > 0) {
193
- response += `\n已通知:${result.triggered.join(', ')}`;
203
+ response += `\nNotified: ${result.triggered.join(', ')}`;
194
204
  } else if (result.task.dependsOn.length > 0) {
195
- response += `\n等待依赖:${result.task.dependsOn.map(id => '#' + id).join(', ')}`;
205
+ response += `\nWaiting on: ${result.task.dependsOn.map(id => '#' + id).join(', ')}`;
196
206
  }
197
207
  return response;
198
208
  }
199
209
 
200
210
  // DONE
201
211
  if (args.action === 'done') {
202
- if (args.id == null) return 'Error: done 需要 id 参数';
212
+ if (args.id == null) return 'Error: done requires "id" parameter';
203
213
 
204
214
  const result = await completeTask({
205
215
  teamName: currentAgent.team, projectDir, serveUrl,
@@ -210,9 +220,9 @@ export function createToolDefs() {
210
220
 
211
221
  if (!result.ok) return `Error: ${result.error}`;
212
222
 
213
- let response = `任务 #${result.task.id}「${result.task.title}」已完成`;
223
+ let response = `Task #${result.task.id} "${result.task.title}" completed`;
214
224
  if (result.triggered.length > 0) {
215
- response += `\n已触发下游:\n${result.triggered.join('\n')}`;
225
+ response += `\nTriggered downstream:\n${result.triggered.join('\n')}`;
216
226
  }
217
227
  return response;
218
228
  }
@@ -220,17 +230,17 @@ export function createToolDefs() {
220
230
  // LIST
221
231
  if (args.action === 'list') {
222
232
  const tasks = listTasks(currentAgent.team, projectDir);
223
- if (tasks.length === 0) return '暂无任务';
233
+ if (tasks.length === 0) return 'No tasks';
224
234
 
225
235
  return tasks.map(t => {
226
236
  const status = t.status === 'done' ? '✓' : '⏳';
227
- const deps = t.dependsOn.length > 0 ? ` (依赖 ${t.dependsOn.map(id => '#' + id).join(',')})` : '';
237
+ const deps = t.dependsOn.length > 0 ? ` (depends on ${t.dependsOn.map(id => '#' + id).join(',')})` : '';
228
238
  const desc = t.description ? `\n ${t.description}` : '';
229
239
  return `#${t.id} ${status} ${t.title} → ${t.assignee}${deps}${desc}`;
230
240
  }).join('\n');
231
241
  }
232
242
 
233
- return `Error: 未知指令 "${args.action}",可用: create, done, list`;
243
+ return `Error: unknown action "${args.action}". Available: create, done, list`;
234
244
  },
235
245
  },
236
246
  };