feat(chat): configurable interactive answer timeout (fork of #362 with blocking fixes) - #386
Conversation
Make the answer window shared by the AskUserQuestion card and the tool approval bar configurable via settings.system.interactiveTimeoutMinutes (minutes, default 3, preserving current behavior). A very large value (e.g. 99999) approximates never timing out. The window is injected into both tools at runtime; every deadline stays a finite timestamp, so the countdown logic needs no special never-handling. Refs Stack-Cairn#354 (timeout configurability).
… a fine-grained slider Move the interactive answer timeout off its standalone input card and into the AskUserQuestion tool row, replacing the fixed read-only 'allow' label with a stop-based slider. The stop table keeps 1-60 minutes fine-grained (1,2,3,4,5,6,8,10,12,15,20,25,30,40,50,60) and jumps straight to 99999 past one hour, so the slider reads as a plain duration without exposing any internal 'never' semantics. The tool detail copy no longer hardcodes '3 minutes'; it now points at the adjustable slider.
Keep a single one-line comment on the stop table and drop the duplicate block inside the render body; both mirrored files stay byte-identical.
…list 前端改完滑块重启即回默认值:该键此前不在 save_system 白名单里, 落库时被丢弃。补入白名单 + normalize_interactive_timeout_minutes_value (正数保留、上限钳 99999、非正/非法回默认 3,与前端归一化同口径), 并更新 row_count 断言(9→10)与 key 列表,新增 round-trip 测试覆盖 7 个归一化用例。
99999 分钟换算成毫秒(≈5.99e9)超过 setTimeout 的 IDL long 上限 (2^31-1),裸调用会被 ToInt32 回绕:99999 被截成约 19.7 天, 而 35792~200000 分钟区间回绕成负数被钳为 0 → 「永不超时」瞬间 立刻超时。新增 scheduleAtDeadline 分段续期(单跳最多 2^31-1 ms, 到点按剩余时间续排,回调恒异步),替换 AskUserQuestion 与工具审批 的裸 setTimeout;补最大档回归 + scheduleAtDeadline 语义测试。 两端镜像,tsc/lint/mirror 均过。
原 PR 把 deadline 上界校验整个取消,远端浏览器时钟偏移时会显示 荒谬长倒计时,且把回归写进断言。上界改为最大可配置窗口 (99999 分钟):长窗口仍被采信,超出视为时钟不可比回退挂载近似。 同时两端 SystemToolsSection 过 biome 格式化(lint 从红转绿, CI 不再失败)。补超上限不采信测试。
|
PR governance checks passed. Awaiting human review. |
|
Screenshot for PR #386 - configurable interactive timeout (99999 min ≈ never timeout) |
|
代码太长了, 前端写的拖动条根本没必要min, max, 请优化一下代码质量 |
|
前端传回的值是确定的, 无需后端过多检验 |
|
v4f还是太菜了hhh |
|
感觉这样调整会有问题,超长时间后选择完进行下一步,会重建缓存造成巨额扣费。 |
这本身不是个可选项嘛 |
保留interactiveTimeoutMinutes的默认值与保存白名单,移除与前端滑块重复的范围归一化。\n\n将持久化测试收敛为滑块实际提交值的往返验证,并删除两端镜像中未使用的最小值常量,保持WebUI与桌面端一致。
|
已在d613d63处理代码质量意见:Rust侧仅保留默认值与保存白名单,移除范围钳制和重复归一化;持久化测试收敛为实际滑块值的往返验证;同步删除未使用的最小值常量并保持GUI/WebUI镜像一致。 |
|
想了一下感觉还是可以加上的,前端重构了下,佬解决一下冲突吧 |
…rflow Resolve conflicts from the agent-ui unification refactor (Stack-Cairn#399), which collapsed the agent-gui / agent-gateway/web mirror copies into a shared crates/agent-ui package and removed the mirror-manifest system. Conflict resolution: - 3 modify/delete: the PR's edits to the old mirror copies of AskUserQuestionCard.tsx, askUserQuestion.ts and SystemToolsSection.tsx already landed in the rename-tracked agent-ui versions (incl. INTERACTIVE_TIMEOUT_MAX_MINUTES trust-window and scheduleAtDeadline); removed the now-redundant deleted copies. - 2 content (askUserQuestionTools.ts, toolApproval.ts): import scheduleAtDeadline from the new @liveagent/ui/lib/chat/askUserQuestion path alongside main's typebox change. - 1 test (ask-user-question-card.test.mjs): adopt main's mocks-based loader, keep the PR's INTERACTIVE_TIMEOUT_MAX_MINUTES destructure. - 1 doc (chat-runtime.md): keep the PR's configurable-timeout wording, adopt main's agent-ui card path. - Silent merge fix: system settings row_count 10 -> 11 in tests.rs; the PR and main each independently added one system key (interactiveTimeoutMinutes / workspaceResourceSettings). Verified: agent-gui + agent-gateway/web tsc clean; gateway/web 540/540; agent-gui frontend 1508/1509 (sole failure is the pre-existing gemini-3-pro-preview model-catalog test, also red on clean main); Rust settings 51/51; biome clean on the resolved TS files. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
@su-fen 冲突已解决,已 merge 最新 main(agent-ui 统一重构 #399)并推送,现在 PR 状态是 MERGEABLE ✅ 主要是重构把 agent-gui / agent-gateway/web 的镜像副本收敛成了共享的
本地验证:agent-gui + agent-gateway/web |
|
@su-fen 多谢 review!冲突已解决,现在是 MERGEABLE。关于你最早提的「超长窗口 → 选择完继续会重建缓存造成巨额扣费」,我把成本这块的实际情况说清楚,也给这条留个记录: 结论:这是一次性、有上限的成本,不是持续/巨额扣费;默认配置根本触发不了,而且已有现成的缓存档位可以进一步规避。
所以是:默认安全 + 长窗口自愿取舍 + 有 |
|
补充:联调时发现的一个既有问题(与本 PR 无直接关系,记录在此备查) 桌面 + WebUI 双端联调本功能时发现:AskUserQuestion 提问卡在桌面端正常显示并倒计时,但 WebUI 端在提问挂起期间刷新 / 重连后看不到这张卡。 确认的机制:挂起中的提问 不写任何持久化聊天事件——
所以只要 WebUI 在提问挂起期间刷新 / 重连,就走历史回放,而历史里没有这条挂起提问 → 整行消失(连「向你提问」标签都没有)。桌面端直接读内存挂起表,不受影响。 与本 PR 的关系:决定这张卡「是否出现」的链路—— 之所以在这里记一笔:本 PR 把交互窗口做成可配置、可以设得很长,挂起时间越长,用户在挂起期间刷新 WebUI 的概率越高,也就更容易撞到这个既有缺口。 修复方向(建议独立 PR):给挂起的提问落一个可回放的标记(或由网关缓存挂起态),使 WebUI 重连 / 刷新时历史回放能重建这张卡。这会动到 |
背景
接手原 #362(作者暂时没空继续)。本 PR 在 #362 的 3 个 commit 基础上,修复了三个阻塞问题,使其真正可用。
修复内容
1. 🐛 设置持久化:
interactiveTimeoutMinutes进 Rust 保存白名单原 #362 只改了前端,该键不在
save_system白名单里,落库时被丢弃 → 重启即回默认 3 分钟,功能等于没生效(实测确认)。system.rs保存白名单 +normalize_interactive_timeout_minutes_value(正数保留、上限钳 99999、非正/非法回默认 3,与前端归一化同口径)row_count断言 9→10、key 列表,新增 round-trip 测试覆盖 7 个归一化用例2. 🔧 setTimeout 32 位溢出:分段续期
99999 分钟 ≈ 5.99e9 ms 超过
setTimeout的 IDL long 上限(2^31-1),裸调用 ToInt32 回绕:99999 被截成约 19.7 天,35792~200000 分钟区间回绕成负数被钳为 0 → 「永不超时」瞬间立刻超时。scheduleAtDeadline:单跳最多 2^31-1 ms,到点按剩余续排,回调恒异步setTimeoutscheduleAtDeadline语义测试。原 feat(chat): configurable interactive answer timeout #362 的测试用 1 小时窗口,在安全范围内,测不出此 bug3. 🧹 卡片 deadline 上界:由"取消"改为"最大可配置窗口"
原 #362 取消上界校验,远端时钟偏移会显示荒谬倒计时,且把回归写进断言。现上界取
INTERACTIVE_TIMEOUT_MAX_MINUTES(99999 分钟):长窗口仍被采信,超出视为时钟不可比回退挂载近似。补超上限不采信测试。附带:biome 格式化(CI 修复)
两端
SystemToolsSection.tsx过biome check --write,pnpm lint从红转绿(原 #362 会在 CI 挂掉)。验证
settings::tests:42 pass(含新增 round-trip)tsc --noEmitcleanbiome checkclean已知限制(沿用原 #362 设计,未扩大范围)
Screenshots / preview
交互式应答超时滑块配置为 99999 分钟(≈永不超时)时,AskUserQuestion 卡片挂起并显示超长倒计时(约 99998 分钟剩余),不会如修复前那样瞬间超时落定。
关联 issue
Closes #354