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.
@@ -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 origin = await git(repo, 'remote', 'get-url', 'origin');
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 origin = await git(config.repo, 'remote', 'get-url', 'origin');
142
- const main = await git(config.repo, 'ls-remote', '--heads', 'origin', 'refs/heads/main');
143
- if (!main) throw new Error('shared origin is missing refs/heads/main');
144
- const ledger = await git(config.repo, 'ls-remote', '--heads', 'origin', 'refs/heads/agent-collab');
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', 'origin', `HEAD:${probe}`);
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: 'Git root with readable origin/main',
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成员接入:私钥留在本机;共享仓库必须允许读取 main,并具备推送协作分支的权限。\n\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, origin } = await createMemberConfig({ teamPath, identityPath, repoPath, dataDir, configPath, testArgv: parsedArgv });
351
- output.write(`\n身份 ${agentId} 已验证;共享 origin:${origin}\n执行模式:desktop_poll(不启动后台 Codex SDK)\n`);
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,不改 origin)', true)) {
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 账本分支?首次初始化会向 origin 推送该分支。', true)) {
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 } = {}) {