ai_co_discuss 0.2.43 → 0.2.46
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 +7 -6
- package/backend/free_room.py +10 -3
- package/backend/room_import.py +2 -0
- package/backend/runtime.py +10 -1
- package/backend/schema.sql +9 -0
- package/backend/server.py +144 -10
- package/docs/architecture.md +3 -2
- package/docs/cli.md +3 -3
- package/docs/runner.md +2 -2
- package/docs/workflow.md +8 -2
- package/package.json +1 -1
- package/runner/api_participant.mjs +9 -4
- package/runner/discuss.py +19 -4
- package/runner/discussion_runner.mjs +20 -7
- package/runner/scheduler.mjs +1 -1
- package/web/app.js +45 -5
- package/web/modern.css +1 -1
- package/web/modern.js +1028 -854
package/runner/discuss.py
CHANGED
|
@@ -188,12 +188,15 @@ def room_commands(subcommands, registry):
|
|
|
188
188
|
"""Register room discovery, progressive reading, creation, and updates."""
|
|
189
189
|
for action, description in (("list", "列出房间概要"), ("view", "只读目标、阶段、焦点及当前安排"),
|
|
190
190
|
("brief", "按需读取背景、边界和专家配置"),
|
|
191
|
+
("conclusion", "收束前申请最新收束状态(阻碍项与 revision);主持人收束前必做"),
|
|
191
192
|
("protocol", "按需读取当前服务的参与协议")):
|
|
192
193
|
parser = add_leaf(subcommands, registry, f"room {action}", description, action != "list")
|
|
193
194
|
if action == "list":
|
|
194
195
|
parser.add_argument("--submission-id", type=submission_id, help="按建房提交编号精确查询")
|
|
195
|
-
limits = add_leaf(subcommands, registry, "room limits", "查看上限;--check
|
|
196
|
+
limits = add_leaf(subcommands, registry, "room limits", "查看上限;--check 检查并执行到限处理;--done 通知程序目标已完成", True)
|
|
196
197
|
limits.add_argument("--check", action="store_true")
|
|
198
|
+
limits.add_argument("--done", action="store_true", help="主持人宣告目标已完成:按到限方式暂停或进入总结,不直接收束;须带 --conclusion")
|
|
199
|
+
limits.add_argument("--conclusion", help="room conclusion 返回的 revision;--done 必填")
|
|
197
200
|
create = add_leaf(subcommands, registry, "room create", "创建话题;不自动启动讨论或监控")
|
|
198
201
|
create.add_argument("--folder-id", help="直接在指定文件夹中创建;省略则未归类")
|
|
199
202
|
add_topic(create, create=True)
|
|
@@ -201,6 +204,7 @@ def room_commands(subcommands, registry):
|
|
|
201
204
|
help="新话题唯一编号;未知结果先查询,重试沿用编号和原始资料")
|
|
202
205
|
update = add_leaf(subcommands, registry, "room update", "维护话题;省略的字段保持不变", True)
|
|
203
206
|
add_topic(update)
|
|
207
|
+
update.add_argument("--conclusion", help="room conclusion 返回的 revision;主持人设为 concluded 时必填,状态变化后需重新获取")
|
|
204
208
|
|
|
205
209
|
|
|
206
210
|
def message_commands(subcommands, registry):
|
|
@@ -255,7 +259,8 @@ def turn_commands(subcommands, registry):
|
|
|
255
259
|
"""Keep planning, notification text, and delivery confirmation distinct."""
|
|
256
260
|
descriptions = {"list": "分页读取安排历史", "current": "读取阶段和最近安排;需核对状态及专家",
|
|
257
261
|
"view": "读取一个安排", "prompt": "生成简短通知;不发送、不标记送达",
|
|
258
|
-
"
|
|
262
|
+
"roster": "申请最新专家状态(在场专家、本轮待发言者与 revision);整轮安排前必做",
|
|
263
|
+
"create": "安排一位专家,或用 --plan-file 一次安排整轮(须带 --roster);不发送通知",
|
|
259
264
|
"continue": "处理用户操作或打断后,按原排队继续下一位",
|
|
260
265
|
"mark-notified": "实际送达后显式确认", "cancel": "取消指定未完成安排"}
|
|
261
266
|
for action, description in descriptions.items():
|
|
@@ -263,12 +268,13 @@ def turn_commands(subcommands, registry):
|
|
|
263
268
|
if action == "list":
|
|
264
269
|
parser.add_argument("--page", type=positive_int, default=1)
|
|
265
270
|
parser.add_argument("--submission-id", type=submission_id, help="按安排提交编号精确查询结果")
|
|
266
|
-
elif action not in ("create", "current", "continue"):
|
|
271
|
+
elif action not in ("create", "current", "continue", "roster"):
|
|
267
272
|
parser.add_argument("id", type=positive_int, help="安排编号")
|
|
268
273
|
if action == "create":
|
|
269
274
|
parser.add_argument("--expert", type=ai_name, help="单个安排的专家;与 --plan-file 二选一")
|
|
270
275
|
add_text(parser, "question")
|
|
271
276
|
parser.add_argument("--plan-file", help='整轮安排 JSON 数组 [{"expert":"Claude","question":"…"}],按顺序接力;- 从标准输入读取')
|
|
277
|
+
parser.add_argument("--roster", help="turn roster 返回的 revision;整轮安排(--plan-file)必填,专家状态变化后需重新获取")
|
|
272
278
|
parser.add_argument("--submission-id", type=submission_id, required=True,
|
|
273
279
|
help="新安排唯一编号;重试沿用同编号和内容")
|
|
274
280
|
|
|
@@ -435,13 +441,20 @@ def plan_room(args):
|
|
|
435
441
|
return RequestPlan("/api/rooms", "POST", payload, "overview")
|
|
436
442
|
base = f"/api/rooms/{args.room}"
|
|
437
443
|
if args.action == "limits":
|
|
444
|
+
if args.done:
|
|
445
|
+
return RequestPlan(base + "/limits", "POST", {"actor": require_actor(args), "action": "done",
|
|
446
|
+
**({"conclusion": args.conclusion} if args.conclusion else {})}, "overview")
|
|
438
447
|
return RequestPlan(base + "/limits", "POST", {"actor": require_actor(args)}, "overview") if args.check else RequestPlan(base + "/limits")
|
|
439
448
|
if args.action == "update":
|
|
440
449
|
payload = topic_payload(args)
|
|
441
450
|
if not payload:
|
|
442
451
|
raise CliError("room update 至少需要一个话题字段")
|
|
443
452
|
payload["actor"] = require_actor(args)
|
|
453
|
+
if args.conclusion:
|
|
454
|
+
payload["conclusion"] = args.conclusion
|
|
444
455
|
return RequestPlan(base + "/topic", "POST", payload, "overview")
|
|
456
|
+
if args.action == "conclusion":
|
|
457
|
+
return RequestPlan(base + "/conclusion")
|
|
445
458
|
if args.action == "protocol":
|
|
446
459
|
return RequestPlan(f"/r/{args.room}/protocol")
|
|
447
460
|
return RequestPlan(base, view="brief" if args.action == "brief" else "overview")
|
|
@@ -531,6 +544,8 @@ def plan_turn(args):
|
|
|
531
544
|
if args.submission_id:
|
|
532
545
|
params["submission_id"] = args.submission_id
|
|
533
546
|
return RequestPlan(base + "/turns", params=params)
|
|
547
|
+
if args.action == "roster":
|
|
548
|
+
return RequestPlan(base + "/turns/roster")
|
|
534
549
|
if args.action == "create":
|
|
535
550
|
return RequestPlan(base + "/turns", "POST", turn_payload(args))
|
|
536
551
|
if args.action == "continue":
|
|
@@ -555,7 +570,7 @@ def turn_payload(args):
|
|
|
555
570
|
raise CliError(f"无效 JSON: {error}") from error
|
|
556
571
|
if not isinstance(turns, list) or not all(isinstance(item, dict) for item in turns):
|
|
557
572
|
raise CliError("--plan-file 必须是由 expert、question 对象组成的数组")
|
|
558
|
-
return {**payload, "turns": turns}
|
|
573
|
+
return {**payload, "turns": turns, **({"roster": args.roster} if args.roster else {})}
|
|
559
574
|
if not args.expert or question is None:
|
|
560
575
|
raise CliError("需要 --expert 与 --question/--question-file,或改用 --plan-file")
|
|
561
576
|
return {**payload, "expert": args.expert, "question": question}
|
|
@@ -230,7 +230,7 @@ export function task_files(config, event, token, binding, { suggestions = [], en
|
|
|
230
230
|
instructions: `${adapter.adapter === 'antigravity_cli' ? `无人值守下 run_command 只能运行 ${cli} 的 subcommands 子命令,其他命令会被自动拒绝并使会话直接结束;浏览项目用 list_dir、find_by_name、grep_search、view_file。` : ''}读取指定技能;网站命令只用 ${cli} 的 subcommands 子命令,命令以 ${cli} 开头,不用 python discuss.py、cd、管道或其他入口;${auto_enter ? '接入程序已进入本次执行,不要再执行 execution enter。' : `使用 ${cli} execution enter 房间 执行编号 --access-file 凭据路径 进入。进入成功后才处理本次安排;`}凭据文件由 CLI 内部读取,不要打印或读取其 token。${access_rule}答案草稿只保存到 draft_directory,不修改任务、凭据、日志或调度数据库;${project_rule}只处理本次工作,不建定时任务。主持创建下一安排或收束即结束,专家提交原文后结束。无效或已进入的任务不要重复执行。${suggestion_rule}` });
|
|
231
231
|
const read_rule = windows_rg() ? '读取含中文的技能或文本文件时用 rg --encoding utf-8 . 文件路径,不用 Get-Content(Windows PowerShell 会按系统编码读出乱码,需要反复重试)。' : '';
|
|
232
232
|
const scope = `本次只放行任务内 subcommands 列出的 ${cli} 子命令,命令以 ${cli} 开头,不用 python discuss.py、cd、管道或其他入口。任务内 cwd 是讨论项目目录;${project_rule}项目读写仍受软件实际权限和议题授权约束。不读取凭据内容。${read_rule}`;
|
|
233
|
-
const plan_rule = event.role === 'host' ? '
|
|
233
|
+
const plan_rule = event.role === 'host' ? '安排专家回应时先写发言计划,先用 turn roster 取得最新专家状态,再用 turn create --plan-file 加 --roster 一次排满 waiting 中的每位专家,不要每次只排一位。' : '';
|
|
234
234
|
const intro = `用户已启动此房间的全自动讨论。请以 ${event.participant} 的${event.role === 'host' ? '主持' : '专家'}职责处理本次工作。${plan_rule}`;
|
|
235
235
|
const prompt = auto_enter
|
|
236
236
|
? `${intro}接入程序已为本次执行完成 execution enter,不要再执行。任务内容如下(同一份保存在 ${task_path},无需再读):
|
|
@@ -316,13 +316,19 @@ export class DiscussionRunner {
|
|
|
316
316
|
*/
|
|
317
317
|
async smart_schedule(row, event) {
|
|
318
318
|
if (!this.config.scheduler) return {};
|
|
319
|
-
let plan;
|
|
320
|
-
try {
|
|
319
|
+
let plan, seen;
|
|
320
|
+
try {
|
|
321
|
+
// What the decision is based on; applying it first checks nothing new (a message or a topic edit) arrived since.
|
|
322
|
+
const room = await api(this.config, '');
|
|
323
|
+
seen = { latest_id: room.latest_id, held_since: room.held_since || 0 };
|
|
324
|
+
plan = await this.plan_schedule(event);
|
|
325
|
+
}
|
|
321
326
|
catch (error) { this.log(`智能调度判断失败,交给主持人:${error.message}`); return {}; }
|
|
322
327
|
if (!plan) return {};
|
|
323
328
|
const suggestions = plan.message_id ? [{ message_id: plan.message_id, suggestion: plan.label, confidence: plan.confidence }] : [];
|
|
324
329
|
if (plan.action === 'host') {
|
|
325
|
-
|
|
330
|
+
// A skip made no judgment (no model call, so no detail); only a decision actually asked of the model is shown.
|
|
331
|
+
if (plan.detail) await this.record_decision(plan, false);
|
|
326
332
|
return { suggestions };
|
|
327
333
|
}
|
|
328
334
|
const token = randomUUID();
|
|
@@ -336,7 +342,7 @@ export class DiscussionRunner {
|
|
|
336
342
|
}
|
|
337
343
|
this.store.record(row.id, { status: 'started', last_renew: Date.now() });
|
|
338
344
|
try {
|
|
339
|
-
await this.apply_schedule(plan, token);
|
|
345
|
+
await this.apply_schedule(plan, token, seen);
|
|
340
346
|
await this.record_decision(plan, true);
|
|
341
347
|
this.log(`智能调度 #${row.id}:${plan.label}(置信度 ${plan.confidence})`);
|
|
342
348
|
} catch (error) {
|
|
@@ -394,6 +400,8 @@ export class DiscussionRunner {
|
|
|
394
400
|
if (!waiting.length) return skip('round', '本轮未发言的专家都是手动接入,无法自动安排');
|
|
395
401
|
// An expert the user just skipped is not picked again unless nobody else is left.
|
|
396
402
|
const skipped = room.current_turn?.status === 'cancelled' ? room.current_turn.expert : null;
|
|
403
|
+
// Asking whether to call on the host alone is pointless: handing back already wakes it.
|
|
404
|
+
if (waiting.length === 1 && waiting[0] === room.host) return skip('round', '本轮只剩主持人未发言');
|
|
397
405
|
const candidates = waiting.filter(name => name !== skipped);
|
|
398
406
|
return this.next_speaker_plan(room, { ...context, spoken: [...spoken] }, candidates.length ? candidates : waiting, anchor);
|
|
399
407
|
}
|
|
@@ -435,13 +443,18 @@ export class DiscussionRunner {
|
|
|
435
443
|
return { action: 'restore', kind: 'round', confidence: null, label, after_message_id: anchor, turns: plan_turns };
|
|
436
444
|
}
|
|
437
445
|
/** Perform the host's writes for a decided plan under its own execution credential. */
|
|
438
|
-
async apply_schedule(plan, token) {
|
|
446
|
+
async apply_schedule(plan, token, seen) {
|
|
439
447
|
const room = await api(this.config, '');
|
|
448
|
+
if (room.latest_id !== seen.latest_id || (room.held_since || 0) !== seen.held_since) throw new Error('判断期间房间有新留言或用户操作,放弃应用');
|
|
440
449
|
const actor = room.host;
|
|
441
450
|
if (room.floor.holder !== actor) await api(this.config, '/floor', { actor, speaker: actor, action: 'take', revision: room.floor.revision }, token);
|
|
442
451
|
if (plan.phase) await api(this.config, '/topic', { actor, phase: plan.phase }, token);
|
|
443
452
|
if (plan.continue_plan) await api(this.config, '/turns/continue', { actor }, token);
|
|
444
|
-
else
|
|
453
|
+
else {
|
|
454
|
+
// Like any host, the program asks for the latest roster right before it arranges turns.
|
|
455
|
+
const { revision } = await api(this.config, '/turns/roster');
|
|
456
|
+
await api(this.config, '/turns', { actor, turns: plan.turns, roster: revision, submission_id: randomUUID() }, token);
|
|
457
|
+
}
|
|
445
458
|
}
|
|
446
459
|
/** Show the decision in the transcript; a failed record never affects the discussion. */
|
|
447
460
|
async record_decision(plan, applied) {
|
package/runner/scheduler.mjs
CHANGED
|
@@ -179,7 +179,7 @@ export async function decide_next_speaker(home, scheduler, { title, goal, user_r
|
|
|
179
179
|
return { action: 'next', expert: choice, confidence, detail, label: `本轮继续,请 ${choice} 接着发言` };
|
|
180
180
|
}
|
|
181
181
|
if (typeof obligation === 'number' && obligation < ROUTE_CONFIDENCE) {
|
|
182
|
-
return { action: 'host', confidence, detail, label:
|
|
182
|
+
return { action: 'host', confidence, detail, label: `有明确依据的概率 ${obligation.toFixed(2)},未达 ${ROUTE_CONFIDENCE.toFixed(2)},交给主持人安排` };
|
|
183
183
|
}
|
|
184
184
|
return { action: 'host', confidence, detail, label: choice === HOST_CHOICE && confidence >= ROUTE_CONFIDENCE ? '建议由主持人调整安排,交给主持人' : unsure_label(candidates.includes(choice) ? `请 ${choice} 发言` : choice === HOST_CHOICE ? '交给主持人' : '') };
|
|
185
185
|
}
|
package/web/app.js
CHANGED
|
@@ -1046,7 +1046,7 @@ function finish_member_request(room_id, token, record, result) {
|
|
|
1046
1046
|
}
|
|
1047
1047
|
|
|
1048
1048
|
/** Owner changes use the website; only automatic members need the local service to reconnect. */
|
|
1049
|
-
async function change_member({ member_id, name, action, role }) {
|
|
1049
|
+
async function change_member({ member_id, name, action, role, latest_id }) {
|
|
1050
1050
|
const room_id = state.room.id;
|
|
1051
1051
|
state.free_error = ""; state.free_notice = "";
|
|
1052
1052
|
try {
|
|
@@ -1061,7 +1061,7 @@ async function change_member({ member_id, name, action, role }) {
|
|
|
1061
1061
|
if (state.room?.id === room_id) state.free_notice = member.access === "manual" ? `${name} 已请回,凭证已更新;请用新的启动指令让它重新注册。` : `${name} 已请回,续接原会话。`;
|
|
1062
1062
|
} else {
|
|
1063
1063
|
await request_json(`/api/rooms/${room_id}/members/${member_id}${action === "interrupt" ? "/interrupt" : ""}`,
|
|
1064
|
-
action === "interrupt" ? {} : { action, ...(role ? { role } : {}) });
|
|
1064
|
+
action === "interrupt" ? {} : { action, ...(role ? { role } : {}), ...(latest_id !== undefined ? { latest_id } : {}) });
|
|
1065
1065
|
}
|
|
1066
1066
|
if (state.room?.id === room_id) await refresh_room();
|
|
1067
1067
|
} catch (error) { if (state.room?.id === room_id) state.free_error = error.message; }
|
|
@@ -1426,10 +1426,18 @@ function read_limits(prefix) {
|
|
|
1426
1426
|
}));
|
|
1427
1427
|
}
|
|
1428
1428
|
|
|
1429
|
+
/** Rounds against the limit: the current segment, with rounds the user appended shown as M+N (M is the original limit). */
|
|
1430
|
+
function limit_rounds(limits = {}) {
|
|
1431
|
+
const done = limits.segment_rounds ?? limits.completed_rounds ?? 0;
|
|
1432
|
+
const cap = limits.round_cap ?? limits.max_rounds ?? 0;
|
|
1433
|
+
const total = limits.extra_rounds ? `${limits.max_rounds}+${limits.extra_rounds}` : "";
|
|
1434
|
+
return { done, cap, total, text: cap ? `${done} / ${cap}${total ? `(${total})` : ""} 轮` : `${done} 轮` };
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1429
1437
|
/** Describe configured boundaries and actual progress without implying completion. */
|
|
1430
1438
|
function limits_label(limits = {}) {
|
|
1431
1439
|
const finished = state.room?.phase === "concluded";
|
|
1432
|
-
const rounds = limits.
|
|
1440
|
+
const rounds = limits.round_cap || limits.max_rounds ? limit_rounds(limits).text : `${limits.completed_rounds || 0} 轮 · 未设轮次上限`;
|
|
1433
1441
|
const duration = limits.max_minutes ? `总时长 ${limits.max_minutes} 分钟` : "未设总时长";
|
|
1434
1442
|
const progress = finished ? "讨论已收束" : !limits.started_at ? "尚未计时" : `已用 ${Math.floor((limits.elapsed_seconds || 0) / 60)} 分钟${limits.remaining_seconds == null ? "" : ` · 剩余 ${Math.ceil(limits.remaining_seconds / 60)} 分钟`}`;
|
|
1435
1443
|
const status = finished ? "" : limits.closing ? "正在收束" : limits.reached ? "已达上限,停止新增安排" : "";
|
|
@@ -2330,6 +2338,38 @@ async function skip_turn() {
|
|
|
2330
2338
|
if (state.room?.id === room_id) show_toast(resume ? "已跳过本次发言并恢复讨论,等待主持人继续安排" : "已跳过本次发言,等待主持人继续安排");
|
|
2331
2339
|
}
|
|
2332
2340
|
|
|
2341
|
+
/** Resume the automatic run after a user decision made while paused or just reopened. */
|
|
2342
|
+
async function resume_after_limit_decision() {
|
|
2343
|
+
const path = `/api/rooms/${state.room.id}/runtime`;
|
|
2344
|
+
const runtime = await request_json(path);
|
|
2345
|
+
await request_json(path, { action: "start", mode: "automatic", revision: runtime.revision, deadline: 0 });
|
|
2346
|
+
}
|
|
2347
|
+
|
|
2348
|
+
/** Append rounds after a limit pause: counting restarts from now and the board shows M+N. */
|
|
2349
|
+
async function extend_rounds(rounds) {
|
|
2350
|
+
const room_id = state.room.id;
|
|
2351
|
+
await request_json(`/api/rooms/${room_id}/limits`, { actor: "用户", action: "extend", rounds });
|
|
2352
|
+
await resume_after_limit_decision();
|
|
2353
|
+
await refresh_room();
|
|
2354
|
+
show_toast(`已追加 ${rounds} 轮并重启讨论`);
|
|
2355
|
+
}
|
|
2356
|
+
|
|
2357
|
+
/** Ask the host for a summary of the paused discussion; it ends in the concluded state. */
|
|
2358
|
+
async function request_summary() {
|
|
2359
|
+
await request_json(`/api/rooms/${state.room.id}/limits`, { actor: "用户", action: "summarize" });
|
|
2360
|
+
await resume_after_limit_decision();
|
|
2361
|
+
await refresh_room();
|
|
2362
|
+
show_toast("已请主持人总结,总结后讨论收束");
|
|
2363
|
+
}
|
|
2364
|
+
|
|
2365
|
+
/** Reopen a concluded discussion with a fresh round and time budget; history stays. */
|
|
2366
|
+
async function reopen_discussion({ max_rounds, max_minutes }) {
|
|
2367
|
+
await request_json(`/api/rooms/${state.room.id}/limits`, { actor: "用户", action: "reopen", max_rounds, max_minutes });
|
|
2368
|
+
await resume_after_limit_decision();
|
|
2369
|
+
await refresh_room();
|
|
2370
|
+
show_toast("讨论已重启,按新的轮次与时长上限继续");
|
|
2371
|
+
}
|
|
2372
|
+
|
|
2333
2373
|
/** End early with a host summary limited to the configured closing reserve. */
|
|
2334
2374
|
async function force_close() {
|
|
2335
2375
|
await request_json(`/api/rooms/${state.room.id}/limits`, { actor: "用户", force: true });
|
|
@@ -3267,7 +3307,7 @@ async function execute_view_action(name, value) {
|
|
|
3267
3307
|
copy_turn, notify: () => change_turn_status("notified"), cancel_turn: () => change_turn_status("cancelled"), skip_turn,
|
|
3268
3308
|
take_floor: () => change_floor("take"), return_floor: () => change_floor("return"),
|
|
3269
3309
|
copy_online: () => copy_context(), copy_cli: () => copy_context("cli"), copy_background,
|
|
3270
|
-
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, force_close, conclude_discussion,
|
|
3310
|
+
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, force_close, conclude_discussion, extend_rounds, request_summary, reopen_discussion,
|
|
3271
3311
|
save_launch_defaults, launch_current_room, resume_launch, discard_launch, retry_launch_connection, check_launch_connection: load_launch_settings,
|
|
3272
3312
|
load_room_configuration, save_room_configuration, interrupt_member, change_member, invite_member, set_proxy, copy_online_notice,
|
|
3273
3313
|
prepare_brainstorm, return_conclusion, convert_suggestion, adopt_recommendation,
|
|
@@ -3301,7 +3341,7 @@ window.discuss_view = { snapshot: view_snapshot, write_field: write_view_field,
|
|
|
3301
3341
|
update_profile: update_admin_profile, avatar_from_file: admin_avatar_from_file, validate: validate_admin_form },
|
|
3302
3342
|
request: request_json, identities: IDENTITIES, display_name, sources: SOURCE_LABELS, phases: PHASE_LABELS,
|
|
3303
3343
|
runtime_label, execution_label, limits_label, participant_model, participant_access, can_retry_execution,
|
|
3304
|
-
turns: TURN_LABELS, turn_label, turn_guidance, floor_status, progress_round, progress_speaker, brief_meta, max_message_length: MAX_MESSAGE_LENGTH, provenance: message_provenance, format_time };
|
|
3344
|
+
turns: TURN_LABELS, turn_label, turn_guidance, floor_status, progress_round, limit_rounds, progress_speaker, brief_meta, max_message_length: MAX_MESSAGE_LENGTH, provenance: message_provenance, format_time };
|
|
3305
3345
|
|
|
3306
3346
|
restore_theme();
|
|
3307
3347
|
restore_directory_sort();
|