xuym-codex-collab 0.5.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/README.md +7 -3
- package/apps/onboarding/bundle.mjs +1 -6
- package/apps/onboarding/guided.mjs +2 -2
- package/apps/onboarding/manager.mjs +73 -48
- package/apps/onboarding/projects.mjs +99 -0
- package/apps/onboarding/server.mjs +2 -1
- package/apps/onboarding/web/app.js +141 -12
- package/apps/onboarding/web/index.html +8 -6
- package/apps/onboarding/web/style.css +14 -10
- package/apps/peer/desktop.mjs +4 -3
- package/apps/peer/mcp.mjs +1 -1
- package/apps/peer/runner.mjs +4 -3
- package/apps/peer/runtime.mjs +6 -1
- package/apps/peer/wecom.mjs +1 -1
- package/docs/desktop-poll.md +2 -2
- package/docs/onboarding-audit.md +12 -1
- package/docs/onboarding.md +30 -6
- package/docs/peer.md +4 -2
- package/docs/team-registry.md +7 -1
- package/docs/terminal-onboarding.md +3 -3
- package/package.json +1 -1
- package/packages/codex-kit/templates/.agents/skills/team-collab/SKILL.md +2 -2
- package/packages/codex-kit/templates/.agents/skills/team-collab-desktop/SKILL.md +2 -2
- package/packages/peer/config.mjs +2 -0
- package/packages/peer/git.mjs +14 -0
- package/packages/peer/protocol.mjs +4 -2
- package/packages/peer/repository.mjs +67 -0
- package/packages/peer/service.mjs +11 -8
- package/packages/peer/store.mjs +16 -16
- package/schemas/peer-task.schema.json +15 -13
- package/schemas/team-policy.schema.json +12 -1
- package/scripts/peer/cli.mjs +2 -5
- package/scripts/peer/init-agent.mjs +6 -15
- package/scripts/peer/onboarding.mjs +26 -20
- package/scripts/peer/refresh-project.mjs +1 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { inspectRepository, resolveRemote, teamBaseBranch } from '../../packages/peer/repository.mjs';
|
|
1
2
|
import { createPublicKey } from 'node:crypto';
|
|
2
3
|
import { spawnSync } from 'node:child_process';
|
|
3
4
|
import { emitKeypressEvents } from 'node:readline';
|
|
@@ -62,10 +63,11 @@ export async function getOrCreateIdentityFiles(agentId, directory) {
|
|
|
62
63
|
return { agent_id: agentId, private_key_file: privatePath, public_key_file: publicPath };
|
|
63
64
|
}
|
|
64
65
|
|
|
65
|
-
export function buildTeamPolicy({ repo, reviewer, defaultRoute, agents, routes, policy = {} }) {
|
|
66
|
+
export function buildTeamPolicy({ repo, reviewer, defaultRoute, agents, routes, policy = {}, baseBranch }) {
|
|
66
67
|
const team = validateTeam({
|
|
67
68
|
schema_version: 2,
|
|
68
69
|
repo,
|
|
70
|
+
...(baseBranch !== undefined ? { base_branch: baseBranch } : {}),
|
|
69
71
|
reviewer,
|
|
70
72
|
default_route: defaultRoute,
|
|
71
73
|
agents: Object.fromEntries(agents.map((agent) => [agent.id, {
|
|
@@ -95,7 +97,7 @@ export async function writeTeamPolicy(path, team) {
|
|
|
95
97
|
return path;
|
|
96
98
|
}
|
|
97
99
|
|
|
98
|
-
export async function createMemberConfig({ teamPath, identityPath, repoPath, dataDir, configPath, testArgv = ['sh', 'test.sh'] }) {
|
|
100
|
+
export async function createMemberConfig({ teamPath, identityPath, repoPath, dataDir, configPath, testArgv = ['sh', 'test.sh'], remoteName, remoteUrl }) {
|
|
99
101
|
for (const path of [teamPath, identityPath, repoPath, dataDir, configPath]) {
|
|
100
102
|
if (!path || !isAbsolute(path)) throw new Error('team, identity, repository, data and config paths must be absolute');
|
|
101
103
|
}
|
|
@@ -109,13 +111,13 @@ export async function createMemberConfig({ teamPath, identityPath, repoPath, dat
|
|
|
109
111
|
if (await git(repo, 'rev-parse', '--show-toplevel') !== repo) throw new Error('select the root directory of a Git project');
|
|
110
112
|
const privatePaths = [dataDir, configPath, identityPath];
|
|
111
113
|
if (privatePaths.some((path) => isWithin(repoPath, path) || isWithin(repo, path))) throw new Error('private identity, data and config files must stay outside the code repository');
|
|
112
|
-
const
|
|
113
|
-
const main = await git(repo, 'ls-remote', '--heads', 'origin', 'refs/heads/main');
|
|
114
|
-
if (!main) throw new Error('the shared origin must have a readable main branch');
|
|
114
|
+
const selected = await inspectRepository(repo, { remoteName, expectedRemote: remoteUrl, baseBranch: teamBaseBranch(team) });
|
|
115
115
|
const config = {
|
|
116
116
|
schema_version: 2,
|
|
117
117
|
agent_id: identity.agent_id,
|
|
118
118
|
repo,
|
|
119
|
+
remote_name: selected.remote_name,
|
|
120
|
+
remote_url: selected.remote,
|
|
119
121
|
data_dir: dataDir,
|
|
120
122
|
team_file: teamPath,
|
|
121
123
|
private_key: identity.private_key,
|
|
@@ -133,29 +135,32 @@ export async function createMemberConfig({ teamPath, identityPath, repoPath, dat
|
|
|
133
135
|
await rm(configPath, { force: true });
|
|
134
136
|
throw error;
|
|
135
137
|
}
|
|
136
|
-
return { config, team, origin };
|
|
138
|
+
return { config, team, origin: selected.remote };
|
|
137
139
|
}
|
|
138
140
|
|
|
139
141
|
export async function diagnosePeer(configPath, { testPush = false } = {}) {
|
|
140
|
-
const { config, team } = await readPeerConfig(configPath);
|
|
141
|
-
const
|
|
142
|
-
const
|
|
143
|
-
if (!
|
|
144
|
-
const
|
|
142
|
+
const { config, team: cachedTeam } = await readPeerConfig(configPath);
|
|
143
|
+
const remote = await resolveRemote(config.repo, { remoteName: config.remote_name, expectedRemote: config.remote_url, legacyOrigin: !config.remote_name && !config.remote_url });
|
|
144
|
+
const ledger = await git(config.repo, 'ls-remote', '--heads', remote.remote_name, 'refs/heads/agent-collab');
|
|
145
|
+
if (config.team_registry_root && !ledger) throw new Error('signed team ledger is missing; restore agent-collab before continuing');
|
|
146
|
+
const team = config.team_registry_root ? (await loadRuntime(configPath)).store.team : cachedTeam;
|
|
147
|
+
const selected = await inspectRepository(config.repo, { remoteName: remote.remote_name, expectedRemote: remote.remote, baseBranch: teamBaseBranch(team) });
|
|
145
148
|
let writeAccess = 'not checked';
|
|
146
149
|
if (testPush) {
|
|
147
150
|
const probe = `refs/heads/codex-collab-doctor/${process.pid}-${Date.now()}`;
|
|
148
|
-
await git(config.repo, 'push', '--dry-run',
|
|
151
|
+
await git(config.repo, 'push', '--dry-run', selected.remote_name, `HEAD:${probe}`);
|
|
149
152
|
writeAccess = 'push dry-run passed; no remote ref was created';
|
|
150
153
|
}
|
|
151
154
|
return {
|
|
152
155
|
agent_id: config.agent_id,
|
|
153
156
|
mode: config.execution_mode,
|
|
154
|
-
origin,
|
|
157
|
+
origin: selected.remote,
|
|
158
|
+
remote_name: selected.remote_name,
|
|
159
|
+
base_branch: selected.base_branch,
|
|
155
160
|
team_repo: team.repo,
|
|
156
161
|
checks: {
|
|
157
162
|
identity: 'valid; private key matches team public key',
|
|
158
|
-
repository:
|
|
163
|
+
repository: `Git root with readable ${selected.remote_name}/${selected.base_branch}`,
|
|
159
164
|
ledger: ledger ? 'agent-collab exists' : 'not initialized; first start may create it',
|
|
160
165
|
push_access: writeAccess,
|
|
161
166
|
wecom: config.wecom ? 'configured; authentication requires starting the adapter' : 'not configured',
|
|
@@ -305,8 +310,9 @@ async function createAdminTeam(prompts, output) {
|
|
|
305
310
|
const reviewer = await prompts.ask('人工审核管理员 agent ID', id);
|
|
306
311
|
const defaultRoute = await prompts.ask('默认任务能力标签', capabilities[0]);
|
|
307
312
|
const repo = await prompts.required('共享代码仓库标识(例如 owner/repo)');
|
|
313
|
+
const baseBranch = await prompts.ask('业务基准分支(供所有成员继承;网页向导可自动识别)', 'main');
|
|
308
314
|
const teamPath = resolve(await prompts.ask('团队策略文件路径', join(homedir(), 'codex-collab-team.json')));
|
|
309
|
-
const team = buildTeamPolicy({ repo, reviewer, defaultRoute, agents, routes });
|
|
315
|
+
const team = buildTeamPolicy({ repo, reviewer, defaultRoute, agents, routes, baseBranch });
|
|
310
316
|
await writeTeamPolicy(teamPath, team);
|
|
311
317
|
output.write(`\n团队策略已创建:${teamPath}\n只分发此 team.json;各成员仍须在自己的电脑运行 setup 并使用自己的私钥。\n`);
|
|
312
318
|
return { teamPath, identityPath: identityPaths.private_key_file, agentId: id };
|
|
@@ -335,7 +341,7 @@ async function configureWeCom(prompts, configPath, config, team, output) {
|
|
|
335
341
|
}
|
|
336
342
|
|
|
337
343
|
async function joinTeam(prompts, output, initial = {}) {
|
|
338
|
-
output.write('\n
|
|
344
|
+
output.write('\n成员接入:私钥留在本机;共享仓库必须允许读取团队基准分支,并具备推送协作分支的权限。\n\n');
|
|
339
345
|
const teamPath = resolve(initial.teamPath || await prompts.required('管理员提供的 team.json 路径'));
|
|
340
346
|
const identityPath = resolve(initial.identityPath || await prompts.required('本机私有身份文件路径'));
|
|
341
347
|
const identity = await readPrivateJson(identityPath);
|
|
@@ -347,10 +353,10 @@ async function joinTeam(prompts, output, initial = {}) {
|
|
|
347
353
|
let parsedArgv;
|
|
348
354
|
try { parsedArgv = JSON.parse(rawArgv); }
|
|
349
355
|
catch { throw new Error('固定验证命令必须是 JSON 字符串数组,例如 ["npm", "test"]'); }
|
|
350
|
-
const { config, team
|
|
351
|
-
output.write(`\n身份 ${agentId}
|
|
356
|
+
const { config, team } = await createMemberConfig({ teamPath, identityPath, repoPath, dataDir, configPath, testArgv: parsedArgv });
|
|
357
|
+
output.write(`\n身份 ${agentId} 已验证;共享远程:${config.remote_name};基准分支:${teamBaseBranch(team)}\n执行模式:desktop_poll(不启动后台 Codex SDK)\n`);
|
|
352
358
|
|
|
353
|
-
if (await prompts.confirm('把本项目接入 Codex MCP + Skill?(只写本机 project config
|
|
359
|
+
if (await prompts.confirm('把本项目接入 Codex MCP + Skill?(只写本机 project config)', true)) {
|
|
354
360
|
const attached = await attachProject({ configPath, repoPath: config.repo });
|
|
355
361
|
output.write(`MCP 配置:${attached.mcp_config}\nSkill:${attached.skill}\n`);
|
|
356
362
|
}
|
|
@@ -361,7 +367,7 @@ async function joinTeam(prompts, output, initial = {}) {
|
|
|
361
367
|
const diagnosis = await diagnosePeer(configPath, { testPush: true });
|
|
362
368
|
output.write('\n连接检查:\n');
|
|
363
369
|
for (const [name, result] of Object.entries(diagnosis.checks)) output.write(`- ${name}: ${result}\n`);
|
|
364
|
-
if (await prompts.confirm('现在初始化/同步共享 agent-collab
|
|
370
|
+
if (await prompts.confirm('现在初始化/同步共享 agent-collab 账本分支?首次初始化会向所选协作远程推送该分支。', true)) {
|
|
365
371
|
await loadRuntime(configPath);
|
|
366
372
|
output.write('共享账本分支已就绪。\n');
|
|
367
373
|
}
|
|
@@ -6,7 +6,7 @@ import { readPeerConfig } from '../../apps/peer/runtime.mjs';
|
|
|
6
6
|
import { git } from '../../packages/peer/store.mjs';
|
|
7
7
|
import { bindingPrompt } from './setup-desktop-project.mjs';
|
|
8
8
|
const root = fileURLToPath(new URL('../../', import.meta.url));
|
|
9
|
-
const legacyHashes = new Set(['46543ca4fe00836f0ae0d7d7c12ea2756c9ab0220f01d29c391d608c5e405c53', '6e4d1ea6a644fb3078c541e40bcc9e8bf5069462994152f3ec2fc3aa5fedd5fa', 'f98ad425e2ea3c8ab6fb86febef7d9cb288029ef55bea6937dda6de8b467fece']);
|
|
9
|
+
const legacyHashes = new Set(['f0f5f51ec7f2e0952d15e079d37e9f00b958ac7fda0d819c0a20e4ed620abaff', '46543ca4fe00836f0ae0d7d7c12ea2756c9ab0220f01d29c391d608c5e405c53', '6e4d1ea6a644fb3078c541e40bcc9e8bf5069462994152f3ec2fc3aa5fedd5fa', 'f98ad425e2ea3c8ab6fb86febef7d9cb288029ef55bea6937dda6de8b467fece']);
|
|
10
10
|
const hash = (value) => createHash('sha256').update(value).digest('hex');
|
|
11
11
|
const inside = (parent, child) => { const path = relative(parent, child); return !path || path !== '..' && !path.startsWith('../') && !isAbsolute(path); };
|
|
12
12
|
async function plainPath(base, path, { allowMissing = false } = {}) {
|