ai_co_discuss 0.1.10 → 0.1.11

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.
Files changed (230) hide show
  1. package/.agents/skills/discuss-host/references/launch.md +2 -2
  2. package/.agents/skills/discuss-participant/SKILL.md +4 -0
  3. package/README.md +5 -3
  4. package/backend/runtime.py +20 -16
  5. package/backend/schema.sql +41 -5
  6. package/backend/server.py +285 -22
  7. package/docs/THIRD_PARTY_NOTICES.md +2 -0
  8. package/docs/architecture.md +37 -14
  9. package/docs/cli.md +21 -3
  10. package/docs/deployment.md +33 -3
  11. package/docs/runner.md +17 -9
  12. package/docs/workflow.md +4 -4
  13. package/package.json +7 -6
  14. package/runner/codiscuss.mjs +1 -0
  15. package/runner/discuss.py +111 -11
  16. package/runner/discussion_adapters.mjs +8 -0
  17. package/runner/discussion_launch.mjs +50 -9
  18. package/runner/discussion_runner.mjs +22 -8
  19. package/runner/discussion_service.mjs +12 -5
  20. package/runner/discussion_sidecar.mjs +1 -1
  21. package/web/app.js +441 -76
  22. package/web/backup.js +6 -5
  23. package/web/index.html +17 -9
  24. package/web/markdown.css +1 -1
  25. package/web/modern.css +1 -1
  26. package/web/modern.js +6485 -5844
  27. package/web/pdfjs/LICENSE +177 -0
  28. package/web/pdfjs/cmaps/78-EUC-H.bcmap +0 -0
  29. package/web/pdfjs/cmaps/78-EUC-V.bcmap +0 -0
  30. package/web/pdfjs/cmaps/78-H.bcmap +0 -0
  31. package/web/pdfjs/cmaps/78-RKSJ-H.bcmap +0 -0
  32. package/web/pdfjs/cmaps/78-RKSJ-V.bcmap +0 -0
  33. package/web/pdfjs/cmaps/78-V.bcmap +0 -0
  34. package/web/pdfjs/cmaps/78ms-RKSJ-H.bcmap +0 -0
  35. package/web/pdfjs/cmaps/78ms-RKSJ-V.bcmap +0 -0
  36. package/web/pdfjs/cmaps/83pv-RKSJ-H.bcmap +0 -0
  37. package/web/pdfjs/cmaps/90ms-RKSJ-H.bcmap +0 -0
  38. package/web/pdfjs/cmaps/90ms-RKSJ-V.bcmap +0 -0
  39. package/web/pdfjs/cmaps/90msp-RKSJ-H.bcmap +0 -0
  40. package/web/pdfjs/cmaps/90msp-RKSJ-V.bcmap +0 -0
  41. package/web/pdfjs/cmaps/90pv-RKSJ-H.bcmap +0 -0
  42. package/web/pdfjs/cmaps/90pv-RKSJ-V.bcmap +0 -0
  43. package/web/pdfjs/cmaps/Add-H.bcmap +0 -0
  44. package/web/pdfjs/cmaps/Add-RKSJ-H.bcmap +0 -0
  45. package/web/pdfjs/cmaps/Add-RKSJ-V.bcmap +0 -0
  46. package/web/pdfjs/cmaps/Add-V.bcmap +0 -0
  47. package/web/pdfjs/cmaps/Adobe-CNS1-0.bcmap +0 -0
  48. package/web/pdfjs/cmaps/Adobe-CNS1-1.bcmap +0 -0
  49. package/web/pdfjs/cmaps/Adobe-CNS1-2.bcmap +0 -0
  50. package/web/pdfjs/cmaps/Adobe-CNS1-3.bcmap +0 -0
  51. package/web/pdfjs/cmaps/Adobe-CNS1-4.bcmap +0 -0
  52. package/web/pdfjs/cmaps/Adobe-CNS1-5.bcmap +0 -0
  53. package/web/pdfjs/cmaps/Adobe-CNS1-6.bcmap +0 -0
  54. package/web/pdfjs/cmaps/Adobe-CNS1-UCS2.bcmap +0 -0
  55. package/web/pdfjs/cmaps/Adobe-GB1-0.bcmap +0 -0
  56. package/web/pdfjs/cmaps/Adobe-GB1-1.bcmap +0 -0
  57. package/web/pdfjs/cmaps/Adobe-GB1-2.bcmap +0 -0
  58. package/web/pdfjs/cmaps/Adobe-GB1-3.bcmap +0 -0
  59. package/web/pdfjs/cmaps/Adobe-GB1-4.bcmap +0 -0
  60. package/web/pdfjs/cmaps/Adobe-GB1-5.bcmap +0 -0
  61. package/web/pdfjs/cmaps/Adobe-GB1-UCS2.bcmap +0 -0
  62. package/web/pdfjs/cmaps/Adobe-Japan1-0.bcmap +0 -0
  63. package/web/pdfjs/cmaps/Adobe-Japan1-1.bcmap +0 -0
  64. package/web/pdfjs/cmaps/Adobe-Japan1-2.bcmap +0 -0
  65. package/web/pdfjs/cmaps/Adobe-Japan1-3.bcmap +0 -0
  66. package/web/pdfjs/cmaps/Adobe-Japan1-4.bcmap +0 -0
  67. package/web/pdfjs/cmaps/Adobe-Japan1-5.bcmap +0 -0
  68. package/web/pdfjs/cmaps/Adobe-Japan1-6.bcmap +0 -0
  69. package/web/pdfjs/cmaps/Adobe-Japan1-UCS2.bcmap +0 -0
  70. package/web/pdfjs/cmaps/Adobe-Korea1-0.bcmap +0 -0
  71. package/web/pdfjs/cmaps/Adobe-Korea1-1.bcmap +0 -0
  72. package/web/pdfjs/cmaps/Adobe-Korea1-2.bcmap +0 -0
  73. package/web/pdfjs/cmaps/Adobe-Korea1-UCS2.bcmap +0 -0
  74. package/web/pdfjs/cmaps/B5-H.bcmap +0 -0
  75. package/web/pdfjs/cmaps/B5-V.bcmap +0 -0
  76. package/web/pdfjs/cmaps/B5pc-H.bcmap +0 -0
  77. package/web/pdfjs/cmaps/B5pc-V.bcmap +0 -0
  78. package/web/pdfjs/cmaps/CNS-EUC-H.bcmap +0 -0
  79. package/web/pdfjs/cmaps/CNS-EUC-V.bcmap +0 -0
  80. package/web/pdfjs/cmaps/CNS1-H.bcmap +0 -0
  81. package/web/pdfjs/cmaps/CNS1-V.bcmap +0 -0
  82. package/web/pdfjs/cmaps/CNS2-H.bcmap +0 -0
  83. package/web/pdfjs/cmaps/CNS2-V.bcmap +3 -0
  84. package/web/pdfjs/cmaps/ETHK-B5-H.bcmap +0 -0
  85. package/web/pdfjs/cmaps/ETHK-B5-V.bcmap +0 -0
  86. package/web/pdfjs/cmaps/ETen-B5-H.bcmap +0 -0
  87. package/web/pdfjs/cmaps/ETen-B5-V.bcmap +0 -0
  88. package/web/pdfjs/cmaps/ETenms-B5-H.bcmap +3 -0
  89. package/web/pdfjs/cmaps/ETenms-B5-V.bcmap +0 -0
  90. package/web/pdfjs/cmaps/EUC-H.bcmap +0 -0
  91. package/web/pdfjs/cmaps/EUC-V.bcmap +0 -0
  92. package/web/pdfjs/cmaps/Ext-H.bcmap +0 -0
  93. package/web/pdfjs/cmaps/Ext-RKSJ-H.bcmap +0 -0
  94. package/web/pdfjs/cmaps/Ext-RKSJ-V.bcmap +0 -0
  95. package/web/pdfjs/cmaps/Ext-V.bcmap +0 -0
  96. package/web/pdfjs/cmaps/GB-EUC-H.bcmap +0 -0
  97. package/web/pdfjs/cmaps/GB-EUC-V.bcmap +0 -0
  98. package/web/pdfjs/cmaps/GB-H.bcmap +4 -0
  99. package/web/pdfjs/cmaps/GB-V.bcmap +0 -0
  100. package/web/pdfjs/cmaps/GBK-EUC-H.bcmap +0 -0
  101. package/web/pdfjs/cmaps/GBK-EUC-V.bcmap +0 -0
  102. package/web/pdfjs/cmaps/GBK2K-H.bcmap +0 -0
  103. package/web/pdfjs/cmaps/GBK2K-V.bcmap +0 -0
  104. package/web/pdfjs/cmaps/GBKp-EUC-H.bcmap +0 -0
  105. package/web/pdfjs/cmaps/GBKp-EUC-V.bcmap +0 -0
  106. package/web/pdfjs/cmaps/GBT-EUC-H.bcmap +0 -0
  107. package/web/pdfjs/cmaps/GBT-EUC-V.bcmap +0 -0
  108. package/web/pdfjs/cmaps/GBT-H.bcmap +0 -0
  109. package/web/pdfjs/cmaps/GBT-V.bcmap +0 -0
  110. package/web/pdfjs/cmaps/GBTpc-EUC-H.bcmap +0 -0
  111. package/web/pdfjs/cmaps/GBTpc-EUC-V.bcmap +0 -0
  112. package/web/pdfjs/cmaps/GBpc-EUC-H.bcmap +0 -0
  113. package/web/pdfjs/cmaps/GBpc-EUC-V.bcmap +0 -0
  114. package/web/pdfjs/cmaps/H.bcmap +0 -0
  115. package/web/pdfjs/cmaps/HKdla-B5-H.bcmap +0 -0
  116. package/web/pdfjs/cmaps/HKdla-B5-V.bcmap +0 -0
  117. package/web/pdfjs/cmaps/HKdlb-B5-H.bcmap +0 -0
  118. package/web/pdfjs/cmaps/HKdlb-B5-V.bcmap +0 -0
  119. package/web/pdfjs/cmaps/HKgccs-B5-H.bcmap +0 -0
  120. package/web/pdfjs/cmaps/HKgccs-B5-V.bcmap +0 -0
  121. package/web/pdfjs/cmaps/HKm314-B5-H.bcmap +0 -0
  122. package/web/pdfjs/cmaps/HKm314-B5-V.bcmap +0 -0
  123. package/web/pdfjs/cmaps/HKm471-B5-H.bcmap +0 -0
  124. package/web/pdfjs/cmaps/HKm471-B5-V.bcmap +0 -0
  125. package/web/pdfjs/cmaps/HKscs-B5-H.bcmap +0 -0
  126. package/web/pdfjs/cmaps/HKscs-B5-V.bcmap +0 -0
  127. package/web/pdfjs/cmaps/Hankaku.bcmap +0 -0
  128. package/web/pdfjs/cmaps/Hiragana.bcmap +0 -0
  129. package/web/pdfjs/cmaps/KSC-EUC-H.bcmap +0 -0
  130. package/web/pdfjs/cmaps/KSC-EUC-V.bcmap +0 -0
  131. package/web/pdfjs/cmaps/KSC-H.bcmap +0 -0
  132. package/web/pdfjs/cmaps/KSC-Johab-H.bcmap +0 -0
  133. package/web/pdfjs/cmaps/KSC-Johab-V.bcmap +0 -0
  134. package/web/pdfjs/cmaps/KSC-V.bcmap +0 -0
  135. package/web/pdfjs/cmaps/KSCms-UHC-H.bcmap +0 -0
  136. package/web/pdfjs/cmaps/KSCms-UHC-HW-H.bcmap +0 -0
  137. package/web/pdfjs/cmaps/KSCms-UHC-HW-V.bcmap +0 -0
  138. package/web/pdfjs/cmaps/KSCms-UHC-V.bcmap +0 -0
  139. package/web/pdfjs/cmaps/KSCpc-EUC-H.bcmap +0 -0
  140. package/web/pdfjs/cmaps/KSCpc-EUC-V.bcmap +0 -0
  141. package/web/pdfjs/cmaps/Katakana.bcmap +0 -0
  142. package/web/pdfjs/cmaps/LICENSE +36 -0
  143. package/web/pdfjs/cmaps/NWP-H.bcmap +0 -0
  144. package/web/pdfjs/cmaps/NWP-V.bcmap +0 -0
  145. package/web/pdfjs/cmaps/RKSJ-H.bcmap +0 -0
  146. package/web/pdfjs/cmaps/RKSJ-V.bcmap +0 -0
  147. package/web/pdfjs/cmaps/Roman.bcmap +0 -0
  148. package/web/pdfjs/cmaps/UniCNS-UCS2-H.bcmap +0 -0
  149. package/web/pdfjs/cmaps/UniCNS-UCS2-V.bcmap +0 -0
  150. package/web/pdfjs/cmaps/UniCNS-UTF16-H.bcmap +0 -0
  151. package/web/pdfjs/cmaps/UniCNS-UTF16-V.bcmap +0 -0
  152. package/web/pdfjs/cmaps/UniCNS-UTF32-H.bcmap +0 -0
  153. package/web/pdfjs/cmaps/UniCNS-UTF32-V.bcmap +0 -0
  154. package/web/pdfjs/cmaps/UniCNS-UTF8-H.bcmap +0 -0
  155. package/web/pdfjs/cmaps/UniCNS-UTF8-V.bcmap +0 -0
  156. package/web/pdfjs/cmaps/UniGB-UCS2-H.bcmap +0 -0
  157. package/web/pdfjs/cmaps/UniGB-UCS2-V.bcmap +0 -0
  158. package/web/pdfjs/cmaps/UniGB-UTF16-H.bcmap +0 -0
  159. package/web/pdfjs/cmaps/UniGB-UTF16-V.bcmap +0 -0
  160. package/web/pdfjs/cmaps/UniGB-UTF32-H.bcmap +0 -0
  161. package/web/pdfjs/cmaps/UniGB-UTF32-V.bcmap +0 -0
  162. package/web/pdfjs/cmaps/UniGB-UTF8-H.bcmap +0 -0
  163. package/web/pdfjs/cmaps/UniGB-UTF8-V.bcmap +0 -0
  164. package/web/pdfjs/cmaps/UniJIS-UCS2-H.bcmap +0 -0
  165. package/web/pdfjs/cmaps/UniJIS-UCS2-HW-H.bcmap +0 -0
  166. package/web/pdfjs/cmaps/UniJIS-UCS2-HW-V.bcmap +0 -0
  167. package/web/pdfjs/cmaps/UniJIS-UCS2-V.bcmap +0 -0
  168. package/web/pdfjs/cmaps/UniJIS-UTF16-H.bcmap +0 -0
  169. package/web/pdfjs/cmaps/UniJIS-UTF16-V.bcmap +0 -0
  170. package/web/pdfjs/cmaps/UniJIS-UTF32-H.bcmap +0 -0
  171. package/web/pdfjs/cmaps/UniJIS-UTF32-V.bcmap +0 -0
  172. package/web/pdfjs/cmaps/UniJIS-UTF8-H.bcmap +0 -0
  173. package/web/pdfjs/cmaps/UniJIS-UTF8-V.bcmap +0 -0
  174. package/web/pdfjs/cmaps/UniJIS2004-UTF16-H.bcmap +0 -0
  175. package/web/pdfjs/cmaps/UniJIS2004-UTF16-V.bcmap +0 -0
  176. package/web/pdfjs/cmaps/UniJIS2004-UTF32-H.bcmap +0 -0
  177. package/web/pdfjs/cmaps/UniJIS2004-UTF32-V.bcmap +0 -0
  178. package/web/pdfjs/cmaps/UniJIS2004-UTF8-H.bcmap +0 -0
  179. package/web/pdfjs/cmaps/UniJIS2004-UTF8-V.bcmap +0 -0
  180. package/web/pdfjs/cmaps/UniJISPro-UCS2-HW-V.bcmap +0 -0
  181. package/web/pdfjs/cmaps/UniJISPro-UCS2-V.bcmap +0 -0
  182. package/web/pdfjs/cmaps/UniJISPro-UTF8-V.bcmap +0 -0
  183. package/web/pdfjs/cmaps/UniJISX0213-UTF32-H.bcmap +0 -0
  184. package/web/pdfjs/cmaps/UniJISX0213-UTF32-V.bcmap +0 -0
  185. package/web/pdfjs/cmaps/UniJISX02132004-UTF32-H.bcmap +0 -0
  186. package/web/pdfjs/cmaps/UniJISX02132004-UTF32-V.bcmap +0 -0
  187. package/web/pdfjs/cmaps/UniKS-UCS2-H.bcmap +0 -0
  188. package/web/pdfjs/cmaps/UniKS-UCS2-V.bcmap +0 -0
  189. package/web/pdfjs/cmaps/UniKS-UTF16-H.bcmap +0 -0
  190. package/web/pdfjs/cmaps/UniKS-UTF16-V.bcmap +0 -0
  191. package/web/pdfjs/cmaps/UniKS-UTF32-H.bcmap +0 -0
  192. package/web/pdfjs/cmaps/UniKS-UTF32-V.bcmap +0 -0
  193. package/web/pdfjs/cmaps/UniKS-UTF8-H.bcmap +0 -0
  194. package/web/pdfjs/cmaps/UniKS-UTF8-V.bcmap +0 -0
  195. package/web/pdfjs/cmaps/V.bcmap +0 -0
  196. package/web/pdfjs/cmaps/WP-Symbol.bcmap +0 -0
  197. package/web/pdfjs/iccs/CGATS001Compat-v2-micro.icc +0 -0
  198. package/web/pdfjs/iccs/LICENSE +116 -0
  199. package/web/pdfjs/pdf.min.mjs +28 -0
  200. package/web/pdfjs/pdf.worker.min.mjs +28 -0
  201. package/web/pdfjs/standard_fonts/FoxitDingbats.pfb +0 -0
  202. package/web/pdfjs/standard_fonts/FoxitFixed.pfb +0 -0
  203. package/web/pdfjs/standard_fonts/FoxitFixedBold.pfb +0 -0
  204. package/web/pdfjs/standard_fonts/FoxitFixedBoldItalic.pfb +0 -0
  205. package/web/pdfjs/standard_fonts/FoxitFixedItalic.pfb +0 -0
  206. package/web/pdfjs/standard_fonts/FoxitSerif.pfb +0 -0
  207. package/web/pdfjs/standard_fonts/FoxitSerifBold.pfb +0 -0
  208. package/web/pdfjs/standard_fonts/FoxitSerifBoldItalic.pfb +0 -0
  209. package/web/pdfjs/standard_fonts/FoxitSerifItalic.pfb +0 -0
  210. package/web/pdfjs/standard_fonts/FoxitSymbol.pfb +0 -0
  211. package/web/pdfjs/standard_fonts/LICENSE_FOXIT +27 -0
  212. package/web/pdfjs/standard_fonts/LICENSE_LIBERATION +363 -0
  213. package/web/pdfjs/standard_fonts/LiberationSans-Bold.ttf +0 -0
  214. package/web/pdfjs/standard_fonts/LiberationSans-BoldItalic.ttf +0 -0
  215. package/web/pdfjs/standard_fonts/LiberationSans-Italic.ttf +0 -0
  216. package/web/pdfjs/standard_fonts/LiberationSans-Regular.ttf +0 -0
  217. package/web/pdfjs/wasm/LICENSE_JBIG2 +196 -0
  218. package/web/pdfjs/wasm/LICENSE_OPENJPEG +39 -0
  219. package/web/pdfjs/wasm/LICENSE_PDFJS_JBIG2 +13 -0
  220. package/web/pdfjs/wasm/LICENSE_PDFJS_OPENJPEG +22 -0
  221. package/web/pdfjs/wasm/LICENSE_PDFJS_QCMS +19 -0
  222. package/web/pdfjs/wasm/LICENSE_QCMS +21 -0
  223. package/web/pdfjs/wasm/jbig2.wasm +0 -0
  224. package/web/pdfjs/wasm/jbig2_nowasm_fallback.js +15 -0
  225. package/web/pdfjs/wasm/openjpeg.wasm +0 -0
  226. package/web/pdfjs/wasm/openjpeg_nowasm_fallback.js +17 -0
  227. package/web/pdfjs/wasm/qcms_bg.wasm +0 -0
  228. package/web/pdfjs/wasm/quickjs-eval.js +16 -0
  229. package/web/pdfjs/wasm/quickjs-eval.wasm +0 -0
  230. package/web/style.css +0 -574
@@ -24,7 +24,7 @@
24
24
  "boundaries": "讨论边界",
25
25
  "host": "Codex",
26
26
  "experts": { "Codex": "cli", "Claude": "cli" },
27
- "limits": { "max_rounds": 3, "max_minutes": 60, "execution_minutes": 10, "on_limit": "pause", "summary_minutes": 5 }
27
+ "limits": { "max_rounds": 3, "max_minutes": 60, "execution_minutes": 0, "on_limit": "pause", "summary_minutes": 5 }
28
28
  },
29
29
  "participants": {
30
30
  "Codex": { "adapter": "codex" }
@@ -32,7 +32,7 @@
32
32
  }
33
33
  ```
34
34
 
35
- 此示例的主持在 Codex App;若当前是独立 Codex CLI,应使用 `codex_cli`。可执行文件、App 来源聊天或 Sidecar 等缺项仍须来自实际本机配置。主持也须出现在 `experts` 中并设为 `cli`。示例中的上限不能覆盖用户已经指定的参数;总时长为 0 时,另用顶层 `minutes` 指定本次有限运行时长。
35
+ 此示例的主持在 Codex App;若当前是独立 Codex CLI,应使用 `codex_cli`。可执行文件、App 来源聊天或 Sidecar 等缺项仍须来自实际本机配置。主持也须出现在 `experts` 中并设为 `cli`。示例中的上限不能覆盖用户已经指定的参数;总时长为 0 时可不限总时长运行,按轮次上限或手动停止结束。单次执行默认 0(不限),主动设置的单次执行超时仍有效。只有用户另设本次时长时才提供顶层 `minutes`,不能用它延长房间既定截止。
36
36
 
37
37
  已有房间改为指定顶层 `room_id`,省略 `room`;受保护房间另提供原私有配置的 `access_file` 路径,不读取或打印令牌。首次启动命令不替代已暂停房间的继续操作,已有运行按当前房间状态接续。
38
38
 
@@ -42,6 +42,8 @@ description: "AI CoDiscuss 的通用参与技能,所有参与者(包括主
42
42
 
43
43
  增量读取用实际已读编号 `message list ROOM --after ID`,按 `next_after` / `has_more` 续读;`latest_id` 不等于已读。筛选后的游标只代表该查询范围,不能用来声称已读全场。阶段和安排以最新服务端状态为准。
44
44
 
45
+ 消息的 `attachments` 提供文件名、类型、大小与编号;需要核对内容时用 `attachment view ROOM ID`、`attachment download ROOM ID --dest 明确的新文件路径`,再用当前软件的实际工具读取。独立阶段仍遵循上述材料边界;下载或看见文件名不等于已读,无法解析时如实说明。
46
+
45
47
  ## 发言权与提交边界
46
48
 
47
49
  专家只回答属于自己的当前未完成安排(pending/notified),使用真实 `--turn`;没有安排就等待,不自行接管主持。提交前核对阶段、安排和上限。暂停、收束、`limits.reached` 或 `limits.closing` 时不新增专家观点,保留草稿;主持人按主持技能处理到限或总结。
@@ -56,6 +58,8 @@ description: "AI CoDiscuss 的通用参与技能,所有参与者(包括主
56
58
 
57
59
  观点用 `message post`,摘要用 `--summary` / `--summary-file`,完整正文优先 UTF-8 `--body-file`;自己的观点署名自己。主持消息用 `moderation`。代发保留真实作者、完整原文及引用关系,摘要归实际撰写者,不把自己的改写冒充对方回答。
58
60
 
61
+ 需要提交原文件时,先 `attachment upload ROOM --as 本人 --file FILE --submission-id 上传编号`,再把回执中的附件 ID 通过 `message post --attachment ID` 关联,可重复指定(最多 10 个,单个 20 MiB)。受保护上传带本次 `--access-file`,须在回答完成、执行资格结束前上传。上传和消息分别保存提交编号;结果不明用 `attachment list ROOM --submission-id 上传编号` 核对,重试沿用原文件与原编号。
62
+
59
63
  创建房间、安排、消息前保存 `--submission-id` 与原内容;同次重试沿用,新内容换新编号。成功须核对服务端返回的持久记录、作者和关联;返回不含所需证据时按编号补读,避免重复获取同一份已确认内容。答案落库后只读核验即可结束,不补启停回执或使用旧资格继续写入。
60
64
 
61
65
  结果不明先按原 `submission_id` 精确查询,找到就沿用;明确可重试时仅用原编号原内容重试一次,再不明则保留草稿并报告。不换身份、职责或编号绕过拒绝。首次使用某类写入、代发、超长正文或遇到失败时,读取 [写入与恢复](references/writing.md) 的相关部分;不把全部故障流程作为每次发言的检查表。
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 轮 / 不限时长**;网页启动全自动讨论前需设置有限时长。
66
+ 3. 选择“全自动讨论”,点击“邀请 AI”选择本场专家,并从中指定 AI 主持人;参与区只显示已加入的 AI,点击“移出”调整名单。已配置的 CLI 专家点击卡片正面,在背面配置模型与思考强度,留空使用 CLI 默认值。分别设置轮次和时长上限,点击数值展开滚轮或直接输入。新建默认 **3 轮 / 不限时长**,可直接启动全自动讨论,按轮次上限或手动停止结束;单次 AI 执行也默认不限时,可按需设置 1–60 分钟上限。
67
67
  4. 勾选“创建后立即启动 AI”,复核软件配置。需要修改时展开“调整启动配置”;未配置的软件需选择 CLI / App 类型并补齐路径。项目目录、读写权限和已有会话按本场需要填写。
68
68
  5. 点击“创建并启动讨论”。程序完成建房、登记与首次任务交付;在进度看板核对 AI 是否实际进入,并查看保存的发言。
69
69
 
@@ -105,7 +105,7 @@ codiscuss local connect https://你的站点
105
105
 
106
106
  下次打开会保留原讨论,已暂停的房间需在进度看板明确重启。网页已连接或创建成功不代表 AI 已回答,应核对实际执行进展与讨论记录。独立服务管理、停止归属及故障排查见 [接入说明](docs/runner.md#常驻讨论服务);私有配置需保留在原位置。
107
107
 
108
- 软件失败时先查看暂停原因,登录和权限问题在对应软件中处理。本机网站的已登记房间可在“接入 → 修正本场软件连接”修改路径,待服务确认采用后重启;线上连接暂不提供已登记房间的路径修正。保存本场路径不会改动后续讨论的默认配置,详见 [配置修正](docs/runner.md#已启动后的配置修正)。
108
+ 软件失败时先查看暂停原因,登录和权限问题在对应软件中处理。本机网站的已登记房间,可从进度看板 Codex 旁的“…”菜单直接选择“切换 App / CLI”,在弹窗内暂停并保存;软件路径在“接入 → 修正本场软件连接”修改。原会话与讨论进度保留,待服务确认采用后重启;线上连接暂不提供此修正入口。切到 App 后模型和文件权限按 App 设置,切回 CLI 前须让 App 释放原聊天。本场修改不影响后续讨论的默认配置,详见 [配置修正](docs/runner.md#已启动后的配置修正)。
109
109
 
110
110
  **手动接入:** 选择“手动复制冷启动”,将“接入”页的参与指令交给对应 AI 会话;接入后可由软件原生定时或人工触发,无需每轮复制本地 AI 的回答。在线专家由主持人操作浏览器或由用户转交,首次确定分工后沿用。详见 [讨论工作流](docs/workflow.md)。
111
111
 
@@ -135,7 +135,9 @@ codiscuss local connect https://你的站点
135
135
 
136
136
  默认上限为 **3 轮、不限时长、到限暂停**,可改为请主持人总结,并设置单次执行上限与收束时间。计数和停止规则见 [讨论上限](docs/workflow.md#讨论上限)。
137
137
 
138
- 讨论结束后,可在左上方频道条目的“更多操作 → 导出对话”下载原始数据和全文,窄屏从频道导航进入;经典主题使用“备份当前讨论”按钮。也可用 `discuss-report` 整理带来源的综合报告。备份目前不支持导入恢复,也不下载外部附件。
138
+ 消息支持选择文件、拖拽或粘贴截图,每条最多 10 个附件、单个最多 20 MiB。PNG、JPEG、GIF、WebP 可放大查看,PDF 可在站内翻页、缩放,其他文件提供下载(不在线解析 Office 文件)。本地 AI 可通过 CLI 按需下载原文件;实际能否理解图片或文档取决于参与软件,上传不代表 AI 已读取。
139
+
140
+ 讨论结束后,可在左上方频道条目的“更多操作 → 导出对话”下载原始数据、全文及已发送附件原文件,窄屏从频道导航进入。也可用 `discuss-report` 整理带来源的综合报告。备份目前不支持导入恢复,也不下载外链内容。
139
141
 
140
142
  网站目前**没有用户登录和读取权限隔离**;公开部署前请按 [安全说明](.github/SECURITY.md) 配置访问边界。同一模型名称只能配置一个参与者,安排串行进行;在线读取、原生定时和浏览器操作能力取决于参与软件。
141
143
 
@@ -6,9 +6,8 @@ import time
6
6
  import uuid
7
7
 
8
8
  LEASE_SECONDS = 120
9
- WORK_SECONDS = 1200
10
9
  MAX_RUN_SECONDS = 86400
11
- LIMIT_DEFAULTS = {"max_rounds": 0, "max_minutes": 0, "execution_minutes": 20,
10
+ LIMIT_DEFAULTS = {"max_rounds": 0, "max_minutes": 0, "execution_minutes": 0,
12
11
  "on_limit": "pause", "summary_minutes": 5}
13
12
  PUBLIC_EVENT_FIELDS = ("id", "room_id", "run_id", "generation", "cause", "participant",
14
13
  "role", "turn_id", "status", "session_id", "lease_until", "result", "created_at")
@@ -45,7 +44,7 @@ def limit_values(payload, current=None):
45
44
  result = {key: (current or {}).get(key, value) for key, value in LIMIT_DEFAULTS.items()}
46
45
  result.update(payload)
47
46
  for key, minimum, maximum in (("max_rounds", 0, 100), ("max_minutes", 0, 1440),
48
- ("execution_minutes", 1, 60), ("summary_minutes", 1, 60)):
47
+ ("execution_minutes", 0, 60), ("summary_minutes", 1, 60)):
49
48
  if type(result[key]) is not int or not minimum <= result[key] <= maximum:
50
49
  raise ValueError(f"{key} 须为 {minimum}–{maximum} 的整数")
51
50
  if result["on_limit"] not in ("pause", "summarize"):
@@ -93,7 +92,7 @@ def require_within_limits(room):
93
92
  def execution_seconds(connection, room_id):
94
93
  """Read the per-execution budget shared by entry, renewal and writes."""
95
94
  row = connection.execute("SELECT execution_minutes FROM discussion_limits WHERE room_id=?", (room_id,)).fetchone()
96
- return row[0] * 60 if row else WORK_SECONDS
95
+ return (row[0] if row else LIMIT_DEFAULTS["execution_minutes"]) * 60
97
96
 
98
97
 
99
98
  def runtime_view(connection, room_id):
@@ -103,7 +102,7 @@ def runtime_view(connection, room_id):
103
102
  return {"mode": "manual", "status": "ready", "revision": 0, "protected": False}
104
103
  result = {key: row[key] for key in row.keys() if key != "control_hash"}
105
104
  result["protected"] = True
106
- result["expired"] = row["status"] == "running" and row["deadline"] <= int(time.time())
105
+ result["expired"] = row["status"] == "running" and 0 < row["deadline"] <= int(time.time())
107
106
  event = connection.execute("SELECT id,participant,role,status,result FROM discussion_events WHERE room_id=? ORDER BY id DESC LIMIT 1", (room_id,)).fetchone()
108
107
  result["latest_execution"] = dict(event) if event else None
109
108
  result["participants"] = participant_executions(connection, room_id)
@@ -157,11 +156,12 @@ def authorize_write(connection, room_id, token, operation, payload):
157
156
  actor = payload.get("actor", payload.get("submitted_by", payload.get("author", "用户")))
158
157
  if actor == "用户":
159
158
  return
159
+ work_seconds = execution_seconds(connection, room_id)
160
160
  event = connection.execute("""SELECT * FROM discussion_events WHERE room_id=? AND token_hash=?
161
161
  AND run_id=? AND generation=? AND status='started' AND lease_until>?
162
- AND started_at+?>?""", (room_id, hashed, run["run_id"], run["generation"],
163
- int(time.time()), execution_seconds(connection, room_id), int(time.time()))).fetchone()
164
- if not hashed or not event or run["status"] != "running" or run["deadline"] <= int(time.time()):
162
+ AND (?=0 OR started_at+?>?)""", (room_id, hashed, run["run_id"], run["generation"],
163
+ int(time.time()), work_seconds, work_seconds, int(time.time()))).fetchone()
164
+ if not hashed or not event or run["status"] != "running" or 0 < run["deadline"] <= int(time.time()):
165
165
  raise PermissionError("执行资格无效、已结束或已到期;请重新读取运行状态")
166
166
  if actor != event["participant"]:
167
167
  raise PermissionError("执行凭据与实际参与身份不符")
@@ -173,6 +173,8 @@ def authorize_write(connection, room_id, token, operation, payload):
173
173
  elif operation == "floor":
174
174
  if payload.get("speaker", actor) != actor:
175
175
  raise PermissionError("自动执行只能操作本人发言权")
176
+ elif operation == "attachments":
177
+ pass
176
178
  elif event["role"] != "host" or operation not in ("topic", "state", "turns", "limits"):
177
179
  raise PermissionError("本次执行不能进行此操作")
178
180
 
@@ -212,7 +214,7 @@ def configure_runtime(connection, room_id, token, payload):
212
214
 
213
215
 
214
216
  def validate_start(connection, room_id, payload):
215
- """Require an unarchived AI-hosted room and a finite user-specified deadline."""
217
+ """Validate an AI-hosted run; zero deadline leaves only the configured limits."""
216
218
  row = connection.execute("SELECT * FROM topic_settings JOIN room_directory USING(room_id) WHERE room_id=?",
217
219
  (room_id,)).fetchone()
218
220
  limits = connection.execute(LIMITS_SQL, (room_id,)).fetchone()
@@ -224,9 +226,9 @@ def validate_start(connection, room_id, payload):
224
226
  configured = min(value for value in (configured, closing_deadline, previous_deadline) if value)
225
227
  deadline = payload.get("deadline", configured)
226
228
  if type(deadline) is int and configured:
227
- deadline = min(deadline, configured)
228
- if type(deadline) is not int or not int(time.time()) < deadline <= int(time.time()) + MAX_RUN_SECONDS:
229
- raise ValueError("deadline 须为未来 24 小时内的 Unix 秒数")
229
+ deadline = min(deadline or configured, configured)
230
+ if type(deadline) is not int or (deadline != 0 and not int(time.time()) < deadline <= int(time.time()) + MAX_RUN_SECONDS):
231
+ raise ValueError("deadline 须为 0(不限时长)或未来 24 小时内的 Unix 秒数")
230
232
  experts = json.loads(row["experts"])
231
233
  if row["archived"] or row["phase"] == "concluded" or row["host"] == "用户":
232
234
  raise ValueError("自动运行需要未归档、未收束的话题及 AI 主持人")
@@ -262,12 +264,14 @@ def event_action(connection, room_id, event_id, token, payload):
262
264
  now = int(time.time())
263
265
  work_seconds = execution_seconds(connection, room_id)
264
266
  action = payload.get("action")
267
+ work_deadline = (now if action == "enter" else event["started_at"]) + work_seconds if work_seconds else 0
268
+ lease_until = min(now + LEASE_SECONDS, work_deadline or now + LEASE_SECONDS, run["deadline"] or now + LEASE_SECONDS)
265
269
  controller = token_hash(token) == run["control_hash"]
266
270
  if not controller and (not token_hash(token) or token_hash(token) != event["token_hash"]):
267
271
  raise PermissionError("执行凭据无效")
268
272
  if action == "view":
269
273
  return {key: event[key] for key in PUBLIC_EVENT_FIELDS}
270
- if run["status"] != "running" or run["deadline"] <= now or event["generation"] != run["generation"]:
274
+ if run["status"] != "running" or 0 < run["deadline"] <= now or event["generation"] != run["generation"]:
271
275
  raise FileExistsError("本次运行已停止或执行已过期")
272
276
  if action == "reserve" and controller:
273
277
  reserve_event(connection, event, payload)
@@ -275,12 +279,12 @@ def event_action(connection, room_id, event_id, token, payload):
275
279
  if event["status"] != "dispatched" or not payload.get("session_id"):
276
280
  raise FileExistsError("本次任务已进入或不可进入;不要重复执行")
277
281
  connection.execute("UPDATE discussion_events SET status='started',session_id=?,started_at=?,lease_until=? WHERE id=?",
278
- (str(payload["session_id"])[:200], now, min(now + LEASE_SECONDS, now + work_seconds, run["deadline"]), event_id))
282
+ (str(payload["session_id"])[:200], now, lease_until, event_id))
279
283
  elif action == "renew" and controller and event["status"] == "started":
280
- if event["lease_until"] <= now or event["started_at"] + work_seconds <= now:
284
+ if event["lease_until"] <= now or 0 < work_deadline <= now:
281
285
  raise FileExistsError("执行租约已到期,不能自动重新激活")
282
286
  connection.execute("UPDATE discussion_events SET lease_until=? WHERE id=?",
283
- (min(now + LEASE_SECONDS, event["started_at"] + work_seconds, run["deadline"]), event_id))
287
+ (lease_until, event_id))
284
288
  elif action == "fail" and event["status"] in ("dispatched", "started"):
285
289
  connection.execute("UPDATE discussion_events SET status='failed',result=? WHERE id=?",
286
290
  (str(payload.get("reason", "需要用户处理"))[:500], event_id))
@@ -52,6 +52,27 @@ CREATE TABLE IF NOT EXISTS messages (
52
52
  UNIQUE(room_id, submission_id)
53
53
  );
54
54
  CREATE INDEX IF NOT EXISTS messages_room ON messages(room_id, id);
55
+ CREATE TABLE IF NOT EXISTS attachments (
56
+ id TEXT PRIMARY KEY, room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
57
+ message_id INTEGER REFERENCES messages(id) ON DELETE CASCADE,
58
+ position INTEGER NOT NULL DEFAULT 0,
59
+ file_name TEXT NOT NULL, content_type TEXT NOT NULL,
60
+ size INTEGER NOT NULL, sha256 TEXT NOT NULL,
61
+ actor TEXT NOT NULL, submission_id TEXT NOT NULL, created_at TEXT NOT NULL,
62
+ UNIQUE(room_id, submission_id)
63
+ );
64
+ CREATE INDEX IF NOT EXISTS attachments_message ON attachments(message_id, position);
65
+ CREATE TRIGGER IF NOT EXISTS validate_attachment_link BEFORE UPDATE OF message_id ON attachments
66
+ WHEN NEW.message_id IS NOT NULL AND (
67
+ (OLD.message_id IS NOT NULL AND OLD.message_id != NEW.message_id)
68
+ OR NOT EXISTS (
69
+ SELECT 1 FROM messages m JOIN message_details d ON d.message_id=m.id
70
+ WHERE m.id=NEW.message_id AND m.room_id=NEW.room_id AND d.submitted_by=NEW.actor
71
+ )
72
+ )
73
+ BEGIN
74
+ SELECT RAISE(ABORT, 'attachment_unavailable');
75
+ END;
55
76
  CREATE TABLE IF NOT EXISTS message_summaries (
56
77
  message_id INTEGER PRIMARY KEY REFERENCES messages(id),
57
78
  text TEXT NOT NULL DEFAULT '', author TEXT NOT NULL DEFAULT ''
@@ -191,7 +212,7 @@ CREATE TABLE IF NOT EXISTS discussion_limits (
191
212
  room_id TEXT PRIMARY KEY REFERENCES rooms(id) ON DELETE CASCADE,
192
213
  max_rounds INTEGER NOT NULL DEFAULT 0,
193
214
  max_minutes INTEGER NOT NULL DEFAULT 0,
194
- execution_minutes INTEGER NOT NULL DEFAULT 20,
215
+ execution_minutes INTEGER NOT NULL DEFAULT 0,
195
216
  on_limit TEXT NOT NULL DEFAULT 'pause',
196
217
  summary_minutes INTEGER NOT NULL DEFAULT 5,
197
218
  started_at INTEGER NOT NULL DEFAULT 0,
@@ -199,10 +220,11 @@ CREATE TABLE IF NOT EXISTS discussion_limits (
199
220
  stop_reason TEXT NOT NULL DEFAULT '',
200
221
  revision INTEGER NOT NULL DEFAULT 0
201
222
  );
202
- INSERT OR IGNORE INTO discussion_limits(room_id) SELECT id FROM rooms;
203
- CREATE TRIGGER IF NOT EXISTS limits_room_created AFTER INSERT ON rooms
223
+ INSERT OR IGNORE INTO discussion_limits(room_id,execution_minutes) SELECT id,0 FROM rooms;
224
+ DROP TRIGGER IF EXISTS limits_room_created;
225
+ CREATE TRIGGER limits_room_created AFTER INSERT ON rooms
204
226
  BEGIN
205
- INSERT INTO discussion_limits(room_id) VALUES (NEW.id);
227
+ INSERT INTO discussion_limits(room_id,execution_minutes) VALUES (NEW.id,0);
206
228
  END;
207
229
  CREATE TRIGGER IF NOT EXISTS limits_topic_started AFTER INSERT ON topic_settings
208
230
  WHEN NEW.phase IN ('initial','discussing','reviewing')
@@ -225,7 +247,7 @@ CREATE TABLE IF NOT EXISTS discussion_runtime (
225
247
  generation INTEGER NOT NULL DEFAULT 1,
226
248
  status TEXT NOT NULL CHECK(status IN ('ready','running','paused','stopped','concluded')),
227
249
  control_hash TEXT NOT NULL,
228
- deadline INTEGER NOT NULL DEFAULT 0,
250
+ deadline INTEGER NOT NULL DEFAULT 0, -- 0 means no run duration limit; execution leases remain bounded.
229
251
  revision INTEGER NOT NULL DEFAULT 1,
230
252
  resume_phase TEXT NOT NULL DEFAULT 'initial',
231
253
  updated_at TEXT NOT NULL
@@ -258,6 +280,20 @@ CREATE TABLE IF NOT EXISTS event_timings (
258
280
  dispatched_at INTEGER NOT NULL DEFAULT 0,
259
281
  finished_at INTEGER NOT NULL DEFAULT 0
260
282
  );
283
+ -- Freeze dispatch-to-publication time independently of the recent execution list.
284
+ CREATE TABLE IF NOT EXISTS message_timings (
285
+ message_id INTEGER PRIMARY KEY REFERENCES messages(id) ON DELETE CASCADE,
286
+ elapsed_seconds INTEGER NOT NULL CHECK(elapsed_seconds>=0)
287
+ );
288
+ -- Only completed expert events identify a historical message unambiguously.
289
+ INSERT INTO message_timings(message_id,elapsed_seconds)
290
+ SELECT m.id,MAX(0,CAST(strftime('%s',m.created_at) AS INTEGER)-t.dispatched_at)
291
+ FROM messages AS m JOIN discussion_events AS e
292
+ ON e.room_id=m.room_id AND e.result='message:'||m.id AND e.participant=m.author
293
+ JOIN event_timings AS t ON t.event_id=e.id
294
+ WHERE m.source='cli' AND e.role='expert' AND e.status='completed'
295
+ AND e.started_at>0 AND t.dispatched_at>0 AND t.finished_at>0
296
+ ON CONFLICT(message_id) DO NOTHING;
261
297
  CREATE TRIGGER IF NOT EXISTS record_event_timing AFTER UPDATE OF status ON discussion_events
262
298
  WHEN OLD.status<>NEW.status
263
299
  BEGIN