文件最后提交记录最后更新时间
feat(tuix): light/dark theme switch with OSC 11 auto-detection UI palette now adapts to the terminal's background colour. Adds a [ui] theme config field ("auto" default, "light", "dark"), re-routes the markdown / syntect / pill SGR layer through runtime accessors instead of compile-time constants, and ships a second contrast-corrected palette designed for #FFFFFF backgrounds. Auto-detection (UiTheme::Auto): - After raw mode is on, write OSC 11 \x1b]11;?\x07 to stdout. - libc::poll on stdin with a 100ms budget. - Parse rgb:RRRR/GGGG/BBBB, compute Rec. 709 luminance, > 128/255 → light. - No response (Mac Terminal.app, Windows conhost, OSC-stripping SSH relay) → silently fall back to dark; matches the legacy behaviour. highlight/theme.rs: 10 token-colour constants become atomic-backed accessor fns; close codes / bold / italic / muted stay const because they're attribute toggles, not colours. highlight/mod.rs: syntect Theme is built per palette and cached in two OnceLock<Theme>s selected at call time. Light palette RGB values are picked for ≥ 4.5:1 WCAG AA contrast on #FFFFFF. The screenshot trigger — fn main rendering the main identifier invisible because of the old #61AFEF (2.04:1) — now uses #0D47A1 (8.8:1). Pill SGR (session-name badge on input box top rule) adapts too: dark stays at \x1b[7;96m (reverse + bright cyan); light switches to \x1b[1;35m (bold + standard magenta) — the reverse-cyan chip turns into pale-aqua-on-white in light terminal profiles and the chip disappears, the no-reverse bold-magenta form stays readable. Includes 12 OSC 11 parser tests (pure-white/black, 8-bit/16-bit emulator variants, ST vs. BEL terminators, leading-garbage tolerance, threshold boundaries, Rec. 709 weighting verification) plus 13 new theme palette tests. All 26 highlight + 40 markdown + 206 render tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> 14 天前
fix(webui): /webui 用 TUI 当前会话播种 LiveSession,续聊后直接落到该会话 atomcode -c 续聊后 ctx.current_session 已是续聊会话,但 /webui 调用的是 ensure_live_session——新建空 LiveSession(空消息 + 随机 LIVE_SESSION_ID),从不把 当前会话搬进去。于是 webui 连 /live 拿到空快照、落到空白新页面而非续聊会话。 - live_api.rs:新增 ensure_live_session_seeded(initial, session_id),新建时用给定消息 播种并复用 session_id(LIVE_SESSION_ID + 执行器 id 一致,后续每轮覆盖同一会话文件、 不产生重复);ensure_live_session 改为空播种封装。lib.rs 导出之。 - commands.rs:/webui 在开浏览器之前先用 ctx.current_session 的消息+id 播种(非空才复用 id;先播种再开浏览器,避免浏览器抢先连 /live 建出空会话)。attach_live_session 加 render_snapshot 参数:/webui 传 false(画面已有该对话,跳过快照回放避免重复刷), /sync 传 true(重新附着补 webui 期间对话)。 live 测试 core(4)/daemon(3) 全过;LiveSession initial 播种本就被 core 测试覆盖。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> 1 天前
fix(tuix): normalize Windows path separators in @-mention file index 28 天前
feat(live): webui↔TUI 模型双向实时同步 + 修复同步模式 TUI 审批不生效 模型同步:此前 webui 下拉框只改本地态、且仅在发消息时才把 provider 带给后端 (LIVE_PROVIDER),TUI 的活动模型与头部完全收不到通知。新增一条贯穿四层的 模型变更广播: - core:LiveEvent::ProviderChanged + LiveSession::notify_provider_changed; AgentEvent::ProviderChanged。 - daemon:live_set_provider(设 LIVE_PROVIDER + 广播);POST /live/provider 端点(持久化 default_provider 到 config.toml + 广播,下拉框一变即生效,不必先 发消息);/live 快照新增 provider 字段,新 tab 连上即回显正确模型。 - tuix:live_sync 转发 ProviderChanged;handle_agent_event 据此更新 default_provider/model_name + 通知 agent + 刷新头部(已是该 provider 则跳过, 避免自身 /model 切换的回声);/model 选择器切换后 live_set_provider 广播给 webui(反向)。 - webui:postLiveProvider;下拉框变更即在 sync 下上报;收到 provider/snapshot 即 setProvider(不回调 onChange,避免回环)。 切换发起方落盘一次(webui→端点;TUI→选择器 save_and_reload),TUI 收到广播只 做内存态同步,不二次落盘。 审批修复:同步模式下,工具这一轮跑在进程内 LiveSession 协调器(DaemonTurnExecutor) 里,审批只能经 LiveSession::approve 投递(webui /live/permission 即如此)。但 TUI 的 handle_approval_key 无条件把决定发给 TUI 自己的 agent——而同步模式下该 agent 没跑这一轮,决定石沉大海:工具一直 Running 卡死、webui 审批卡片也不关。新增 deliver_approval:sync_forwarder 存在时把决定投给 current_live_session().approve (A 降级为 Allow,与 webui 一致),否则照旧发 TUI agent;Ctrl+C 拒绝同样改走它。 工具随之继续并广播 ToolCallResult,触发 webui 侧清卡片逻辑,两个症状一并解决。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> 1 天前
feat(undo): UndoToPrompt command + ConversationTruncated/UndoFailed events Agent truncates the authoritative conversation and replies; TUI persists the truncated session, replays scrollback, and refills the input box. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> 1 天前
fix(codingplan): 漂移监控按用户实际档位查询模型列表,消除 Lite/Pro 永久误报 models-v2 的 plan_available 是相对所请求的 plan_type 计算的(ModelEntry 文档): setup 用用户实际档位(如 Lite)请求并注册 provider,但漂移监控 spawn_check 硬编码用 PlanType::Max 请求——Max 档下高档模型(如 GLM-5.1)也被标成 plan_available=true, 服务端列表比本地(按实际档位配置)多出来,decide_warning 永久返回 StaleList,状态栏 一直显示"CodingPlan 模型列表更新 — 可执行 /login"。 - types.rs:新增纯函数 PlanType::from_plan_name,把 /status 的 plan_name 映回档位; 无法识别(Free/空)返回 None。 - monitor.rs:spawn_check 先 status_v2() 取真实档位,再以该档位请求 list_models_v2; 无法确定档位时静默跳过,不再用 Max 兜底。升级套餐后自纠正。 新增测试 plan_type_from_plan_name;monitor/coding_plan 既有测试全过。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> 1 天前
fix(onboarding): persist auth.toml after QR poll authorisation LoginSession::finish returns AuthInfo but does NOT write auth.toml — that's the caller's job. The CLI login() driver pairs finish + save_auth; spawn_oauth_poll was calling finish alone and dropping the result, so the token was never persisted. User-visible: after WeChat scan completed and the modal closed, the subsequent run_codingplan_flow saw is_logged_in() == false and ran the CLI-style OAuth login a SECOND time — printing its own ASCII QR + URL into scrollback and opening the browser again to a fresh AtomGit login page. The user had to scan a second time for /codingplan to actually claim. Screenshot in the bug report shows the duplicate "登录 AtomGit — 使用微信扫描下方二维码:" panel that this fix eliminates. Mirror the CLI pattern: pair session.finish() with atomcode_core::auth::save_auth(&auth_info). Failed save surfaces as OauthEvent::Failed (transport-or-disk error worth telling the user about) rather than silently dropping into the doubled-login fallback path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> 14 天前
feat(codingplan): switch to v2 endpoints + tier cascade + locked-model display Re-platform CodingPlan onto the v2 API contract. Same end-to-end logic (login -> claim -> models -> status), new wire shape, additional tier-aware semantics. Pointed at pre-prod (pre-api.gitcode.com) per the rollout plan; flip back to api.gitcode.com is a single-line edit in client::API_BASE when prod cuts over. Endpoint changes: POST /api/v5/coding-plan/claim -> POST /coding-plan/claim-v2 GET /api/v5/coding-plan/models -> GET /coding-plan/models-v2 GET /api/v5/coding-plan/status -> GET /coding-plan/status-v2 Behavioural deltas: 1. **Tier cascade on claim**. claim-v2 takes a plan_type body field — Max / Pro / Lite. step_claim now walks PlanType::CASCADE_ORDER (Max first), POSTs each tier in turn, stops at the first that returns success=true OR duplicate=true (already-held tiers count as success). 2xx with both flags false is a per-tier refusal — keep walking and surface the last reason if every tier refuses. 5xx / transport errors abort the whole cascade — those don't get more useful when retried at a lower tier. 2. **Plan-aware model availability**. models-v2 carries a new plan_available: bool per entry: true iff the user's currently-claimed tier covers that model. Rule per the spec: is_infinity = 1 AND tier <= claimed plan. (is_infinity itself is no longer in the response — server computes the gate and ships only the boolean.) step_models_and_register filters to plan_available=true for provider registration so users can't /model into something that 403s on the first request, but the full list (including locked entries) flows into ModelsInfo::all_models for display. 3. **Locked-model render**. The "Added N providers" section now gets a sibling block listing locked models with ANSI SGR 9 strikethrough (\x1b[9m...\x1b[29m) plus an explicit "(locked)" suffix — terminals that don't honour SGR 9 still convey the meaning. Mirrors the spec's "若不可用的模型也展示出来 (用横线划掉)". 4. **Tier surfaces in claim row**. ClaimInfo carries the plan_type the cascade landed on. Render appends it as (Max) / (Pro) / (Lite) next to the claim message so users see *which* tier was granted (Max-targeting that fell back to Pro is visually distinct from a fresh Max claim). External callers (event_loop/monitor.rs, event_loop/usage_monitor.rs, event_loop/commands.rs) just swap the endpoint name. The drift monitor passes PlanType::Max to list_models_v2 and filters by plan_available — that's the broadest view, and matches what the post-setup config holds without having to persist the cascade-landed tier anywhere. Tests (4 new, all coding_plan/): * model_entry_parses_docs_example — pinned to v2 wire shape (drops is_infinity, adds plan_available). * model_entry_locked_round_tripsplan_available=false survives parse. * plan_type_wire_form_and_cascade — Max/Pro/Lite literal strings + cascade order contract. * render_shows_locked_models_with_strikethrough — end-to-end render of an Lite-tier claim with one locked Max model: asserts the SGR 9 wrap, the (locked) suffix, the "(Lite)" tier on the claim row, and that the locked model does NOT leak into the "Added providers" list. All 1675 workspace lib tests pass single-threaded. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> 26 天前