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 +2 -2
- package/package.json +1 -1
- package/runner/discussion_adapters.mjs +10 -5
- package/runner/discussion_runner.mjs +4 -4
- package/web/app.js +9 -1
- package/web/modern.css +1 -1
- package/web/modern.js +17 -11
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
|
|
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
|
|
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
|
@@ -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;
|
|
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 ((
|
|
157
|
-
|
|
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:
|
|
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
|
|
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:
|
|
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();
|