ai_co_discuss 0.2.79 → 0.2.81
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/.agents/skills/discuss-host/SKILL.md +1 -0
- package/package.json +1 -1
- package/runner/discussion_adapters.mjs +5 -0
- package/runner/discussion_launch.mjs +13 -11
- package/runner/discussion_models.mjs +54 -13
- package/runner/discussion_runner.mjs +2 -2
- package/runner/expert_prompts.mjs +4 -3
- package/runner/free_runner.mjs +2 -2
- package/web/app.js +9 -5
- package/web/markdown.css +1 -1
- package/web/modern.css +1 -1
- package/web/modern.js +598 -598
|
@@ -65,6 +65,7 @@ description: "AI CoDiscuss 的主持技能,仅主持人调用,且须同时
|
|
|
65
65
|
- 计划须包含 `waiting` 中的每位专家;`unregistered` 中尚未注册的手动专家不能排入,它注册后会自动加入本轮队列。名单变化后 `revision` 失效需重取,缺人或未带 `revision` 都会被拒绝;本轮不需要某人时先让其暂离。
|
|
66
66
|
- 计划只覆盖当前一轮,跨轮追问要看本轮回答后再定;只有各轮问题不依赖回答的固定流程(如依次报数)才可排满多轮。后一位的问题取决于前一位回答时也不必等前一位答完:把问题写成“先读取最新回答,再按……回应”,由专家自己读取。
|
|
67
67
|
- 网站在专家间自动接力,末位答完才交回主持;接力不发通知。用户留言或打断时服务端会自动把控制权交回主持。只有在线专家须逐个转交、或必须等用户先决定时,才只排一位,并在 state 里写明原因。
|
|
68
|
+
- `members` 中 `adapter` 为 `api` 的专家读不到本地文件,只能从对话了解项目。议题涉及本地文件时:开场的主持说明多交代项目情况(相关文件、结构、要点,必要时贴关键片段);`initial` 中它不读他人观点,靠这份介绍作答;进入交锋后把它排在能读文件的专家之后,问题里请它先读前面的回答;它在回答里索要的信息,安排能读文件的专家补充。
|
|
68
69
|
|
|
69
70
|
### 名单变化
|
|
70
71
|
|
package/package.json
CHANGED
|
@@ -18,6 +18,11 @@ const CODEX_PERMISSIONS_MIN_MINOR = 163;
|
|
|
18
18
|
/** Website subcommands an automatic task may run; service, launch, run, api and setup stay unapproved. */
|
|
19
19
|
export const TASK_SUBCOMMANDS = ['room', 'message', 'turn', 'state', 'execution', 'health', 'schema'];
|
|
20
20
|
|
|
21
|
+
/** The agy project allows only codiscuss commands and headless agy ends the turn on a denied one, so every agy task states it; no member prompt can drop it. */
|
|
22
|
+
export function agy_command_rule(adapter, cli) {
|
|
23
|
+
return adapter === 'antigravity_cli' ? `无人值守下 run_command 只能运行 ${cli} 的 subcommands 子命令,其他命令会被自动拒绝并使会话直接结束;列目录、搜索或读取文件用 list_dir、find_by_name、grep_search、view_file。` : '';
|
|
24
|
+
}
|
|
25
|
+
|
|
21
26
|
/** Only failures before a software invocation are safe for automatic redelivery. */
|
|
22
27
|
function not_sent(error) { return Object.assign(error, { delivery_status: 'not_sent' }); }
|
|
23
28
|
|
|
@@ -9,7 +9,7 @@ import { pathToFileURL } from 'node:url';
|
|
|
9
9
|
import { parseArgs } from 'node:util';
|
|
10
10
|
|
|
11
11
|
import { atomic_json } from './discussion_adapters.mjs';
|
|
12
|
-
import { software_effort, software_models } from './discussion_models.mjs';
|
|
12
|
+
import { software_defaults, software_effort, software_models } from './discussion_models.mjs';
|
|
13
13
|
import { API_PROVIDERS, api_check, api_models, save_api_key, saved_api_keys, validate_api_profile } from './discussion_api.mjs';
|
|
14
14
|
import { SCHEDULER_PROVIDERS, scheduler_check } from './scheduler.mjs';
|
|
15
15
|
import { MAX_EXPERT_PROMPT } from './expert_prompts.mjs';
|
|
@@ -200,7 +200,7 @@ export function pick_directory({ platform = process.platform, spawn_process = sp
|
|
|
200
200
|
const MODEL_CACHE_MS = 24 * 60 * 60 * 1000;
|
|
201
201
|
|
|
202
202
|
/** Return the software catalog, served from the private service home when still valid. */
|
|
203
|
-
export async function cached_software_models(home, profile, { refresh = false, query = software_models, query_effort = software_effort } = {}) {
|
|
203
|
+
export async function cached_software_models(home, profile, { refresh = false, query = software_models, query_effort = software_effort, query_defaults = software_defaults } = {}) {
|
|
204
204
|
const path = join(home, 'model-catalogs.json');
|
|
205
205
|
let stamp = '';
|
|
206
206
|
try { const file = statSync(profile.executable); stamp = `${file.size}:${file.mtimeMs}`; } catch {}
|
|
@@ -209,8 +209,9 @@ export async function cached_software_models(home, profile, { refresh = false, q
|
|
|
209
209
|
let cache = {};
|
|
210
210
|
try { cache = JSON.parse(readFileSync(path, 'utf8')); } catch {}
|
|
211
211
|
const cached = entry => !refresh && entry && Date.now() - entry.at < MODEL_CACHE_MS ? entry.result : null;
|
|
212
|
-
|
|
213
|
-
|
|
212
|
+
// Defaults are read on every request, so a changed software setting shows without waiting out the cache.
|
|
213
|
+
const [result, effort, defaults] = await Promise.all([cached(cache[key]) || query(profile),
|
|
214
|
+
profile.adapter === 'antigravity_cli' ? cached(cache[effort_key]) || query_effort(profile) : null, query_defaults(profile)]);
|
|
214
215
|
const updates = {};
|
|
215
216
|
if (!cached(cache[key]) && !result.error && result.models.length) {
|
|
216
217
|
updates[key] = { at: Date.now(), result };
|
|
@@ -224,7 +225,7 @@ export async function cached_software_models(home, profile, { refresh = false, q
|
|
|
224
225
|
for (const old of Object.keys(cache)) if (old.startsWith(prefix) && !old.startsWith(key.slice(0, -1))) delete cache[old];
|
|
225
226
|
atomic_json(path, { ...cache, ...updates });
|
|
226
227
|
}
|
|
227
|
-
return effort ? {
|
|
228
|
+
return { ...result, defaults, ...(effort ? { efforts: effort.levels, ...(effort.error ? { effort_error: effort.error } : {}) } : {}) };
|
|
228
229
|
}
|
|
229
230
|
|
|
230
231
|
/** Read private defaults without selecting a global room or identity. */
|
|
@@ -233,8 +234,8 @@ export function launch_settings(home) {
|
|
|
233
234
|
return existsSync(path) ? JSON.parse(readFileSync(path, 'utf8')) : { participants: {} };
|
|
234
235
|
}
|
|
235
236
|
|
|
236
|
-
/** Read
|
|
237
|
-
export function room_models(home, raw) {
|
|
237
|
+
/** Read the registered room's model choices and what each software selects for empty ones, never connection secrets. */
|
|
238
|
+
export async function room_models(home, raw, { read_defaults = software_defaults } = {}) {
|
|
238
239
|
const input = launch_input({ ...raw, entry: 'web' });
|
|
239
240
|
if (!input.room_id) throw new Error('读取本场模型须提供 room_id');
|
|
240
241
|
const paths = read_registry(home).filter(path => {
|
|
@@ -243,11 +244,12 @@ export function room_models(home, raw) {
|
|
|
243
244
|
});
|
|
244
245
|
if (paths.length > 1) throw new Error('本场存在多份登记配置,无法确定模型设置');
|
|
245
246
|
const profiles = paths.length ? configuration_snapshot(paths[0]).config.participants : {};
|
|
246
|
-
const participants = Object.fromEntries(Object.entries(profiles).map(([name, profile]) => {
|
|
247
|
+
const participants = Object.fromEntries(await Promise.all(Object.entries(profiles).map(async ([name, profile]) => {
|
|
247
248
|
const cli = ['codex_cli', 'claude', 'antigravity_cli', 'api'].includes(profile.adapter);
|
|
249
|
+
const defaults = await read_defaults(profile);
|
|
248
250
|
return [name, { adapter: profile.adapter, model: cli ? profile.model || '' : '',
|
|
249
|
-
effort: EFFORT_LEVELS[profile.adapter] ? profile.effort || '' : '' }];
|
|
250
|
-
}));
|
|
251
|
+
effort: EFFORT_LEVELS[profile.adapter] ? profile.effort || '' : '', default_model: defaults.model, default_effort: defaults.effort }];
|
|
252
|
+
})));
|
|
251
253
|
return { room_id: input.room_id, participants };
|
|
252
254
|
}
|
|
253
255
|
|
|
@@ -886,7 +888,7 @@ export async function launch_main(args = process.argv.slice(2), input_text) {
|
|
|
886
888
|
else if (positionals[0] === 'api-key' && input) result = save_api_key(home, input);
|
|
887
889
|
else if (positionals[0] === 'api-check' && input) result = await api_check(home, input);
|
|
888
890
|
else if (positionals[0] === 'scheduler-check' && input) result = await scheduler_check(home, input);
|
|
889
|
-
else if (positionals[0] === 'room-models' && input) result = room_models(home, input);
|
|
891
|
+
else if (positionals[0] === 'room-models' && input) result = await room_models(home, input);
|
|
890
892
|
else if (positionals[0] === 'models' && input?.adapter === 'api') result = await api_models(home, input);
|
|
891
893
|
else if (positionals[0] === 'models' && input) {
|
|
892
894
|
const saved = Object.values(launch_settings(home).participants).find(profile => profile.adapter === input.adapter);
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/** Discover model selectors from the installed software without starting an AI turn. */
|
|
2
2
|
import { spawn } from 'node:child_process';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import { isAbsolute, join } from 'node:path';
|
|
5
|
+
import { homedir, tmpdir } from 'node:os';
|
|
5
6
|
import { createInterface } from 'node:readline';
|
|
6
7
|
|
|
7
8
|
const QUERY_TIMEOUT_MS = 30000;
|
|
@@ -9,6 +10,38 @@ const MAX_OUTPUT_BYTES = 1024 * 1024;
|
|
|
9
10
|
const SOURCES = { codex_cli: 'codex-app-server', claude: 'claude-cli', antigravity_cli: 'antigravity-cli' };
|
|
10
11
|
const MODEL_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/@[\]-]*$/;
|
|
11
12
|
|
|
13
|
+
/** Read the string keys of a TOML file by table; Codex settings need nothing richer. */
|
|
14
|
+
function toml_strings(text) {
|
|
15
|
+
const tables = { '': {} };
|
|
16
|
+
let table = '';
|
|
17
|
+
for (const line of text.split(/\r?\n/)) {
|
|
18
|
+
const header = line.match(/^\s*\[\s*([^\]]+?)\s*\]\s*(?:#.*)?$/);
|
|
19
|
+
if (header) { table = header[1].replaceAll('"', ''); tables[table] ||= {}; continue; }
|
|
20
|
+
const pair = line.match(/^\s*([A-Za-z0-9_-]+)\s*=\s*"([^"]*)"/);
|
|
21
|
+
if (pair) tables[table][pair[1]] = pair[2];
|
|
22
|
+
}
|
|
23
|
+
return tables;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* What Codex or Claude Code runs with when the card leaves model or effort empty, read from the software's own user settings.
|
|
28
|
+
* Empty where the settings say nothing.
|
|
29
|
+
*/
|
|
30
|
+
export function cli_defaults(adapter, { env = process.env, home = homedir() } = {}) {
|
|
31
|
+
try {
|
|
32
|
+
if (adapter === 'codex_cli') {
|
|
33
|
+
const tables = toml_strings(readFileSync(join(env.CODEX_HOME || join(home, '.codex'), 'config.toml'), 'utf8'));
|
|
34
|
+
const top = tables[''], chosen = top.profile ? tables[`profiles.${top.profile}`] || {} : {};
|
|
35
|
+
return { model: chosen.model || top.model || '', effort: chosen.model_reasoning_effort || top.model_reasoning_effort || '' };
|
|
36
|
+
}
|
|
37
|
+
if (adapter === 'claude') {
|
|
38
|
+
const settings = JSON.parse(readFileSync(join(env.CLAUDE_CONFIG_DIR || join(home, '.claude'), 'settings.json'), 'utf8'));
|
|
39
|
+
return { model: typeof settings.model === 'string' ? settings.model : '', effort: typeof settings.effortLevel === 'string' ? settings.effortLevel : '' };
|
|
40
|
+
}
|
|
41
|
+
} catch { /* Missing or unreadable settings mean the software's built-in default. */ }
|
|
42
|
+
return { model: '', effort: '' };
|
|
43
|
+
}
|
|
44
|
+
|
|
12
45
|
/** Return only model identifiers, display names and supported effort values. */
|
|
13
46
|
function model_entry(id, label, efforts) {
|
|
14
47
|
if (typeof id !== 'string' || !MODEL_PATTERN.test(id)) return null;
|
|
@@ -16,9 +49,9 @@ function model_entry(id, label, efforts) {
|
|
|
16
49
|
...(Array.isArray(efforts) ? { efforts: [...new Set(efforts.filter(item => typeof item === 'string'))] } : {}) };
|
|
17
50
|
}
|
|
18
51
|
|
|
19
|
-
/** Keep all query processes noninteractive and avoid user/project startup hooks. */
|
|
20
|
-
function query_arguments({ adapter, model },
|
|
21
|
-
if (
|
|
52
|
+
/** Keep all query processes noninteractive and avoid user/project startup hooks; agy's native slash commands answer without an agent turn. */
|
|
53
|
+
function query_arguments({ adapter, model }, command = '') {
|
|
54
|
+
if (command) return ['-p', `/${command}`, '--output-format', 'json', ...(command === 'effort' && model ? ['--model', model] : [])];
|
|
22
55
|
if (adapter === 'codex_cli') return ['app-server', '--listen', 'stdio://'];
|
|
23
56
|
if (adapter === 'antigravity_cli') return ['models'];
|
|
24
57
|
return ['--safe-mode', '-p', '--input-format', 'stream-json', '--output-format', 'stream-json',
|
|
@@ -30,7 +63,7 @@ function query_catalog(profile, options) {
|
|
|
30
63
|
return new Promise((resolve_result, reject) => {
|
|
31
64
|
const env = { ...process.env };
|
|
32
65
|
delete env.CLAUDECODE;
|
|
33
|
-
const child = (options.spawn || spawn)(profile.executable, query_arguments(profile, options.
|
|
66
|
+
const child = (options.spawn || spawn)(profile.executable, query_arguments(profile, options.command), {
|
|
34
67
|
cwd: tmpdir(), env, shell: false, windowsHide: true, stdio: ['pipe', 'pipe', 'pipe'],
|
|
35
68
|
});
|
|
36
69
|
let finished = false, bytes = 0;
|
|
@@ -42,7 +75,7 @@ function query_catalog(profile, options) {
|
|
|
42
75
|
const timer = setTimeout(() => finish(new Error('读取模型列表超时,请检查软件登录和网络后重试')), options.timeout_ms ?? QUERY_TIMEOUT_MS);
|
|
43
76
|
const lines = createInterface({ input: child.stdout });
|
|
44
77
|
const send = value => child.stdin.write(`${JSON.stringify(value)}\n`);
|
|
45
|
-
const receive = catalog_reader(profile.adapter, send, finish, options.
|
|
78
|
+
const receive = catalog_reader(profile.adapter, send, finish, options.command);
|
|
46
79
|
child.once('error', () => finish(new Error('无法启动模型查询,请检查 CLI 路径')));
|
|
47
80
|
child.stdin.on('error', () => finish(new Error('模型查询连接已关闭')));
|
|
48
81
|
child.stderr.on('data', () => {}); // CLI diagnostics can contain private account or configuration data.
|
|
@@ -94,12 +127,12 @@ function json_line(line) {
|
|
|
94
127
|
}
|
|
95
128
|
|
|
96
129
|
/** Claude initialization exposes its picker; agy emits tab-separated ids and labels. */
|
|
97
|
-
function catalog_reader(adapter, send, finish,
|
|
98
|
-
if (
|
|
130
|
+
function catalog_reader(adapter, send, finish, command = '') {
|
|
131
|
+
if (command) return { start: () => {}, line: line => {
|
|
99
132
|
const reply = json_line(line), data = reply?.command?.data;
|
|
100
|
-
if (reply?.status
|
|
101
|
-
|
|
102
|
-
}
|
|
133
|
+
if (reply?.status !== 'SUCCESS' || reply.command?.name !== command || !data) return;
|
|
134
|
+
if (command === 'effort' && Array.isArray(data.available)) finish(null, data.adjustable ? data.available.filter(level => typeof level === 'string') : []);
|
|
135
|
+
if (command === 'model') finish(null, { model: MODEL_PATTERN.test(data.id || '') ? data.id : '', effort: typeof data.effort === 'string' ? data.effort : '' });
|
|
103
136
|
} };
|
|
104
137
|
if (adapter === 'codex_cli') return codex_reader(send, finish);
|
|
105
138
|
if (adapter === 'claude') return {
|
|
@@ -137,6 +170,14 @@ export async function software_models(profile, options = {}) {
|
|
|
137
170
|
export async function software_effort(profile, options = {}) {
|
|
138
171
|
if (profile.adapter !== 'antigravity_cli' || !isAbsolute(profile.executable || '')
|
|
139
172
|
|| (profile.model && !MODEL_PATTERN.test(profile.model))) return { error: '无法读取所选模型的思考档位' };
|
|
140
|
-
try { return { levels: await query_catalog(profile, { ...options,
|
|
173
|
+
try { return { levels: await query_catalog(profile, { ...options, command: 'effort' }) }; }
|
|
141
174
|
catch { return { error: '无法读取原生思考档位;保留 CLI 默认值,或检查软件版本后重试' }; }
|
|
142
175
|
}
|
|
176
|
+
|
|
177
|
+
/** What a CLI runs with when the card leaves model or effort empty: agy answers its native /model, the others keep it in settings files. */
|
|
178
|
+
export async function software_defaults(profile, options = {}) {
|
|
179
|
+
if (profile.adapter !== 'antigravity_cli') return cli_defaults(profile.adapter);
|
|
180
|
+
if (!isAbsolute(profile.executable || '')) return { model: '', effort: '' };
|
|
181
|
+
try { return await query_catalog(profile, { ...options, command: 'model' }); }
|
|
182
|
+
catch { return { model: '', effort: '' }; }
|
|
183
|
+
}
|
|
@@ -7,7 +7,7 @@ import { DatabaseSync } from 'node:sqlite';
|
|
|
7
7
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
8
8
|
import { parseArgs } from 'node:util';
|
|
9
9
|
import { setTimeout as sleep } from 'node:timers/promises';
|
|
10
|
-
import { atomic_json, deliver_task, remove_agy_projects, setup_sidecar, sidecar_receipt, TASK_SUBCOMMANDS } from './discussion_adapters.mjs';
|
|
10
|
+
import { agy_command_rule, atomic_json, deliver_task, remove_agy_projects, setup_sidecar, sidecar_receipt, TASK_SUBCOMMANDS } from './discussion_adapters.mjs';
|
|
11
11
|
import { read_api_key } from './discussion_api.mjs';
|
|
12
12
|
import { with_expert_prompt } from './expert_prompts.mjs';
|
|
13
13
|
import { decide_interjection, decide_next_speaker, decide_round, validate_scheduler } from './scheduler.mjs';
|
|
@@ -231,7 +231,7 @@ export function task_files(config, event, token, binding, { suggestions = [], en
|
|
|
231
231
|
draft_directory, ...(suggestions.length ? { scheduler_suggestions: suggestions } : {}),
|
|
232
232
|
role: event.role, turn_id: event.turn_id, skills, cli, subcommands: TASK_SUBCOMMANDS,
|
|
233
233
|
...(bound_access ? { access_env: 'DISCUSS_ACCESS_FILE' } : { access_file: access_path }),
|
|
234
|
-
instructions:
|
|
234
|
+
instructions: `${agy_command_rule(adapter.adapter, cli)}读取指定技能;网站命令只用 ${cli} 的 subcommands 子命令,命令以 ${cli} 开头,不用 python discuss.py、cd、管道或其他入口;${auto_enter ? '接入程序已进入本次执行,不要再执行 execution enter。' : `使用 ${cli} execution enter 房间 执行编号 --access-file 凭据路径 进入。进入成功后才处理本次安排;`}凭据文件由 CLI 内部读取,不要打印或读取其 token。${access_rule}答案草稿只保存到 draft_directory,不修改任务、凭据、日志或调度数据库;${project_rule}只处理本次工作,不建定时任务。主持创建下一安排或收束即结束,专家提交原文后结束。无效或已进入的任务不要重复执行。${suggestion_rule}` });
|
|
235
235
|
const read_rule = windows_rg() ? '读取含中文的技能或文本文件时用 rg --encoding utf-8 . 文件路径,不用 Get-Content(Windows PowerShell 会按系统编码读出乱码,需要反复重试)。' : '';
|
|
236
236
|
const scope = `本次只放行任务内 subcommands 列出的 ${cli} 子命令,命令以 ${cli} 开头,不用 python discuss.py、cd、管道或其他入口。任务内 cwd 是讨论项目目录;${project_rule}项目读写仍受软件实际权限和议题授权约束。不读取凭据内容。${read_rule}`;
|
|
237
237
|
const plan_rule = event.role === 'host' ? '安排专家回应时先写发言计划,先用 turn roster 取得最新专家状态,再用 turn create --plan-file 加 --roster 一次排满 waiting 中的每位专家,不要每次只排一位。' : '';
|
|
@@ -7,9 +7,10 @@
|
|
|
7
7
|
export const MAX_EXPERT_PROMPT = 4000;
|
|
8
8
|
|
|
9
9
|
export const EXPERT_PROMPT_PRESETS = {
|
|
10
|
-
//
|
|
11
|
-
antigravity_cli:
|
|
12
|
-
|
|
10
|
+
// agy streams no thinking text, so without a word between tools the page cannot show how far it got.
|
|
11
|
+
antigravity_cli: '多步任务中,在关键转折处用一句话写出进展再继续。',
|
|
12
|
+
// An API expert sees only the room, so what it knows of local files comes from other members and it must ask for the rest.
|
|
13
|
+
api: '你读不到本地文件,相关内容以其他成员贴出的为准,不凭文件名猜测。缺信息时在回答里写明缺什么。',
|
|
13
14
|
};
|
|
14
15
|
|
|
15
16
|
/** The prompt this member receives: its own text when set, otherwise its software's preset. */
|
package/runner/free_runner.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { spawnSync } from 'node:child_process';
|
|
|
3
3
|
import { createHash, randomUUID } from 'node:crypto';
|
|
4
4
|
import { appendFileSync, mkdirSync, readFileSync, statSync } from 'node:fs';
|
|
5
5
|
import { basename, join, resolve } from 'node:path';
|
|
6
|
-
import { atomic_json, deliver_task, sidecar_receipt, TASK_SUBCOMMANDS } from './discussion_adapters.mjs';
|
|
6
|
+
import { agy_command_rule, atomic_json, deliver_task, sidecar_receipt, TASK_SUBCOMMANDS } from './discussion_adapters.mjs';
|
|
7
7
|
import { api } from './discussion_runner.mjs';
|
|
8
8
|
import { with_expert_prompt } from './expert_prompts.mjs';
|
|
9
9
|
import { ROUTE_CONFIDENCE, ROUTE_CONTEXT_MESSAGES, ROUTE_PROMPT_VERSION, ROUTE_WIDE_CONFIDENCE, decide_group_order, route_free_message } from './scheduler.mjs';
|
|
@@ -87,7 +87,7 @@ export function free_task_files(config, member, execution, binding) {
|
|
|
87
87
|
read_after: execution.read_until, cwd: profile.cwd || null, project_access: profile.project_access, draft_directory,
|
|
88
88
|
skills: [join(config.project, '.agents/skills/discuss-participant/SKILL.md')], cli, subcommands: TASK_SUBCOMMANDS,
|
|
89
89
|
...(bound_access ? { access_env: 'DISCUSS_ACCESS_FILE' } : { access_file: access_path }),
|
|
90
|
-
instructions: `${discussion ? '讨论房间:用户直接向你提问(' + messages + ')。这不是你的发言安排,不要领取发言权、不要安排他人,也不改变讨论进度,只回答这个提问。' : '自由协作房间:用户点名了你(' + messages + ')。'}${access_rule}先用 ${cli} message list ${config.room} --after ${execution.read_until}${access_arg} 读取新消息,按时间顺序处理,后面的消息可能修正前面的,以最新指令为准。${host_rule}完成后用 ${cli} message post ${config.room} --as ${member.name} --kind chat --execution ${execution.id} --body-file 草稿 --submission-id 新UUID${access_arg} 提交一次回答。提交被拒绝说明本次执行已被打断或过期:保留草稿并结束,不要去掉 --execution 改作普通发言。草稿只写入 draft_directory;${project_rule}不读取或打印凭据内容,不建定时任务。` });
|
|
90
|
+
instructions: `${agy_command_rule(profile.adapter, cli)}${discussion ? '讨论房间:用户直接向你提问(' + messages + ')。这不是你的发言安排,不要领取发言权、不要安排他人,也不改变讨论进度,只回答这个提问。' : '自由协作房间:用户点名了你(' + messages + ')。'}${access_rule}先用 ${cli} message list ${config.room} --after ${execution.read_until}${access_arg} 读取新消息,按时间顺序处理,后面的消息可能修正前面的,以最新指令为准。${host_rule}完成后用 ${cli} message post ${config.room} --as ${member.name} --kind chat --execution ${execution.id} --body-file 草稿 --submission-id 新UUID${access_arg} 提交一次回答。提交被拒绝说明本次执行已被打断或过期:保留草稿并结束,不要去掉 --execution 改作普通发言。草稿只写入 draft_directory;${project_rule}不读取或打印凭据内容,不建定时任务。` });
|
|
91
91
|
const prompt = `用户在${discussion ? '讨论' : '自由协作'}房间点名了你(${member.name}),对应消息 ${messages}。请先读取任务文件:${task_path},按其中说明读取新消息、完成请求,并用 ${cli} message post 带 --execution ${execution.id} 提交一次回答后结束。命令只用任务内 subcommands 列出的 ${cli} 子命令;${project_rule}不读取凭据内容。`;
|
|
92
92
|
return { id, participant: member.name, directory: config.directory, draft_directory,
|
|
93
93
|
skills_directory: join(config.project, '.agents', 'skills'), access_path, prompt: with_expert_prompt(prompt, profile) };
|
package/web/app.js
CHANGED
|
@@ -1889,15 +1889,17 @@ async function load_room_models() {
|
|
|
1889
1889
|
}
|
|
1890
1890
|
}
|
|
1891
1891
|
|
|
1892
|
-
/** Describe registered launch choices
|
|
1892
|
+
/** Describe registered launch choices; an empty choice shows what the software's own settings select, or says the software decides. */
|
|
1893
1893
|
function participant_model(name, room, saved) {
|
|
1894
1894
|
// Nothing to say beats a placeholder: a manual member's settings live in its own session and unread ones are not guessed.
|
|
1895
1895
|
if (room?.runtime?.mode !== "automatic" || (room.members || []).find(member => member.name === name)?.access === "manual") return "";
|
|
1896
1896
|
const profile = saved?.room_id === room.id ? saved.participants?.[name] : null;
|
|
1897
1897
|
if (!profile) return "";
|
|
1898
1898
|
if (["codex", "antigravity"].includes(profile.adapter)) return "模型与强度由 App 设置";
|
|
1899
|
-
const model = profile.model || "
|
|
1900
|
-
|
|
1899
|
+
const model = profile.model || profile.default_model || "软件默认模型";
|
|
1900
|
+
// agy's default effort belongs to its default model, so a chosen model keeps its own default instead.
|
|
1901
|
+
const default_effort = profile.adapter === "antigravity_cli" && profile.model ? "" : profile.default_effort;
|
|
1902
|
+
const effort = profile.effort || default_effort || (profile.adapter === "antigravity_cli" ? "模型默认强度" : "软件默认强度");
|
|
1901
1903
|
return `${model} · ${effort}`;
|
|
1902
1904
|
}
|
|
1903
1905
|
|
|
@@ -1953,9 +1955,10 @@ function launch_model_state(name, launch = state.launch) {
|
|
|
1953
1955
|
const catalog = launch.model_catalogs?.[model_catalog_key(profile)];
|
|
1954
1956
|
const models = catalog?.models || [];
|
|
1955
1957
|
const selected = models.find(model => model.id === profile.model);
|
|
1956
|
-
const
|
|
1958
|
+
const defaults = catalog?.defaults || {};
|
|
1959
|
+
const items = [{ label: defaults.model ? `软件默认(${defaults.model})` : "软件默认模型", value: "__default__" }, ...models.map(model => ({ label: model.label || model.id, value: model.id }))];
|
|
1957
1960
|
if (profile.model && !selected) items.push({ label: `${profile.model}(已保存,列表未确认)`, value: profile.model, disabled: true });
|
|
1958
|
-
return { supported: MODEL_CLIENTS.includes(profile.adapter), items, loading: Boolean(catalog?.loading),
|
|
1961
|
+
return { supported: MODEL_CLIENTS.includes(profile.adapter), items, defaults, loading: Boolean(catalog?.loading),
|
|
1959
1962
|
efforts: profile.adapter === "antigravity_cli" ? catalog?.efforts : selected?.efforts, source: MODEL_SOURCE_LABELS[catalog?.source] || catalog?.source || "",
|
|
1960
1963
|
effort_note: profile.adapter === "antigravity_cli" ? catalog?.effort_error || (catalog?.loading ? "正在读取原生思考档位…" : "默认强度由 CLI 决定。") : !selected
|
|
1961
1964
|
? "选择模型后显示可用思考强度;默认值由软件决定。" : "所选模型不提供单独思考强度。",
|
|
@@ -1982,6 +1985,7 @@ async function load_launch_models(name, force = false) {
|
|
|
1982
1985
|
current.models = result.models || [];
|
|
1983
1986
|
current.source = result.source || "";
|
|
1984
1987
|
current.efforts = result.efforts;
|
|
1988
|
+
current.defaults = result.defaults || {};
|
|
1985
1989
|
current.effort_error = result.effort_error || "";
|
|
1986
1990
|
current.error = result.error || (!current.models.length ? "软件未返回可用模型,可使用默认模型" : "");
|
|
1987
1991
|
} catch (error) { current.error = error.message; }
|