ai_co_discuss 0.2.23 → 0.2.25

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/docs/runner.md CHANGED
@@ -294,7 +294,7 @@ Codex 要求 CLI 0.163.0 或更新版本,使用 permission profiles 和本地
294
294
 
295
295
  2026-10-18 本机实测(本轮权限调整前):Claude 主持、Codex 与 Gemini 专家完成一场最小讨论。Windows 的 Codex / agy 经 PowerShell 调用时优先执行 npm 生成的 `codiscuss.ps1`;Codex elevated 沙箱内即使当前用户为 RemoteSigned 也拒绝 `.ps1`,Codex 自行改用 `cmd.exe` 调用 `codiscuss.cmd` 后成功。`agy` 非交互模式下被拒绝的工具也可能伴随退出码 0,最终仍以网站业务保存为准。`agy` 显式使用 `--print-timeout 0`,由房间的单次执行时限和整场截止控制停止,避免额外的 CLI 默认时限提前截断。
296
296
 
297
- 结构化失败(Codex `turn.failed`、Antigravity 非 `SUCCESS` 结果)、非零退出码、缺少会话 ID 或成功结束事件均记录为失败;软件已启动后不自动重发。正常退出后若网站尚未保存本次结果,复查后暂停。同一参与者兼任主持与专家时,主持保存安排后进程可能仍在退出;该参与者由本服务创建的进程全部退出前不交付下一次执行,避免续接同一会话时出现两个写入者。暂停或停止接入服务会终止本服务创建的 CLI 进程,保留会话绑定和本机日志。
297
+ 结构化失败(Codex `turn.failed`、Antigravity 非 `SUCCESS` 结果)、非零退出码、缺少会话 ID 或成功结束事件均记录为失败;软件已启动后不自动重发。正常退出后若网站尚未保存本次结果,复查后暂停。agy 无人值守时遇到未放行的命令会自动拒绝并直接结束会话;接入程序记录被拒的命令,并写入暂停原因,任务提示也要求 agy 仅用 `run_command` 运行 codiscuss 子命令、浏览项目改用 `list_dir` 等工具。同一参与者兼任主持与专家时,主持保存安排后进程可能仍在退出;该参与者由本服务创建的进程全部退出前不交付下一次执行,避免续接同一会话时出现两个写入者。暂停或停止接入服务会终止本服务创建的 CLI 进程,保留会话绑定和本机日志。
298
298
 
299
299
  2026-10-19 权限调整验证:Windows / Codex CLI 0.163.0 的本地配置检查采用了本次 profile;以原生 `sandbox`(`unelevated`)运行无模型的文件操作,确认项目 cwd 保持、中文文件可读、只读模式拒绝项目与运行目录写入、可写模式允许项目写入但仍拒绝运行目录写入,两种模式均可写草稿并访问本机 HTTP。此证据不覆盖 elevated 模式、其他平台或本轮调整后的真实模型讨论。
300
300
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai_co_discuss",
3
- "version": "0.2.23",
3
+ "version": "0.2.25",
4
4
  "bin": {
5
5
  "codiscuss": "runner/codiscuss.mjs"
6
6
  },
@@ -380,6 +380,12 @@ function cli_event(adapter, state, event, task) {
380
380
  return;
381
381
  }
382
382
  if (event.event === 'init') bind_cli_session(state, event.conversation_id, task);
383
+ const step = event.step_update;
384
+ // Headless agy ends its turn when a command needs approval; remember which one so the pause can name it.
385
+ if (step?.state === 'ERROR' && /permission/.test(step.tool_info?.error?.message || '')) {
386
+ const command = step.tool_info.parameters?.CommandLine || step.tool_name || '';
387
+ state.denial = `运行了未授权的命令「${String(command).slice(0, 120)}」,无人值守模式下无法确认权限而被自动拒绝,随后会话结束`;
388
+ }
383
389
  if (event.event === 'result') {
384
390
  const result = event.result || {};
385
391
  if (result.conversation_id) bind_cli_session(state, result.conversation_id, task);
@@ -443,7 +449,7 @@ function monitor_cli(child, config, binding, task) {
443
449
  if (!state.spawned) return;
444
450
  const error = cli_exit_error(state, code, signal);
445
451
  if (error) task.on_failure(error);
446
- else task.on_exit?.();
452
+ else task.on_exit?.(state.denial);
447
453
  });
448
454
  return state;
449
455
  }
@@ -197,7 +197,7 @@ export function task_files(config, event, token, binding) {
197
197
  project_name: adapter.project_name || null, cwd: adapter.cwd || null, project_access: adapter.project_access,
198
198
  draft_directory,
199
199
  role: event.role, turn_id: event.turn_id, skills, cli, subcommands: TASK_SUBCOMMANDS, access_file: access_path,
200
- instructions: `读取指定技能;网站命令只用 ${cli} 的 subcommands 子命令,命令以 ${cli} 开头,不用 python discuss.py、cd、管道或其他入口;${auto_enter ? '接入程序已进入本次执行,不要再执行 execution enter。' : `使用 ${cli} execution enter 房间 执行编号 --access-file 凭据路径 进入。进入成功后才处理本次安排;`}凭据文件由 CLI 内部读取,不要打印或读取其 token。每次业务写入都带 --access-file。答案草稿只保存到 draft_directory,不修改任务、凭据、日志或调度数据库;${project_rule}只处理本次工作,不建定时任务。主持创建下一安排或收束即结束,专家提交原文后结束。无效或已进入的任务不要重复执行。` });
200
+ instructions: `${adapter.adapter === 'antigravity_cli' ? `无人值守下 run_command 只能运行 ${cli} 的 subcommands 子命令,其他命令会被自动拒绝并使会话直接结束;浏览项目用 list_dir、find_by_name、grep_search、view_file。` : ''}读取指定技能;网站命令只用 ${cli} 的 subcommands 子命令,命令以 ${cli} 开头,不用 python discuss.py、cd、管道或其他入口;${auto_enter ? '接入程序已进入本次执行,不要再执行 execution enter。' : `使用 ${cli} execution enter 房间 执行编号 --access-file 凭据路径 进入。进入成功后才处理本次安排;`}凭据文件由 CLI 内部读取,不要打印或读取其 token。每次业务写入都带 --access-file。答案草稿只保存到 draft_directory,不修改任务、凭据、日志或调度数据库;${project_rule}只处理本次工作,不建定时任务。主持创建下一安排或收束即结束,专家提交原文后结束。无效或已进入的任务不要重复执行。` });
201
201
  const read_rule = windows_rg() ? '读取含中文的技能或文本文件时用 rg --encoding utf-8 . 文件路径,不用 Get-Content(Windows PowerShell 会按系统编码读出乱码,需要反复重试)。' : '';
202
202
  const scope = `本次只放行任务内 subcommands 列出的 ${cli} 子命令,命令以 ${cli} 开头,不用 python discuss.py、cd、管道或其他入口。任务内 cwd 是讨论项目目录;${project_rule}项目读写仍受软件实际权限和议题授权约束。不读取凭据内容。${read_rule}`;
203
203
  const plan_rule = event.role === 'host' ? '安排专家回应时先写发言计划,用 turn create --plan-file 一次排满本轮每位专家,不要每次只排一位。' : '';
@@ -217,7 +217,7 @@ export class DiscussionRunner {
217
217
  this.store = store;
218
218
  this.deliver = deliver;
219
219
  this.children = new Set();
220
- this.exited = new Set();
220
+ this.exited = new Map();
221
221
  this.running = new Map();
222
222
  this.seen = new Set();
223
223
  this.blocked = new Set();
@@ -262,7 +262,7 @@ export class DiscussionRunner {
262
262
  on_process: child => this.track(event.participant, child),
263
263
  on_output: (path, chunk) => appendFileSync(path, chunk, { mode: 0o600 }),
264
264
  on_failure: error => { try { this.store.record(row.id, { error }); } catch { /* Store may already be closed at shutdown. */ } },
265
- on_exit: () => this.exited.add(row.id),
265
+ on_exit: detail => this.exited.set(row.id, detail || ''),
266
266
  });
267
267
  if (result.session_id) this.store.bind(event.participant, result.session_id);
268
268
  this.store.record(row.id, { status: result.status, receipt: result.receipt || '' });
@@ -311,7 +311,7 @@ export class DiscussionRunner {
311
311
  if (this.exited.has(row.id) && ['dispatched', 'started'].includes(event.status)) {
312
312
  // A business write may have just completed the task; recheck before declaring a silent exit.
313
313
  const latest = await api(this.config, `/events/${row.id}`, { action: 'view' });
314
- if (['dispatched', 'started'].includes(latest.status)) return this.fail(row, `${event.participant} 会话已结束,但未保存本次结果`);
314
+ if (['dispatched', 'started'].includes(latest.status)) return this.fail(row, `${event.participant} 会话已结束,但未保存本次结果${this.exited.get(row.id) ? `:${this.exited.get(row.id)}` : ''}`);
315
315
  return this.reconcile(row, latest);
316
316
  }
317
317
  if (event.status === 'started' && event.lease_until <= Math.floor(Date.now() / 1000)) return this.fail(row, '执行租约到期;旧会话不能继续写入');
package/web/app.js CHANGED
@@ -1568,6 +1568,14 @@ async function load_launch_settings() {
1568
1568
  finally { refresh_launch_recoveries(); render_launch_settings(); publish_view(); }
1569
1569
  }
1570
1570
 
1571
+ /** Re-check the local connection and explain when nothing has been connected yet. */
1572
+ async function retry_launch_connection() {
1573
+ await load_launch_settings();
1574
+ if (state.launch.available || state.launch.error) return;
1575
+ state.launch.error = "尚未检测到本机连接,请先在运行 AI 的电脑上执行上方命令并完成网页授权。";
1576
+ publish_view();
1577
+ }
1578
+
1571
1579
  /** Read saved choices for the active room without using drafts or delaying discussion refreshes. */
1572
1580
  async function load_room_models() {
1573
1581
  const room = state.room;
@@ -1599,6 +1607,14 @@ function participant_model(name, room, saved) {
1599
1607
  return `${model} · ${effort}`;
1600
1608
  }
1601
1609
 
1610
+ /** Label how a member answers: the registered adapter says API / App; other locally woken members are CLI. */
1611
+ function participant_access(name, room) {
1612
+ const adapter = (room?.members || []).find(member => member.name === name)?.adapter;
1613
+ if (adapter === "api") return "API";
1614
+ if (["codex", "antigravity"].includes(adapter)) return "App";
1615
+ return room?.experts?.[name] === "cli" ? "CLI" : "在线";
1616
+ }
1617
+
1602
1618
  // App and web clients keep model selection in the original conversation.
1603
1619
  const MODEL_CLIENTS = ["codex_cli", "claude", "antigravity_cli"];
1604
1620
  const MODEL_SOURCE_LABELS = { "codex-app-server": "来自 Codex CLI 模型列表", "claude-cli": "来自 Claude CLI 模型列表", "antigravity-cli": "来自 Antigravity CLI 模型列表" };
@@ -2960,7 +2976,7 @@ async function execute_view_action(name, value) {
2960
2976
  take_floor: () => change_floor("take"), return_floor: () => change_floor("return"),
2961
2977
  copy_online: () => copy_context(), copy_cli: () => copy_context("cli"), copy_background,
2962
2978
  choose_runtime, export_runtime, download_member_access, copy_member_join, pause_runtime, check_limits, pause_discussion, restart_discussion, force_close, conclude_discussion,
2963
- save_launch_defaults, launch_current_room, resume_launch, discard_launch, retry_launch_connection: load_launch_settings,
2979
+ save_launch_defaults, launch_current_room, resume_launch, discard_launch, retry_launch_connection, check_launch_connection: load_launch_settings,
2964
2980
  load_room_configuration, save_room_configuration, interrupt_member, change_member, invite_member, interrupt_free, set_proxy, copy_online_notice,
2965
2981
  prepare_brainstorm, return_conclusion, convert_suggestion,
2966
2982
  copy_reading: () => copy_text(`${location.origin}/r/${state.room.id}`), copy: copy_text };
@@ -2987,7 +3003,7 @@ window.discuss_view = { snapshot: view_snapshot, write_field: write_view_field,
2987
3003
  attachment_markup, attachment_size, attachment_file_icon, drop_attachments, paste_attachments,
2988
3004
  write_expert: write_view_expert, add_instance: add_view_instance, platform_of, execute: execute_view_action, theme: change_view_theme,
2989
3005
  request: request_json, identities: IDENTITIES, display_name, sources: SOURCE_LABELS, phases: PHASE_LABELS,
2990
- runtime_label, execution_label, limits_label, participant_model,
3006
+ runtime_label, execution_label, limits_label, participant_model, participant_access,
2991
3007
  turns: TURN_LABELS, turn_label, turn_guidance, floor_status, progress_round, progress_speaker, brief_meta, max_message_length: MAX_MESSAGE_LENGTH, provenance: message_provenance, format_time };
2992
3008
 
2993
3009
  restore_theme();