ai_co_discuss 0.2.48 → 0.2.49
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 +10 -14
- package/.agents/skills/discuss-host/references/launch.md +1 -1
- package/.agents/skills/discuss-host/references/manual.md +5 -5
- package/.agents/skills/discuss-participant/SKILL.md +11 -12
- package/.agents/skills/discuss-participant/references/free.md +1 -1
- package/.agents/skills/discuss-participant/references/manual.md +2 -2
- package/.agents/skills/discuss-participant/references/writing.md +6 -11
- package/README.md +7 -9
- package/backend/free_room.py +39 -70
- package/backend/room_import.py +1 -5
- package/backend/runtime.py +21 -35
- package/backend/schema.sql +52 -60
- package/backend/server.py +130 -141
- package/docs/architecture.md +14 -12
- package/docs/cli.md +12 -21
- package/docs/deployment.md +2 -2
- package/docs/runner.md +8 -6
- package/docs/skills.md +3 -3
- package/docs/workflow.md +22 -40
- package/package.json +1 -1
- package/runner/api_participant.mjs +1 -11
- package/runner/codiscuss.mjs +0 -1
- package/runner/discuss.py +19 -37
- package/runner/discussion_adapters.mjs +1 -1
- package/runner/discussion_runner.mjs +1 -2
- package/runner/discussion_service.mjs +46 -1
- package/runner/free_runner.mjs +4 -3
- package/web/app.js +71 -77
- package/web/backup.js +1 -1
- package/web/index.html +5 -7
- package/web/modern.css +1 -1
- package/web/modern.js +1758 -1886
package/runner/discuss.py
CHANGED
|
@@ -30,17 +30,19 @@ MAX_BRIEF_LENGTH = 1600
|
|
|
30
30
|
PARTICIPANTS = ("用户", "Codex", "Claude", "Gemini", "DeepSeek", "Qwen", "Grok")
|
|
31
31
|
# A platform is a type; `Claude-评审` is a second Claude instance with its own session. Mirrors backend/identity.py.
|
|
32
32
|
INSTANCE_PATTERN = re.compile(r"(Codex|Claude|Gemini|DeepSeek|Qwen|Grok)(?:-[0-9A-Za-z_一-鿿]{1,16})?")
|
|
33
|
-
PHASES = ("preparing", "initial", "discussing", "
|
|
34
|
-
KINDS = ("opinion", "moderation", "
|
|
33
|
+
PHASES = ("preparing", "initial", "discussing", "paused", "concluded")
|
|
34
|
+
KINDS = ("opinion", "moderation", "chat")
|
|
35
|
+
# Old rooms keep receipts they already hold, so reading can still filter them; new ones cannot be written.
|
|
36
|
+
READ_KINDS = (*KINDS, "receipt")
|
|
35
37
|
OVERVIEW_FIELDS = ("id", "title", "goal", "host", "phase", "focus", "message_count",
|
|
36
|
-
"latest_id", "state_until", "current_turn", "queued_turns", "held_since", "
|
|
38
|
+
"latest_id", "state_until", "current_turn", "queued_turns", "held_since", "runtime", "limits", "completed_rounds", "answered_turns", "duplicate", "submission_id",
|
|
37
39
|
"mode", "members", "free", "parent_room", "linked_rooms")
|
|
38
40
|
BRIEF_FIELDS = ("id", "title", "goal", "host", "user_request", "background", "boundaries", "experts")
|
|
39
41
|
ROOM_LIST_FIELDS = ("id", "title", "goal", "message_count", "latest_id", "created_at")
|
|
40
42
|
GUIDANCE = {
|
|
41
43
|
"initial": "先基于目标、背景和边界独立形成观点;提交前不读他人观点或主持总结。",
|
|
42
|
-
"paused": "
|
|
43
|
-
"concluded": "
|
|
44
|
+
"paused": "停止处理安排并停止本讨论自己的后台任务,核对实际状态。",
|
|
45
|
+
"concluded": "话题已收束;停止本讨论自己的后台任务并核对实际状态。",
|
|
44
46
|
}
|
|
45
47
|
EXIT_CODES = {"0": "成功", "1": "HTTP/API 错误", "2": "参数或输入错误",
|
|
46
48
|
"3": "网络失败或响应无法确认", "130": "被中断,写入结果需核对"}
|
|
@@ -169,9 +171,8 @@ def add_topic(parser, create=False):
|
|
|
169
171
|
parser.add_argument("--phase", choices=PHASES, default=argparse.SUPPRESS)
|
|
170
172
|
parser.add_argument("--experts-file", default=argparse.SUPPRESS,
|
|
171
173
|
help='完整参与者映射 JSON,如 {"Codex":"cli","Gemini":"cli"};替换整个映射')
|
|
172
|
-
for name in ("max-rounds", "max-minutes", "execution-minutes"
|
|
174
|
+
for name in ("max-rounds", "max-minutes", "execution-minutes"):
|
|
173
175
|
parser.add_argument(f"--{name}", type=non_negative_int, default=argparse.SUPPRESS)
|
|
174
|
-
parser.add_argument("--on-limit", choices=("pause", "summarize"), default=argparse.SUPPRESS)
|
|
175
176
|
|
|
176
177
|
|
|
177
178
|
def add_leaf(subcommands, registry, name, description, needs_room=False):
|
|
@@ -197,7 +198,7 @@ def room_commands(subcommands, registry):
|
|
|
197
198
|
parser.add_argument("--submission-id", type=submission_id, help="按建房提交编号精确查询")
|
|
198
199
|
limits = add_leaf(subcommands, registry, "room limits", "查看上限;--check 检查并执行到限处理;--done 通知程序目标已完成", True)
|
|
199
200
|
limits.add_argument("--check", action="store_true")
|
|
200
|
-
limits.add_argument("--done", action="store_true", help="
|
|
201
|
+
limits.add_argument("--done", action="store_true", help="主持人宣告目标已完成:暂停讨论,由用户决定追加轮次或结束;须带 --conclusion")
|
|
201
202
|
limits.add_argument("--conclusion", help="room conclusion 返回的 revision;--done 必填")
|
|
202
203
|
create = add_leaf(subcommands, registry, "room create", "创建话题;不自动启动讨论或监控")
|
|
203
204
|
create.add_argument("--folder-id", help="直接在指定文件夹中创建;省略则未归类")
|
|
@@ -218,11 +219,11 @@ def message_commands(subcommands, registry):
|
|
|
218
219
|
listing.add_argument("--query", help="关键词或消息编号")
|
|
219
220
|
listing.add_argument("--author", type=participant)
|
|
220
221
|
listing.add_argument("--mention", type=participant)
|
|
221
|
-
listing.add_argument("--kind", choices=
|
|
222
|
+
listing.add_argument("--kind", choices=READ_KINDS, help="筛选观点、主持说明、协作消息或旧房间的历史回执")
|
|
222
223
|
listing.add_argument("--submission-id", type=submission_id, help="按提交编号精确查询结果")
|
|
223
224
|
view = add_leaf(subcommands, registry, "message view", "读取一条精确原文与来源", True)
|
|
224
225
|
view.add_argument("id", type=positive_int, help="消息编号")
|
|
225
|
-
post = add_leaf(subcommands, registry, "message post", "
|
|
226
|
+
post = add_leaf(subcommands, registry, "message post", "提交观点、主持说明、协作回答或代发", True)
|
|
226
227
|
add_text(post, "body")
|
|
227
228
|
post.add_argument("--attachment", action="append", type=attachment_id, default=[],
|
|
228
229
|
help="已上传附件 ID;可重复指定,最多 10 个")
|
|
@@ -230,7 +231,7 @@ def message_commands(subcommands, registry):
|
|
|
230
231
|
post.add_argument("--summary-by", type=participant, help="摘要撰写者:原作者或实际操作者;默认操作者")
|
|
231
232
|
post.add_argument("--author", type=participant, help="原作者;默认等于 --as;不同即代发")
|
|
232
233
|
post.add_argument("--kind", choices=KINDS, default="opinion")
|
|
233
|
-
post.add_argument("--turn", type=positive_int, help="所回答的安排编号,仅 opinion
|
|
234
|
+
post.add_argument("--turn", type=positive_int, help="所回答的安排编号,仅 opinion 可用;省略时回答你当前有效的安排")
|
|
234
235
|
post.add_argument("--execution", type=positive_int, help="自由房间:本次执行编号,仅 chat 可用;执行失效后不能改作普通发言")
|
|
235
236
|
post.add_argument("--to", type=ai_name, action="append", default=[],
|
|
236
237
|
help="自由房间:主持人点名派活,普通成员提出回应建议(须房主确认);可重复")
|
|
@@ -290,15 +291,6 @@ def state_commands(subcommands, registry):
|
|
|
290
291
|
parser.add_argument("--until", type=int, required=True, help="实际已读的材料截止编号,可为 0")
|
|
291
292
|
|
|
292
293
|
|
|
293
|
-
def floor_commands(subcommands, registry):
|
|
294
|
-
"""Expose explicit floor inspection, nominated claims and early returns."""
|
|
295
|
-
add_leaf(subcommands, registry, "floor view", "读取当前持有者、下一位与版本", True)
|
|
296
|
-
for action in ("take", "return"):
|
|
297
|
-
parser = add_leaf(subcommands, registry, f"floor {action}", "领取或归还发言权", True)
|
|
298
|
-
parser.add_argument("--speaker", type=participant, help="实际持有者;代操作时显式指定,默认 --as")
|
|
299
|
-
parser.add_argument("--revision", type=non_negative_int, required=True, help="刚读取的发言权版本;变化后需重新核对")
|
|
300
|
-
|
|
301
|
-
|
|
302
294
|
def run_commands(subcommands, registry):
|
|
303
295
|
"""Expose mode and lifecycle controls independently of discussion phases."""
|
|
304
296
|
for action in ("view", "configure", "start", "pause", "stop"):
|
|
@@ -308,7 +300,7 @@ def run_commands(subcommands, registry):
|
|
|
308
300
|
parser.add_argument("--mode", choices=("manual", "automatic"))
|
|
309
301
|
if action == "start":
|
|
310
302
|
parser.add_argument("--deadline", type=non_negative_int, help="截止时间 Unix 秒数;0 表示不限时长,仍受话题上限约束;省略时使用话题上限")
|
|
311
|
-
parser.add_argument("--phase", choices=("initial", "discussing"
|
|
303
|
+
parser.add_argument("--phase", choices=("initial", "discussing"))
|
|
312
304
|
|
|
313
305
|
|
|
314
306
|
def execution_commands(subcommands, registry):
|
|
@@ -337,7 +329,7 @@ def build_parser(prog="discuss"):
|
|
|
337
329
|
registry = {}
|
|
338
330
|
for name, register in (("room", room_commands), ("message", message_commands),
|
|
339
331
|
("attachment", attachment_commands),
|
|
340
|
-
("turn", turn_commands), ("state", state_commands),
|
|
332
|
+
("turn", turn_commands), ("state", state_commands),
|
|
341
333
|
("run", run_commands), ("execution", execution_commands), ("member", member_commands)):
|
|
342
334
|
group = subcommands.add_parser(name, help=f"{name} 操作", allow_abbrev=False)
|
|
343
335
|
add_common(group)
|
|
@@ -410,7 +402,7 @@ def require_actor(args):
|
|
|
410
402
|
def topic_payload(args):
|
|
411
403
|
"""Collect only supplied topic fields; expert configuration is a full map."""
|
|
412
404
|
payload = {}
|
|
413
|
-
limits = {key: getattr(args, key) for key in ("max_rounds", "max_minutes", "execution_minutes"
|
|
405
|
+
limits = {key: getattr(args, key) for key in ("max_rounds", "max_minutes", "execution_minutes") if hasattr(args, key)}
|
|
414
406
|
if limits:
|
|
415
407
|
payload["limits"] = limits
|
|
416
408
|
for field in ("title", "goal", "user_request", "background", "boundaries", "focus"):
|
|
@@ -473,7 +465,7 @@ def plan_message(args):
|
|
|
473
465
|
return RequestPlan(base, params={key: value for key, value in params.items() if value is not None})
|
|
474
466
|
actor = require_actor(args)
|
|
475
467
|
if args.turn and args.kind != "opinion":
|
|
476
|
-
raise CliError("只有 opinion 可以关联 --turn
|
|
468
|
+
raise CliError("只有 opinion 可以关联 --turn")
|
|
477
469
|
if (args.execution or args.request or args.to) and (args.kind != "chat" or args.turn):
|
|
478
470
|
raise CliError("--execution / --request 仅用于自由房间的 chat 回答,不能与 --turn 同时使用")
|
|
479
471
|
if args.execution and args.request:
|
|
@@ -578,16 +570,6 @@ def turn_payload(args):
|
|
|
578
570
|
return {**payload, "expert": args.expert, "question": question}
|
|
579
571
|
|
|
580
572
|
|
|
581
|
-
def plan_floor(args):
|
|
582
|
-
"""Send one versioned operation without silently claiming on a failed write."""
|
|
583
|
-
path = f"/api/rooms/{args.room}/floor"
|
|
584
|
-
if args.action == "view":
|
|
585
|
-
return RequestPlan(path)
|
|
586
|
-
actor = require_actor(args)
|
|
587
|
-
return RequestPlan(path, "POST", {"actor": actor, "speaker": args.speaker or actor,
|
|
588
|
-
"action": args.action, "revision": args.revision})
|
|
589
|
-
|
|
590
|
-
|
|
591
573
|
def plan_state(args):
|
|
592
574
|
"""Keep a host summary's cutoff explicit instead of assuming latest means read."""
|
|
593
575
|
base = f"/api/rooms/{args.room}"
|
|
@@ -658,7 +640,7 @@ def build_plan(args):
|
|
|
658
640
|
raise CliError("一次命令只能有一个字段从标准输入读取")
|
|
659
641
|
handlers = {"room": plan_room, "message": plan_message, "turn": plan_turn,
|
|
660
642
|
"attachment": plan_attachment,
|
|
661
|
-
"state": plan_state, "
|
|
643
|
+
"state": plan_state, "api": plan_api,
|
|
662
644
|
"run": plan_run, "execution": plan_execution, "member": plan_member}
|
|
663
645
|
plan = RequestPlan("/api/health") if args.resource == "health" else handlers[args.resource](args)
|
|
664
646
|
if getattr(args, "dry_run", False) and plan.method != "POST" and plan.download_file is None:
|
|
@@ -782,8 +764,8 @@ def project_result(value, plan, site):
|
|
|
782
764
|
return {"rooms": [select_fields(room, ROOM_LIST_FIELDS) for room in value["rooms"]]}
|
|
783
765
|
if plan.view in ("overview", "brief", "state", "current"):
|
|
784
766
|
fields = {"overview": OVERVIEW_FIELDS, "brief": BRIEF_FIELDS,
|
|
785
|
-
"state": ("id", "state_text", "state_until", "host_brief", "
|
|
786
|
-
"current": ("id", "phase", "current_turn", "queued_turns", "held_since", "
|
|
767
|
+
"state": ("id", "state_text", "state_until", "host_brief", "answered_turns"),
|
|
768
|
+
"current": ("id", "phase", "current_turn", "queued_turns", "held_since", "answered_turns")}[plan.view]
|
|
787
769
|
result = select_fields(value, fields)
|
|
788
770
|
if plan.view in ("overview", "current"):
|
|
789
771
|
result["guidance"] = GUIDANCE.get(value["phase"], "按本次主持或专家职责,结合最新阶段、安排和必要原文决定下一步。")
|
|
@@ -16,7 +16,7 @@ const CLI_EXIT_GRACE_MS = 1500;
|
|
|
16
16
|
const CLI_MAX_RUN_MS = 2 * 60 * 60_000;
|
|
17
17
|
const CODEX_PERMISSIONS_MIN_MINOR = 163;
|
|
18
18
|
/** Website subcommands an automatic task may run; service, launch, run, api and setup stay unapproved. */
|
|
19
|
-
export const TASK_SUBCOMMANDS = ['room', 'message', 'turn', 'state', '
|
|
19
|
+
export const TASK_SUBCOMMANDS = ['room', 'message', 'turn', 'state', 'execution', 'health', 'schema'];
|
|
20
20
|
|
|
21
21
|
/** Only failures before a software invocation are safe for automatic redelivery. */
|
|
22
22
|
function not_sent(error) { return Object.assign(error, { delivery_status: 'not_sent' }); }
|
|
@@ -448,7 +448,6 @@ export class DiscussionRunner {
|
|
|
448
448
|
const room = await api(this.config, '');
|
|
449
449
|
if (room.latest_id !== seen.latest_id || (room.held_since || 0) !== seen.held_since) throw new Error('判断期间房间有新留言或用户操作,放弃应用');
|
|
450
450
|
const actor = room.host;
|
|
451
|
-
if (room.floor.holder !== actor) await api(this.config, '/floor', { actor, speaker: actor, action: 'take', revision: room.floor.revision }, token);
|
|
452
451
|
if (plan.phase) await api(this.config, '/topic', { actor, phase: plan.phase }, token);
|
|
453
452
|
if (plan.continue_plan) await api(this.config, '/turns/continue', { actor }, token);
|
|
454
453
|
else {
|
|
@@ -539,7 +538,7 @@ export class DiscussionRunner {
|
|
|
539
538
|
if (/^HTTP 404: 房间不存在/.test(error.message)) remove_agy_projects(this.config.directory);
|
|
540
539
|
throw error;
|
|
541
540
|
}
|
|
542
|
-
if (room.limits?.reached &&
|
|
541
|
+
if (room.limits?.reached && room.runtime.status === 'running') {
|
|
543
542
|
const generation = room.runtime.generation;
|
|
544
543
|
room = await api(this.config, '/limits', { actor: '用户' });
|
|
545
544
|
if (room.runtime.generation !== generation) this.stop_children();
|
|
@@ -165,6 +165,50 @@ export function unregister_room(home, config_path) {
|
|
|
165
165
|
});
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
+
/**
|
|
169
|
+
* A discussion room's dispatcher plus delivery of the user's direct questions. The questions are free-room requests that sit
|
|
170
|
+
* beside the discussion's turns: they wake a named member at any phase, and the website refuses to run one member's question
|
|
171
|
+
* and discussion turn at the same time, so one session never has two writers.
|
|
172
|
+
*/
|
|
173
|
+
export class DiscussionWithQuestions {
|
|
174
|
+
constructor(discussion, questions) { this.discussion = discussion; this.questions = questions; }
|
|
175
|
+
get members() { return this.discussion.members; }
|
|
176
|
+
get children() { return new Set([...this.discussion.children, ...this.questions.children]); }
|
|
177
|
+
get config() { return this.discussion.config; }
|
|
178
|
+
async tick() {
|
|
179
|
+
const result = await this.discussion.tick();
|
|
180
|
+
this.questions.adopt({ ...this.discussion.config, draft_directory: this.discussion.config.draft_directory });
|
|
181
|
+
try { await this.questions.tick(); }
|
|
182
|
+
catch (error) { this.questions.log(`直接提问投递出错:${error.message}`); }
|
|
183
|
+
return result;
|
|
184
|
+
}
|
|
185
|
+
stop_children() { this.discussion.stop_children(); this.questions.stop_children(); }
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* A free room's dispatcher. Its requests are always delivered; once the website says the room is in a discussion
|
|
190
|
+
* (the owner opened one), the discussion engine runs beside them, and when it ends the room simply goes on freely.
|
|
191
|
+
*/
|
|
192
|
+
export class FreeRoomWithDiscussion {
|
|
193
|
+
constructor(questions) { this.questions = questions; this.discussion = null; }
|
|
194
|
+
get config() { return this.questions.config; }
|
|
195
|
+
get members() { return this.discussion?.members; }
|
|
196
|
+
get children() { return new Set([...(this.discussion?.children || []), ...this.questions.children]); }
|
|
197
|
+
adopt(config) { this.questions.adopt(config); }
|
|
198
|
+
async tick() {
|
|
199
|
+
const room = await api(this.questions.config, '');
|
|
200
|
+
let result;
|
|
201
|
+
if (room.mode !== 'free') {
|
|
202
|
+
this.discussion ??= new DiscussionRunner(this.questions.config, this.questions.store, undefined, this.questions.log);
|
|
203
|
+
this.discussion.config = this.questions.config;
|
|
204
|
+
result = await this.discussion.tick();
|
|
205
|
+
}
|
|
206
|
+
await this.questions.tick();
|
|
207
|
+
return result;
|
|
208
|
+
}
|
|
209
|
+
stop_children() { this.discussion?.stop_children(); this.questions.stop_children(); }
|
|
210
|
+
}
|
|
211
|
+
|
|
168
212
|
/** One registered room: its own data-directory lock, store and dispatcher. */
|
|
169
213
|
class RoomWorker {
|
|
170
214
|
constructor(path, create_runner, log, home) {
|
|
@@ -280,7 +324,8 @@ class RoomWorker {
|
|
|
280
324
|
|
|
281
325
|
/** Keep workers aligned with the registry and tick each one per poll. */
|
|
282
326
|
export class DiscussionService {
|
|
283
|
-
constructor(home, create_runner = (config, store, log) => config.mode === 'free' ? new
|
|
327
|
+
constructor(home, create_runner = (config, store, log) => config.mode === 'free' ? new FreeRoomWithDiscussion(new FreeRoomRunner(config, store, undefined, log))
|
|
328
|
+
: new DiscussionWithQuestions(new DiscussionRunner(config, store, undefined, log), new FreeRoomRunner(config, store, undefined, log)), log = () => {}) {
|
|
284
329
|
this.home = home;
|
|
285
330
|
this.create_runner = create_runner;
|
|
286
331
|
this.log = log;
|
package/runner/free_runner.mjs
CHANGED
|
@@ -69,7 +69,8 @@ export function free_task_files(config, member, execution, binding) {
|
|
|
69
69
|
: profile.project_access === 'write' ? '用户允许按请求修改 cwd 中的项目文件。' : 'cwd 中的项目文件只读,不修改。';
|
|
70
70
|
const messages = execution.requests.map(item => `#${item.message_id}`).join('、');
|
|
71
71
|
// The host routes unnamed messages and receives hand-backs; it may name members in its answer.
|
|
72
|
-
const
|
|
72
|
+
const discussion = config.mode !== 'free';
|
|
73
|
+
const host_rule = member.role === 'host' && !discussion
|
|
73
74
|
? `你是本房间的主持人:purpose 为 route 的请求是用户未点名的消息,handoff 表示你派出的成员都已结束。先用 ${cli} room view ${config.room} 查看成员与状态,再决定由谁处理;需要派活时在回答中加 --to 成员名(可重复,不能点名自己),被点名的成员都结束后你会再被唤醒一次。不需要他人时不加 --to,本次即结束。只派分给各成员的文件或模块,避免多人改同一文件。`
|
|
74
75
|
: '';
|
|
75
76
|
atomic_json(task_path, { site: config.site, room: config.room, mode: 'free', member: member.name,
|
|
@@ -78,8 +79,8 @@ export function free_task_files(config, member, execution, binding) {
|
|
|
78
79
|
read_after: execution.read_until, cwd: profile.cwd || null, project_access: profile.project_access, draft_directory,
|
|
79
80
|
skills: [join(config.project, '.agents/skills/discuss-participant/SKILL.md')], cli, subcommands: TASK_SUBCOMMANDS,
|
|
80
81
|
...(bound_access ? { access_env: 'DISCUSS_ACCESS_FILE' } : { access_file: access_path }),
|
|
81
|
-
instructions:
|
|
82
|
-
const prompt =
|
|
82
|
+
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}不读取或打印凭据内容,不建定时任务。` });
|
|
83
|
+
const prompt = `用户在${discussion ? '讨论' : '自由协作'}房间点名了你(${member.name}),对应消息 ${messages}。请先读取任务文件:${task_path},按其中说明读取新消息、完成请求,并用 ${cli} message post 带 --execution ${execution.id} 提交一次回答后结束。命令只用任务内 subcommands 列出的 ${cli} 子命令;${project_rule}不读取凭据内容。`;
|
|
83
84
|
return { id, participant: member.name, directory: config.directory, draft_directory,
|
|
84
85
|
skills_directory: join(config.project, '.agents', 'skills'), access_path, prompt };
|
|
85
86
|
}
|
package/web/app.js
CHANGED
|
@@ -48,7 +48,7 @@ const PLATFORM_IDENTITIES = {
|
|
|
48
48
|
};
|
|
49
49
|
const IDENTITIES = by_platform(PLATFORM_IDENTITIES, (identity, name) => ({ ...identity, label: name, description: "AI 实例" }));
|
|
50
50
|
const state = { rooms: [], folders: [], room: null, page: 1, pages: 1, query: "", mention_only: false, free_notice: "", free_error: "",
|
|
51
|
-
free_proxy: "", create_parent: null, named_followup: false,
|
|
51
|
+
free_proxy: "", create_parent: null, named_followup: false, create_discussion: false,
|
|
52
52
|
archive_view: false, directory_sort: "manual", management: null, dragged_room: null, folder_moves: new Map(), folder_version: 0,
|
|
53
53
|
reply_to: null, turn_reply_to: null, tab: "topic", refresh_version: 0, context_dirty: false, topic_dirty: false,
|
|
54
54
|
snapshot_id: 0, pending_submission: null, pending_turn: null, pending_room: null, toast_timer: null,
|
|
@@ -690,7 +690,7 @@ function render_room() {
|
|
|
690
690
|
if (!state.room) return;
|
|
691
691
|
const room = state.room;
|
|
692
692
|
element("room-title").textContent = room.title;
|
|
693
|
-
element("room-subtitle").textContent = `${room.archived ? '已存档 · ' : ''}${room.message_count}
|
|
693
|
+
element("room-subtitle").textContent = `${room.archived ? '已存档 · ' : ''}${room.message_count} 条发言`;
|
|
694
694
|
element("message-count").textContent = room.message_count;
|
|
695
695
|
element("room-goal").textContent = room.goal || "尚未填写议题目标。";
|
|
696
696
|
element("latest-id").textContent = `#${room.latest_id}`;
|
|
@@ -704,16 +704,10 @@ function render_room() {
|
|
|
704
704
|
}
|
|
705
705
|
render_access();
|
|
706
706
|
render_topic();
|
|
707
|
-
render_receipts();
|
|
708
707
|
render_host_brief();
|
|
709
708
|
update_composer_role();
|
|
710
709
|
}
|
|
711
710
|
|
|
712
|
-
/** Describe the actual holder or nominated recipient for both themes. */
|
|
713
|
-
function floor_status(room) {
|
|
714
|
-
return room.floor?.holder ? `${display_name(room.floor.holder)} 持有` : `待 ${display_name(room.floor?.next_speaker || room.host)} 领取`;
|
|
715
|
-
}
|
|
716
|
-
|
|
717
711
|
/** A round completes only after all configured experts have contributed. */
|
|
718
712
|
function progress_round(room) {
|
|
719
713
|
const groups = round_groups(room);
|
|
@@ -771,16 +765,6 @@ function render_host_brief() {
|
|
|
771
765
|
element("host-brief-meta").textContent = brief_meta(room);
|
|
772
766
|
}
|
|
773
767
|
|
|
774
|
-
/** Show bounded receipt references without interpreting free-form reports as status. */
|
|
775
|
-
function render_receipts() {
|
|
776
|
-
const receipts = state.room.latest_receipts;
|
|
777
|
-
const links = receipts?.map((receipt) => `<button type="button" class="text-button" data-receipt-detail="${receipt.id}">${escape_html(display_name(receipt.author))} #${receipt.id} · ${escape_html(new Date(receipt.created_at).toLocaleString("zh-CN"))}${receipt.id > state.room.state_until ? " · 晚于摘要" : ""}</button>`).join("") || "尚无运行回执。";
|
|
778
|
-
for (const id of ["topic-receipts", "state-receipts"]) {
|
|
779
|
-
element(id).hidden = !Array.isArray(receipts);
|
|
780
|
-
element(`${id}-links`).innerHTML = links;
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
|
|
784
768
|
/** Describe roles separately from authors and the actual submitter. */
|
|
785
769
|
function message_provenance(message) {
|
|
786
770
|
const role = message.kind === "receipt" ? "运行回执" : message.kind === "moderation" ? "主持发言" : (message.author === "用户" ? "用户发言" : "专家观点");
|
|
@@ -961,10 +945,15 @@ function message_payload() {
|
|
|
961
945
|
return payload;
|
|
962
946
|
}
|
|
963
947
|
const mode = element("message-kind").value;
|
|
964
|
-
const author =
|
|
948
|
+
const author = mode === "moderation" ? "用户" : element("author-select").value;
|
|
965
949
|
if (author !== "用户" && state.room.experts?.[author] !== "online") throw new Error("本地 AI 须自行提交回答;网页仅代发在线成员的真实原文");
|
|
966
950
|
const payload = { author, text, reply_to: state.reply_to, turn_id: state.turn_reply_to,
|
|
967
|
-
kind:
|
|
951
|
+
kind: mode === "moderation" ? mode : "opinion", submitted_by: "用户" };
|
|
952
|
+
// @mentions of active members outside code are direct questions; the website queues them beside the discussion.
|
|
953
|
+
if (payload.kind === "opinion" && payload.author === "用户") {
|
|
954
|
+
const named = (state.room.members || []).filter(member => member.status === "active" && member.name !== "用户" && mentioned(text, member.name)).map(member => member.name);
|
|
955
|
+
if (named.length) payload.to = named;
|
|
956
|
+
}
|
|
968
957
|
if (payload.kind === "opinion" && payload.author !== "用户") {
|
|
969
958
|
payload.summary = element("message-summary").value.trim();
|
|
970
959
|
payload.summary_by = element("message-summary-source").value === "submitter" ? payload.submitted_by : payload.author;
|
|
@@ -1002,6 +991,7 @@ function reset_create_form() {
|
|
|
1002
991
|
state.launch.workspace = {};
|
|
1003
992
|
state.launch.scheduler = {};
|
|
1004
993
|
state.named_followup = false;
|
|
994
|
+
state.create_discussion = false;
|
|
1005
995
|
state.launch.profiles = JSON.parse(JSON.stringify(state.launch.saved_profiles || {}));
|
|
1006
996
|
Object.values(state.launch.profiles).forEach(fill_api_key);
|
|
1007
997
|
state.launch.notice = "";
|
|
@@ -1427,9 +1417,9 @@ function render_access() {
|
|
|
1427
1417
|
|
|
1428
1418
|
/** Read numeric limits from the shared classic/modern form controls. */
|
|
1429
1419
|
function read_limits(prefix) {
|
|
1430
|
-
return Object.fromEntries(["max_rounds", "max_minutes", "execution_minutes"
|
|
1420
|
+
return Object.fromEntries(["max_rounds", "max_minutes", "execution_minutes"].map(key => {
|
|
1431
1421
|
const value = element(`${prefix}-limit-${key}${prefix === "topic" ? "-input" : ""}`).value;
|
|
1432
|
-
return [key,
|
|
1422
|
+
return [key, Number(value)];
|
|
1433
1423
|
}));
|
|
1434
1424
|
}
|
|
1435
1425
|
|
|
@@ -1447,9 +1437,8 @@ function limits_label(limits = {}) {
|
|
|
1447
1437
|
const rounds = limits.round_cap || limits.max_rounds ? limit_rounds(limits).text : `${limits.completed_rounds || 0} 轮 · 未设轮次上限`;
|
|
1448
1438
|
const duration = limits.max_minutes ? `总时长 ${limits.max_minutes} 分钟` : "未设总时长";
|
|
1449
1439
|
const progress = finished ? "讨论已收束" : !limits.started_at ? "尚未计时" : `已用 ${Math.floor((limits.elapsed_seconds || 0) / 60)} 分钟${limits.remaining_seconds == null ? "" : ` · 剩余 ${Math.ceil(limits.remaining_seconds / 60)} 分钟`}`;
|
|
1450
|
-
const status = finished ? "" : limits.
|
|
1451
|
-
const
|
|
1452
|
-
const action = limits.on_limit === "summarize" ? `主持人总结 · ${summary}` : "到限暂停";
|
|
1440
|
+
const status = finished ? "" : limits.reached ? "已达上限,停止新增安排" : "";
|
|
1441
|
+
const action = "到限暂停";
|
|
1453
1442
|
const execution = limits.execution_minutes ? `${limits.execution_minutes} 分钟` : "不限时长";
|
|
1454
1443
|
return `${rounds};${duration};${progress}。单次自动执行 ${execution}。${action}${status ? `;${status}` : ""}。`;
|
|
1455
1444
|
}
|
|
@@ -1538,7 +1527,13 @@ async function submit_room(event, folder_id = null) {
|
|
|
1538
1527
|
const launching = free ? Object.values(payload.experts).includes("cli") || (state.launch.available && Boolean(creation_launch_options().scheduler)) : state.launch.create && state.launch.available;
|
|
1539
1528
|
if (launching && !state.launch.available) throw new Error("本机唤醒的成员需要连接本机服务;也可改为手动接入或网页代发");
|
|
1540
1529
|
const launch_draft = launching ? creation_launch_options() : null;
|
|
1541
|
-
|
|
1530
|
+
// A discussion is only a way to push the room forward: the room is created as a free room, then opened with these limits.
|
|
1531
|
+
const discussion = free && state.create_discussion ? { goal: payload.goal.trim(), named_followup: state.named_followup,
|
|
1532
|
+
limits: read_limits("new-room") } : null;
|
|
1533
|
+
if (discussion && !(launching && payload.host && payload.experts[payload.host] === "cli")) throw new Error("开启讨论需要一位由本机唤醒的 AI 主持人;请先添加并设为主持人");
|
|
1534
|
+
// The key joins the signature only when a discussion is requested, so recovered requests keep matching their stored signature.
|
|
1535
|
+
const signed = (current_payload, current_draft) => JSON.stringify(discussion ? { payload: current_payload, launch_draft: current_draft, discussion } : { payload: current_payload, launch_draft: current_draft });
|
|
1536
|
+
const signature = signed(payload, launch_draft);
|
|
1542
1537
|
const navigation = state.navigation_version;
|
|
1543
1538
|
if (!launching) element("room-dialog").close();
|
|
1544
1539
|
state.launch.error = "";
|
|
@@ -1564,7 +1559,8 @@ async function submit_room(event, folder_id = null) {
|
|
|
1564
1559
|
}
|
|
1565
1560
|
state.pending_room = null;
|
|
1566
1561
|
state.create_parent = null;
|
|
1567
|
-
if (
|
|
1562
|
+
if (discussion) await open_discussion_after_creation(room.id, discussion);
|
|
1563
|
+
if (signed(room_creation_payload(folder_id), launching ? creation_launch_options() : null) === signature) {
|
|
1568
1564
|
element("room-form").reset();
|
|
1569
1565
|
reset_creation_mode();
|
|
1570
1566
|
render_expert_inputs("new-expert-modes", default_experts());
|
|
@@ -1575,6 +1571,18 @@ async function submit_room(event, folder_id = null) {
|
|
|
1575
1571
|
if (state.navigation_version === navigation) { state.archive_view = false; await select_room(room.id); }
|
|
1576
1572
|
}
|
|
1577
1573
|
|
|
1574
|
+
/** Open and start the discussion of a room just created; a failure leaves the room as an ordinary free room. */
|
|
1575
|
+
async function open_discussion_after_creation(room_id, { goal, named_followup, limits }) {
|
|
1576
|
+
const path = `/api/rooms/${room_id}`;
|
|
1577
|
+
try {
|
|
1578
|
+
await request_json(`${path}/discussion`, { action: "start", ...(goal ? { goal } : {}), limits, named_followup });
|
|
1579
|
+
const runtime = await request_json(`${path}/runtime`);
|
|
1580
|
+
await request_json(`${path}/runtime`, { action: "start", mode: "automatic", revision: runtime.revision, deadline: 0 });
|
|
1581
|
+
} catch (error) {
|
|
1582
|
+
show_toast(`房间已创建,但开启讨论失败:${error.message};可在右侧成员栏重试`);
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1578
1586
|
/** Read a site-bound connection without exposing its credential to the shared view snapshot. */
|
|
1579
1587
|
function restore_local_connection() {
|
|
1580
1588
|
const fragment = new URLSearchParams(location.hash?.slice(1) || "");
|
|
@@ -1939,6 +1947,12 @@ function write_named_followup(value) {
|
|
|
1939
1947
|
publish_view();
|
|
1940
1948
|
}
|
|
1941
1949
|
|
|
1950
|
+
/** Choose whether a new room opens a discussion right away; the room itself is always the same collaboration room. */
|
|
1951
|
+
function write_create_discussion(value) {
|
|
1952
|
+
state.create_discussion = Boolean(value);
|
|
1953
|
+
publish_view();
|
|
1954
|
+
}
|
|
1955
|
+
|
|
1942
1956
|
/** Change the smart scheduling draft; a new provider starts from its preset model and any key saved for its root. */
|
|
1943
1957
|
function write_scheduler(field, value) {
|
|
1944
1958
|
const scheduler = state.launch.scheduler;
|
|
@@ -2333,8 +2347,6 @@ async function skip_turn() {
|
|
|
2333
2347
|
const revision = runtime.revision;
|
|
2334
2348
|
let saved = false;
|
|
2335
2349
|
try {
|
|
2336
|
-
const floor = room.floor;
|
|
2337
|
-
if (floor.holder !== turn.expert) await request_json(`${path}/floor`, { actor: "用户", speaker: turn.expert, action: "take", revision: floor.revision });
|
|
2338
2350
|
await request_json(`${path}/messages`, { author: turn.expert, submitted_by: "用户", kind: "opinion", turn_id: turn.id,
|
|
2339
2351
|
text: "(本次发言已被用户跳过)", submission_id: new_submission_id() });
|
|
2340
2352
|
saved = true;
|
|
@@ -2364,14 +2376,6 @@ async function extend_rounds(rounds) {
|
|
|
2364
2376
|
show_toast(`已追加 ${rounds} 轮并重启讨论`);
|
|
2365
2377
|
}
|
|
2366
2378
|
|
|
2367
|
-
/** Ask the host for a summary of the paused discussion; it ends in the concluded state. */
|
|
2368
|
-
async function request_summary() {
|
|
2369
|
-
await request_json(`/api/rooms/${state.room.id}/limits`, { actor: "用户", action: "summarize" });
|
|
2370
|
-
await resume_after_limit_decision();
|
|
2371
|
-
await refresh_room();
|
|
2372
|
-
show_toast("已请主持人总结,总结后讨论收束");
|
|
2373
|
-
}
|
|
2374
|
-
|
|
2375
2379
|
/** Reopen a concluded discussion with a fresh round and time budget; history stays. */
|
|
2376
2380
|
async function reopen_discussion({ max_rounds, max_minutes }) {
|
|
2377
2381
|
await request_json(`/api/rooms/${state.room.id}/limits`, { actor: "用户", action: "reopen", max_rounds, max_minutes });
|
|
@@ -2380,13 +2384,6 @@ async function reopen_discussion({ max_rounds, max_minutes }) {
|
|
|
2380
2384
|
show_toast("讨论已重启,按新的轮次与时长上限继续");
|
|
2381
2385
|
}
|
|
2382
2386
|
|
|
2383
|
-
/** End early with a host summary limited to the configured closing reserve. */
|
|
2384
|
-
async function force_close() {
|
|
2385
|
-
await request_json(`/api/rooms/${state.room.id}/limits`, { actor: "用户", force: true });
|
|
2386
|
-
await refresh_room();
|
|
2387
|
-
show_toast("已取消当前安排,等待主持人总结收尾");
|
|
2388
|
-
}
|
|
2389
|
-
|
|
2390
2387
|
/** End early without a summary, keeping every original message. */
|
|
2391
2388
|
async function conclude_discussion() {
|
|
2392
2389
|
const turn = state.room.current_turn;
|
|
@@ -2398,6 +2395,24 @@ async function conclude_discussion() {
|
|
|
2398
2395
|
show_toast("讨论已直接收束,历史记录保留");
|
|
2399
2396
|
}
|
|
2400
2397
|
|
|
2398
|
+
/** Turn this free room into a discussion within the given limits and start the run; the local service takes it over at its next check. */
|
|
2399
|
+
async function start_room_discussion({ goal, max_rounds, max_minutes }) {
|
|
2400
|
+
const room_id = state.room.id, path = `/api/rooms/${room_id}`;
|
|
2401
|
+
if (!localStorage.getItem(`discuss-access:${room_id}`)) throw new Error("此浏览器缺少房间控制凭据,请使用创建房间的浏览器");
|
|
2402
|
+
await request_json(`${path}/discussion`, { action: "start", ...(goal ? { goal } : {}), limits: { max_rounds, max_minutes } });
|
|
2403
|
+
const runtime = await request_json(`${path}/runtime`);
|
|
2404
|
+
await request_json(`${path}/runtime`, { action: "start", mode: "automatic", revision: runtime.revision, deadline: 0 });
|
|
2405
|
+
await refresh_room();
|
|
2406
|
+
show_toast("讨论已开启,由主持人推进;需本机接入程序保持运行");
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2409
|
+
/** Conclude the discussion and go on as a free room; every message and the conclusion stay. */
|
|
2410
|
+
async function end_room_discussion() {
|
|
2411
|
+
await request_json(`/api/rooms/${state.room.id}/discussion`, { action: "end" });
|
|
2412
|
+
await refresh_room();
|
|
2413
|
+
show_toast("讨论已结束,房间回到自由协作,历史记录保留");
|
|
2414
|
+
}
|
|
2415
|
+
|
|
2401
2416
|
/** Revoke active execution before the local adapters are stopped. */
|
|
2402
2417
|
async function pause_runtime() {
|
|
2403
2418
|
await request_json(`/api/rooms/${state.room.id}/runtime`, { action: "pause", revision: state.room.runtime.revision });
|
|
@@ -2540,7 +2555,7 @@ async function adopt_recommendation({ message_id, mode }) {
|
|
|
2540
2555
|
|
|
2541
2556
|
/** A completed creation starts the next form with fresh mode defaults. */
|
|
2542
2557
|
function reset_creation_mode() {
|
|
2543
|
-
state.creation_mode = "
|
|
2558
|
+
state.creation_mode = "free";
|
|
2544
2559
|
state.creation_drafts = {};
|
|
2545
2560
|
}
|
|
2546
2561
|
|
|
@@ -2574,9 +2589,9 @@ function render_topic() {
|
|
|
2574
2589
|
for (const field of ["title", "goal", "focus", "user_request", "background", "boundaries"]) element(`topic-${field}-input`).value = room[field] || "";
|
|
2575
2590
|
render_host_options("topic-host-select", Object.keys(room.experts).filter(name => room.experts[name] === "cli"), room.host);
|
|
2576
2591
|
element("topic-phase-select").value = room.phase;
|
|
2577
|
-
for (const key of ["max_rounds", "max_minutes", "execution_minutes"
|
|
2592
|
+
for (const key of ["max_rounds", "max_minutes", "execution_minutes"]) {
|
|
2578
2593
|
const input = element(`topic-limit-${key}-input`);
|
|
2579
|
-
input.value = room.limits?.[key] ||
|
|
2594
|
+
input.value = room.limits?.[key] || "";
|
|
2580
2595
|
input.disabled = !["preparing", "paused"].includes(room.phase);
|
|
2581
2596
|
}
|
|
2582
2597
|
render_expert_inputs("topic-expert-modes", room.experts);
|
|
@@ -2589,11 +2604,6 @@ function render_topic() {
|
|
|
2589
2604
|
|
|
2590
2605
|
/** Display the actual assignment state without implying an external delivery. */
|
|
2591
2606
|
function render_current_turn() {
|
|
2592
|
-
const floor = state.room.floor;
|
|
2593
|
-
element("floor-status").textContent = floor_status(state.room);
|
|
2594
|
-
element("take-floor").disabled = !floor || Boolean(floor.holder);
|
|
2595
|
-
element("take-floor").textContent = `为 ${display_name(floor?.next_speaker || state.room.host)} 领取`;
|
|
2596
|
-
element("return-floor").disabled = !floor?.holder;
|
|
2597
2607
|
const turn = state.room.current_turn;
|
|
2598
2608
|
const active = turn && ["pending", "notified"].includes(turn.status);
|
|
2599
2609
|
const inactive = ["paused", "concluded"].includes(state.room.phase);
|
|
@@ -2607,20 +2617,10 @@ function render_current_turn() {
|
|
|
2607
2617
|
element("cancel-turn").disabled = !active;
|
|
2608
2618
|
element("view-turn-answer").hidden = !turn?.answer_id;
|
|
2609
2619
|
const limited = state.room.limits?.reached;
|
|
2610
|
-
const unavailable = active || inactive || limited || !Object.keys(state.room.experts).length
|
|
2620
|
+
const unavailable = active || inactive || limited || !Object.keys(state.room.experts).length;
|
|
2611
2621
|
element("schedule-turn").disabled = Boolean(unavailable);
|
|
2612
2622
|
element("schedule-note").textContent = inactive ? "话题已暂停或收束;恢复阶段后才能继续安排。" :
|
|
2613
|
-
(active ? "先完成或取消当前安排,再安排下一位。" : limited ? "已达到讨论上限,请检查到限处理;需要继续时先暂停并调整参数。" :
|
|
2614
|
-
}
|
|
2615
|
-
|
|
2616
|
-
/** Perform one explicit, versioned floor operation on behalf of its speaker. */
|
|
2617
|
-
async function change_floor(action) {
|
|
2618
|
-
const floor = state.room.floor;
|
|
2619
|
-
const speaker = action === "take" ? floor.next_speaker : floor.holder;
|
|
2620
|
-
try {
|
|
2621
|
-
await request_json(`/api/rooms/${state.room.id}/floor`, { actor: "用户", speaker, action, revision: floor.revision });
|
|
2622
|
-
show_toast(action === "take" ? `${display_name(speaker)} 已领取发言权` : "发言权已归还");
|
|
2623
|
-
} finally { await refresh_room(); }
|
|
2623
|
+
(active ? "先完成或取消当前安排,再安排下一位。" : limited ? "已达到讨论上限,请检查到限处理;需要继续时先暂停并调整参数。" : "创建后本地专家可自行检查;在线专家需由主持人或用户联系。");
|
|
2624
2624
|
}
|
|
2625
2625
|
|
|
2626
2626
|
/** Distinguish local work discovery from contacting an online expert. */
|
|
@@ -2719,12 +2719,12 @@ function cancel_turn_reply() {
|
|
|
2719
2719
|
/** Keep moderator statements distinct from opinions in the same identity. */
|
|
2720
2720
|
function update_composer_role() {
|
|
2721
2721
|
const mode = element("message-kind").value;
|
|
2722
|
-
element("author-select").disabled =
|
|
2723
|
-
if (
|
|
2722
|
+
element("author-select").disabled = mode === "moderation";
|
|
2723
|
+
if (mode === "moderation") element("author-select").value = "用户";
|
|
2724
2724
|
const needs_summary = ["opinion", "forward"].includes(mode) && element("author-select").value !== "用户";
|
|
2725
2725
|
element("message-summary-fields").hidden = !needs_summary;
|
|
2726
2726
|
element("message-summary").required = needs_summary;
|
|
2727
|
-
element("author-note").textContent = mode === "
|
|
2727
|
+
element("author-note").textContent = mode === "moderation" ? "主持发言" :
|
|
2728
2728
|
(mode === "forward" ? "由用户代发" : (element("author-select").value === "用户" ? "发言" : "的观点代发"));
|
|
2729
2729
|
}
|
|
2730
2730
|
|
|
@@ -2738,7 +2738,6 @@ function bind_host_controls() {
|
|
|
2738
2738
|
"local-room-configuration-load": load_room_configuration, "local-room-configuration-save": save_room_configuration,
|
|
2739
2739
|
"local-room-configuration-restart": restart_discussion,
|
|
2740
2740
|
"runtime-pause": pause_runtime, "check-discussion-limits": check_limits,
|
|
2741
|
-
"take-floor": () => change_floor("take"), "return-floor": () => change_floor("return"),
|
|
2742
2741
|
"copy-turn": copy_turn, "confirm-notified": () => change_turn_status("notified"), "cancel-turn": () => change_turn_status("cancelled"),
|
|
2743
2742
|
"forward-turn": prepare_turn_answer, "cancel-turn-answer": cancel_turn_reply,
|
|
2744
2743
|
"view-turn-answer": () => show_message(state.room.current_turn.answer_id),
|
|
@@ -2758,10 +2757,6 @@ async function run_action(action) {
|
|
|
2758
2757
|
|
|
2759
2758
|
/** Delegate room and message actions to their actual visible controls. */
|
|
2760
2759
|
function bind_delegated_actions() {
|
|
2761
|
-
element("room-content").addEventListener("click", (event) => {
|
|
2762
|
-
const receipt = event.target.closest("[data-receipt-detail]");
|
|
2763
|
-
if (receipt) run_action(() => show_message(receipt.dataset.receiptDetail));
|
|
2764
|
-
});
|
|
2765
2760
|
element("room-list").addEventListener("click", (event) => {
|
|
2766
2761
|
const management = event.target.closest("[data-manage-room]");
|
|
2767
2762
|
if (management) {
|
|
@@ -3148,7 +3143,7 @@ function view_snapshot() {
|
|
|
3148
3143
|
free_notice: state.free_notice, free_error: state.free_error, free_proxy: state.free_proxy,
|
|
3149
3144
|
create_parent: state.create_parent, access: access_snapshot(), admin: admin_snapshot(),
|
|
3150
3145
|
tab: state.tab, snapshot_id: state.snapshot_id, context_dirty: state.context_dirty,
|
|
3151
|
-
theme: document.documentElement.dataset.theme, fields, experts, launch: structuredClone(state.launch), named_followup: state.named_followup };
|
|
3146
|
+
theme: document.documentElement.dataset.theme, fields, experts, launch: structuredClone(state.launch), named_followup: state.named_followup, create_discussion: state.create_discussion };
|
|
3152
3147
|
}
|
|
3153
3148
|
|
|
3154
3149
|
/** Update a shared draft using the same input events as the original controls. */
|
|
@@ -3315,9 +3310,8 @@ async function execute_view_action(name, value) {
|
|
|
3315
3310
|
schedule: () => schedule_turn(event), filter: filter_view, reply: set_view_reply,
|
|
3316
3311
|
cancel_reply, cancel_answer: cancel_turn_reply, prepare_answer: prepare_turn_answer,
|
|
3317
3312
|
copy_turn, notify: () => change_turn_status("notified"), cancel_turn: () => change_turn_status("cancelled"), skip_turn,
|
|
3318
|
-
take_floor: () => change_floor("take"), return_floor: () => change_floor("return"),
|
|
3319
3313
|
copy_online: () => copy_context(), copy_cli: () => copy_context("cli"), copy_background,
|
|
3320
|
-
choose_runtime, export_runtime, download_member_access, copy_member_access_text, copy_member_join, copy_member_launch: name => copy_member_join(name, true), pause_runtime, check_limits, pause_discussion, restart_discussion, retry_execution,
|
|
3314
|
+
choose_runtime, export_runtime, download_member_access, copy_member_access_text, copy_member_join, copy_member_launch: name => copy_member_join(name, true), pause_runtime, check_limits, pause_discussion, restart_discussion, retry_execution, conclude_discussion, start_room_discussion, end_room_discussion, extend_rounds, reopen_discussion,
|
|
3321
3315
|
save_launch_defaults, launch_current_room, resume_launch, discard_launch, retry_launch_connection, check_launch_connection: load_launch_settings,
|
|
3322
3316
|
load_room_configuration, save_room_configuration, interrupt_member, change_member, invite_member, set_proxy, copy_online_notice,
|
|
3323
3317
|
prepare_brainstorm, return_conclusion, convert_suggestion, adopt_recommendation,
|
|
@@ -3343,7 +3337,7 @@ function change_view_theme(theme) {
|
|
|
3343
3337
|
|
|
3344
3338
|
window.discuss_view = { snapshot: view_snapshot, write_field: write_view_field,
|
|
3345
3339
|
write_launch: write_launch_field, write_room_connection, write_room_adapter, launch_clients: LAUNCH_CLIENTS,
|
|
3346
|
-
launch_model_state, load_launch_models, write_launch_model, save_launch_api_key, write_scheduler, save_scheduler_api_key, check_scheduler, write_named_followup, check_launch_api, launch_summary, launch_can_set_access, launch_can_bind_session, pick_launch_directory, restore_launch_profile, reset_create_form, empty_discussion_message,
|
|
3340
|
+
launch_model_state, load_launch_models, write_launch_model, save_launch_api_key, write_scheduler, save_scheduler_api_key, check_scheduler, write_named_followup, write_create_discussion, check_launch_api, launch_summary, launch_can_set_access, launch_can_bind_session, pick_launch_directory, restore_launch_profile, reset_create_form, empty_discussion_message,
|
|
3347
3341
|
start_drag: start_directory_drag,
|
|
3348
3342
|
attachment_markup, attachment_size, attachment_file_icon, drop_attachments, paste_attachments,
|
|
3349
3343
|
write_expert: write_view_expert, add_instance: add_view_instance, platform_of, execute: execute_view_action, theme: change_view_theme,
|
|
@@ -3351,7 +3345,7 @@ window.discuss_view = { snapshot: view_snapshot, write_field: write_view_field,
|
|
|
3351
3345
|
update_profile: update_admin_profile, avatar_from_file: admin_avatar_from_file, validate: validate_admin_form },
|
|
3352
3346
|
request: request_json, identities: IDENTITIES, display_name, sources: SOURCE_LABELS, phases: PHASE_LABELS,
|
|
3353
3347
|
runtime_label, execution_label, limits_label, participant_model, participant_access, can_retry_execution,
|
|
3354
|
-
turns: TURN_LABELS, turn_label, turn_guidance,
|
|
3348
|
+
turns: TURN_LABELS, turn_label, turn_guidance, progress_round, round_groups, limit_rounds, progress_speaker, brief_meta, max_message_length: MAX_MESSAGE_LENGTH, provenance: message_provenance, format_time };
|
|
3355
3349
|
|
|
3356
3350
|
restore_theme();
|
|
3357
3351
|
restore_directory_sort();
|