ai_co_discuss 0.3.92 → 0.3.93

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai_co_discuss",
3
- "version": "0.3.92",
3
+ "version": "0.3.93",
4
4
  "bin": {
5
5
  "codiscuss": "runner/codiscuss.mjs"
6
6
  },
@@ -19,7 +19,8 @@ const AGY_MODEL_TAG = [0xe2, 0x01];
19
19
 
20
20
  /**
21
21
  * One operation a headless agy run refused, from the failed tool step, with the project grant that would allow it.
22
- * agy names the check that failed: a sandboxed or unsandboxed command, or a file write.
22
+ * agy names the check that failed: a sandboxed or unsandboxed command, a file write, or (1.5.3+) a file read outside
23
+ * the project folders.
23
24
  */
24
25
  export function agy_denial(step) {
25
26
  const message = step.tool_info?.error?.message || '';
@@ -28,9 +29,9 @@ export function agy_denial(step) {
28
29
  // Older agy did not name the check; a refused step with a command line was a command.
29
30
  const kind = message.match(/permission check failed for (\w+)/)?.[1] || (parameters.CommandLine ? 'command' : '');
30
31
  if (!kind) return null;
31
- if (kind === 'write_file') {
32
- const path = String(parameters.Path || parameters.TargetFile || message.match(/write_file\(([^)]+)\)/)?.[1] || '');
33
- return { tool: 'write_file', detail: path.slice(0, DENIAL_FIELD), rule: path ? `write_file(${resolve(dirname(path)).replaceAll('\\', '/')})` : '' };
32
+ if (kind === 'write_file' || kind === 'read_file') {
33
+ const path = String(parameters.Path || parameters.TargetFile || message.match(new RegExp(`${kind}\\(([^)]+)\\)`))?.[1] || '');
34
+ return { tool: kind, detail: path.slice(0, DENIAL_FIELD), rule: path ? `${kind}(${resolve(dirname(path)).replaceAll('\\', '/')})` : '' };
34
35
  }
35
36
  const command = String(parameters.CommandLine || '');
36
37
  const rule = `${kind === 'unsandboxed' ? 'unsandboxed_command' : 'command'}(${command})`;
@@ -53,6 +54,18 @@ function check_agy_shell(user_home) {
53
54
  }
54
55
  }
55
56
 
57
+ /**
58
+ * Skill folders agy loads by itself: its own folder and every entry in its global skills.json. agy shows the model these
59
+ * skills' paths, so a run must be able to read them (1.5.3 refuses reads outside the added directories); agy leaves ~/
60
+ * unexpanded on Windows.
61
+ */
62
+ export function agy_skill_directories(user_home = homedir()) {
63
+ const entries = read_json(join(user_home, '.gemini', 'config', 'skills.json')).entries;
64
+ const listed = (Array.isArray(entries) ? entries : []).map(entry => entry?.path).filter(value => typeof value === 'string')
65
+ .map(value => process.platform !== 'win32' && value.startsWith('~/') ? join(user_home, value.slice(2)) : value);
66
+ return [join(user_home, '.gemini', 'config', 'skills'), ...listed].filter(value => isAbsolute(value) && existsSync(value));
67
+ }
68
+
56
69
  /** Test directory containment on the current platform, including Windows drive boundaries. */
57
70
  function within_directory(parent, child) {
58
71
  const path = relative(resolve(parent), resolve(child));
@@ -94,7 +107,7 @@ function agy_project_grants(config, task, resources, user_home) {
94
107
  const read_only = (task.extra_directories || []).filter(item => item.access === 'read').map(item => item.path);
95
108
  const projects = [...new Set([project_directory(config), ...folders].filter(directory => directory && !same_path(directory, task.draft_directory)
96
109
  && !read_only.some(path => same_path(path, directory))))];
97
- const protected_paths = [task.directory, ...task.skill_directories, join(user_home, '.gemini'),
110
+ const protected_paths = [task.directory, ...task.skill_directories, ...agy_skill_directories(user_home), join(user_home, '.gemini'),
98
111
  ...(config.project_access === 'read' ? projects : []), ...read_only];
99
112
  if (protected_paths.some(directory => within_directory(directory, task.draft_directory))) {
100
113
  throw new Error('agy 草稿目录不能位于只读项目、运行目录、技能或 .gemini 配置目录中,未交付讨论任务');
@@ -156,7 +169,8 @@ export function remove_agy_projects(directory, user_home = homedir()) {
156
169
  function invocation(config, binding, task, user_home) {
157
170
  const project = config.session_id ? null : prepare_agy_project(config, binding, task, user_home);
158
171
  const args = ['--output-format', 'stream-json', '--print-timeout', '0', '--add-dir', task.directory,
159
- '--add-dir', task.draft_directory, ...[...task.skill_directories, ...(task.extra_directories || []).map(item => item.path)].flatMap(directory => ['--add-dir', directory])];
172
+ '--add-dir', task.draft_directory, ...[...task.skill_directories, ...agy_skill_directories(user_home),
173
+ ...(task.extra_directories || []).map(item => item.path)].flatMap(directory => ['--add-dir', directory])];
160
174
  // Headless agy refuses `--mode auto` ("not available for this account", 1.4.10–1.4.11) though its interactive window
161
175
  // offers it; 全放开 uses the flag its own denial message suggests on top of the open project grants.
162
176
  args.push('--mode', 'accept-edits', ...(config.project_access === 'full' ? ['--dangerously-skip-permissions'] : []),
@@ -176,7 +190,8 @@ function lifecycle(state, event, task, bind) {
176
190
  const denied = step?.state === 'ERROR' ? agy_denial(step) : null;
177
191
  if (denied) {
178
192
  if (!state.denials.some(item => item.detail === denied.detail) && state.denials.length < MAX_DENIALS) state.denials.push(denied);
179
- state.denial = `${denied.tool === 'write_file' ? '写入' : '运行'}了未授权的${denied.tool === 'write_file' ? '文件' : '命令'}「${denied.detail.slice(0, 120)}」,无人值守模式下无法确认权限而被自动拒绝,随后会话结束`;
193
+ const action = { write_file: '写入了未授权的文件', read_file: '读取了未授权的文件' }[denied.tool] || '运行了未授权的命令';
194
+ state.denial = `${action}「${denied.detail.slice(0, 120)}」,无人值守模式下无法确认权限而被自动拒绝,随后会话结束`;
180
195
  }
181
196
  if (event.event === 'result') {
182
197
  const result = event.result || {};