openteam 0.6.0 → 0.7.0

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.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Agent-centric team collaboration for OpenCode",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -223,6 +223,11 @@ function getCollaborationRules() {
223
223
  - **直接输出文字对方看不到**,必须用 \`msg\` 工具
224
224
  - 收到 \`[from agent]\` 消息后,必须用 \`msg\` 回复对方才能看到
225
225
 
226
+ ### Boss 消息(重要)
227
+ - 收到 \`[from boss]\` 时**直接回复**即可(boss 在同一会话中,不是 agent)
228
+ - **绝对禁止** \`msg(who="boss", ...)\`,msg 只能发给团队 agent
229
+ - boss 能直接看到你的输出,不需要任何工具
230
+
226
231
  ### 任务汇报(重要)
227
232
  - **任务完成后必须用 \`msg\` 向任务分配者汇报结果**
228
233
  - 汇报内容:完成了什么、关键产出、是否有遗留问题
@@ -233,14 +238,5 @@ function getCollaborationRules() {
233
238
  - 完成后调 \`taskboard(action="done", id=N)\` 标记完成,系统会自动通知下游
234
239
  - 用 \`taskboard(action="list")\` 查看所有任务及状态
235
240
  - 任务完成标记后不需要额外用 msg 汇报(系统自动处理流转)
236
-
237
- ### Boss 消息回复方式
238
- - 收到 \`[from boss]\` 时**直接回复**即可(boss 在同一会话中)
239
- - **禁止**用 \`msg(who="boss", ...)\`,boss 不是 agent
240
-
241
- ### 记忆系统
242
- - 系统会在对话结束后**自动巩固**有价值的信息到长期记忆,你无需刻意记录
243
- - \`<memory>\` 中的 index 类型记忆显示了你所有笔记的摘要,需要详情时用 \`recall\` 查阅
244
- - 用 \`review\` 和 \`reread\` 可以回顾历史对话
245
241
  </collaboration-rules>`;
246
242
  }
@@ -4,8 +4,10 @@
4
4
 
5
5
  import blessed from 'blessed';
6
6
 
7
- // 模块级变量:保存当前消息列表原始数据,供展开详情用
7
+ // 模块级变量:保存当前列表原始数据,供展开详情用
8
8
  let _currentMessages = [];
9
+ let _currentTasks = [];
10
+ let _lastFocused = null;
9
11
 
10
12
  // Agent 颜色映射(按首次出现顺序轮转)
11
13
  const AGENT_COLORS = ['green', 'cyan', 'magenta', 'blue', 'yellow', 'red'];
@@ -27,6 +29,7 @@ export function createDashboard(teamName) {
27
29
  const screen = blessed.screen({
28
30
  smartCSR: true,
29
31
  fullUnicode: true,
32
+ mouse: true,
30
33
  title: `OpenTeam Dashboard - ${teamName}`,
31
34
  });
32
35
 
@@ -90,7 +93,7 @@ export function createDashboard(teamName) {
90
93
  height: '100%-33',
91
94
  tags: true,
92
95
  border: { type: 'line' },
93
- label: ' 消息流 (↑↓选择 Enter展开 q退出) ',
96
+ label: ' 消息流 (点击/Enter展开 Tab切换 q退出) ',
94
97
  scrollable: true,
95
98
  keys: true,
96
99
  vi: true,
@@ -105,16 +108,15 @@ export function createDashboard(teamName) {
105
108
  items: [],
106
109
  });
107
110
 
108
- // 任务看板(钉在底部)
109
- const taskBoard = blessed.box({
111
+ // 任务看板(钉在底部,可选中)
112
+ const taskBoard = blessed.list({
110
113
  bottom: 0,
111
114
  left: 0,
112
115
  width: '100%',
113
116
  height: 12,
114
- content: '',
115
117
  tags: true,
116
118
  border: { type: 'line' },
117
- label: ' 任务看板 ',
119
+ label: ' 任务看板 (点击/Enter详情 Tab切换) ',
118
120
  scrollable: true,
119
121
  keys: true,
120
122
  vi: true,
@@ -123,7 +125,10 @@ export function createDashboard(teamName) {
123
125
  style: {
124
126
  fg: 'white',
125
127
  border: { fg: 'blue' },
128
+ selected: { bg: 'blue', fg: 'white' },
129
+ item: { fg: 'white' },
126
130
  },
131
+ items: [],
127
132
  });
128
133
 
129
134
  // 消息详情弹窗(默认隐藏)
@@ -156,12 +161,30 @@ export function createDashboard(teamName) {
156
161
  screen.append(taskBoard);
157
162
  screen.append(detailBox);
158
163
 
164
+ // Tab 焦点切换(消息流 ↔ 任务看板)
165
+ const focusable = [messageStream, taskBoard];
166
+ let focusIndex = 0;
167
+
168
+ screen.key(['tab'], () => {
169
+ if (!detailBox.hidden) return; // 弹窗打开时不切换
170
+ focusIndex = (focusIndex + 1) % focusable.length;
171
+ focusable[focusIndex].focus();
172
+ screen.render();
173
+ });
174
+
175
+ screen.key(['S-tab'], () => {
176
+ if (!detailBox.hidden) return;
177
+ focusIndex = (focusIndex - 1 + focusable.length) % focusable.length;
178
+ focusable[focusIndex].focus();
179
+ screen.render();
180
+ });
181
+
159
182
  // 全局退出
160
183
  screen.key(['q', 'C-c'], () => {
161
184
  if (!detailBox.hidden) {
162
185
  // 如果详情弹窗打开,先关闭弹窗
163
186
  detailBox.hide();
164
- messageStream.focus();
187
+ (_lastFocused || messageStream).focus();
165
188
  screen.render();
166
189
  return;
167
190
  }
@@ -170,6 +193,7 @@ export function createDashboard(teamName) {
170
193
 
171
194
  // Enter 展开消息详情
172
195
  messageStream.on('select', (item, index) => {
196
+ _lastFocused = messageStream;
173
197
  const msg = _currentMessages[index];
174
198
  if (!msg) return;
175
199
 
@@ -184,6 +208,43 @@ export function createDashboard(teamName) {
184
208
  msg.fullContent || msg.content,
185
209
  ].join('\n');
186
210
 
211
+ detailBox.setLabel(' 消息详情 (Esc/q 关闭) ');
212
+ detailBox.setContent(detail);
213
+ detailBox.setScrollPerc(0);
214
+ detailBox.show();
215
+ detailBox.focus();
216
+ screen.render();
217
+ });
218
+
219
+ // Enter 展开任务详情
220
+ taskBoard.on('select', (item, index) => {
221
+ _lastFocused = taskBoard;
222
+ const task = _currentTasks[index];
223
+ if (!task) return;
224
+
225
+ const created = new Date(task.createdAt).toLocaleString('zh-CN', { hour12: false });
226
+ const doneAt = task.doneAt ? new Date(task.doneAt).toLocaleString('zh-CN', { hour12: false }) : '—';
227
+ const status = task.status === 'done' ? '{green-fg}已完成{/green-fg}' : '{yellow-fg}待处理{/yellow-fg}';
228
+ const deps = task.dependsOn.length > 0
229
+ ? task.dependsOn.map(id => '#' + id).join(', ')
230
+ : '无';
231
+
232
+ const detail = [
233
+ `{bold}任务 #${task.id}{/bold}`,
234
+ '',
235
+ `{bold}标题:{/bold} ${task.title}`,
236
+ `{bold}分配人:{/bold} {cyan-fg}${task.assignee}{/cyan-fg}`,
237
+ `{bold}状态:{/bold} ${status}`,
238
+ `{bold}依赖:{/bold} ${deps}`,
239
+ `{bold}创建时间:{/bold} ${created}`,
240
+ `{bold}完成时间:{/bold} ${doneAt}`,
241
+ '',
242
+ '{bold}描述:{/bold}',
243
+ '─'.repeat(60),
244
+ task.description || '{gray-fg}(无描述){/gray-fg}',
245
+ ].join('\n');
246
+
247
+ detailBox.setLabel(' 任务详情 (Esc/q 关闭) ');
187
248
  detailBox.setContent(detail);
188
249
  detailBox.setScrollPerc(0);
189
250
  detailBox.show();
@@ -194,10 +255,25 @@ export function createDashboard(teamName) {
194
255
  // Esc 关闭详情弹窗
195
256
  detailBox.key(['escape', 'q'], () => {
196
257
  detailBox.hide();
197
- messageStream.focus();
258
+ (_lastFocused || messageStream).focus();
198
259
  screen.render();
199
260
  });
200
261
 
262
+ // 点击弹窗外区域关闭详情
263
+ screen.on('click', (mouse) => {
264
+ if (detailBox.hidden) return;
265
+ // 检查点击是否在 detailBox 范围外
266
+ const top = detailBox.atop;
267
+ const left = detailBox.aleft;
268
+ const bottom = top + detailBox.height;
269
+ const right = left + detailBox.width;
270
+ if (mouse.x < left || mouse.x >= right || mouse.y < top || mouse.y >= bottom) {
271
+ detailBox.hide();
272
+ (_lastFocused || messageStream).focus();
273
+ screen.render();
274
+ }
275
+ });
276
+
201
277
  // 默认焦点在消息流
202
278
  messageStream.focus();
203
279
  screen.render();
@@ -290,13 +366,15 @@ function formatActivity(agent) {
290
366
  /**
291
367
  * 更新任务看板
292
368
  */
293
- export function updateTaskBoard(box, tasks) {
294
- if (!tasks || tasks.length === 0) {
295
- box.setContent('{yellow-fg}暂无任务{/yellow-fg}');
369
+ export function updateTaskBoard(listBox, tasks) {
370
+ _currentTasks = tasks || [];
371
+
372
+ if (_currentTasks.length === 0) {
373
+ listBox.setItems(['{yellow-fg}暂无任务{/yellow-fg}']);
296
374
  return;
297
375
  }
298
376
 
299
- const lines = tasks.map(t => {
377
+ const items = _currentTasks.map(t => {
300
378
  const status = t.status === 'done'
301
379
  ? '{green-fg}✓{/green-fg}'
302
380
  : '{yellow-fg}⏳{/yellow-fg}';
@@ -307,7 +385,7 @@ export function updateTaskBoard(box, tasks) {
307
385
  let deps = '';
308
386
  if (t.status === 'pending' && t.dependsOn.length > 0) {
309
387
  const pendingDeps = t.dependsOn.filter(depId => {
310
- const dep = tasks.find(d => d.id === depId);
388
+ const dep = _currentTasks.find(d => d.id === depId);
311
389
  return dep && dep.status !== 'done';
312
390
  });
313
391
  if (pendingDeps.length > 0) {
@@ -318,7 +396,7 @@ export function updateTaskBoard(box, tasks) {
318
396
  return `${id} ${status} ${title} ${assignee} ${deps}`;
319
397
  });
320
398
 
321
- box.setContent(lines.join('\n'));
399
+ listBox.setItems(items);
322
400
  }
323
401
 
324
402
  /**
@@ -21,7 +21,7 @@ export function createToolDefs() {
21
21
  return {
22
22
  msg: {
23
23
  description:
24
- '发消息(异步,像发微信)。直接输出文字对方看不到,必须用 msg。收到 [from xxx] 消息后需用 msg 回复对方才能看到。Leader 可广播。',
24
+ '给团队 agent 发消息(异步,像发微信)。直接输出文字对方看不到,必须用 msg。收到 [from agent] 消息后需用 msg 回复对方才能看到。注意:收到 [from boss] 时直接回复即可,不要用 msg——boss 在同一会话中,不是 agent。Leader 可广播。',
25
25
  args: {
26
26
  who: tool.schema
27
27
  .string()
@@ -62,6 +62,10 @@ export function createToolDefs() {
62
62
  }
63
63
 
64
64
  // 单点发送
65
+ if (args.who === 'boss') {
66
+ return 'Error: boss 在同一会话中,直接回复即可,不需要用 msg。';
67
+ }
68
+
65
69
  if (!isAgentInTeam(currentAgent.team, args.who)) {
66
70
  return `Error: 团队里没有 "${args.who}",可选: ${teamConfig.agents.join(', ')}`;
67
71
  }