ai_co_discuss 0.2.85 → 0.2.87

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/backend/server.py CHANGED
@@ -1851,7 +1851,7 @@ class DiscussionHandler(BaseHTTPRequestHandler):
1851
1851
  """Forward authenticated browser requests to the shared Node entrypoint and pin their website."""
1852
1852
  site = self.local_request_site(method)
1853
1853
  route = parts[2:]
1854
- if method == "OPTIONS" and route in (["settings"], ["launch"], ["room-models"], ["room-member"], ["room-member-access"], ["room-profiles"], ["room-restore"], ["models"], ["api-key"], ["api-check"],
1854
+ if method == "OPTIONS" and route in (["settings"], ["launch"], ["room-models"], ["room-member"], ["room-member-access"], ["room-member-adapter"], ["room-profiles"], ["room-restore"], ["models"], ["api-key"], ["api-check"],
1855
1855
  ["scheduler-check"], ["pick-directory"], ["projects"], ["machine-link"]):
1856
1856
  return self.respond({"ok": True})
1857
1857
  if route == ["settings"] and method in ("GET", "POST"):
@@ -1870,7 +1870,7 @@ class DiscussionHandler(BaseHTTPRequestHandler):
1870
1870
  command = "api-check"
1871
1871
  elif route == ["scheduler-check"] and method == "POST":
1872
1872
  command = "scheduler-check"
1873
- elif route in (["room-member"], ["room-member-access"]) and method == "POST":
1873
+ elif route in (["room-member"], ["room-member-access"], ["room-member-adapter"]) and method == "POST":
1874
1874
  command = route[0]
1875
1875
  elif route in (["room-profiles"], ["room-restore"]) and method == "POST":
1876
1876
  command = route[0]
@@ -1887,7 +1887,7 @@ class DiscussionHandler(BaseHTTPRequestHandler):
1887
1887
  if self.headers.get_content_type() != "application/json":
1888
1888
  raise ValueError("本机操作须使用 application/json")
1889
1889
  payload = self.read_payload()
1890
- if command in ("start", "room-config", "room-models", "room-member", "room-member-access", "room-profiles", "room-restore"):
1890
+ if command in ("start", "room-config", "room-models", "room-member", "room-member-access", "room-member-adapter", "room-profiles", "room-restore"):
1891
1891
  payload.pop("access_file", None)
1892
1892
  payload.update(site=site, entry="web")
1893
1893
  if command == "machine-link":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai_co_discuss",
3
- "version": "0.2.85",
3
+ "version": "0.2.87",
4
4
  "bin": {
5
5
  "codiscuss": "runner/codiscuss.mjs"
6
6
  },
@@ -53,6 +53,7 @@ const HELP = `用法:codiscuss launch settings [--file FILE]
53
53
  codiscuss launch scheduler-check --file FILE
54
54
  codiscuss launch room-member --file FILE
55
55
  codiscuss launch room-member-access --file FILE
56
+ codiscuss launch room-member-adapter --file FILE
56
57
  codiscuss launch room-profiles --file FILE
57
58
  codiscuss launch room-restore --file FILE
58
59
  codiscuss launch pick-directory
@@ -62,7 +63,7 @@ const HELP = `用法:codiscuss launch settings [--file FILE]
62
63
  settings 读取本机软件默认配置;指定文件时保存,不启动 AI。
63
64
  start 使用启动 JSON 创建或接入房间并启动;--dry-run 只校验和预览。
64
65
  room-config 修正已暂停房间的软件路径或切换 Codex App / CLI,不启动讨论;须提供 site、room_id、token。
65
- room-models 只读指定 site、room_id 的本机登记模型与思考强度,不启动讨论。
66
+ room-models 只读指定 site、room_id 的本机登记模型与思考强度,不启动讨论;quick 为 true 时不查询 agy 的默认模型(较慢),该成员标 defaults_pending。
66
67
  models 按 adapter、executable 或 API profile 读取模型目录,不启动讨论或生成回答。
67
68
  api-key 将 base_url、api_key 保存于本机私有目录,只返回 api_key_ref。
68
69
  api-check 用 API profile 向模型发送一次极短请求,核对地址、密钥、协议与模型是否可用。
@@ -72,6 +73,7 @@ projects 读取本机已配置的项目(名称与绝对路径);提供 JSON
72
73
  machine-link 返回本机连接指定 site 的电脑编号、名称与凭据,供已登录管理员的网页完成配对;不存在时创建。
73
74
  room-member 为运行中的自由房间或讨论邀请、请回一位成员(讨论只接受本机唤醒的成员);须提供 site、room_id、token、action(add/rejoin)、name。
74
75
  room-member-access 修改一位本机唤醒成员的项目文件权限(project_access 为 read/write),下次投递起生效、保留原会话;须提供 site、room_id、token、name、project_access。
76
+ room-member-adapter 把自由房间里空闲的 Codex 成员在 App(codex)与 CLI(codex_cli)间切换,保留原会话;须提供 site、room_id、token、name、adapter。
75
77
  room-profiles 导出房间的本机成员软件配置与实际会话 ID(不含凭据),供备份附带;须提供 site、room_id、token。
76
78
  room-restore 把导入房间的本机成员配置登记到本机并核对原会话存在,Claude Code 成员保持手动;须提供 site、room_id、token、local_members。
77
79
  JSON 指定 site、entry(conversation/web)、room(建房内容,含 submission_id)
@@ -242,7 +244,10 @@ export function launch_settings(home) {
242
244
  return existsSync(path) ? JSON.parse(readFileSync(path, 'utf8')) : { participants: {} };
243
245
  }
244
246
 
245
- /** Read the registered room's model choices, what each software selects for empty ones, and its smart scheduler, never connection secrets. */
247
+ /**
248
+ * Read the registered room's model choices, what each software selects for empty ones, and its smart scheduler, never connection secrets.
249
+ * With `quick`, agy's default model is not queried and its entry is marked `defaults_pending`.
250
+ */
246
251
  export async function room_models(home, raw, { read_defaults = software_defaults } = {}) {
247
252
  const input = launch_input({ ...raw, entry: 'web' });
248
253
  if (!input.room_id) throw new Error('读取本场模型须提供 room_id');
@@ -255,8 +260,10 @@ export async function room_models(home, raw, { read_defaults = software_defaults
255
260
  const profiles = config.participants || {};
256
261
  const participants = Object.fromEntries(await Promise.all(Object.entries(profiles).map(async ([name, profile]) => {
257
262
  const cli = ['codex_cli', 'claude', 'antigravity_cli', 'api'].includes(profile.adapter);
258
- const defaults = await read_defaults(profile);
259
- return [name, { adapter: profile.adapter, model: cli ? profile.model || '' : '',
263
+ // agy has no settings file and is run to name its default model, which takes seconds; a quick read leaves that to a later full read.
264
+ const pending = input.quick === true && profile.adapter === 'antigravity_cli';
265
+ const defaults = pending ? { model: '', effort: '' } : await read_defaults(profile);
266
+ return [name, { ...(pending ? { defaults_pending: true } : {}), adapter: profile.adapter, model: cli ? profile.model || '' : '',
260
267
  effort: EFFORT_LEVELS[profile.adapter] ? profile.effort || '' : '', default_model: defaults.model, default_effort: defaults.effort,
261
268
  project_access: profile.adapter === 'api' ? 'read' : profile.project_access || config.project_access || 'read',
262
269
  // An agy member resuming its own conversation keeps that conversation's project, so this tool cannot set its access.
@@ -589,6 +596,36 @@ export async function room_member_access(home, raw) {
589
596
  } finally { release(); }
590
597
  }
591
598
 
599
+ /**
600
+ * Switch an idle free-room Codex member between the App and the CLI, keeping its conversation: the App resumes the
601
+ * bound chat by its session, and the CLI resumes it only after the App has released it. Model and effort are chosen
602
+ * in the App, so switching to it drops them.
603
+ */
604
+ export async function room_member_adapter(home, raw) {
605
+ const { input, path, config: registered } = await member_room_context(home, raw);
606
+ const { name, adapter } = input;
607
+ if (registered.mode !== 'free') throw new Error('讨论进行中不能切换;结束讨论后再切换,或暂停后在“接入”页修正');
608
+ if (!CODEX_ADAPTERS.includes(adapter)) throw new Error('adapter 只能为 codex(App)或 codex_cli(CLI)');
609
+ const room = await site_request(input.site, `/api/rooms/${input.room_id}`, input.token);
610
+ const member = room.free.members.find(item => item.name === name);
611
+ if (member?.pending > 0 || ['dispatched', 'started'].includes(member?.execution?.status)) throw new Error(`${name} 正在处理请求,请等它完成或先停止`);
612
+ const release = acquire_lock(join(dirname(path), 'configuration-edit'));
613
+ try {
614
+ const config = configuration_snapshot(path).config;
615
+ const profile = config.participants?.[name];
616
+ if (!CODEX_ADAPTERS.includes(profile?.adapter)) throw new Error(`${name} 不是本机唤醒的 Codex 成员`);
617
+ if (profile.adapter === adapter) return { room_id: input.room_id, member: name, adapter };
618
+ const context = { config, path };
619
+ const next = { ...profile };
620
+ switch_codex_adapter(context, name, next, adapter);
621
+ if (adapter === 'codex') { delete next.model; delete next.effort; }
622
+ const updated = { ...config, participants: { ...config.participants, [name]: next } };
623
+ validate_config(updated, dirname(path));
624
+ atomic_json(path, updated);
625
+ return { room_id: input.room_id, member: name, adapter };
626
+ } finally { release(); }
627
+ }
628
+
592
629
  /** A Codex desktop chat is resumed through the app, any other Codex session through the CLI. */
593
630
  function codex_session_adapter(session_id, env = process.env) {
594
631
  const sessions = join(env.CODEX_HOME || join(homedir(), '.codex'), 'sessions');
@@ -1025,6 +1062,7 @@ export async function launch_main(args = process.argv.slice(2), input_text) {
1025
1062
  }
1026
1063
  else if (positionals[0] === 'room-member' && input) result = await room_member(home, input);
1027
1064
  else if (positionals[0] === 'room-member-access' && input) result = await room_member_access(home, input);
1065
+ else if (positionals[0] === 'room-member-adapter' && input) result = await room_member_adapter(home, input);
1028
1066
  else if (positionals[0] === 'room-profiles' && input) result = await room_profiles(home, input);
1029
1067
  else if (positionals[0] === 'room-restore' && input) result = await restore_room(home, input);
1030
1068
  else if (positionals[0] === 'pick-directory') result = await pick_directory();