openteam 0.7.2 → 1.0.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/bin/openteam.js +15 -18
- package/package.json +8 -7
- package/src/adapters/base.js +73 -0
- package/src/adapters/claude-code.js +48 -0
- package/src/adapters/opencode.js +37 -0
- package/src/capabilities/taskboard.js +31 -13
- package/src/foundation/config.js +4 -3
- package/src/foundation/constants.js +7 -7
- package/src/foundation/state.js +16 -145
- package/src/foundation/terminal.js +85 -0
- package/src/interfaces/cli.js +45 -93
- package/src/interfaces/daemon/index.js +43 -56
- package/src/interfaces/daemon/links.js +168 -0
- package/src/interfaces/daemon/panes.js +57 -39
- package/src/interfaces/daemon/serve.js +27 -73
- package/src/interfaces/dashboard/data.js +79 -147
- package/src/interfaces/dashboard/index.js +5 -4
- package/src/interfaces/dashboard/ui.js +78 -23
- package/src/server/hub.js +133 -0
- package/src/server/index.js +212 -0
- package/src/server/mcp.js +161 -0
- package/src/server/routes.js +139 -0
- package/src/wrapper/index.js +262 -0
- package/src/capabilities/lifecycle.js +0 -307
- package/src/capabilities/messaging.js +0 -242
- package/src/foundation/opencode.js +0 -235
- package/src/index.js +0 -38
- package/src/interfaces/plugin/hooks.js +0 -16
- package/src/interfaces/plugin/tools.js +0 -247
package/bin/openteam.js
CHANGED
|
@@ -7,30 +7,25 @@
|
|
|
7
7
|
import { createRequire } from 'module';
|
|
8
8
|
import { program } from 'commander';
|
|
9
9
|
import {
|
|
10
|
-
cmdStart,
|
|
11
|
-
cmdInspect, cmdDashboard,
|
|
10
|
+
cmdStart, cmdList, cmdStop,
|
|
11
|
+
cmdInspect, cmdDashboard,
|
|
12
12
|
} from '../src/interfaces/cli.js';
|
|
13
13
|
|
|
14
14
|
const require = createRequire(import.meta.url);
|
|
15
15
|
const { version } = require('../package.json');
|
|
16
16
|
|
|
17
|
-
program.name('openteam').description('
|
|
17
|
+
program.name('openteam').description('Agent team collaboration framework').version(version);
|
|
18
18
|
|
|
19
19
|
program
|
|
20
20
|
.command('start [team]')
|
|
21
21
|
.description('启动团队(创建 tmux/zellij session)')
|
|
22
22
|
.option('-d, --detach', '后台运行')
|
|
23
23
|
.option('--dir <directory>', '项目目录')
|
|
24
|
+
.option('--cli <type>', 'CLI 类型(claude-code / opencode)', 'claude-code')
|
|
24
25
|
.option('--tmux', '强制使用 tmux')
|
|
25
26
|
.option('--zellij', '强制使用 zellij')
|
|
26
27
|
.action(cmdStart);
|
|
27
28
|
|
|
28
|
-
program
|
|
29
|
-
.command('attach [team] [agent]', { hidden: true })
|
|
30
|
-
.description('附加到 agent 会话')
|
|
31
|
-
.option('--dir <directory>', '项目目录')
|
|
32
|
-
.action(cmdAttach);
|
|
33
|
-
|
|
34
29
|
program
|
|
35
30
|
.command('list')
|
|
36
31
|
.alias('ls')
|
|
@@ -45,7 +40,7 @@ program
|
|
|
45
40
|
|
|
46
41
|
program
|
|
47
42
|
.command('inspect <team>')
|
|
48
|
-
.description('
|
|
43
|
+
.description('查看团队详细状态与 agent 在线情况')
|
|
49
44
|
.option('--dir <directory>', '项目目录')
|
|
50
45
|
.action(cmdInspect);
|
|
51
46
|
|
|
@@ -55,21 +50,23 @@ program
|
|
|
55
50
|
.option('--dir <directory>', '项目目录')
|
|
56
51
|
.action(cmdDashboard);
|
|
57
52
|
|
|
58
|
-
//
|
|
59
|
-
program
|
|
60
|
-
.command('agent-attach <team> <agent>', { hidden: true })
|
|
61
|
-
.description('等待 agent 会话就绪后 attach')
|
|
62
|
-
.option('--dir <directory>', '项目目录')
|
|
63
|
-
.action(cmdAgentAttach);
|
|
64
|
-
|
|
53
|
+
// 内部命令(不在帮助中显示)
|
|
65
54
|
program
|
|
66
55
|
.command('daemon <team>', { hidden: true })
|
|
67
|
-
.option('--port <port>', '
|
|
56
|
+
.option('--port <port>', 'server 端口', parseInt)
|
|
68
57
|
.option('--dir <directory>', '项目目录')
|
|
69
58
|
.option('--mux <type>', '复用器类型', 'tmux')
|
|
59
|
+
.option('--cli <type>', 'CLI 类型', 'claude-code')
|
|
70
60
|
.action(async (teamName, options) => {
|
|
71
61
|
const { runDaemon } = await import('../src/interfaces/daemon/index.js');
|
|
72
62
|
await runDaemon(teamName, options.dir || process.cwd(), options);
|
|
73
63
|
});
|
|
74
64
|
|
|
65
|
+
program
|
|
66
|
+
.command('wrapper', { hidden: true })
|
|
67
|
+
.description('Wrapper 进程(由 daemon pane 自动调用)')
|
|
68
|
+
.action(async () => {
|
|
69
|
+
await import('../src/wrapper/index.js');
|
|
70
|
+
});
|
|
71
|
+
|
|
75
72
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openteam",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Agent-centric team collaboration
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Agent-centric team collaboration framework",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "./src/index.js",
|
|
7
6
|
"bin": {
|
|
8
7
|
"openteam": "./bin/openteam.js"
|
|
9
8
|
},
|
|
@@ -17,10 +16,10 @@
|
|
|
17
16
|
"test": "node test/smoke.js"
|
|
18
17
|
},
|
|
19
18
|
"keywords": [
|
|
20
|
-
"opencode",
|
|
21
19
|
"agent",
|
|
22
20
|
"team",
|
|
23
|
-
"collaboration"
|
|
21
|
+
"collaboration",
|
|
22
|
+
"mcp"
|
|
24
23
|
],
|
|
25
24
|
"author": "goodfish",
|
|
26
25
|
"license": "MIT",
|
|
@@ -29,8 +28,10 @@
|
|
|
29
28
|
"url": "git+https://github.com/opencode-dev/openteam.git"
|
|
30
29
|
},
|
|
31
30
|
"dependencies": {
|
|
32
|
-
"@
|
|
31
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
33
32
|
"blessed": "^0.1.81",
|
|
34
|
-
"commander": "^12.0.0"
|
|
33
|
+
"commander": "^12.0.0",
|
|
34
|
+
"node-pty": "^1.1.0",
|
|
35
|
+
"zod": "^4.3.6"
|
|
35
36
|
}
|
|
36
37
|
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI 适配器基类 + 工厂函数
|
|
3
|
+
*
|
|
4
|
+
* 每个适配器封装特定 CLI 工具的启动参数、MCP 配置方式等差异。
|
|
5
|
+
* adapters/ 是基础层 — 只提供 CLI 命令构建,不含业务逻辑。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* CLI 适配器基类
|
|
10
|
+
*/
|
|
11
|
+
export class BaseAdapter {
|
|
12
|
+
/**
|
|
13
|
+
* CLI 二进制名称
|
|
14
|
+
* @returns {string}
|
|
15
|
+
*/
|
|
16
|
+
get binary() {
|
|
17
|
+
throw new Error('subclass must implement binary getter');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 构建 CLI 启动命令参数(数组形式,第一个元素是二进制名)
|
|
22
|
+
* @param {object} params
|
|
23
|
+
* @param {string} params.agent - agent 名称
|
|
24
|
+
* @param {string} params.systemPrompt - 追加的系统提示词
|
|
25
|
+
* @param {string} params.mcpConfigPath - MCP 配置文件路径
|
|
26
|
+
* @param {string} params.cwd - 工作目录
|
|
27
|
+
* @returns {string[]} 命令数组,如 ['claude', '--agent', 'pm', ...]
|
|
28
|
+
*/
|
|
29
|
+
buildLaunchArgs({ agent, systemPrompt, mcpConfigPath, cwd, extraArgs = [] }) {
|
|
30
|
+
throw new Error('subclass must implement buildLaunchArgs');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 构建 MCP 配置对象
|
|
35
|
+
* 返回可写入 JSON 文件的配置,让 CLI 知道如何连接 openteam MCP server。
|
|
36
|
+
* @param {object} params
|
|
37
|
+
* @param {string} params.serverUrl - openteam server URL
|
|
38
|
+
* @param {string} params.agent - agent 名称
|
|
39
|
+
* @returns {object} MCP 配置 JSON
|
|
40
|
+
*/
|
|
41
|
+
buildMcpConfig({ serverUrl, agent }) {
|
|
42
|
+
throw new Error('subclass must implement buildMcpConfig');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* MCP 配置文件路径(每个 agent 独立,避免多 agent 覆盖冲突)
|
|
47
|
+
* @param {string} projectDir - 项目目录
|
|
48
|
+
* @param {string} agent - agent 名称
|
|
49
|
+
* @returns {string} MCP 配置文件的绝对路径
|
|
50
|
+
*/
|
|
51
|
+
getMcpConfigPath(projectDir, agent) {
|
|
52
|
+
throw new Error('subclass must implement getMcpConfigPath');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 适配器工厂
|
|
58
|
+
* @param {string} cliType - CLI 类型标识('claude-code' | 'opencode')
|
|
59
|
+
* @returns {BaseAdapter}
|
|
60
|
+
*/
|
|
61
|
+
export async function createAdapter(cliType) {
|
|
62
|
+
// 动态 import 避免启动时加载所有适配器
|
|
63
|
+
const adapters = {
|
|
64
|
+
'claude-code': () => import('./claude-code.js').then(m => new m.ClaudeCodeAdapter()),
|
|
65
|
+
'opencode': () => import('./opencode.js').then(m => new m.OpenCodeAdapter()),
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const factory = adapters[cliType];
|
|
69
|
+
if (!factory) {
|
|
70
|
+
throw new Error(`Unknown CLI type: "${cliType}". Available: ${Object.keys(adapters).join(', ')}`);
|
|
71
|
+
}
|
|
72
|
+
return factory();
|
|
73
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code CLI 适配器
|
|
3
|
+
*
|
|
4
|
+
* claude code CLI 关键 flags:
|
|
5
|
+
* --agent <name> agent 模式
|
|
6
|
+
* --append-system-prompt <text> 追加系统提示词
|
|
7
|
+
* --mcp-config <path> 加载 MCP 配置文件
|
|
8
|
+
* --strict-mcp-config 只用 --mcp-config 指定的 MCP server
|
|
9
|
+
*
|
|
10
|
+
* MCP 配置格式(.mcp.json 风格):
|
|
11
|
+
* { "openteam": { "type": "http", "url": "http://host:port/mcp?agent=xxx" } }
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import os from 'os';
|
|
15
|
+
import path from 'path';
|
|
16
|
+
import { BaseAdapter } from './base.js';
|
|
17
|
+
|
|
18
|
+
export class ClaudeCodeAdapter extends BaseAdapter {
|
|
19
|
+
get binary() { return 'claude'; }
|
|
20
|
+
|
|
21
|
+
buildLaunchArgs({ agent, systemPrompt, mcpConfigPath, cwd, extraArgs = [] }) {
|
|
22
|
+
const args = [this.binary];
|
|
23
|
+
if (agent) args.push('--agent', agent);
|
|
24
|
+
if (systemPrompt) args.push('--append-system-prompt', systemPrompt);
|
|
25
|
+
if (mcpConfigPath) args.push('--mcp-config', mcpConfigPath);
|
|
26
|
+
args.push(...extraArgs);
|
|
27
|
+
return args;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
buildMcpConfig({ serverUrl, agent }) {
|
|
31
|
+
// claude code --mcp-config 格式:需要 mcpServers 包裹
|
|
32
|
+
return {
|
|
33
|
+
mcpServers: {
|
|
34
|
+
openteam: {
|
|
35
|
+
type: 'http',
|
|
36
|
+
url: `${serverUrl}/mcp?agent=${encodeURIComponent(agent)}`,
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
getMcpConfigPath(projectDir, agent) {
|
|
43
|
+
// 用临时目录避免污染项目的 .mcp.json(可能有用户自己的 MCP 配置)
|
|
44
|
+
// 每个 agent 独立文件,避免多 agent 覆盖冲突
|
|
45
|
+
// wrapper 通过 --mcp-config 显式传入这个路径
|
|
46
|
+
return path.join(os.tmpdir(), `openteam-mcp-${agent}-${encodeURIComponent(path.basename(projectDir))}.json`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode CLI 适配器
|
|
3
|
+
*
|
|
4
|
+
* TODO: opencode 的 CLI flags 和 MCP 配置方式待确认。
|
|
5
|
+
* 当前实现基于推测,联调时根据实际文档调整。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import os from 'os';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import { BaseAdapter } from './base.js';
|
|
11
|
+
|
|
12
|
+
export class OpenCodeAdapter extends BaseAdapter {
|
|
13
|
+
get binary() { return 'opencode'; }
|
|
14
|
+
|
|
15
|
+
buildLaunchArgs({ agent, systemPrompt, mcpConfigPath, cwd, extraArgs = [] }) {
|
|
16
|
+
const args = [this.binary];
|
|
17
|
+
// TODO: opencode 的 system prompt 和 agent 参数待确认
|
|
18
|
+
if (systemPrompt) args.push('--prompt', systemPrompt);
|
|
19
|
+
args.push(...extraArgs);
|
|
20
|
+
return args;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
buildMcpConfig({ serverUrl, agent }) {
|
|
24
|
+
// TODO: opencode 的 MCP 配置格式待确认
|
|
25
|
+
return {
|
|
26
|
+
openteam: {
|
|
27
|
+
type: 'http',
|
|
28
|
+
url: `${serverUrl}/mcp?agent=${encodeURIComponent(agent)}`,
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
getMcpConfigPath(projectDir, agent) {
|
|
34
|
+
// TODO: opencode 的 MCP 配置路径待确认
|
|
35
|
+
return path.join(os.tmpdir(), `openteam-mcp-opencode-${agent}-${encodeURIComponent(path.basename(projectDir))}.json`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -1,19 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 任务看板 — 创建/完成/依赖检查/自动通知
|
|
3
|
+
*
|
|
4
|
+
* 通知通过 hub.deliver() 投递到消息队列,由 wrapper 轮询取走注入 CLI。
|
|
3
5
|
*/
|
|
4
6
|
|
|
5
7
|
import { loadTasks, saveTasks } from '../foundation/tasks.js';
|
|
6
|
-
import { deliverMessage } from './messaging.js';
|
|
7
8
|
import { isAgentInTeam, getTeamLeader } from '../foundation/config.js';
|
|
8
9
|
import { createLogger } from '../foundation/logger.js';
|
|
9
10
|
|
|
10
11
|
const log = createLogger('taskboard');
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
|
-
* 创建任务(仅 leader 可调,权限校验由
|
|
14
|
+
* 创建任务(仅 leader 可调,权限校验由 mcp.js 负责)
|
|
15
|
+
* @param {object} params
|
|
16
|
+
* @param {string} params.teamName
|
|
17
|
+
* @param {string} params.projectDir
|
|
18
|
+
* @param {MessageHub} params.hub - 消息中枢
|
|
19
|
+
* @param {string} params.title
|
|
20
|
+
* @param {string} params.description
|
|
21
|
+
* @param {string} params.assignee
|
|
22
|
+
* @param {number[]} params.dependsOn
|
|
23
|
+
* @param {string} params.trace
|
|
14
24
|
* @returns {{ ok: true, task: object, triggered: string[] } | { ok: false, error: string }}
|
|
15
25
|
*/
|
|
16
|
-
export async function createTask({ teamName, projectDir,
|
|
26
|
+
export async function createTask({ teamName, projectDir, hub, title, description, assignee, dependsOn = [], trace }) {
|
|
17
27
|
// 1. 校验 assignee 在团队中
|
|
18
28
|
if (!isAgentInTeam(teamName, assignee)) {
|
|
19
29
|
return { ok: false, error: `团队中没有 "${assignee}"` };
|
|
@@ -45,16 +55,23 @@ export async function createTask({ teamName, projectDir, serveUrl, title, descri
|
|
|
45
55
|
log.info('task.created', { trace, id: task.id, title, assignee, dependsOn });
|
|
46
56
|
|
|
47
57
|
// 4. 检查依赖是否已满足,满足则通知
|
|
48
|
-
const triggered =
|
|
58
|
+
const triggered = notifyIfReady(task, data.tasks, hub, trace);
|
|
49
59
|
|
|
50
60
|
return { ok: true, task, triggered };
|
|
51
61
|
}
|
|
52
62
|
|
|
53
63
|
/**
|
|
54
64
|
* 完成任务
|
|
65
|
+
* @param {object} params
|
|
66
|
+
* @param {string} params.teamName
|
|
67
|
+
* @param {string} params.projectDir
|
|
68
|
+
* @param {MessageHub} params.hub - 消息中枢
|
|
69
|
+
* @param {string} params.agentName
|
|
70
|
+
* @param {number} params.taskId
|
|
71
|
+
* @param {string} params.trace
|
|
55
72
|
* @returns {{ ok: true, task: object, triggered: string[] } | { ok: false, error: string }}
|
|
56
73
|
*/
|
|
57
|
-
export async function completeTask({ teamName, projectDir,
|
|
74
|
+
export async function completeTask({ teamName, projectDir, hub, agentName, taskId, trace }) {
|
|
58
75
|
const data = loadTasks(teamName, projectDir);
|
|
59
76
|
const task = data.tasks.find(t => t.id === taskId);
|
|
60
77
|
|
|
@@ -74,7 +91,7 @@ export async function completeTask({ teamName, projectDir, serveUrl, agentName,
|
|
|
74
91
|
for (const t of data.tasks) {
|
|
75
92
|
if (t.status !== 'pending') continue;
|
|
76
93
|
if (t.dependsOn.length === 0) continue;
|
|
77
|
-
if (!t.dependsOn.includes(taskId)) continue;
|
|
94
|
+
if (!t.dependsOn.includes(taskId)) continue;
|
|
78
95
|
|
|
79
96
|
const allDone = t.dependsOn.every(depId => {
|
|
80
97
|
const dep = data.tasks.find(d => d.id === depId);
|
|
@@ -82,7 +99,7 @@ export async function completeTask({ teamName, projectDir, serveUrl, agentName,
|
|
|
82
99
|
});
|
|
83
100
|
|
|
84
101
|
if (allDone) {
|
|
85
|
-
const result =
|
|
102
|
+
const result = notifyAssignee(t, hub, trace);
|
|
86
103
|
triggered.push(`#${t.id} ${t.title} → ${t.assignee} (${result})`);
|
|
87
104
|
}
|
|
88
105
|
}
|
|
@@ -92,7 +109,7 @@ export async function completeTask({ teamName, projectDir, serveUrl, agentName,
|
|
|
92
109
|
if (leader && leader !== agentName) {
|
|
93
110
|
const leaderMsg = `[task #${taskId} done] ${task.title} — ${agentName} 已完成`;
|
|
94
111
|
try {
|
|
95
|
-
|
|
112
|
+
hub.deliver({ from: agentName, to: leader, message: leaderMsg });
|
|
96
113
|
} catch (err) {
|
|
97
114
|
log.warn('notifyLeader failed', { trace, taskId, leader, error: err.message });
|
|
98
115
|
}
|
|
@@ -114,10 +131,10 @@ export function listTasks(teamName, projectDir) {
|
|
|
114
131
|
/**
|
|
115
132
|
* 检查任务依赖是否满足,满足则通知 assignee
|
|
116
133
|
*/
|
|
117
|
-
|
|
134
|
+
function notifyIfReady(task, allTasks, hub, trace) {
|
|
118
135
|
if (task.dependsOn.length === 0) {
|
|
119
136
|
// 无依赖,立即通知
|
|
120
|
-
const result =
|
|
137
|
+
const result = notifyAssignee(task, hub, trace);
|
|
121
138
|
return [`#${task.id} ${task.title} → ${task.assignee} (${result})`];
|
|
122
139
|
}
|
|
123
140
|
|
|
@@ -127,7 +144,7 @@ async function notifyIfReady(task, allTasks, teamName, projectDir, serveUrl, tra
|
|
|
127
144
|
});
|
|
128
145
|
|
|
129
146
|
if (allDone) {
|
|
130
|
-
const result =
|
|
147
|
+
const result = notifyAssignee(task, hub, trace);
|
|
131
148
|
return [`#${task.id} ${task.title} → ${task.assignee} (${result})`];
|
|
132
149
|
}
|
|
133
150
|
|
|
@@ -137,13 +154,14 @@ async function notifyIfReady(task, allTasks, teamName, projectDir, serveUrl, tra
|
|
|
137
154
|
/**
|
|
138
155
|
* 发送任务就绪通知(通知失败不影响任务操作本身)
|
|
139
156
|
*/
|
|
140
|
-
|
|
157
|
+
function notifyAssignee(task, hub, trace) {
|
|
141
158
|
const message = task.description
|
|
142
159
|
? `[task #${task.id}] ${task.title}:${task.description}`
|
|
143
160
|
: `[task #${task.id}] ${task.title}`;
|
|
144
161
|
|
|
145
162
|
try {
|
|
146
|
-
|
|
163
|
+
const result = hub.deliver({ from: 'system', to: task.assignee, message });
|
|
164
|
+
return `${task.assignee}: ${result.online ? 'delivered' : 'queued'}`;
|
|
147
165
|
} catch (err) {
|
|
148
166
|
log.warn('notifyAssignee failed', { trace, taskId: task.id, assignee: task.assignee, error: err.message });
|
|
149
167
|
return `${task.assignee}: 通知失败 (${err.message})`;
|
package/src/foundation/config.js
CHANGED
|
@@ -71,16 +71,17 @@ export function isAgentInTeam(teamName, agentName) {
|
|
|
71
71
|
|
|
72
72
|
/**
|
|
73
73
|
* List all teams
|
|
74
|
+
* 扫描 ~/.openteam/teams/ 下含 team.json 的目录
|
|
74
75
|
*/
|
|
75
76
|
export function listTeams() {
|
|
76
|
-
if (!fs.existsSync(PATHS.
|
|
77
|
+
if (!fs.existsSync(PATHS.TEAMS_DIR)) return [];
|
|
77
78
|
|
|
78
|
-
const entries = fs.readdirSync(PATHS.
|
|
79
|
+
const entries = fs.readdirSync(PATHS.TEAMS_DIR, { withFileTypes: true });
|
|
79
80
|
const teams = [];
|
|
80
81
|
|
|
81
82
|
for (const entry of entries) {
|
|
82
83
|
if (!entry.isDirectory()) continue;
|
|
83
|
-
const teamConfigPath = path.join(PATHS.
|
|
84
|
+
const teamConfigPath = path.join(PATHS.TEAMS_DIR, entry.name, FILES.TEAM_CONFIG);
|
|
84
85
|
if (fs.existsSync(teamConfigPath)) {
|
|
85
86
|
teams.push(entry.name);
|
|
86
87
|
}
|
|
@@ -5,17 +5,17 @@ import crypto from 'crypto';
|
|
|
5
5
|
const homeDir = os.homedir();
|
|
6
6
|
|
|
7
7
|
export const PATHS = {
|
|
8
|
-
OPENCODE_DIR: path.join(homeDir, '.opencode'),
|
|
9
|
-
AGENTS_DIR: path.join(homeDir, '.opencode/agents'),
|
|
10
8
|
OPENTEAM_DIR: path.join(homeDir, '.openteam'),
|
|
11
9
|
SETTINGS: path.join(homeDir, '.openteam', 'settings.json'),
|
|
10
|
+
AGENTS_DEFS_DIR: path.join(homeDir, '.openteam', 'agents'), // agent 定义(所有团队共享)
|
|
11
|
+
SKILLS_DIR: path.join(homeDir, '.openteam', 'skills'), // skill 定义
|
|
12
|
+
TEAMS_DIR: path.join(homeDir, '.openteam', 'teams'), // 团队配置 + 运行时状态
|
|
12
13
|
};
|
|
13
14
|
|
|
14
15
|
export const FILES = {
|
|
15
16
|
TEAM_CONFIG: 'team.json',
|
|
16
17
|
STATE: '.state.json',
|
|
17
|
-
RUNTIME: '.runtime.json',
|
|
18
|
-
ACTIVE_SESSIONS: '.active-sessions.json',
|
|
18
|
+
RUNTIME: '.runtime.json', // 迁移兼容用
|
|
19
19
|
TASKS: '.tasks.json',
|
|
20
20
|
};
|
|
21
21
|
|
|
@@ -26,7 +26,7 @@ export const DEFAULTS = {
|
|
|
26
26
|
};
|
|
27
27
|
|
|
28
28
|
export function getTeamDir(teamName) {
|
|
29
|
-
return path.join(PATHS.
|
|
29
|
+
return path.join(PATHS.TEAMS_DIR, teamName);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
/**
|
|
@@ -38,10 +38,10 @@ export function projectDirHash(projectDir) {
|
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
40
|
* 获取团队的项目级状态目录(纯计算,不创建)
|
|
41
|
-
* 路径: ~/.
|
|
41
|
+
* 路径: ~/.openteam/teams/<teamName>/<hash>/
|
|
42
42
|
*/
|
|
43
43
|
export function getTeamStateDir(teamName, projectDir) {
|
|
44
|
-
return path.join(PATHS.
|
|
44
|
+
return path.join(PATHS.TEAMS_DIR, teamName, projectDirHash(projectDir));
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
/**
|