Skip to content

feat(chat): configurable interactive answer timeout (fork of #362 with blocking fixes) - #386

Open
devil233-ui wants to merge 9 commits into
Stack-Cairn:mainfrom
devil233-ui:fix/interactive-timeout-persistence-and-setoverflow
Open

feat(chat): configurable interactive answer timeout (fork of #362 with blocking fixes)#386
devil233-ui wants to merge 9 commits into
Stack-Cairn:mainfrom
devil233-ui:fix/interactive-timeout-persistence-and-setoverflow

Conversation

@devil233-ui

@devil233-ui devil233-ui commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

背景

接手原 #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,到点按剩余续排,回调恒异步
  • 替换 AskUserQuestion 与工具审批的裸 setTimeout
  • 补最大档回归(用真实 99999 分钟) + scheduleAtDeadline 语义测试。原 feat(chat): configurable interactive answer timeout #362 的测试用 1 小时窗口,在安全范围内,测不出此 bug

3. 🧹 卡片 deadline 上界:由"取消"改为"最大可配置窗口"

#362 取消上界校验,远端时钟偏移会显示荒谬倒计时,且把回归写进断言。现上界取 INTERACTIVE_TIMEOUT_MAX_MINUTES(99999 分钟):长窗口仍被采信,超出视为时钟不可比回退挂载近似。补超上限不采信测试。

附带:biome 格式化(CI 修复)

两端 SystemToolsSection.tsxbiome check --writepnpm lint 从红转绿(原 #362 会在 CI 挂掉)。

验证

  • agent-gui 前端:1422 pass(原 1418 + 新增 4)
  • gateway web:493 pass
  • Rust settings::tests:42 pass(含新增 round-trip)
  • 两端 tsc --noEmit clean
  • 两端 biome check clean
  • mirror check:119 文件一致

已知限制(沿用原 #362 设计,未扩大范围)

  • WebUI 滑块为纯装饰:值同步到桌面端,注入仅在 GUI 生效
  • 已挂起的交互不应用新窗口,只对新发起的生效

Screenshots / preview

交互式应答超时滑块配置为 99999 分钟(≈永不超时)时,AskUserQuestion 卡片挂起并显示超长倒计时(约 99998 分钟剩余),不会如修复前那样瞬间超时落定。

interactive-timeout-99999

截图取自实机 dev 版(含本 PR 全部修复):滑块已调至 99999 档,卡片正常挂起、倒计时显示超长窗口。

关联 issue

Closes #354

FlowerRealm and others added 6 commits August 2, 2026 01:15
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 不再失败)。补超上限不采信测试。
@StackCairn
StackCairn marked this pull request as draft August 6, 2026 04:18
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

PR governance checks passed. Awaiting human review.

@devil233-ui

Copy link
Copy Markdown
Contributor Author

Screenshot for PR #386 - configurable interactive timeout (99999 min ≈ never timeout)

@devil233-ui
devil233-ui marked this pull request as ready for review August 6, 2026 05:12
@FlowerRealm

Copy link
Copy Markdown
Contributor

代码太长了, 前端写的拖动条根本没必要min, max, 请优化一下代码质量

@FlowerRealm

Copy link
Copy Markdown
Contributor

前端传回的值是确定的, 无需后端过多检验

@devil233-ui

Copy link
Copy Markdown
Contributor Author

v4f还是太菜了hhh

@su-fen

su-fen commented Aug 8, 2026

Copy link
Copy Markdown
Member

感觉这样调整会有问题,超长时间后选择完进行下一步,会重建缓存造成巨额扣费。
功能待定

@devil233-ui

Copy link
Copy Markdown
Contributor Author

感觉这样调整会有问题,超长时间后选择完进行下一步,会重建缓存造成巨额扣费。 功能待定

这本身不是个可选项嘛

保留interactiveTimeoutMinutes的默认值与保存白名单,移除与前端滑块重复的范围归一化。\n\n将持久化测试收敛为滑块实际提交值的往返验证,并删除两端镜像中未使用的最小值常量,保持WebUI与桌面端一致。
@devil233-ui

Copy link
Copy Markdown
Contributor Author

已在d613d63处理代码质量意见:Rust侧仅保留默认值与保存白名单,移除范围钳制和重复归一化;持久化测试收敛为实际滑块值的往返验证;同步删除未使用的最小值常量并保持GUI/WebUI镜像一致。rangemin/max保留,因为它控制的是离散档位数组索引,HTML默认范围为0–100,移除后会允许无效索引;该边界只约束前端控件,不再重复做后端范围校验。已通过97项相关前端测试、119文件镜像检查和目标Rust测试。

@su-fen

su-fen commented Aug 9, 2026

Copy link
Copy Markdown
Member

想了一下感觉还是可以加上的,前端重构了下,佬解决一下冲突吧

…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>
@devil233-ui

Copy link
Copy Markdown
Contributor Author

@su-fen 冲突已解决,已 merge 最新 main(agent-ui 统一重构 #399)并推送,现在 PR 状态是 MERGEABLE ✅

主要是重构把 agent-gui / agent-gateway/web 的镜像副本收敛成了共享的 crates/agent-ui 包、并移除了 mirror-manifest 机制,所以本 PR 之前改的镜像副本要么随重命名跟到 agent-ui、要么删掉冗余副本。解决明细:

  • 3 处 modify/deleteAskUserQuestionCard.tsxaskUserQuestion.tsSystemToolsSection.tsx 的旧镜像副本删除;本 PR 的改动(INTERACTIVE_TIMEOUT_MAX_MINUTES 信任窗口、scheduleAtDeadline 溢出修复)已落到重命名后的 agent-ui 版本。
  • 2 处内容冲突askUserQuestionTools.tstoolApproval.ts):从新路径 @liveagent/ui/lib/chat/askUserQuestion 引入 scheduleAtDeadline,同时保留 main 的 typebox 改动。
  • 1 处测试ask-user-question-card.test.mjs):采用 main 的 mocks 加载方式,保留本 PR 的 INTERACTIVE_TIMEOUT_MAX_MINUTES 解构。
  • 1 处文档chat-runtime.md):保留本 PR 的可配置超时说明,路径改为 agent-ui。
  • 1 处静默合并修复:系统设置 row_count 10 → 11。本 PR 和 main 各自新增了一个系统键(interactiveTimeoutMinutes / workspaceResourceSettings),git 没识别为冲突但计数需修正。

本地验证:agent-gui + agent-gateway/web tsc 通过;gateway/web 测试 540/540;agent-gui 前端 1508/1509(唯一失败是 gemini-3-pro-preview 模型目录测试,在干净的 main 上也是红的,与本 PR 无关);Rust 设置测试 51/51;改动的 TS 文件 biome 干净。

@devil233-ui

devil233-ui commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

@su-fen 多谢 review!冲突已解决,现在是 MERGEABLE。关于你最早提的「超长窗口 → 选择完继续会重建缓存造成巨额扣费」,我把成本这块的实际情况说清楚,也给这条留个记录:

结论:这是一次性、有上限的成本,不是持续/巨额扣费;默认配置根本触发不了,而且已有现成的缓存档位可以进一步规避。

  1. 默认碰不到interactiveTimeoutMinutes 默认 3 分钟,而 Anthropic ephemeral 缓存默认 TTL 是 5 分钟——默认窗口会在缓存过期前就自动落定,冷缓存续跑在默认配置下不会发生。

  2. 有专门的档位对冲promptCacheRetention: "long" 在官方 API 上会把 TTL 映射成 1h(见 crates/agent-gui/src/lib/providers/runtime/anthropicCache.ts)。真要开长问答窗口的人,把它设成 long 就能覆盖绝大多数人类作答时长,缓存全程不过期。

  3. 代价是「一次」冷续跑,不滚动:挂起期间不发任何请求;只有超过 TTL 后那 一个 续跑请求的前缀按未命中计价(外加一次 cache write),之后照常命中。成本正比于上下文、有上限,不会累积成「巨额」。

  4. 机制不是本 PR 引入的:任何「人在环」的长暂停(工具审批、甚至用户就是没答)超过 TTL 都是同样的冷续跑;本 PR 只是把这个等待窗口做成可配置。而且本 PR 里的 scheduleAtDeadline 溢出修复才是关键——没有它,大窗口会撞上 setTimeout 的 32 位延迟上限静默回绕、几乎立刻按推荐项落定,那才是真 bug。

  5. 强制短超时反而可能更贵:到点自动选推荐 = 拿一个用户没真正做的默认往下走,选错就是整轮跑偏、更多工具调用甚至重来,烧的 token 常常远超一次冷续跑。

所以是:默认安全 + 长窗口自愿取舍 + 有 promptCacheRetention: long 兜底,成本可控。

@devil233-ui

Copy link
Copy Markdown
Contributor Author

补充:联调时发现的一个既有问题(与本 PR 无直接关系,记录在此备查)

桌面 + WebUI 双端联调本功能时发现:AskUserQuestion 提问卡在桌面端正常显示并倒计时,但 WebUI 端在提问挂起期间刷新 / 重连后看不到这张卡。

确认的机制:挂起中的提问 不写任何持久化聊天事件——askUserQuestionTools.tsexecute 只在内存 pendingByToolCallIdawait,直到用户作答 / 超时 / 中止才产生 ToolResultMessage。WebUI 只有两条路拿到这张卡:

  1. 实时 tool_call 网关事件(桌面 onToolExecutionStart 推送,带完整 questions + deadline)——链路正常时能出卡;
  2. 历史回放——但历史里没有挂起态。

所以只要 WebUI 在提问挂起期间刷新 / 重连,就走历史回放,而历史里没有这条挂起提问 → 整行消失(连「向你提问」标签都没有)。桌面端直接读内存挂起表,不受影响。

与本 PR 的关系:决定这张卡「是否出现」的链路——ToolCallItem 门控、transcript rows.ts 重建、runAgentConversationTurn 事件发射、两端 adapter——本 PR 一个字都没动,与 main 一致;本 PR 只改了卡片内部(AskUserQuestionCard)和超时逻辑。因此这个「WebUI 看不到卡」是 main 既有缺口,不是本 PR 引入的,也不阻塞本 PR 合并

之所以在这里记一笔:本 PR 把交互窗口做成可配置、可以设得很长,挂起时间越长,用户在挂起期间刷新 WebUI 的概率越高,也就更容易撞到这个既有缺口。

修复方向(建议独立 PR):给挂起的提问落一个可回放的标记(或由网关缓存挂起态),使 WebUI 重连 / 刷新时历史回放能重建这张卡。这会动到 main 的共享持久化逻辑、双端都受影响,评审和风险应单独走,不并入本 PR。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] 交互式提问默认不超时并支持配置超时时间

3 participants