ai_co_discuss 0.1.8 → 0.1.10

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/README.md CHANGED
@@ -63,7 +63,7 @@ codiscuss setup
63
63
 
64
64
  1. 安装完成后进入自动打开的网站;以后运行 `codiscuss local open` 打开或复用本机网站,默认地址为 `http://127.0.0.1:8765`。
65
65
  2. 点击“新建讨论”,填写**话题名称**和**用户原文**。把问题、背景与顾虑写在原文中,讨论目标、背景和边界由 AI 主持人据此补全。
66
- 3. 选择“全自动讨论”,勾选本场专家并从中指定 AI 主持人;分别设置轮次和时长上限,点击数值展开滚轮或直接输入,默认 **3 轮 / 1 小时**。
66
+ 3. 选择“全自动讨论”,勾选本场专家并从中指定 AI 主持人;分别设置轮次和时长上限,点击数值展开滚轮或直接输入。新建默认 **3 轮 / 不限时长**;网页启动全自动讨论前需设置有限时长。
67
67
  4. 勾选“创建后立即启动 AI”,复核软件配置。需要修改时展开“调整启动配置”;未配置的软件需选择 CLI / App 类型并补齐路径。项目目录、读写权限和已有会话按本场需要填写。
68
68
  5. 点击“创建并启动讨论”。程序完成建房、登记与首次任务交付;在进度看板核对 AI 是否实际进入,并查看保存的发言。
69
69
 
@@ -133,7 +133,7 @@ codiscuss local connect https://你的站点
133
133
  | 软件原生定时 | AI 软件定时检查当前安排 | 选择“手动复制冷启动”,软件具备原生定时能力 |
134
134
  | 人工触发 | 用户要求对应会话继续 | 选择“手动复制冷启动”;在线回答需代发 |
135
135
 
136
- 默认上限为 **3 轮、1 小时、到限暂停**,可改为请主持人总结,并设置单次执行上限与收束时间。计数和停止规则见 [讨论上限](docs/workflow.md#讨论上限)。
136
+ 默认上限为 **3 轮、不限时长、到限暂停**,可改为请主持人总结,并设置单次执行上限与收束时间。计数和停止规则见 [讨论上限](docs/workflow.md#讨论上限)。
137
137
 
138
138
  讨论结束后,可在左上方频道条目的“更多操作 → 导出对话”下载原始数据和全文,窄屏从频道导航进入;经典主题使用“备份当前讨论”按钮。也可用 `discuss-report` 整理带来源的综合报告。备份目前不支持导入恢复,也不下载外部附件。
139
139
 
@@ -124,7 +124,7 @@ Python 在一个只读事务快照中查询;D1 使用同一个 batch 事务读
124
124
  | `ChannelDirectory.vue` | 频道、文件夹和存档管理 |
125
125
  | `ChannelDiscussion.vue` | 议题卡、消息流、原文导航和输入区 |
126
126
  | `TopicPanel.vue`、`WorkspaceDialogs.vue` | 话题资料、主持操作、接入步骤及编辑对话框 |
127
- | `CreateDiscussion.vue`、`LimitPicker.vue`、`LaunchSettings.vue` | 新建讨论表单及本机启动配置;轮次和时长是独立字段,按需展开浮层滚轮,选择即时生效,支持精确输入、各自不限和到限条件预览;整块滚轮(含中间数字)支持拖动与鼠标滚动,先阻尼滑行再吸附预计停点,轻点中间数字进入输入;时长浮层分小时/分钟两列,收起与摘要共用时长格式;支持键盘,减少动态效果时保留操作惯性并去掉三维翻转 |
127
+ | `CreateDiscussion.vue`、`LimitPicker.vue`、`LaunchSettings.vue` | 新建讨论表单及本机启动配置;推进方式与参与专家分组,单选方式使用强调色卡片,专家多选使用中性卡片与方形勾选;轮次和时长独立配置,以“不限”按钮切换各自上限,按需展开滚轮,选择即时生效,并预览到限条件;鼠标滚轮逐格切换且不产生惯性,鼠标及触摸拖动松手后保留惯性,先阻尼滑行再吸附预计停点,轻点中间数字精确输入;时长分小时/分钟两列,收起与摘要共用时长格式;支持键盘,减少动态效果时保留拖动惯性并去掉三维翻转 |
128
128
  | `ProgressBoard.vue`、`ProgressPanel.vue` | 共享进度看板及抽屉容器 |
129
129
  | `use-discussion.js` | 将 `web/app.js` 的共享控制器状态接入 Vue |
130
130
  | `theme.css`、`channel.css` | 组件主题、深浅配色与响应式布局 |
package/docs/runner.md CHANGED
@@ -38,6 +38,10 @@ codiscuss local stop
38
38
 
39
39
  网站数据库为服务目录下 `local/discuss.sqlite3`,配置、登记表和讨论执行数据也位于包外;服务目录位置及 `DISCUSS_SERVICE_HOME` 见下文。源码运行的 `data/discuss.sqlite3` 不自动迁移。网站仍在而讨论服务已停止时,显式 `local open/start` 会要求网站管理进程恢复服务;不会自动恢复已暂停的房间。`local status` 的 `running` 要求网站和讨论服务同时运行,`website_running` 单独表示网站状态。`local stop` 关闭它创建的网站,并停止它启动或恢复且仍由相同 PID 持有的讨论服务;预先运行或后来独立启动的服务会保留。停止服务会暂停其托管中的活动讨论,数据库保留。日志在 `local/website.log`、`local/supervisor.log` 与原 `service.log`。
40
40
 
41
+ Windows 的 MSIX 应用(如 Claude 桌面版)可能把派生进程对 AppData 的写入重定向到 `Packages/<包名>/LocalCache/Local/`;同名数据库、锁和停止请求因此与普通终端分离。管理命令在读状态、建房或启动进程前,用临时文件核实实际写入位置,发现重定向就报出两处路径并停止,避免误报已启动或已停止;帮助和纯 HTTP 网站命令仍可使用。检测依据是文件实际位置,不依赖进程是否报告包身份。Python 启动别名与直接执行原生 Python 也可能进入不同文件视图,排查时须核对网站实际使用的可执行文件与数据库。此机制见 [Microsoft MSIX 文件虚拟化说明](https://learn.microsoft.com/en-us/windows/msix/desktop/desktop-to-uwp-behind-the-scenes)。
42
+
43
+ 遇到该错误,从应用外的普通终端管理服务;需要所有入口直接管理时,为它们统一设置 AppData 之外的 `DISCUSS_SERVICE_HOME`,已有数据需停机备份后迁移,不会自动合并两份库。若旧版本已在包私有目录启动服务,先核对该目录的 `local/status.json`、`status.json` 和实际 PID,再在普通终端将 `DISCUSS_SERVICE_HOME` 显式设为该物理目录执行 `local stop`;独立启动的讨论服务仍需单独 `service stop`,`local stop` 会明确报告保留的 PID。停止请求必须与目标进程使用同一份目录,仅看到另一个目录没有锁不能证明目标已退出。
44
+
41
45
  已安装后升级不要使用 `npx`(它会运行已安装的旧版本):先 `codiscuss local stop`,再 `npm install --global --prefer-online ai_co_discuss@latest`,然后重跑 `codiscuss setup`;`--prefer-online` 避免本地缓存把 `latest` 解析为旧版本。
42
46
 
43
47
  卸载先 `codiscuss local stop`,按需要另用 `codiscuss service stop` 停止预先独立运行的服务,再执行 `npm uninstall --global ai_co_discuss`;自定义 prefix 时带回相同 `--prefix`。安装器不删除数据库、私有配置或已复制技能。
package/docs/workflow.md CHANGED
@@ -21,7 +21,7 @@
21
21
  | 达到上限后 | 暂停保留现场,或交给实际主持人总结;不自动宣布达成共识 |
22
22
  | 收束预留 | 1–60 分钟,仅选择总结时生效;须小于非零总时长,并计入总时长 |
23
23
 
24
- 网页新建默认 3 轮、60 分钟、单次执行 10 分钟、到限暂停;收束预留默认 5 分钟。新建表单分别显示轮次和时长上限,点击数值才展开滚轮;拖动或鼠标滚动后带惯性吸附,选择即时生效,点击外部即可收起,无需确认。时长收起时显示小时及剩余分钟,展开后分为小时、分钟两列,按 1 小时、1 分钟选择;轻点各列中间数字可直接输入,总时长保持 1–1440 分钟,24 小时时分钟固定为 0。滚动先按阻尼减速,再按预计停止位置吸附;系统“减少动画”下仍保留用户操作的惯性,只去掉数字的三维翻转。两项各自提供“不限”,关闭“不限”恢复此前数值,互不改动;下方实时说明哪个上限先到便触发所选动作。旧房间和未传参数的 CLI 建房保持轮次/总时长不限、单次自动执行 20 分钟。自动运行仍必须有未来 24 小时内的明确截止:可直接采用话题时限,也可由启动参数进一步缩短。
24
+ 网页新建默认 3 轮、不限时长、单次执行 10 分钟、到限暂停;收束预留默认 5 分钟。新建表单分别显示轮次和时长上限,点击数值才展开滚轮;鼠标滚轮逐格切换,停止滚动即停住,鼠标或手指在屏幕上拖动则保留松手惯性。选择即时生效,点击外部即可收起,无需确认。时长收起时显示小时及剩余分钟,展开后分为小时、分钟两列,按 1 小时、1 分钟选择;轻点各列中间数字可直接输入,启用时长上限后范围为 1–1440 分钟,24 小时时分钟固定为 0。拖动松手后先按阻尼减速,再按预计停止位置吸附;系统“减少动画”下仍保留拖动惯性,只去掉数字的三维翻转。两项各自提供“不限”,关闭“不限”恢复此前数值,首次启用时长上限为 1 小时,互不改动;下方实时说明哪个上限先到便触发所选动作。旧房间和未传参数的 CLI 建房保持轮次/总时长不限、单次自动执行 20 分钟。自动运行仍必须有未来 24 小时内的明确截止:网页启动前需设置有限的话题时长;CLI 可显式提供本次运行时长,且不能延长话题已设的截止。
25
25
 
26
26
  “主持”及“接入”显示配置、已用/剩余时间、已完成轮次和到限状态。新专家安排及观点由服务端拒绝越界,原提交的幂等重试和用户留言保留。自动程序定期调用到限处理;手动主持每次原生检查时执行 `room limits ROOM --check --as 本人`,也可由用户点击界面中的检查按钮。仅打开页面不触发写入或启动 AI。
27
27
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai_co_discuss",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "bin": {
5
5
  "codiscuss": "runner/codiscuss.mjs"
6
6
  },
@@ -8,7 +8,7 @@ import { parseArgs } from 'node:util';
8
8
 
9
9
  import { atomic_json } from './discussion_adapters.mjs';
10
10
  import { acquire_lock, api, DiscussionStore, service_drafts, task_files, validate_config } from './discussion_runner.mjs';
11
- import { CONNECTION_FIELDS, configuration_snapshot, read_registry, registered_identity, register_room, service_home, service_pid, start_service } from './discussion_service.mjs';
11
+ import { assert_shared_home, CONNECTION_FIELDS, configuration_snapshot, read_registry, registered_identity, register_room, service_home, service_pid, start_service } from './discussion_service.mjs';
12
12
 
13
13
  const CLIENTS = { Codex: ['codex_cli', 'codex'], Claude: ['claude'], Gemini: ['antigravity_cli', 'antigravity'] };
14
14
  // Reasoning effort is a separate CLI argument only for Codex and Claude; agy encodes it in the model name.
@@ -339,6 +339,7 @@ async function prepare_launch(home, input, directory, options = {}) {
339
339
 
340
340
  /** Launch a room through either entry, using a durable journal for safe retries. */
341
341
  export async function launch_discussion(home, raw, options = {}) {
342
+ assert_shared_home(home);
342
343
  const input = launch_input(raw);
343
344
  const key = createHash('sha256').update(`${input.site}/${input.room_id || input.room.submission_id}`).digest('hex');
344
345
  const directory = join(home, 'launches', key);
@@ -401,6 +402,7 @@ export async function launch_main(args = process.argv.slice(2), input_text) {
401
402
  if (values.help || !positionals.length) return process.stdout.write(HELP);
402
403
  const input = input_text === undefined ? (values.file ? JSON.parse(readFileSync(values.file, 'utf8').replace(/^\uFEFF/, '')) : null) : JSON.parse(input_text);
403
404
  const home = service_home();
405
+ assert_shared_home(home);
404
406
  let result;
405
407
  if (positionals[0] === 'settings') result = input ? save_launch_settings(home, input) : launch_settings(home);
406
408
  else if (positionals[0] === 'start' && input) result = await launch_discussion(home, input, { dry_run: values['dry-run'], browser_request: values.browser });
@@ -9,7 +9,7 @@ import { parseArgs } from 'node:util';
9
9
 
10
10
  import { atomic_json } from './discussion_adapters.mjs';
11
11
  import { acquire_lock } from './discussion_runner.mjs';
12
- import { main as service_main, service_home, service_pid, service_ready, start_service } from './discussion_service.mjs';
12
+ import { assert_shared_home, main as service_main, service_home, service_pid, service_ready, start_service } from './discussion_service.mjs';
13
13
  import { detect_python, local_settings } from './discussion_setup.mjs';
14
14
  import { tone } from './discussion_tui.mjs';
15
15
 
@@ -142,6 +142,7 @@ async function requested_service(home, status, owned_service) {
142
142
 
143
143
  /** Run one supervisor, recording readiness and handling an explicit local stop request. */
144
144
  async function supervise(home) {
145
+ assert_shared_home(home);
145
146
  const directory = join(home, 'local');
146
147
  const release = acquire_lock(directory);
147
148
  const status_path = join(directory, 'status.json');
@@ -239,7 +240,8 @@ function local_summary(command, result, opened) {
239
240
  status: result.running ? [`本机网站运行中:${site}`]
240
241
  : result.website_running ? [`本机网站运行中:${site},但讨论服务未运行;运行 codiscuss local open 恢复`]
241
242
  : ['本机网站未运行;运行 codiscuss local open 启动'],
242
- stop: ['本机网站与讨论服务已停止;数据已保留,运行 codiscuss local open 重新启动'],
243
+ stop: result.service_pid ? [`本机网站已停止;独立讨论服务仍在运行(PID ${result.service_pid}),可用 codiscuss service stop 单独停止;数据已保留`]
244
+ : ['本机网站与讨论服务已停止;数据已保留,运行 codiscuss local open 重新启动'],
243
245
  connect: [`已连接 ${result.connected};本机网站 ${tone.cyan(result.local_url || '')}`,
244
246
  opened ? '已在浏览器打开连接链接,请在该页面完成接入;连接链接不要转发' : `请在浏览器打开连接链接完成接入,链接不要转发:${result.url}`],
245
247
  disconnect: [`已撤销 ${result.disconnected} 的本机启动权限;正在运行的讨论需在进度看板暂停`],
@@ -258,6 +260,7 @@ export async function local_main(args, output) {
258
260
  const [command = 'open', site] = positionals;
259
261
  const home = service_home();
260
262
  if (values.help || command === 'help') return process.stdout.write(LOCAL_HELP);
263
+ assert_shared_home(home);
261
264
  const report = (result, opened) => {
262
265
  if (output) return output(result);
263
266
  local_summary(command, result, opened);
@@ -1,7 +1,7 @@
1
1
  /** Resident local service that keeps every registered automatic room consumed; no model API orchestration. */
2
2
  import { spawn, spawnSync } from 'node:child_process';
3
- import { createHash } from 'node:crypto';
4
- import { appendFileSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, unlinkSync } from 'node:fs';
3
+ import { createHash, randomUUID } from 'node:crypto';
4
+ import { appendFileSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, realpathSync, unlinkSync } from 'node:fs';
5
5
  import { homedir } from 'node:os';
6
6
  import { dirname, join, resolve } from 'node:path';
7
7
  import { DatabaseSync } from 'node:sqlite';
@@ -35,6 +35,29 @@ export function service_home() {
35
35
  return join(process.env.LOCALAPPDATA || join(homedir(), '.local', 'state'), 'ai-codiscuss', 'service');
36
36
  }
37
37
 
38
+ /** Reject an MSIX-private write view before reading shared service state or changing it. */
39
+ export function assert_shared_home(home) {
40
+ if (process.platform !== 'win32') return;
41
+ let directory = resolve(home);
42
+ while (!existsSync(directory)) {
43
+ const parent = dirname(directory);
44
+ if (parent === directory) throw new Error(`服务目录没有可访问的父目录:${home}`);
45
+ directory = parent;
46
+ }
47
+ const probe = join(directory, `.codiscuss-path-${randomUUID()}`);
48
+ closeSync(openSync(probe, 'wx', 0o600));
49
+ try {
50
+ const actual = realpathSync.native(probe);
51
+ // Compare the container suffix so a short user-profile name (ADMINI~1) is not a redirect.
52
+ const container = actual.match(/\\AppData\\Local\\Packages\\[^\\]+\\LocalCache\\(?:Local|Roaming)\\/i)?.[0];
53
+ if (container && !probe.toLowerCase().includes(container.toLowerCase())) {
54
+ throw new Error(`Windows MSIX 正在重定向服务目录 ${home},实际写入 ${dirname(actual)}。`
55
+ + '请从应用外的普通终端执行 codiscuss local start/stop;'
56
+ + '或让所有入口使用同一个 AppData 之外的 DISCUSS_SERVICE_HOME。原数据不会自动迁移。');
57
+ }
58
+ } finally { unlinkSync(probe); }
59
+ }
60
+
38
61
  /** Read the registered private room configuration paths. */
39
62
  export function read_registry(home) {
40
63
  const path = join(home, 'rooms.json');
@@ -203,6 +226,7 @@ export function service_ready(home, pid = service_pid(home)) {
203
226
 
204
227
  /** Foreground service loop; `start` runs this detached in the background. */
205
228
  export async function run_service(home, output = () => {}) {
229
+ assert_shared_home(home);
206
230
  const release = acquire_lock(home);
207
231
  const log_line = message => {
208
232
  const line = `${new Date().toISOString()} ${message}`;
@@ -236,6 +260,7 @@ export async function run_service(home, output = () => {}) {
236
260
 
237
261
  /** Start the detached service once; an already running service is reused. */
238
262
  export async function start_service(home) {
263
+ assert_shared_home(home);
239
264
  const running = service_pid(home);
240
265
  if (service_ready(home, running)) return { running: true, ready: true, pid: running, started: false };
241
266
  mkdirSync(home, { recursive: true });
@@ -329,6 +354,7 @@ export async function main(args = process.argv.slice(2), print = value => proces
329
354
  const { values, positionals } = parseArgs({ args, allowPositionals: true, options: { config: { type: 'string' } } });
330
355
  const [command, option] = positionals;
331
356
  const home = service_home();
357
+ assert_shared_home(home);
332
358
  const require_config = () => { if (!values.config) throw new Error('请用 --config 指定私有房间配置'); return values.config; };
333
359
  switch (command) {
334
360
  case 'run': return run_service(home);
@@ -8,7 +8,7 @@ import { parseArgs } from 'node:util';
8
8
 
9
9
  import { atomic_json } from './discussion_adapters.mjs';
10
10
  import { launch_settings, save_launch_settings } from './discussion_launch.mjs';
11
- import { service_home } from './discussion_service.mjs';
11
+ import { assert_shared_home, service_home } from './discussion_service.mjs';
12
12
  import { install_skills } from './discussion_skills.mjs';
13
13
  import { ask, checkbox_prompt, intro, outro, pad, short_path, spinner, step, styled, text_width, tone } from './discussion_tui.mjs';
14
14
  export { checkbox_prompt } from './discussion_tui.mjs';
@@ -58,6 +58,7 @@ export function local_settings(home = service_home()) {
58
58
  function setup_environment(options) {
59
59
  if (Number(process.versions.node.split('.')[0]) < MIN_NODE_MAJOR) throw new Error('需要 Node.js 24+,请升级后重试。');
60
60
  const home = service_home();
61
+ assert_shared_home(home);
61
62
  const previous = local_settings(home);
62
63
  const python = detect_python(options.python || previous.python);
63
64
  const port = Number(options.port || previous.port || DEFAULT_PORT);
package/web/index.html CHANGED
@@ -109,7 +109,7 @@
109
109
  </div>
110
110
  <footer class="workspace-footer"><span>共议聊天室 <span class="footer-dot">·</span> 记录每一个不同的观点</span><span id="demo-note">示例发言为预置内容,未连接真实 AI</span></footer>
111
111
  </div>
112
- <dialog id="room-dialog"><form id="room-form"><div class="dialog-heading"><h2>新建话题</h2><button type="button" class="dialog-close" id="close-room-dialog" aria-label="关闭">×</button></div><div class="dialog-content"><label>话题名称<input id="new-room-title" maxlength="80" placeholder="例如:产品第一版应该保留哪些能力?" required></label><label>议题目标<textarea id="new-room-goal" maxlength="1000" rows="3" placeholder="本次讨论要解决什么问题?"></textarea></label><label>主持人<select id="new-room-host"></select></label><label>运行方式<select id="new-room-mode"><option value="manual">手动复制冷启动</option><option value="automatic">全自动讨论</option></select></label><fieldset><legend>讨论上限</legend><div class="topic-form-grid"><label>讨论轮次上限<input id="new-room-limit-max_rounds" type="number" min="0" max="100" step="1" value="3" placeholder="留空不限"></label><label>讨论总时长(分钟)<input id="new-room-limit-max_minutes" type="number" min="0" max="1440" step="1" value="60" placeholder="留空不限"></label><label>单次 AI 执行上限(分钟,仅自动)<input id="new-room-limit-execution_minutes" type="number" min="1" max="60" step="1" value="10" placeholder="留空不限"></label><label>收束预留(分钟,仅总结时)<input id="new-room-limit-summary_minutes" type="number" min="1" max="60" step="1" value="5" placeholder="留空不限"></label><label>达到上限后<select id="new-room-limit-on_limit"><option value="pause">暂停保留现场</option><option value="summarize">由主持人总结</option></select></label></div><p class="local-note">轮次和总时长留空或 0 表示不限;首次进入讨论阶段或创建安排后开始计时,暂停期间不计时。总结预留计入总时长。一轮指每位参与专家各完成一次观点。</p></fieldset><details class="topic-details"><summary>用户原文、背景、边界与参与专家</summary><div class="topic-detail-content"><label>用户原文<textarea id="new-room-user_request" maxlength="4000" rows="3"></textarea></label><label>背景<textarea id="new-room-background" maxlength="4000" rows="3"></textarea></label><label>讨论边界<textarea id="new-room-boundaries" maxlength="4000" rows="3"></textarea></label><div id="new-expert-modes" class="expert-mode-grid"></div></div></details><p class="local-note">AI 主持人需选择本地 CLI 方式,也可同时作为专家参与。</p><fieldset><legend>网页启动入口</legend><label><input type="checkbox" data-launch-name="create" data-launch-field="">创建后立即启动 AI(需选择全自动)</label><div id="new-local-launch-settings" hidden></div><button type="button" id="new-local-launch-save" class="button secondary">保存本机软件配置</button><p id="new-local-launch-notice" role="status"></p></fieldset><button class="button primary full-width" type="submit">创建话题</button></div></form></dialog>
112
+ <dialog id="room-dialog"><form id="room-form"><div class="dialog-heading"><h2>新建话题</h2><button type="button" class="dialog-close" id="close-room-dialog" aria-label="关闭">×</button></div><div class="dialog-content"><label>话题名称<input id="new-room-title" maxlength="80" placeholder="例如:产品第一版应该保留哪些能力?" required></label><label>议题目标<textarea id="new-room-goal" maxlength="1000" rows="3" placeholder="本次讨论要解决什么问题?"></textarea></label><label>主持人<select id="new-room-host"></select></label><label>运行方式<select id="new-room-mode"><option value="manual">手动复制冷启动</option><option value="automatic">全自动讨论</option></select></label><fieldset><legend>讨论上限</legend><div class="topic-form-grid"><label>讨论轮次上限<input id="new-room-limit-max_rounds" type="number" min="0" max="100" step="1" value="3" placeholder="留空不限"></label><label>讨论总时长(分钟)<input id="new-room-limit-max_minutes" type="number" min="0" max="1440" step="1" value="0" placeholder="留空不限"></label><label>单次 AI 执行上限(分钟,仅自动)<input id="new-room-limit-execution_minutes" type="number" min="1" max="60" step="1" value="10" placeholder="留空不限"></label><label>收束预留(分钟,仅总结时)<input id="new-room-limit-summary_minutes" type="number" min="1" max="60" step="1" value="5" placeholder="留空不限"></label><label>达到上限后<select id="new-room-limit-on_limit"><option value="pause">暂停保留现场</option><option value="summarize">由主持人总结</option></select></label></div><p class="local-note">轮次和总时长留空或 0 表示不限;首次进入讨论阶段或创建安排后开始计时,暂停期间不计时。总结预留计入总时长。一轮指每位参与专家各完成一次观点。</p></fieldset><details class="topic-details"><summary>用户原文、背景、边界与参与专家</summary><div class="topic-detail-content"><label>用户原文<textarea id="new-room-user_request" maxlength="4000" rows="3"></textarea></label><label>背景<textarea id="new-room-background" maxlength="4000" rows="3"></textarea></label><label>讨论边界<textarea id="new-room-boundaries" maxlength="4000" rows="3"></textarea></label><div id="new-expert-modes" class="expert-mode-grid"></div></div></details><p class="local-note">AI 主持人需选择本地 CLI 方式,也可同时作为专家参与。</p><fieldset><legend>网页启动入口</legend><label><input type="checkbox" data-launch-name="create" data-launch-field="">创建后立即启动 AI(需选择全自动)</label><div id="new-local-launch-settings" hidden></div><button type="button" id="new-local-launch-save" class="button secondary">保存本机软件配置</button><p id="new-local-launch-notice" role="status"></p></fieldset><button class="button primary full-width" type="submit">创建话题</button></div></form></dialog>
113
113
  <dialog id="message-dialog"><div class="dialog-heading"><h2 id="detail-title">消息原文</h2><button class="dialog-close" id="close-message-dialog" aria-label="关闭">×</button></div><div id="detail-body" class="dialog-content"></div></dialog>
114
114
  <dialog id="settings-dialog" aria-labelledby="settings-title"><div class="dialog-heading"><h2 id="settings-title">设置</h2><button type="button" class="dialog-close" id="close-settings" aria-label="关闭设置">×</button></div><div class="dialog-content settings-content"><h3>界面主题</h3><div class="theme-picker" role="group" aria-label="界面主题"><button type="button" data-theme-option="channel" aria-pressed="true">协作频道</button><button type="button" data-theme-option="channel-light" aria-pressed="false">协作频道 · 浅色</button><button type="button" data-theme-option="millennium" aria-pressed="false">千禧聊天室</button></div></div></dialog>
115
115
  <dialog id="manage-dialog" aria-labelledby="manage-title"><form id="manage-form"><div class="dialog-heading"><h2 id="manage-title">管理频道</h2><button type="button" id="close-manage" class="dialog-close" aria-label="关闭">×</button></div><div class="dialog-content"><p id="manage-description"></p><label id="manage-input-label">频道名称<input id="manage-input" maxlength="80" autocomplete="off"></label><button type="submit" id="manage-submit" class="button primary">确认</button></div></form></dialog>