feat(realtime): schedule mutations, real database, and command.result fix - #210
Merged
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
LUPENGHAN
added a commit
to LUPENGHAN/timeflow
that referenced
this pull request
Aug 12, 2026
normalize_datetime_args decided whether a datetime string already carried a timezone by checking for a literal "+" or "Z", which misses negative offsets like "-05:00". fromisoformat parses that as already aware, but the code then force-replaced its tzinfo with LOCAL anyway -- silently shifting the instant by the difference between the two zones (e.g. a schedule meant for 07:00-05:00 gets stored as 07:00+08:00, 13 hours off). Parse first and only attach LOCAL when the result is genuinely naive, matching how _optional_datetime already decides the same thing. Found by fennoai's review on PR 1024XEngineer#210.
…apping layer - Add account_id to StreamInfo/StreamIdentity/StreamContext protocols - Change RealtimeAgent to use tools_factory for per-account tool binding - Extract tool mapping functions from conversation layer to tool_mapping.py - Implement ToolBox with five schedule tools and exception translation - Add SeededScheduleService as temporary data source - Validate candidates field for ambiguous_target questions Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- schedule_create/update/delete join the existing query tool, with instructions covering when to ask before touching a schedule versus acting on it. - account_id now flows from the WS transport through to tool binding, so a session's tools are scoped to its authenticated account. - A session stays open past a turn that asked a question, so a follow-up answer reaches a model that still remembers asking it. - Hardens the Qwen-Audio transport pump: a binary frame is skipped rather than treated as fatal, and non-JSON text fails the turn with a reported reason instead of raising.
Switch main.py off the local duplicate data layer (DatabaseScheduleService plus its own repository and transaction handling) onto the already-merged ScheduleApplicationService + SqlAlchemyScheduleUnitOfWork (1024XEngineer#192, 1024XEngineer#200), and update the two database-backed end-to-end tests to match. Drops ~900 lines of implementation and tests that duplicated what was already upstream.
RealtimeAgent.tool_requested passed the whole ToolBox outcome dict
(itself {operation, status, schedule}) into CommandResult.schedule, so
the client received payload.schedule.schedule.title instead of
payload.schedule.title -- every mutation reached the client with no
usable schedule data. list_schedules had the same problem in reverse:
its matches never had anywhere correct to go at all.
CommandResult and the wire payload now carry separate optional
schedule/schedules fields, matching protocol §5.5/§5.6, and only the
one that applies is sent (schedules is exclude_none'd off the wire
for a mutation, and vice versa for a query).
Verified against a real voice round trip over the actual database, not
just the added regression tests.
…kend
Settings gains voice_agent_mode ("1" realtime model, "2" LLM+ASR+TTS
pipeline), validated against those two values. main.py's _build_agent
already dispatches on it (committed in 6b88acf); mode 2 fails closed
with a clear message since the conversation pipeline does not
implement the Agent port yet.
ruff format wanted a call collapsed onto one line; missed running the formatter locally before the earlier commit.
normalize_datetime_args decided whether a datetime string already carried a timezone by checking for a literal "+" or "Z", which misses negative offsets like "-05:00". fromisoformat parses that as already aware, but the code then force-replaced its tzinfo with LOCAL anyway -- silently shifting the instant by the difference between the two zones (e.g. a schedule meant for 07:00-05:00 gets stored as 07:00+08:00, 13 hours off). Parse first and only attach LOCAL when the result is genuinely naive, matching how _optional_datetime already decides the same thing. Found by fennoai's review on PR 1024XEngineer#210.
LUPENGHAN
force-pushed
the
feature/realtime-tools
branch
from
August 12, 2026 09:07
b218b20 to
aa5f8fe
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
关联 Issue
#197 —— 完成其中的写操作部分,在 #201(真机测通只读查询)之上继续。
改动
在 #201 建好的边界后面,把「只能查」补成「能查能改」,并把数据层从占位实现换成真数据库。
intelligence/realtime/schedule_tools.pyToolBox新增schedule_create/schedule_update/schedule_delete三个工具的 schema 与执行,和已有的schedule_query并列intelligence/realtime/tool_mapping.pyCreateScheduleCommand/UpdateScheduleCommand/Delete{Once,Recurring}ScheduleCommand的映射与校验intelligence/realtime/instructions.pyschedule_query拿 id 和 revision(不能凭印象编)、删除前必须确认、工具失败要如实说gateway/websocket/handlers/agent_audio.py_StreamIdentity带上account_id,工具按账号隔离,不靠调用方自觉main.pyScheduleApplicationService+SqlAlchemyScheduleUnitOfWork(#192、#200 已经在 upstream 的实现),换掉本地重复写的数据层(约 900 行)intelligence/ports.pyCommandResult拆成可选的schedule/schedules两个字段,不再共用一个必填 dictgateway/websocket/agent_ports.pyCommandOutcomeprotocol 同步加schedulesgateway/websocket/messages/agent.pyVoiceCommandResultPayload同步加schedules,序列化时exclude_none掉不适用的那个gateway/websocket/handlers/agent_result.pydeliver_result把两个字段分别透传,不再塞进同一个intelligence/realtime/agent.pytool_requested不再把整个 outcome 塞进schedule,改成分别取outcome["schedule"]/outcome["schedules"]infrastructure/settings.py、.env.exampleTIMEFLOW_VOICE_AGENT_MODE开关:1走实时模型(本 PR),2走级联管线,管线还没接Agent端口前直接拒绝启动,不允许静默用错后端顺手修的一个 bug:command.result 嵌套
RealtimeAgent.tool_requested把整个工具 outcome({operation, status, schedule})塞进了CommandResult.schedule,客户端拿到的是payload.schedule.schedule.title,不是协议 §5.5 要求的payload.schedule.title——日程字段全部读不到。查询那条路径(§5.6 要求的payload.schedules复数)原来更是完全没地方放。上面表格里那几处改动把这个修掉,两条路径都补了回归测试。真机实测
用
docs/tools/serve.py+ws-console.html起真实语音(非合成音频)跑通两条路径,数据库用的是真的 Postgres,不是假实现:schedule_createasr.completed → command.result1134.7 ms;payload.schedule.title= 「开会」、start_time=2026-08-13T15:00:00+08:00,字段齐全schedule_querypayload.schedules返回 5 条真实记录(含一条周期日程),扁平结构,没有再套一层验证
mypy src:64 文件全过pytest:415 passed / 37 skipped(跳过的是需要TIMEFLOW_TEST_DATABASE_URL的 postgres 集成测试)ruff check/ruff format --check:全过本轮不含
this_occurrence/this_and_future范围依赖 occurrence override 序列化,这是继承自 feat(schedule): implement Agent schedule application service #200 的既有缺口,不是本 PR 引入,也没打算在这里顺手补TIMEFLOW_VOICE_AGENT_MODE=2(级联管线)本 PR 只加了开关和 fail-closed 保护,管线本身没有实现