ai_co_discuss 0.2.5 → 0.2.6

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,11 +63,11 @@ codiscuss setup
63
63
 
64
64
  1. 安装完成后进入自动打开的网站;以后运行 `codiscuss local open` 打开或复用本机网站,默认地址为 `http://127.0.0.1:8765`。
65
65
  2. 点击“新建讨论”,填写**话题名称**和**用户原文**。把问题、背景与顾虑写在原文中,讨论目标、背景和边界由 AI 主持人据此补全。
66
- 3. 房间用法选择“头脑风暴”,接入方式选择“程序唤醒”,从卡片下方的“添加”行加入本场专家,点卡片上的皇冠指定 AI 主持人,点击“移出”调整名单。自动 CLI 卡片背面从真实软件目录下拉选择模型,并按模型支持的档位设置思考强度;留空使用 CLI 默认值,App 在原软件设置。分别设置轮次和时长上限,点击数值展开滚轮或直接输入。新建默认 **3 轮 / 不限时长**,按轮次上限或手动停止结束;单次 AI 执行也默认不限时,可按需设置 1–60 分钟上限。
67
- 4. 勾选“创建后立即启动 AI”,复核软件配置。需要修改时展开“调整启动配置”;未配置的软件需选择 CLI / App 类型并补齐路径。项目目录、读写权限和已有会话按本场需要填写。
66
+ 3. 房间用法选择“头脑风暴”,接入方式默认“程序唤醒”,从卡片下方的“添加”行加入本场专家,点卡片上的皇冠指定 AI 主持人,点击“移出”调整名单。自动 CLI 卡片背面从真实软件目录下拉选择模型,并按模型支持的档位设置思考强度;留空使用 CLI 默认值,App 在原软件设置。项目文件只读 / 可写也在每张自动 CLI 卡片上分别设置(默认只读),在线专家始终只读;软件路径缺失时卡片背面直接提示填写。分别设置轮次和时长上限,点击数值展开滚轮或直接输入。新建默认 **3 轮 / 不限时长**,按轮次上限或手动停止结束;单次 AI 执行也默认不限时,可按需设置 1–60 分钟上限。
67
+ 4. “创建后立即启动 AI”默认已勾选。需要一起查看项目时填写项目目录,可直接输入完整路径,也可点“选择…”在本机弹窗选取。复用已有会话等高级覆盖在建房后的“接入”页设置。
68
68
  5. 点击“创建并启动讨论”。程序完成建房、登记与首次任务交付;在进度看板核对 AI 是否实际进入,并查看保存的发言。
69
69
 
70
- 只想让 AI 干活时,在第 3 步将房间用法改为“自由协作”:选择一位或多位本地 AI,默认由你主持,也可指定 AI 主持人;使用“程序唤醒”时点击“创建并连接”,之后在频道里直接发消息。多位时点名或在正文中 `@` 它们,处理中可随时停止。运行中可在右侧成员栏邀请、请离或请回 AI,指定主持人,或发起关联的头脑风暴。详见 [自由协作房间](docs/workflow.md#自由协作房间)。
70
+ 只想让 AI 干活时,在第 3 步将房间用法改为“自由协作”,只需填写话题名称,任务在对话中随时补充:选择一位或多位本地 AI,默认由你主持,也可指定 AI 主持人;使用“程序唤醒”时点击“创建并连接”,之后在频道里直接发消息。多位时点名或在正文中 `@` 它们,处理中可随时停止。运行中可在右侧成员栏邀请、请离或请回 AI,指定主持人,或发起关联的头脑风暴。详见 [自由协作房间](docs/workflow.md#自由协作房间)。
71
71
 
72
72
  也可不勾选立即启动,先建房,再从“接入”页点击“启动讨论”。本机网页正常启动无需下载 JSON、手动登记房间或另执行首次启动命令。
73
73
 
package/backend/server.py CHANGED
@@ -36,6 +36,7 @@ MAX_ATTACHMENT_BYTES = 20 * 1024 * 1024
36
36
  MAX_MESSAGE_ATTACHMENTS = 10
37
37
  MAX_FILENAME_LENGTH = 255
38
38
  LOCAL_LAUNCH_TIMEOUT = 60
39
+ DIRECTORY_PICKER_TIMEOUT = 600 # the folder dialog waits for the person at the computer
39
40
  MAX_TEXT_LENGTH = 32000
40
41
  MAX_STATE_LENGTH = 4000
41
42
  MAX_BRIEF_LENGTH = 1600
@@ -1420,7 +1421,7 @@ class DiscussionHandler(BaseHTTPRequestHandler):
1420
1421
  """Forward authenticated browser requests to the shared Node entrypoint and pin their website."""
1421
1422
  site = self.local_request_site(method)
1422
1423
  route = parts[2:]
1423
- if method == "OPTIONS" and route in (["settings"], ["launch"], ["room-models"], ["room-member"], ["models"]):
1424
+ if method == "OPTIONS" and route in (["settings"], ["launch"], ["room-models"], ["room-member"], ["models"], ["pick-directory"]):
1424
1425
  return self.respond({"ok": True})
1425
1426
  if route == ["settings"] and method in ("GET", "POST"):
1426
1427
  command = "settings"
@@ -1434,6 +1435,8 @@ class DiscussionHandler(BaseHTTPRequestHandler):
1434
1435
  command = "models"
1435
1436
  elif route == ["room-member"] and method == "POST":
1436
1437
  command = "room-member"
1438
+ elif route == ["pick-directory"] and method == "POST":
1439
+ command = "pick-directory"
1437
1440
  else:
1438
1441
  raise LookupError("本机入口不存在")
1439
1442
  payload = None
@@ -1473,11 +1476,13 @@ class DiscussionHandler(BaseHTTPRequestHandler):
1473
1476
  arguments.append("--stdin")
1474
1477
  try:
1475
1478
  result = subprocess.run(arguments, input=json.dumps(payload) if payload is not None else None,
1476
- capture_output=True, encoding="utf-8", timeout=LOCAL_LAUNCH_TIMEOUT,
1479
+ capture_output=True, encoding="utf-8", timeout=DIRECTORY_PICKER_TIMEOUT if command == "pick-directory" else LOCAL_LAUNCH_TIMEOUT,
1477
1480
  creationflags=subprocess.CREATE_NO_WINDOW if os.name == "nt" else 0)
1478
1481
  except FileNotFoundError as error:
1479
1482
  raise ValueError("本机启动需要 Node.js 24+,请安装并加入 PATH") from error
1480
1483
  except subprocess.TimeoutExpired as error:
1484
+ if command == "pick-directory":
1485
+ raise ValueError("目录选择窗口已超时,请重试或直接输入完整路径") from error
1481
1486
  raise ValueError("启动结果未确认,请核对房间状态后沿用原配置重试") from error
1482
1487
  if result.returncode:
1483
1488
  try:
@@ -148,7 +148,7 @@ Python 在一个只读事务快照中查询;D1 使用同一个 batch 事务读
148
148
 
149
149
  ### 主题与交互边界
150
150
 
151
- 新建表单独立选择房间用法(头脑风暴/自由协作)与接入方式(程序唤醒/手动),共用内联角色库。接入方式以分段切换放在参与区标题旁;每次添加生成独立实例,已加入成员以 `ExpertCard.vue` 渲染为翻面卡片:正面是身份与只读配置(状态、软件、模型、强度),背面是设置;卡片上的皇冠指定主持人,移出按钮同步修复主持人选择,角色库为卡片下方的“添加”标签行。自由协作默认用户主持(“我来主持”),可改由一位本地 AI 主持。支持本地参与的卡片翻面设置参与能力和软件入口,纯网页角色不翻面。自动 CLI 从本机软件目录下拉选择模型,不要求手填 ID;模型与强度同层显示,恢复默认只清除当前实例覆盖。App、网页和手动会话在原软件内设置。`EffortSlider.vue` 按所选模型的实际档位显示,首档为空值的 CLI 默认,显示标签与提交值分离(Extra High 对应 xhigh);标题处的档位名随拖动实时预览、松手才提交,悬停、拖动与键盘聚焦分别有光晕反馈,减少动态效果时不做过渡;agy 的强度随模型,不另显示滑杆。目录不可用时显示原因并保留软件默认,不虚构列表。
151
+ 新建表单独立选择房间用法(头脑风暴/自由协作)与接入方式(程序唤醒/手动),共用内联角色库。接入方式以分段切换放在参与区标题旁;每次添加生成独立实例,已加入成员以 `ExpertCard.vue` 渲染为翻面卡片:正面是身份与只读配置(状态、软件、模型、强度、项目权限),背面是设置,项目只读 / 可写按专家分别设置,在线专家固定只读,缺少的软件路径在背面直接填写;卡片上的皇冠指定主持人,移出按钮同步修复主持人选择,角色库为卡片下方的“添加”标签行。自由协作默认用户主持(“我来主持”),可改由一位本地 AI 主持。支持本地参与的卡片翻面设置参与能力和软件入口,纯网页角色不翻面。自动 CLI 从本机软件目录下拉选择模型,不要求手填 ID;模型与强度同层显示,恢复默认只清除当前实例覆盖。App、网页和手动会话在原软件内设置。`EffortSlider.vue` 按所选模型的实际档位显示,首档为空值的 CLI 默认,显示标签与提交值分离(Extra High 对应 xhigh);标题处的档位名随拖动实时预览、松手才提交,悬停、拖动与键盘聚焦分别有光晕反馈,减少动态效果时不做过渡;agy 的强度随模型,不另显示滑杆。目录不可用时显示原因并保留软件默认,不虚构列表。
152
152
 
153
153
  正反面占用同一网格单元,避免翻转引起布局跳动。角度由现有 Motion 弹簧驱动:质量 1、频率 2.615 Hz、阻尼比 0.96(刚度 270.032、阻尼 31.551);目标在 0° / 180° 之间切换,中途反向保留速度,无固定时长。隐藏面设为 inert 并退出辅助技术读取,转过 90° 后移交焦点;Escape 返回,减少动态效果时直接到位。滑块采用单位质量、4.5 Hz 临界阻尼弹簧,拖动由 6 Hz 手部弹簧牵引、8 Hz 端点力限制;松手依据真实位置与速度的投影选择档位,点击直接选档,方向键和 Home / End 覆盖全部档位,取消拖动恢复原选项。模型与强度仅在创建前配置,讨论进度中保持只读。
154
154
 
package/docs/runner.md CHANGED
@@ -69,7 +69,7 @@ Windows 的 MSIX 应用(如 Claude 桌面版)可能把派生进程对 AppDat
69
69
  python server.py --host 127.0.0.1 --local-launch
70
70
  ```
71
71
 
72
- 需要 Node.js 24+ 与已安装、已登录的 AI 客户端;讨论所需的最小工具权限按 [单次执行权限](#单次执行权限) 提供。新建时分别选择房间用法与“程序唤醒”,从卡片下方的“添加”行加入本地参与实例,点卡片上的皇冠指定主持人;自由协作默认用户主持,头脑风暴须选择本地 AI 主持。参与区只显示已加入的实例,用独立“移出”按钮调整名单。自动 CLI 卡片翻到背面后通过真实软件目录下拉选择模型,并按该模型支持的档位显示强度滑杆;首档使用 CLI 默认,agy 的强度包含在模型 ID 中。恢复默认只清除该实例的模型与强度覆盖,App、网页和手动会话在原软件设置。配置入口不依赖勾选立即启动,移出后重新加入保留本次填写。头脑风暴可勾选“创建后立即启动 AI”,按需填写项目目录与项目文件权限(默认只读),再点击“创建并启动讨论”;不勾选则只建房,之后在“接入”页启动。自由协作的“创建并连接”登记自动成员,后续点名产生任务时才唤醒。配置可“保存为本机软件默认配置”,保存本身不启动任何 AI。卡片输入沿用当前页面的启动草稿,不随只建房请求保存到网站。正常首次自动接入无需下载 JSON、手动登记房间或另执行 `run start`。
72
+ 需要 Node.js 24+ 与已安装、已登录的 AI 客户端;讨论所需的最小工具权限按 [单次执行权限](#单次执行权限) 提供。新建时分别选择房间用法与“程序唤醒”,从卡片下方的“添加”行加入本地参与实例,点卡片上的皇冠指定主持人;自由协作默认用户主持,头脑风暴须选择本地 AI 主持。参与区只显示已加入的实例,用独立“移出”按钮调整名单。自动 CLI 卡片翻到背面后通过真实软件目录下拉选择模型,并按该模型支持的档位显示强度滑杆;首档使用 CLI 默认,agy 的强度包含在模型 ID 中。恢复默认只清除该实例的模型与强度覆盖,App、网页和手动会话在原软件设置。配置入口不依赖勾选立即启动,移出后重新加入保留本次填写。接入方式默认“程序唤醒”,“创建后立即启动 AI”默认勾选;本机服务未连接时只建房。项目文件权限在各自动 CLI 卡片上按专家设置(默认只读,在线专家固定只读),卡片只在缺少软件路径、Sidecar IPC 目录或 Codex App 来源聊天时显示对应输入。项目目录可直接输入,也可点“选择…”经 `POST /api/local/pick-directory` 在运行本机服务的电脑上弹出系统目录选择窗口(Windows 用 PowerShell WinForms,macOS 用 osascript,Linux 用 zenity),取消时不返回路径,窗口最长等待 10 分钟。再点击“创建并启动讨论”;不勾选则只建房,之后在“接入”页启动。自由协作的“创建并连接”登记自动成员,后续点名产生任务时才唤醒。复用会话、单个 AI 的工作目录与项目等高级覆盖,以及“保存为本机软件默认配置”,在建房后的“接入”页设置,保存本身不启动任何 AI。卡片输入沿用当前页面的启动草稿,不随只建房请求保存到网站。正常首次自动接入无需下载 JSON、手动登记房间或另执行 `run start`。
73
73
 
74
74
  网页在发送前把完整已提交输入、原提交号和控制凭据保存在当前站点的浏览器本地存储。服务启动失败或响应丢失后,刷新仍会显示“继续未完成的启动”;点击“继续本次启动”沿用原请求和本机已固定配置,无需重新填写,也不随新的默认配置变化。刷新只恢复界面,不自动运行;已暂停、结束、过期或在启动期间改变的房间不会因此重新开始。部分失败明确显示已核实的阶段,已建房不等于回滚;结果不明时保留原请求待核对。
75
75
 
@@ -236,7 +236,7 @@ Windows 的 `executable` 填原生 `.exe`,不填 npm 的 `.cmd` / `.ps1` 包
236
236
 
237
237
  每次执行只开放本场讨论所需的最小权限,不使用全部放行或绕过审批参数。任务文件的 `cli` 为 `codiscuss`;Windows 下 Codex 与 agy 经 PowerShell 执行命令,npm 生成的 `codiscuss.ps1` 会被执行策略拒绝,因此改为 `codiscuss.cmd`,Claude 仍用 `codiscuss`。`subcommands` 列出唯一放行的网站子命令:`room`、`message`、`turn`、`state`、`floor`、`execution`、`health`、`schema`;`service`、`launch`、`run`、`api`、`setup`、`install`、`local` 不放行。提示词要求命令以该 `cli` 开头,不用 `python discuss.py`、`cd` 或管道。规则不含路径,重装或更换 Python 无需更新;子进程 PATH 中的 `codiscuss` 须指向本次安装,setup 的持久命令检查会核对。
238
238
 
239
- 项目文件权限 `project_access` 为 `read`(默认)或 `write`:启动文件顶层设本场值,`participants.NAME.project_access` 覆盖单个 AI,空字符串表示沿用本场值。它只随本次启动保存,不能存入本机默认配置;网页新建讨论在“启动”区直接提供项目目录与项目文件选项,单个 AI 的覆盖在“调整启动配置”中。三种 CLI 都在用户指定的 cwd 启动;省略 cwd 时才使用独立草稿目录。agy 的权限全部由其独立讨论项目执行,见下文。App 适配器的写权限由 App 自身决定,任务文件始终写明本场要求。
239
+ 项目文件权限 `project_access` 为 `read`(默认)或 `write`:启动文件顶层设本场值,`participants.NAME.project_access` 覆盖单个 AI,空字符串表示沿用本场值。它只随本次启动保存,不能存入本机默认配置;网页新建讨论在每张自动 CLI 卡片上设置该 AI 的项目文件权限,不再提供本场共享的权限选项;已建房间的“接入”页仍可设置本场值与单个 AI 覆盖。三种 CLI 都在用户指定的 cwd 启动;省略 cwd 时才使用独立草稿目录。agy 的权限全部由其独立讨论项目执行,见下文。App 适配器的写权限由 App 自身决定,任务文件始终写明本场要求。
240
240
 
241
241
  | 适配器 | 本次追加 | 只读 / 可写差异 |
242
242
  | --- | --- | --- |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai_co_discuss",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "bin": {
5
5
  "codiscuss": "runner/codiscuss.mjs"
6
6
  },
@@ -1,4 +1,5 @@
1
1
  /** Shared launch configuration for an existing AI conversation and the local website. */
2
+ import { spawn } from 'node:child_process';
2
3
  import { createHash, randomUUID } from 'node:crypto';
3
4
  import { existsSync, mkdirSync, readFileSync, statSync } from 'node:fs';
4
5
  import { dirname, isAbsolute, join, resolve } from 'node:path';
@@ -38,12 +39,14 @@ const HELP = `用法:codiscuss launch settings [--file FILE]
38
39
  codiscuss launch room-models --file FILE
39
40
  codiscuss launch models --file FILE
40
41
  codiscuss launch room-member --file FILE
42
+ codiscuss launch pick-directory
41
43
 
42
44
  settings 读取本机软件默认配置;指定文件时保存,不启动 AI。
43
45
  start 使用启动 JSON 创建或接入房间并启动;--dry-run 只校验和预览。
44
46
  room-config 修正已暂停房间的软件路径或切换 Codex App / CLI,不启动讨论;须提供 site、room_id、token。
45
47
  room-models 只读指定 site、room_id 的本机登记模型与思考强度,不启动讨论。
46
48
  models 按 adapter、executable 读取本机 CLI 的模型目录,不启动讨论或生成回答。
49
+ pick-directory 在本机弹出系统目录选择窗口,返回所选路径;取消时不返回路径。
47
50
  room-member 为运行中的自由房间邀请或请回一位成员;须提供 site、room_id、token、action(add/rejoin)、name。
48
51
  JSON 指定 site、entry(conversation/web)、room(建房内容,含 submission_id)
49
52
  或 room_id(已有房间)、participants(本次软件覆盖配置)。
@@ -52,6 +55,37 @@ conversation 还须 current_session_id,当前会话接手首个主持任务。
52
55
  详见 docs/runner.md 的“两种讨论入口与启动配置”。
53
56
  `;
54
57
 
58
+ // Windows PowerShell 5.1 ships WinForms; a topmost owner keeps the dialog above the browser that asked for it.
59
+ const WINDOWS_PICKER = [
60
+ 'Add-Type -AssemblyName System.Windows.Forms',
61
+ '[Console]::OutputEncoding = [Text.Encoding]::UTF8',
62
+ '$owner = New-Object System.Windows.Forms.Form -Property @{ TopMost = $true; ShowInTaskbar = $false }',
63
+ '$dialog = New-Object System.Windows.Forms.FolderBrowserDialog -Property @{ Description = "选择项目目录"; ShowNewFolderButton = $true }',
64
+ 'if ($dialog.ShowDialog($owner) -eq "OK") { $dialog.SelectedPath }',
65
+ ].join('; ');
66
+ const PICKERS = {
67
+ win32: ['powershell.exe', ['-NoProfile', '-STA', '-Command', WINDOWS_PICKER]],
68
+ darwin: ['osascript', ['-e', 'POSIX path of (choose folder with prompt "选择项目目录")']],
69
+ linux: ['zenity', ['--file-selection', '--directory', '--title=选择项目目录']],
70
+ };
71
+
72
+ /** Show the platform's own folder dialog on this computer; cancelling resolves without a path. */
73
+ export function pick_directory({ platform = process.platform, spawn_process = spawn } = {}) {
74
+ const picker = PICKERS[platform];
75
+ if (!picker) return Promise.reject(new Error('此系统不支持目录选择窗口,请直接输入完整路径'));
76
+ return new Promise((resolve_result, reject) => {
77
+ const child = spawn_process(picker[0], picker[1], { shell: false, windowsHide: true, stdio: ['ignore', 'pipe', 'ignore'] });
78
+ let output = '';
79
+ child.stdout.setEncoding('utf8');
80
+ child.stdout.on('data', chunk => { output += chunk; });
81
+ child.once('error', () => reject(new Error('无法打开目录选择窗口,请直接输入完整路径')));
82
+ child.once('close', code => {
83
+ const path = output.trim();
84
+ resolve_result(code === 0 && isAbsolute(path) ? { path } : { cancelled: true });
85
+ });
86
+ });
87
+ }
88
+
55
89
  /** Some CLIs take seconds to list models; reuse a good list for a day unless the executable changes or the user retries. */
56
90
  const MODEL_CACHE_MS = 24 * 60 * 60 * 1000;
57
91
 
@@ -610,6 +644,7 @@ export async function launch_main(args = process.argv.slice(2), input_text) {
610
644
  result = await cached_software_models(home, { adapter: input.adapter, executable: input.executable || saved?.executable }, { refresh: input.refresh === true });
611
645
  }
612
646
  else if (positionals[0] === 'room-member' && input) result = await room_member(home, input);
647
+ else if (positionals[0] === 'pick-directory') result = await pick_directory();
613
648
  else if (positionals[0] === 'start' && input) result = await launch_discussion(home, input, { dry_run: values['dry-run'], browser_request: values.browser });
614
649
  else if (positionals[0] === 'room-config' && input) result = await room_configuration(home, input, { local_guard: values['configuration-locked'] });
615
650
  else throw new Error(HELP);
package/web/app.js CHANGED
@@ -41,7 +41,7 @@ const state = { rooms: [], folders: [], room: null, page: 1, pages: 1, query: ""
41
41
  archive_view: false, directory_sort: "manual", management: null, dragged_room: null, folder_moves: new Map(), folder_version: 0,
42
42
  reply_to: null, turn_reply_to: null, tab: "topic", refresh_version: 0, context_dirty: false, topic_dirty: false,
43
43
  snapshot_id: 0, pending_submission: null, pending_turn: null, pending_room: null, toast_timer: null,
44
- creation_mode: "brainstorm:manual", creation_drafts: {}, access_notice: "", access_error: "" };
44
+ creation_mode: "brainstorm:automatic", creation_drafts: {}, access_notice: "", access_error: "" };
45
45
  const live_updates = { source: null, connected: false, enabled: false, timer: null, poll_timer: null,
46
46
  running: false, dirty: false };
47
47
  state.messages = [];
@@ -58,7 +58,7 @@ state.directory_error = "";
58
58
  state.directory_pending = new Map();
59
59
  state.directory_version = 0;
60
60
  state.navigation_version = 0;
61
- state.launch = { available: false, remote: false, connect_command: "", profiles: {}, workspace: {}, create: false, error: "", notice: "", recoveries: [] };
61
+ state.launch = { available: false, remote: false, connect_command: "", profiles: {}, workspace: {}, create: true, error: "", notice: "", recoveries: [] };
62
62
  const PLATFORM_CLIENTS = { Codex: [{ value: "codex_cli", label: "Codex CLI" }, { value: "codex", label: "Codex App" }],
63
63
  Claude: [{ value: "claude", label: "Claude Code CLI" }],
64
64
  Gemini: [{ value: "antigravity_cli", label: "Antigravity CLI" }, { value: "antigravity", label: "Antigravity App" }] };
@@ -1304,7 +1304,8 @@ function room_creation_payload(folder_id) {
1304
1304
  const common = { title: element("new-room-title").value, goal: element("new-room-goal").value,
1305
1305
  host: element("new-room-host").value, user_request: element("new-room-user_request").value,
1306
1306
  experts: read_expert_inputs("new-expert-modes"), folder_id };
1307
- if (element("new-room-kind").value === "free") return { ...common, mode: "free", host: common.host || "用户" };
1307
+ // Free rooms carry their task in the conversation, so no opening brief is sent even if one was typed for a discussion.
1308
+ if (element("new-room-kind").value === "free") return { ...common, user_request: "", mode: "free", host: common.host || "用户" };
1308
1309
  return { ...common, mode: "brainstorm", background: element("new-room-background").value,
1309
1310
  boundaries: element("new-room-boundaries").value, runtime_mode: element("new-room-mode").value, limits: read_limits("new-room"),
1310
1311
  ...(state.create_parent ? { parent_room_id: state.create_parent } : {}) };
@@ -1329,7 +1330,8 @@ async function submit_room(event, folder_id = null) {
1329
1330
  const payload = room_creation_payload(folder_id);
1330
1331
  const free = payload.mode === "free";
1331
1332
  const automatic = free ? element("new-room-mode").value === "automatic" : payload.runtime_mode === "automatic";
1332
- const launching = automatic && (free || state.launch.create);
1333
+ // A discussion can still be created without the local service and started later from the room.
1334
+ const launching = automatic && (free || state.launch.create && state.launch.available);
1333
1335
  if (launching && !state.launch.available) throw new Error("程序唤醒需要连接本机服务;也可改用手动接入");
1334
1336
  const launch_draft = launching ? launch_options() : null;
1335
1337
  const signature = JSON.stringify({ payload, launch_draft });
@@ -1360,7 +1362,7 @@ async function submit_room(event, folder_id = null) {
1360
1362
  element("room-form").reset();
1361
1363
  reset_creation_mode();
1362
1364
  render_expert_inputs("new-expert-modes", default_experts());
1363
- state.launch.create = false;
1365
+ state.launch.create = true;
1364
1366
  if (launching) element("room-dialog").close();
1365
1367
  }
1366
1368
  if (launching) clear_launch_overrides(launch_draft);
@@ -1489,7 +1491,7 @@ async function resume_launch(id) {
1489
1491
  element("room-form").reset();
1490
1492
  reset_creation_mode();
1491
1493
  render_expert_inputs("new-expert-modes", default_experts());
1492
- state.launch.create = false;
1494
+ state.launch.create = true;
1493
1495
  element("room-dialog").close();
1494
1496
  }
1495
1497
  if (!record.direct) clear_launch_overrides(record.input);
@@ -1696,6 +1698,16 @@ function clear_launch_overrides(draft) {
1696
1698
  publish_view();
1697
1699
  }
1698
1700
 
1701
+ /** Let the user browse for the project folder on the computer running the local service. */
1702
+ async function pick_launch_directory() {
1703
+ try {
1704
+ const result = await local_launch_request("pick-directory", {});
1705
+ if (result.path) write_launch_field("workspace", "cwd", result.path);
1706
+ state.launch.error = "";
1707
+ } catch (error) { state.launch.error = error.message; }
1708
+ publish_view();
1709
+ }
1710
+
1699
1711
  /** Save machine defaults without per-discussion session IDs or project write access. */
1700
1712
  async function save_launch_defaults() {
1701
1713
  // Defaults are per platform; an extra instance's model and effort apply to its own discussion only.
@@ -2053,9 +2065,9 @@ async function interrupt_member(member_id) {
2053
2065
 
2054
2066
  /** A completed creation starts the next form with fresh mode defaults. */
2055
2067
  function reset_creation_mode() {
2056
- state.creation_mode = "brainstorm:manual";
2068
+ state.creation_mode = "brainstorm:automatic";
2057
2069
  state.creation_drafts = {};
2058
- element("new-room-limit-execution_minutes")?.closest("label")?.setAttribute("hidden", "");
2070
+ element("new-room-limit-execution_minutes")?.closest("label")?.removeAttribute("hidden");
2059
2071
  }
2060
2072
 
2061
2073
  /** Render independently configurable expert modes without changing identity names. */
@@ -2784,7 +2796,7 @@ function change_view_theme(theme) {
2784
2796
 
2785
2797
  window.discuss_view = { snapshot: view_snapshot, write_field: write_view_field,
2786
2798
  write_launch: write_launch_field, write_room_connection, write_room_adapter, launch_clients: LAUNCH_CLIENTS,
2787
- launch_model_state, load_launch_models, write_launch_model, launch_summary, empty_discussion_message,
2799
+ launch_model_state, load_launch_models, write_launch_model, launch_summary, pick_launch_directory, empty_discussion_message,
2788
2800
  start_drag: start_directory_drag,
2789
2801
  attachment_markup, attachment_size, attachment_file_icon, drop_attachments, paste_attachments,
2790
2802
  write_expert: write_view_expert, add_instance: add_view_instance, platform_of, execute: execute_view_action, theme: change_view_theme,
package/web/index.html CHANGED
@@ -111,7 +111,7 @@
111
111
  </div>
112
112
  <footer class="workspace-footer"><span>共议聊天室 <span class="footer-dot">·</span> 记录每一个不同的观点</span><span id="demo-note">示例发言为预置内容,未连接真实 AI</span></footer>
113
113
  </div>
114
- <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-kind"><option value="brainstorm">讨论</option><option value="free">自由协作</option></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="0" max="60" step="1" value="0" 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>
114
+ <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-kind"><option value="brainstorm">讨论</option><option value="free">自由协作</option></select></label><label>接入方式<select id="new-room-mode"><option value="automatic" selected>程序唤醒</option><option value="manual">手动接入</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="0" max="60" step="1" value="0" 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>
115
115
  <input id="attachment-input" type="file" multiple hidden aria-label="选择附件">
116
116
  <dialog id="attachment-dialog" class="attachment-dialog" aria-labelledby="attachment-title">
117
117
  <div class="attachment-preview-heading"><h2 id="attachment-title">附件预览</h2><a id="attachment-download" download>下载原文件</a><button id="close-attachment" type="button" aria-label="关闭附件预览">×</button></div>