ai_co_discuss 0.2.24 → 0.2.26

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
@@ -283,7 +283,7 @@ agy 只用一套方法控制权限:
283
283
 
284
284
  1. **只在本工具新建的会话中执行**:每位参与者对应一个独立原生讨论项目,权限只写在该项目里。外部已有会话(包括从当前 agy 对话发起主持、在启动配置中填写 `session_id`)会恢复其原项目,无法套用讨论项目权限,因此启动时即拒绝,交付时也再次拒绝。
285
285
  2. **项目内规则**:allow 为受限的讨论命令规则与草稿目录写入(`write` 时加项目目录写入);deny 为项目目录写入(`read` 时)、运行目录、技能目录、`~/.gemini` 写入与 `mcp(*)`。规则按目录前缀递归匹配,不使用 `/**`;Deny > Ask > Allow,因此草稿不得位于受保护目录中,冲突在交付前报告。
286
- 3. **交付前检查全局设置**:CLI 的 `toolPermission` 须未设置或为 `request-review`;CLI 与共享全局 allow 中不得有任何 `command(...)`、`unsandboxed(...)`、`unsandboxed_command(...)` 或 `*` 放行,否则拒绝交付,因为全局放行会让 shell 绕过项目的文件规则。未授权 shell 在非交互模式下被拒绝。
286
+ 3. **交付前检查全局设置**:CLI 与共享全局 allow 中若有 `*`、`command(*)`、`unsandboxed(*)` 或 `unsandboxed_command(*)` 这类放行全部命令的规则,则拒绝交付,因为它会让 shell 绕过项目的文件规则。具体命令的放行和 `toolPermission` 不检查,属于用户自己的授权。未授权 shell 在非交互模式下被拒绝。
287
287
  4. **不改用户全局设置**:本工具不向 agy 全局设置写入任何规则;`setup` 只移除早期版本记录在 `agy-permission.json` 中的全局规则(先备份到服务目录 `agy-settings.backup.json`),其他设置与用户规则不变。
288
288
 
289
289
  原生工具权限不是操作系统沙箱;工具被拒绝时 agy 仍可能退出码为 0,验收以实际文件和网站结果为准。[原生权限文档](https://antigravity.google/docs/permissions?tab=cli)、[CLI 设置](https://antigravity.google/docs/settings?tab=cli)。
@@ -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.24",
3
+ "version": "0.2.26",
4
4
  "bin": {
5
5
  "codiscuss": "runner/codiscuss.mjs"
6
6
  },
@@ -148,14 +148,13 @@ export function agy_rule() {
148
148
  /** Read optional native settings; malformed settings must not silently disable a permission check. */
149
149
  function read_json(path) { return existsSync(path) ? JSON.parse(readFileSync(path, 'utf8')) : {}; }
150
150
 
151
- /** Native headless review blocks unapproved shell commands; any global command grant could bypass the project rules. */
151
+ /** Native headless review blocks unapproved shell commands; only a global grant of every command could bypass the project rules. */
152
152
  function check_agy_shell(user_home) {
153
153
  const cli = read_json(join(user_home, '.gemini', 'antigravity-cli', 'settings.json'));
154
154
  const shared = read_json(join(user_home, '.gemini', 'config', 'config.json'));
155
155
  const grants = [...(cli.permissions?.allow || []), ...(shared.userSettings?.globalPermissionGrants?.allow || [])];
156
- if ((cli.toolPermission && cli.toolPermission !== 'request-review')
157
- || grants.some(rule => rule === '*' || /^(?:command|unsandboxed|unsandboxed_command)\(/.test(rule))) {
158
- throw new Error('agy 全局设置放行了命令或改了 toolPermission,会绕过讨论项目的权限;请移除全局 command / unsandboxed 放行并保留 request-review(本工具旧版写入的规则可运行 codiscuss setup 清理),未交付讨论任务');
156
+ if (grants.some(rule => rule === '*' || /^(?:command|unsandboxed|unsandboxed_command)\((?:\*|regex:\.\*)\)$/.test(rule))) {
157
+ throw new Error('agy 全局设置放行了所有命令,会绕过讨论项目的权限;请移除全局 command(*) / unsandboxed(*) 放行,具体命令的放行不受影响(本工具旧版写入的规则可运行 codiscuss setup 清理),未交付讨论任务');
159
158
  }
160
159
  }
161
160
 
@@ -380,6 +379,12 @@ function cli_event(adapter, state, event, task) {
380
379
  return;
381
380
  }
382
381
  if (event.event === 'init') bind_cli_session(state, event.conversation_id, task);
382
+ const step = event.step_update;
383
+ // Headless agy ends its turn when a command needs approval; remember which one so the pause can name it.
384
+ if (step?.state === 'ERROR' && /permission/.test(step.tool_info?.error?.message || '')) {
385
+ const command = step.tool_info.parameters?.CommandLine || step.tool_name || '';
386
+ state.denial = `运行了未授权的命令「${String(command).slice(0, 120)}」,无人值守模式下无法确认权限而被自动拒绝,随后会话结束`;
387
+ }
383
388
  if (event.event === 'result') {
384
389
  const result = event.result || {};
385
390
  if (result.conversation_id) bind_cli_session(state, result.conversation_id, task);
@@ -443,7 +448,7 @@ function monitor_cli(child, config, binding, task) {
443
448
  if (!state.spawned) return;
444
449
  const error = cli_exit_error(state, code, signal);
445
450
  if (error) task.on_failure(error);
446
- else task.on_exit?.();
451
+ else task.on_exit?.(state.denial);
447
452
  });
448
453
  return state;
449
454
  }
@@ -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
@@ -1607,6 +1607,14 @@ function participant_model(name, room, saved) {
1607
1607
  return `${model} · ${effort}`;
1608
1608
  }
1609
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
+
1610
1618
  // App and web clients keep model selection in the original conversation.
1611
1619
  const MODEL_CLIENTS = ["codex_cli", "claude", "antigravity_cli"];
1612
1620
  const MODEL_SOURCE_LABELS = { "codex-app-server": "来自 Codex CLI 模型列表", "claude-cli": "来自 Claude CLI 模型列表", "antigravity-cli": "来自 Antigravity CLI 模型列表" };
@@ -2995,7 +3003,7 @@ window.discuss_view = { snapshot: view_snapshot, write_field: write_view_field,
2995
3003
  attachment_markup, attachment_size, attachment_file_icon, drop_attachments, paste_attachments,
2996
3004
  write_expert: write_view_expert, add_instance: add_view_instance, platform_of, execute: execute_view_action, theme: change_view_theme,
2997
3005
  request: request_json, identities: IDENTITIES, display_name, sources: SOURCE_LABELS, phases: PHASE_LABELS,
2998
- runtime_label, execution_label, limits_label, participant_model,
3006
+ runtime_label, execution_label, limits_label, participant_model, participant_access,
2999
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 };
3000
3008
 
3001
3009
  restore_theme();