xuym-codex-collab 0.10.0 → 0.10.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/README.md CHANGED
@@ -15,6 +15,8 @@ codex-collab setup
15
15
 
16
16
  安装后,先运行 `codex-collab setup`。已有业务项目时,进入项目目录再运行可自动带入信息;没有本机 clone 时可让向导拉取。以后直接运行 `codex-collab` 也会打开向导。上面的 `--foreground-scripts` 会让 npm 显示安装完成后的下一步提示。安装过程不会自动打开浏览器或修改项目;未使用此参数时 npm 可能隐藏提示,使用 `--ignore-scripts` 时会跳过提示,都可以直接运行 `codex-collab setup` 开始。
17
17
 
18
+ 升级 npm 包后重新运行 `codex-collab setup`,向导会检查后台服务版本,自动切换到本次安装的版本并打开新链接。同版本重复打开会复用现有服务,已有项目配置、对话绑定和独立运行的机器人连接保留。
19
+
18
20
  向导每屏只处理一个动作。管理员选择“接入一个项目”,确认目录和自动识别的分支、验证命令,再填写 Bot ID/Secret;成员选择“接受同事的邀请”,读取接入包后确认项目目录。群身份验证、等待审批和本机配置会自动衔接。最后复制指令到指定 Codex 对话,页面依据实际绑定和轮询记录显示完成。已在使用旧配置的成员可在首页“其他接入方式”中选择“导入已有配置”。命令启动后台工作台后退出,可以关闭终端和浏览器。macOS 默认设置登录自启;Linux 尝试配置 systemd 用户服务,失败时明确提示。工作台每 15 秒检查一次已开启的机器人进程,意外退出时恢复并在失败后退避重试;手动停止的连接保持停止。
19
21
 
20
22
  | 谁 | 在工作台做什么 | 接入后需要保留什么 |
@@ -26,7 +28,7 @@ codex-collab setup
26
28
 
27
29
  协作远程与业务基准分支自动识别;不要求 `origin/main`。有多个不同远程或默认分支不明确时,管理员选择一次。成员与新增机器人继承签名团队基准,各自可使用不同远程别名。每个新任务固定创建时的 commit,排队和重试期间不会漂移;管理员可在团队设置里为后续任务修改基准分支。
28
30
 
29
- 工作台首页按项目汇总,同一仓库的多个本机机器人共用一张项目卡片。打开项目后,所有团队机器人展示在同一列表里,自己的标记“我的”;本机已接入的机器人可继续配置或管理,其他机器人的职责来自签名团队记录。分支、诊断、重置和团队分配设置按需展开。首页以“让 Codex 组队工作”为入口:在企业微信群里连接你和同事的 Codex 会话,按角色分工、接力完成任务。界面提供纸白、夜色两种配色,以及从本机加载的 Three.js 星尘背景、轻微鼠标视差与短尾迹;触屏和系统减少动态效果设置下显示静态星点,页面进入后台时暂停动画。
31
+ 工作台首页按项目汇总,同一仓库的多个本机机器人共用一张项目卡片。打开项目后,所有团队机器人展示在同一列表里,自己的标记“我的”;本机已接入的机器人可继续配置或管理,其他机器人的职责来自签名团队记录。分支、诊断、重置和团队分配设置按需展开。首页以“让 Codex 组队工作”为入口:在企业微信群里连接你和同事的 Codex 会话,按角色分工、接力完成任务。界面提供纸白、夜色两种配色,首次打开默认夜色,并记住用户的选择;背景使用从本机加载的 Three.js 星尘效果、轻微鼠标视差与短尾迹。触屏和系统减少动态效果设置下显示静态星点,页面进入后台时暂停动画。
30
32
 
31
33
  同一个真人可以拥有多个 Agent,每个 Agent 使用独立机器人、签名身份、业务 clone 和 Codex 对话。管理员在项目页点击“添加我的机器人”,验证同一个企业微信账号后自动接入,无需下载包或填写包密码;给同事分配时仍使用加密接入包,同事可以领取多份。相同 userid 不会合并 Agent 权限,审核通知会指明应使用的机器人。
32
34
 
@@ -8,8 +8,11 @@ import { SetupManager, savePrivate } from './manager.mjs';
8
8
  import { RunnerSupervisor } from './supervisor.mjs';
9
9
  import { workspaceHome } from './workspace.mjs';
10
10
  import { configureStartup, startupStatus } from './startup.mjs';
11
- import { alive } from '../peer/process.mjs';
11
+ import { alive, acquireRunnerControl } from '../peer/process.mjs';
12
12
  const entry = fileURLToPath(import.meta.url);
13
+ const { version } = JSON.parse(await readFile(new URL('../../package.json', import.meta.url), 'utf8'));
14
+ const runtime = { version, entry, node: process.execPath };
15
+ const sameRuntime = (state) => Object.entries(runtime).every(([key, value]) => state.runtime?.[key] === value);
13
16
  function openBrowser(url) {
14
17
  const command = process.platform === 'darwin' ? ['open', url] : process.platform === 'win32' ? ['cmd', '/c', 'start', '', url] : ['xdg-open', url];
15
18
  const child = spawn(command[0], command.slice(1), { stdio: 'ignore' }); child.on('error', () => {}); child.unref();
@@ -38,7 +41,7 @@ export async function runService({ home = workspaceHome() } = {}) {
38
41
  try {
39
42
  const manager = new SetupManager({ home });
40
43
  app = await startSetupServer({ manager });
41
- await lock.writeFile(JSON.stringify({ pid: process.pid, url: app.url, started_at: new Date().toISOString() }) + '\n'); await lock.close();
44
+ await lock.writeFile(JSON.stringify({ pid: process.pid, url: app.url, started_at: new Date().toISOString(), runtime }) + '\n'); await lock.close();
42
45
  const supervisor = new RunnerSupervisor(manager);
43
46
  const tick = async () => {
44
47
  try { await manager.exclusive(() => supervisor.tick()); } catch { /* Local diagnostics show missing/invalid profiles without logging secrets. */ }
@@ -53,9 +56,49 @@ export async function runService({ home = workspaceHome() } = {}) {
53
56
  return { ...app, stop };
54
57
  } catch (error) { await lock.close().catch(() => {}); await rm(path, { force: true }); if (app) await app.close(); throw error; }
55
58
  }
59
+ async function stopOutdatedService(home, state) {
60
+ // A private lock can outlive its process. Authenticate the loopback service
61
+ // before signalling a legacy PID; never terminate an unrelated process.
62
+ let verified = false;
63
+ try {
64
+ const url = new URL(state.url);
65
+ if (url.protocol !== 'http:' || url.hostname !== '127.0.0.1' || !url.port || url.username || url.password || !/^#[a-f0-9]{64}$/.test(url.hash)) throw new Error();
66
+ const response = await fetch(`${url.origin}/api/session`, {
67
+ method: 'POST', redirect: 'error', signal: AbortSignal.timeout(5000),
68
+ headers: { Origin: url.origin, 'Content-Type': 'application/json' },
69
+ body: JSON.stringify({ token: url.hash.slice(1) }),
70
+ });
71
+ verified = response.ok && (await response.json()).ok === true;
72
+ } catch { /* Keep private service URLs and network errors out of diagnostics. */ }
73
+ if (!verified) throw new Error('无法确认旧版向导服务的身份,请检查后台服务后重试。');
74
+ const current = await serviceState(home);
75
+ if (!current || current.pid !== state.pid || current.url !== state.url) return;
76
+ try { process.kill(state.pid, 'SIGTERM'); }
77
+ catch (error) { if (error.code !== 'ESRCH') throw error; }
78
+ for (let attempt = 0; attempt < 150; attempt++) {
79
+ if (!alive(state.pid)) return;
80
+ await delay(100);
81
+ }
82
+ throw new Error('旧版向导服务尚未退出,请稍后重新运行 codex-collab setup。');
83
+ }
56
84
  export async function runLocalSetup({ open = true, home = workspaceHome(), cwd = process.cwd(), foreground = false, startup = true } = {}) {
57
- home = resolve(home); await savePrivate(join(home, 'context.json'), { cwd: resolve(cwd) });
85
+ home = resolve(home); await mkdir(home, { recursive: true, mode: 0o700 });
86
+ // Serialize launchers so two setup commands cannot both stop a service that
87
+ // another launcher has just upgraded. Runner processes keep their own locks.
88
+ const release = await acquireRunnerControl(join(home, 'launcher'), entry);
89
+ try {
90
+ await savePrivate(join(home, 'context.json'), { cwd: resolve(cwd) });
91
+ return await launchLocalSetup({ open, home, foreground, startup });
92
+ } finally { await release(); }
93
+ }
94
+ async function launchLocalSetup({ open, home, foreground, startup }) {
58
95
  let state = await serviceState(home);
96
+ if (state && !sameRuntime(state)) {
97
+ await stopOutdatedService(home, state);
98
+ state = await serviceState(home);
99
+ if (state && !sameRuntime(state)) throw new Error('向导服务正在更新,请稍后重新运行 codex-collab setup。');
100
+ console.log('已更新本机向导服务。');
101
+ }
59
102
  if (foreground) {
60
103
  if (state) { console.log(`本机工作台已运行:${state.url}`); if (open) openBrowser(state.url); return; }
61
104
  const app = await runService({ home }); if (app) { console.log(`Codex Collab 工作台:${app.url}`); if (open) openBrowser(app.url); } return app;
@@ -611,7 +611,7 @@ function initializeAppearance() {
611
611
  for (const choice of document.querySelectorAll('[data-theme-choice]')) choice.setAttribute('aria-pressed', String(choice.dataset.themeChoice === theme));
612
612
  try { localStorage.setItem('codex-collab-theme', theme); } catch {}
613
613
  }
614
- let theme = 'day';
614
+ let theme = 'night';
615
615
  try { theme = new URL(location.href).searchParams.get('theme') || localStorage.getItem('codex-collab-theme') || theme; } catch {}
616
616
  apply(theme);
617
617
  for (const choice of document.querySelectorAll('[data-theme-choice]')) choice.onclick = () => apply(choice.dataset.themeChoice);
@@ -9,7 +9,7 @@
9
9
  <link rel="stylesheet" href="/style.css">
10
10
  <link rel="stylesheet" href="/effects.css">
11
11
  </head>
12
- <body data-theme="day">
12
+ <body data-theme="night">
13
13
  <div id="ambient" aria-hidden="true">
14
14
  <div class="ambient-light light-one"></div>
15
15
  <div class="ambient-light light-two"></div>
@@ -664,10 +664,10 @@
664
664
  <footer class="demo-footer">
665
665
  <span>企业微信 · 本机协作</span>
666
666
  <div class="theme-picker" role="group" aria-label="页面配色">
667
- <button class="theme-choice" data-theme-choice="day" aria-pressed="true">
667
+ <button class="theme-choice" data-theme-choice="day" aria-pressed="false">
668
668
  <i aria-hidden="true">
669
669
  </i>纸白</button>
670
- <button class="theme-choice" data-theme-choice="night" aria-pressed="false">
670
+ <button class="theme-choice" data-theme-choice="night" aria-pressed="true">
671
671
  <i aria-hidden="true">
672
672
  </i>夜色</button>
673
673
  </div>
@@ -0,0 +1,16 @@
1
+ # 0.10.1
2
+
3
+ 修复升级 npm 包后重新打开向导时,旧后台服务仍被复用,导致星尘资源无法加载、页面样式不完整的问题。旧服务持有旧的路由表,却读取安装目录里更新后的 HTML,新增资源会返回 401 或 404。
4
+
5
+ - 后台服务记录启动时的软件版本、入口和 Node 路径。setup 在复用服务前检查这些信息;旧版本和未记录版本的服务会经过本机令牌验证,再正常退出并由新版替换。
6
+ - 并发执行多个 setup 会串行处理升级,同版本继续复用同一个服务。无法确认旧服务身份或旧服务未及时退出时停止升级,不强制结束进程。
7
+ - 服务切换保留项目配置、Codex 对话绑定、机器人运行意图和独立 Runner;打开向导时使用切换后的链接。
8
+
9
+ 升级后运行:
10
+
11
+ ```sh
12
+ npm install --global xuym-codex-collab@0.10.1 --prefer-online
13
+ codex-collab setup
14
+ ```
15
+
16
+ 回归覆盖旧服务迁移、版本不一致时替换、并发打开、同版本复用,以及无法验证身份时保留原进程;检查升级后的全部星尘与 Three.js 资源可访问,业务 API 仍要求登录。
@@ -0,0 +1,10 @@
1
+ # 0.10.2:向导默认夜色主题
2
+
3
+ 向导首次打开时默认显示夜色,减少首次进入时的主题选择步骤。夜色和纸白的手动切换保持不变,选择保存在本机浏览器;URL 中有效的 `theme=day` 或 `theme=night` 仍会优先应用。历史上已移除的主题值继续回退到纸白。
4
+
5
+ 升级:
6
+
7
+ ```sh
8
+ npm install --global xuym-codex-collab@0.10.2 --prefer-online
9
+ codex-collab setup
10
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xuym-codex-collab",
3
- "version": "0.10.0",
3
+ "version": "0.10.2",
4
4
  "description": "Git-backed collaboration tools for Codex teams, with MCP, WeCom, and local runners.",
5
5
  "repository": {
6
6
  "type": "git",