feat: fork on a user prompt rewinds and refills the composer - #47
Conversation
Forking at a user prompt used to copy that prompt into the new session history verbatim. It now rewinds to the state just before the prompt and returns the prompt text as restored_prompt so the UI can refill the composer for the user to edit and resend. Forking at an assistant message keeps the previous behaviour unchanged. Backend: resolve_fork_anchor_index scans back past meta/checkpoint/ timing/net-diff records and refuses to stop on an assistant message with unanswered tool calls, so the forked prefix never ends mid-tool-call. fork_restored_prompt_text falls back to joining text parts when a multimodal user message carries no plain content. Frontend: forkAndSwitch sets the composer-preserve flag before switching sessions, then refills from restored_prompt. Tests: unit tests for anchor resolution and prompt extraction, plus HTTP route tests asserting restored_prompt for user targets and its absence for assistant targets. OpenSpec change fork-user-prompt-to-composer.
…guards - scripts/macos_create_portable_zip.sh: builds a self-contained .app + TUI + share/acecode tree via cmake install; three guards prevent silently shipping stale embedded web UI (frontend newer than embedded assets -> auto reconfigure+rebuild; grep binary to confirm UI embedded). - macos-portable-package skill mirrored into .claude/.acecode/.agents/.codex skills/ so all agent toolchains can invoke it.
Shortens the restored-prompt toast from 已分叉,提示词已回填输入框,可修改后重发 to just 已创建分支会话; the composer refill behaviour is unchanged. Registers the new copy in the i18n source catalog (zh-CN + en-US).
…pt-to-composer # Conflicts: # web/scripts/i18n-en-overrides.mjs
shaohaozhi286
left a comment
There was a problem hiding this comment.
发现原会话草稿覆盖风险,以及附带 macOS 打包脚本的 3 个可定位问题,建议修复后再合并;也可将打包改动拆成独立 PR。验证:本地 pnpm test、pnpm build 通过;CI 中新增 fork anchor/prompt 测试及相关 Fork HTTP 测试通过。用现有草稿 cleanup 回调复现了旧 session ID 被写入 fork 回填文本。CI 的两个终端/工具链失败在主干 run 34356121713 同样存在,不认定为本 PR 回归。macOS 打包问题依据脚本控制流审查,本机未执行 macOS 实机打包。
| const restoredPrompt = forkRestoredPrompt(r); | ||
| if (restoredPrompt) { | ||
| preserveComposerInputOnSessionChangeRef.current = true; | ||
| setComposerValue(restoredPrompt); |
There was a problem hiding this comment.
[P1] 在切换会话前保护原会话尚未保存的草稿。若用户已输入新草稿且 350ms 自动保存尚未完成,composerDirtyRef 为 true。这里先将 composerValue 改为历史提示词,再切换会话;新 render 在第 884 行更新 composerValueRef,而旧会话 cleanup(1743-1751)仍捕获旧 sid,并将该 ref 当前值保存到旧会话,导致原草稿被历史提示词覆盖。提取现有 cleanup 回调按此顺序执行,确实得到 persistDraftValue(original-session, ..., restored-prompt)。建议先用原文本保存并解除旧会话 dirty 状态,或使用会话级快照/pending fork payload,在目标会话完成切换后回填;补充“旧会话有未保存草稿时分叉,返回原会话草稿保持”的回归测试。
| -DBUILD_TESTING=OFF -DACECODE_BUILD_DESKTOP=ON \ | ||
| -DVCPKG_MANIFEST_FEATURES=tests -DVCPKG_MANIFEST_INSTALL=OFF | ||
| echo "== rebuilding acecode-desktop with fresh embedded assets ==" | ||
| "$CMAKE_BIN" --build "$build_dir" --target acecode-desktop |
There was a problem hiding this comment.
[P1] 无论前端是否变化,都应执行一次增量 C++ 构建。当前唯一的 cmake --build 放在 needs_reconfigure 分支里,而该条件只检查 web/dist/index.html 和 embed marker。首次打包后,只修改或拉取 C++ 代码再执行脚本,条件为 false,程序会直接复制旧 acecode/ACECode.app 并成功通过 provider-logos 检查,产物不包含本次后端修复。建议只将 configure 放在条件内,把 --build 移到条件外;增加前端不变、后端变化时仍执行 build 的脚本回归验证。
| "$CMAKE_BIN" -S "$repo_root" -B "$build_dir" -G Ninja \ | ||
| -DCMAKE_BUILD_TYPE=Release \ | ||
| -DCMAKE_TOOLCHAIN_FILE="$HOME/vcpkg/scripts/buildsystems/vcpkg.cmake" \ | ||
| -DVCPKG_TARGET_TRIPLET=x64-osx \ |
There was a problem hiding this comment.
[P2] --arch arm64 必须同步选择 vcpkg triplet 和构建缓存。此处无论参数均强制 x64-osx,配合 CMAKE_OSX_ARCHITECTURES=arm64 会混用 x86_64 依赖;而默认 build 目录始终是 macos-x64-release,前端哈希匹配时甚至不会重新配置,会把旧 x64 二进制命名为 arm64 包。建议从校验后的 arch 推导 x64-osx/arm64-osx 和独立 build 目录,将架构/缓存匹配作为配置条件,并在归档前验证实际二进制架构。
| echo "== zipping with ditto ==" | ||
| rm -f "$output_path" | ||
| ( cd "$repo_root/dist" && /usr/bin/ditto -c -k --keepParent --sequesterRsrc \ | ||
| "$package_name" "$(basename "$output_path")" ) |
There was a problem hiding this comment.
[P2] 归档应写入 --output 指定的完整路径。当前在 repo/dist 中调用 ditto,但仅传 basename(output_path),所以 --output /tmp/custom.zip 实际写到 repo/dist/custom.zip;随后却用 /tmp/custom.zip 解压,导致失败,而且前面的 rm -f 已删除用户指定位置的旧文件。建议先将 output_path 规范化为绝对路径并创建父目录,再把完整路径传给 ditto,验证和报告使用同一条路径。
|
已随集成分支合并到 master,修复提交:2b68a67235740966257a51be353f0f098acf03c6。回填改为目标会话激活后消费,原会话未保存草稿不会被覆盖。打包脚本始终增量构建,隔离并校验架构,且遵守完整输出路径。完整 pnpm test/build 通过,新增草稿生命周期测试及 4 项模拟 macOS 工具的打包回归通过。本机未做原生 macOS 编译或 GUI 启动;C++ fork 测试沿用本 PR CI 验证,已有两个 Linux 基线失败单独记录。 |
背景
桌面版/网页版对会话做分叉时,若选中的是一条用户提示词,原先会把该提示词本身也复制进新会话。期望行为是:分叉点落在该提示词之前(即上一次 agent 回复处),并把提示词原文回填到输入框,供用户修改后重发。
行为
改动
session_rewind:新增resolve_fork_anchor_index、fork_restored_prompt_text;fork 路由新增返回restored_prompt/fork_anchor_role。ChatView.jsx的forkAndSwitch:切换会话前回填 composer 并保留,避免被会话切换清空。scripts/macos_create_portable_zip.sh与配套 skill(含三道防呆,防止把旧前端静默嵌进包里)。验证
备注
本 PR 同时包含一个与功能无关的 chore 提交(macOS 便携包脚本 + skill)。如需拆成独立 PR 我可以另行处理。