Repository navigation
feat: 提示词增强与附件在 prompt 中的表达规范 - #5
yycy134679 wants to merge 19 commits into
Conversation
.kcodeignore 同步 .gitignore 的排除规则,供本地工具复用;docs/prompt-enhance.md 记录提示词增强功能的完整实现规范,作为后续实现的唯一依据。
新增 promptEnhance 设置(改写模式 / 上下文参考 / 结构化覆盖放开 / 模型通道 / 推理强度),
以及三档改写模板和不依赖 React 的纯函数:占位符拼装、结构化内容闸、还原闸、
活动 run 跟踪、设置补齐与通道解析,附 node:test 单测并登记进 CI 的 Focused tests。
拼装一次性扫模板替换占位符:顺序 replaceAll 会让草稿正文里的 {input} 字样被二次改写,
草稿含 $& / $' 时也必须走函数式 replacer。
run 跟踪把「当前是否有活动 run」与「单调递增的 runId」拆开:用墓碑占位活动 run 时,
取消后的下一次点击会被继续判成取消,按钮再也发不出请求,且迟到响应永远清不掉墓碑。
该故障在当前实现里出现过,所以把这段逻辑抽成纯模块并留下回归测试。
schema 注释点明写回形状:ISettingService.update 只合并外层字段,嵌套对象是整体替换,
而 patch schema 的 .partial() 仍会补齐内层默认值——因此必须提交完整 promptEnhance
对象,只交单个字段会被默认值重置掉其它选择。
工具条新增增强 / 还原 / 设置三个入口:点增强用模型改写草稿并原地替换,绝不自动发送; 改写前原文存为一级还原点,草稿被手改后「还原」被拒并说明原因。请求级 60s 超时, 进行中再点即取消,迟到响应按 runId 丢弃;写入异常或读回不一致都写回原文,不留半截文本。 设置页新增「提示词增强」分区(分组 basics),管理改写模式、上下文轮数与开关、 含附件/引用草稿的覆盖放开、独立模型通道及推理强度,改动即写即生效并可恢复默认; 模式与档位选项直接复用共享常量,不在分区里重写一份字面量。写回是「读当前设置 + 完整对象」, 因此保存期间所有控件(含分段控件)禁用,否则连续切两项会用尚未回刷的旧值覆盖前一次选择。 工具条「设置」入口经设置导航意图直达该分区。 composer 用固定 key 跨 session 复用,因此 scope 变化或草稿被清空(发送成功)时主动作废 在途请求并清还原点,避免新会话继承上一个草稿的按钮状态、或留下点了必然被拒的「还原」。 结构化内容闸按规范复用 parseMentionMarkdown,会把 $技能 / /命令 / 路径这类行内 token 一并判为结构化内容;提示语按实际影响逐项说明,需要时由设置放开。 旧 chat.promptEnhance.* 文案无任何引用(属于更早的直连通道方案),改为 chat.toolbar.promptEnhance.* / settings.promptEnhance.*,避免同一功能两套命名。
用户点「增强提示词」必现失败:`params.signal?.addEventListener is not a
function`。
根因:Renderer 经 ProxyChannel 调 Host 服务,实参在
packages/rpc/src/serialization.ts 里对普通对象走 JSON.stringify fallback,
AbortSignal 没有可枚举字段,到 Host 侧变成 `{}`(truthy),服务里
`params.signal?.addEventListener` 直接抛错。(已用探针复现同一条错误串。)
修法:跨进程不再传 AbortSignal,改用可序列化的 operationId —— CLI 早就按
`workspace/generateText` 的 operationId 登记 AbortController,
`workspace/cancelGenerateText` 按 id 触发,本次只把这条既有能力接到服务面:
- shared:导出 KCodeWorkspaceCancelGenerateTextResult。
- services:generateWorkspaceText 优先用调用方带来的 operationId(缺省时才按
signal 自造 uuid);新增 cancelWorkspaceGenerateText 控制面方法,只对已存在的
活跃 workspace client 发取消(不为一条取消拉起 Agent 进程),超时 5s。
- UI:去掉 AbortController,新增 operationId/request 两个纯模块;取消句柄在发起
时固化(operationId + 发起时的 workspace target),由单个 ref 持有,作废或正常
结束时释放,避免 scope 变化后按新 target 把取消打到另一台 Host。
- 测试:新增请求参数不含 signal(回归护栏,经 JSON 序列化往返校验)、target 由
同一函数产出、operationId 唯一等用例。
未验证:真实模型链路上的取消效果需要 provider 凭据,未跑;改动落在 Host 与 Agent
两侧,需重启 `pnpm dev:desktop` 才生效。
用户点增强报 `maxOutputTokens is outside the model option range`,但模型配置里的 上限本身没问题——是请求根本没带预算。 根因:`workspace/generateText` 的模型校验(adapters/src/model/model.ts 的 validateOptions)要求请求合并后的 maxOutputTokens 必须有值且在模型 optionSpecs.maxOutputTokens.max 之内,缺值与越界用的是同一条错误。输出预算属于 单次请求,ModelFactory 刻意不绑定它,git 提交消息那条路靠 auxiliaryModelOptions 兜底,而提示词增强是这个通用入口的第一个调用方,于是漏了。 同一份模型配置还决定档位:Registry 会直接拒「selection 缺 reasoningLevel」与 「档位不在模型 values 内」。原先「设置成 default 就不下发档位」等于每次都被拒。 改法(都在 UI 侧解析,不动 CLI 契约): - 新增 resolvePromptEnhanceModelFacts / resolvePromptEnhanceTarget:从 Selection View 的完整 Model Config 取输出上限与档位集合,预算直接取模型声明的上限 (与 workspace/generateText 非 git 分支同口径);档位落到模型集合上,default 与 不支持的档位都回落到模型默认档(values 末位),并回传被替换的档位供留轨迹。 - 模型不在已发布列表、或无选型时不再发请求(构造不出合法请求);模型视图未就绪时 入口直接禁用,避免把加载中的瞬态说成配置问题。 - 解析模块改用最小结构性 View 形状,测试替身不再是 as unknown as 断言。 - 文档与 shared 注释同步:这两条契约、以及推理档位是 Registry 拒绝而非 provider 静默归一化。 未验证:真实模型链路上的效果需要 provider 凭据;改动需重启 `pnpm dev:desktop` 生效。
洁癖收尾(知识同步):把 docs/prompt-enhance.md 里与最终实现不一致的现役说法改回 真实行为,并清掉实现过程叙事。 - 「增强」禁用条件补上「模型视图未就绪」(modelSelectionView === null)。 - 日志语义补全:可恢复异常各留一条 warn(档位被回退、取消未送达 / Host 未找到待取消 请求、无可用选型、回填未完整落地、模型返回空内容、scope 已变化)。 - 手动验证清单补两条模型契约场景(独立通道档位设「默认」仍能发起;模型被删后给出 可读提示而不是模型校验错误)。 - 设置分区的控件清单按实际用的组件写(去掉未使用的 Input,补 SettingsGroupCard 与 Button)。 - 文案命名规则改成只讲当前事实,不再记叙旧键的删除过程。 - 文档开头不再写成「待实施」的口径。 - oxfmt:docs/prompt-enhance.md 与 packages/ui/test/promptEnhance.test.ts(本次新增 文件,仓库仍有 97 个历史格式漂移文件未动)。 验证:promptEnhance 测试 21 项通过;typecheck 通过;oxlint 0 error(63 条既有 warning);architecture:check --changed 0 新增;文档里 6 段模板正文仍与 prompts.ts 逐字一致(脚本比对)。
症状:点击「增强提示词」报 "Model request failed.",网关使用记录显示
调用成功但输入输出 token 均为 0。
根因:workspace/generateText 走 AI SDK 非流式 doGenerate,CPA 这类
openai-compatible 网关的非流式响应包在 {"success":…,"data":…} 自定义
信封里,AI SDK 按标准 OpenAI 形状做 schema 校验失败后统一报 Invalid
JSON response;同一网关的流式 SSE 是标准格式(当天主回合流式 1224 次
全部成功,非流式 3/3 全败,curl 重放已复现信封)。网关记账 0 token 是
其自身口径,上游实际已生成增强结果。
修复:generateWorkspaceTextImpl 改用 model.streamText 并以
accumulateWorkspaceGenerateTextStream 累积出与非流式等价的结果
(text/finishReason/usage/toolCalls),与主回合执行链、连通性探测同
一条传输路径;Git 提交消息生成同一入口一并受益。取消不受影响(仍是
operationId → AbortController)。回归测试 5 例 colocated 于
workspace-generate-text-accumulate.test.ts 并登记进 CI Focused tests。
症状:模型调用已成功(网关记录正常、model-io 已捕获增强结果文本), 但 UI 报「写入输入框失败,草稿保持不变」,草稿最终停留原文。 根因:replaceEditorText 的 editor.update 未带 discrete,Lexical 非 discrete 更新走批量异步提交;回填三连写完后立即用 getMarkdown() 读回 的是尚未提交的旧 state,读回比对必然失败,走「写回原文」分支。用 headless Lexical 0.42 探针验证:非 discrete 写入后立即读为空旧值, await 一个微任务后才可见;discrete 写入后立即读即新值。 修复:replaceEditorText 的 update 加 discrete: true(与 prependEditorMentionIfMissing 的 discrete 先例同口径),setText 契约 变为「调用返回即已落地」。全部调用方(草稿预填、发送失败回滚、增强 回填/还原、clear)都是整替内容的程序化写入,同步提交只会更正确。 读回比对的恒等性依赖 $getPromptMarkdown 纯序列化(无转义),纯文本 往返恒等;该约束已写入 docs/prompt-enhance.md 回填段。
依据 issue #3(附件被伪装成历史 Read 工具结果、且与用户消息脱钩,模型 因此把附件当背景资料而答非所问)先落规范再改实现。 规范要点:措辞只在 buildPromptAttachmentReminderBodies 一处生成,两条 投递路径与 hydrate 路径共用;删除伪造的 Called/Result of calling the Read tool 外壳,改为明话附件框法(名称 + 路径 + 数据边界句 + 内容完整性 说明);删除要求模型向用户隐瞒截断的语句;保留行号正文。截断对用户可见 列为阶段二,需先定稿 resolve 之后的投影承载通道。
症状(issue #3):上传文本附件后正文只写「读取文档内容」,模型不总结 附件,反而去遍历附件所在目录、读取其它文件后答非所问。 根因:prompt-attachment.ts 的 kind=file 分支把附件渲染成 `Called the Read tool with the following input: {file_path}` + `Result of calling the Read tool:` 两个 system-reminder 块,且该提醒 经 prompt_attachment 元上下文通道以独立消息投递(isMeta)。用户消息 本身不含任何附件痕迹,因此模型既无法区分「刚附上」与「早前读过」, 也拿不到用户指令的指代对象。 修复(阶段一,纯措辞): - 删除伪造的 Read 工具外壳,改为明话附件框法:name + path + 行号正文; - 保留「数据、非指令」边界句,内容完整时显式说明不必重复 Read; - 截断时如实写明「前 N 行 / 共 M 行」,删除要求模型向用户隐瞒截断的语句; - 措辞只在 buildPromptAttachmentReminderBodies 一处生成,live 与 hydrate 共用(新增同形断言防漂移),两条投递路径同时生效。 阶段二(截断对用户可见)单独成批:live 侧的附件展示元信息必须由一条 post-resolve 的 additive 事件送达(TurnStarted 早于附件 resolve,投影 层无 parts 重建路径),跨 contracts/归一化/投影/UI 四层,需要运行桌面端 验证,已写入 docs/prompt-attachment.md。 验证:prompt-attachment 措辞单测 6/6、live/hydrate 同形 2/2,共 34 项 通过;pnpm typecheck(根 + @kcode/core)通过;pnpm lint 0 错误(63 条 基线告警不变);architecture:check --changed 0 违规。
阶段二(docs/prompt-attachment.md):让用户也能看到附件被截断,而不只是 模型知道。 事实来源保持唯一:截断只有 resolve 时才知道(read.truncated / totalLines 写进 FilePart.metadata.preview),展示层只消费、不自行推算——截断除了体积 超限还有 token 上限兜底,依赖内容,客户端算不出与 CLI 一致的结论。 - 新增 additive 会话事件 turn_attachments_resolved(仅展示用途,不进模型 上下文、不参与压缩与冷恢复合成,已归入 cold-event-merge 的 memory-only 分类,避免每次冷恢复刷 unclassified 诊断)。TurnStarted 早于附件 resolve, 无法携带该事实,这是 live 侧唯一的送达点。 - 载荷复用 TurnAttachmentMeta 并加可选 truncated / totalLines;缺省表示 未知,不得写成 false。 - 投影按行内序号与既有附件对齐,只补截断字段,不覆盖 ref / fileName / mime / bytes;未变化时不产生 delta。 - 冷恢复由 attachmentMetasOfMessage 从 part.metadata.preview 派生同一份 事实,与 live 一致。 - 展示:发送后的附件 chip 加「已截断」标记与悬浮说明(zh/en 同步); 发送前不展示(事实发送时才产生)。 验证:投影回归测试 4/4(补字段、不改既有字段、未截断不写入、无附件不产 生字段),CI Focused tests 全量 65/65;pnpm typecheck(根 + @kcode/core + @kcode/bootstrap)通过;pnpm lint 0 错误(63 条基线不变); architecture:check --changed 0 违规。
症状:发一个远超体积上限、但每行都很短的文件(如 20 万行短行), 模型只拿到前 2000 行,提示词却告诉它「It is complete as given」, 消息上也没有「已截断」标记——截断对模型和用户双双隐形。 根因:读取层只把 token 上限硬截标成 truncated;超限文件走 `limit: READ_DEFAULT_MAX_LINES` 的按行限流,属于 range view,读取层 一律返回 truncated=false(那是给 Read 的 readFileState 完整性守卫用 的语义)。附件预读直接复用了这个字段,于是「只交付前 2000 行」被判成 「完整」。 修复:新增 isPartialAttachmentTextRead(read)(truncated === true || numLines < totalLines),resolve 时用它一次性得出截断事实, metadata.preview.truncated 与 recoverability 与它同源,展示事件元信息 随之带上 truncated/totalLines。展示层仍只消费事实。 实测(真实文件系统端口): - 1138KB/12005 行中文文档:交付 984 行,truncated=true、totalLines=12005; - 修复前 20000 行短行文件:交付 2000 行、truncated=false、提示词称内容 完整、事件无标记;修复后 truncated=true、totalLines=20000、提示词改为 「Only the first 2000 lines are included」。 - 整文件读完与空文件(1/1)仍不判截断。 验证:新增 attachments 单测 6/6,附件相关 focused 测试 23/23;pnpm typecheck(根 + @kcode/core)通过;pnpm lint 0 错误(63 条基线告警不 变);architecture:check --changed 0 违规;agent bundle 已重建并确认含 新判定。
问题:默认闸门把附件、网页元素/PPT/代码评论/对话选区引用与 mention 一起拦下, 但取证发现前四类根本不在编辑器里——附件在 composerAttachmentUploadStore、四类引用 是各自 hook 的组件 state,草稿记录 V4ComposerDraft 里没有它们的字段,回填三连 碰不到;只有行内引用是编辑器节点。于是「加了附件就不能增强」属于纯误拦; 而 mention 判定用 parseMentionMarkdown 解析文本形态,还会把「解释一下 $PATH」 「看看 /tmp 目录」这类纯文本草稿误判成引用。 改法:判据换成「回填会不会毁掉它」。 - gates.ts:入参改为 hasInlineReferences + allowInlineReferenceRewrite,删掉 附件/引用/文本形态解析;拒绝原因 structuredDraft → inlineReference。 - LexicalChatInput 句柄新增 hasInlineReferences(),遍历编辑器节点判 $isPromptMentionNode; composer 按 readDraftText 同族注入读取器(readHasInlineReferences)。 - 设置字段改名 allowStructuredOverwrite → allowInlineReferenceRewrite;标签改为 「允许改写草稿里的行内引用」,说明小字按定稿文案「行内引用会变成纯文本」; 被拒 toast 键改名 inlineReferenceBlocked,文案只说行内引用(中英同步)。 - 单测矩阵同步:有节点才拒、放开后放行、附件/手打 token/普通链接一律放行。 - spec(docs/prompt-enhance.md)同步判决依据表、判定入口与已知边界。 门禁:typecheck、lint(0 错误)、architecture:check --changed(0 违规)、 CI focused tests 53/53。旧字段残留在 setting.json 不会导致设置加载失败 (appSettings 非 strict,未知键被剥离,新字段取默认)。
guide 被 drain 时直接内联进当前轮,没有「自己那一轮」,主路径的 turn_attachments_resolved 永远不会为它发射;且当前轮已有原输入行, 按 turnId 补发会错把截断标记打到原输入行上。busy 中引导发送超限 附件时,live 的「已截断」标记因此永不出现(冷恢复由 parts 派生, 反而有),两条路径表现分叉。 修复方式:TurnSteerDrained 载荷的 drainedInputs[] 新增可选 attachments(TurnAttachmentMeta 数组,additive,缺省表示未知)。 - core:drain 时 guide 输入把 resolve 后的 summarizeResolvedTurnAttachmentsForEvent() 结果写入事件载荷; queue 不携带(提升后走主路径 resolve 补发,语义不变)。 - 投影:新增 steerDrainedRowAttachments(),以 intent.attachmentRefs 为基准(保留稳定 ref)按行内序号合并截断字段;无 refs 时直接 采用自带元信息。 - 冷恢复:合成 guide 的 TurnSteerDrained 时用 attachmentMetasOfMessage() 从 parts 派生同形状载荷,live/冷恢复 同口径。 - 测试:turn-attachments-resolved-projection.test.ts 新增 4 个 guide 场景(自带截断且保留 ref、无 refs 直采用、未截断零字段且 原输入行不受影响、无附件保持旧行为)。 - spec:docs/prompt-attachment.md 同步新增条款与验收场景,修正 原先「排队/引导输入都由自己那一轮补发」的错误表述。 顺手清理:ConversationComposer 的 leadingActionsNode memo 依赖里 删除闸门收窄后不再使用的 5 个依赖(hasAttachments 等),消除 多余的整簇工具条重渲染。 验证:tsx --test 52/52 全绿;pnpm typecheck / lint(0 error)/ architecture:check --changed(0 违规)通过。chip 的 live 表现 仍需 pnpm dev:desktop 手动确认(spec 手动验证范围)。
清理 spec 审查发现的两个轻微残留: - settings.ts 原以值形式导入 PROMPT_ENHANCE_SETTINGS_DEFAULTS, 不满足 spec「promptEnhance 纯模块对 @kcode/shared 只用 type-only 导入」的字面约束。改为内联本地默认值常量(与 shared 常量逐字一致),并由 promptEnhance.test.ts 新增漂移断言锁住 两份不漂移:任何一侧改默认值而另一侧没跟上,测试当场红。 - usePromptEnhance 的两处 scope 变化日志(作废在途增强、丢弃 迟到结果)从 info 升为 warn,对齐 spec「可恢复异常各留一条 warn」的清单——在途请求作废属生命周期边界事件,不是正常流程。 - spec(docs/prompt-enhance.md)模块清单同步注明默认值内联与 漂移锁。 验证:tsx --test promptEnhance 22/22(含新漂移锁);typecheck / lint(0 error)/ architecture:check --changed(0 违规)通过。
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Unresolved correctness, data-loss, and truncation-handling issues remain.
Get a fresh assessment by requesting another Copilot review.
Review effort: Lite
Findings: 1
Open (4)
What changed in this PR
Adds prompt enhancement for the v4 composer and standardizes attachment framing and truncation visibility across UI, CLI, protocol, and persistence layers.
Changes:
- Adds three enhancement modes, settings, cancellation, restoration, and model selection.
- Reworks attachment framing and propagates truncation metadata through live and hydrated views.
- Adds streaming generation, protocol updates, tests, documentation, and CI registration.
| File | Summary |
|---|---|
packages/ui/test/promptEnhance.test.ts |
Prompt enhancement tests |
packages/ui/src/v4/ConversationRowView.tsx |
Attachment truncation display |
packages/ui/src/v4/ConversationComposer.tsx |
Composer enhancement integration |
packages/ui/src/v4/composer/usePromptEnhance.ts |
Enhancement lifecycle |
packages/ui/src/v4/composer/PromptEnhanceActions.tsx |
Enhancement controls |
packages/ui/src/v4/composer/promptEnhance/settings.ts |
Settings normalization |
packages/ui/src/v4/composer/promptEnhance/selection.ts |
Model and reasoning selection |
packages/ui/src/v4/composer/promptEnhance/runTracker.ts |
Run tracking |
packages/ui/src/v4/composer/promptEnhance/request.ts |
Request construction |
packages/ui/src/v4/composer/promptEnhance/prompts.ts |
Prompt templates |
packages/ui/src/v4/composer/promptEnhance/operationId.ts |
Cancellation identifiers |
packages/ui/src/v4/composer/promptEnhance/gates.ts |
Request and restore gates |
packages/ui/src/v4/composer/promptEnhance/compose.ts |
Message composition |
packages/ui/src/SettingsPage.tsx |
Settings section registration |
packages/ui/src/settings/SettingsSegmentedTabs.tsx |
Disabled segmented controls |
packages/ui/src/settings/settingsPageConfig.ts |
Settings navigation configuration |
packages/ui/src/settings/PromptEnhanceSection.tsx |
Enhancement settings UI |
packages/ui/src/lib/settingsNavigation.ts |
Settings navigation support |
packages/ui/src/LexicalChatInput.tsx |
Inline reference handling |
packages/ui/src/i18n/locales/zh-CN.ts |
Chinese translations |
packages/ui/src/i18n/locales/en-US.ts |
English translations |
packages/shared/src/validationAppSettings.ts |
Settings validation |
packages/shared/src/protocol.ts |
Application settings types |
packages/shared/src/prompt-enhance.ts |
Shared enhancement configuration |
packages/shared/src/kcode-protocol/index.ts |
Generation and cancellation types |
packages/shared/src/kcode-protocol-v4/rows.ts |
Attachment row metadata |
packages/shared/src/kcode-protocol-v4/attachment-ref.ts |
Attachment reference metadata |
packages/shared/src/index.ts |
Shared exports |
packages/services/src/kcode-agent/kcodeAgentService.ts |
Generation cancellation service |
packages/services/src/kcode-agent/kcodeAgent.ts |
Agent service interface |
docs/prompt-enhance.md |
Enhancement specification |
docs/prompt-attachment.md |
Attachment specification |
apps/kcode-cli/packages/core/src/system-reminder/prompt-attachment.ts |
Attachment prompt framing |
apps/kcode-cli/packages/core/src/system-reminder/prompt-attachment.test.ts |
Attachment wording tests |
apps/kcode-cli/packages/core/src/system-reminder/prompt-attachment-parity.test.ts |
Live/hydrate parity tests |
apps/kcode-cli/packages/core/src/runtime/methods/workspace-generate-text.ts |
Streaming generation |
apps/kcode-cli/packages/core/src/runtime/methods/workspace-generate-text-accumulate.ts |
Stream accumulation |
apps/kcode-cli/packages/core/src/runtime/methods/workspace-generate-text-accumulate.test.ts |
Accumulation tests |
apps/kcode-cli/packages/core/src/runtime/methods/turn.ts |
Resolved attachment events |
apps/kcode-cli/packages/core/src/runtime/methods/steering.ts |
Guide attachment metadata |
apps/kcode-cli/packages/core/src/runtime/helpers/conversation.ts |
Live attachment construction |
apps/kcode-cli/packages/core/src/runtime/helpers/attachments.ts |
Truncation metadata |
apps/kcode-cli/packages/core/src/runtime/helpers/attachments.test.ts |
Truncation tests |
apps/kcode-cli/packages/core/src/agent/session-history-hydrator.ts |
Hydrated attachment construction |
apps/kcode-cli/packages/contracts/src/events/session.events.ts |
Attachment event contracts |
apps/kcode-cli/packages/bootstrap/src/kcode-protocol-v4/turn-attachments-resolved-projection.test.ts |
Projection tests |
apps/kcode-cli/packages/bootstrap/src/kcode-protocol-v4/transcript-hydration.ts |
Hydrated attachment metadata |
apps/kcode-cli/packages/bootstrap/src/kcode-protocol-v4/projection-rows.ts |
Attachment row projection |
apps/kcode-cli/packages/bootstrap/src/kcode-protocol-v4/product-projection.ts |
Live attachment projection |
apps/kcode-cli/packages/bootstrap/src/kcode-protocol-v4/event-normalizer.ts |
Attachment event normalization |
apps/kcode-cli/packages/bootstrap/src/kcode-protocol-v4/cold-event-merge.ts |
Cold-recovery event handling |
.kcodeignore |
Generated-artifact exclusions |
.github/workflows/ci.yml |
Focused test registration |
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| // 回填要么完整落地、要么完整保留:写入异常或读回不一致都写回原文。 | ||
| let filled = false; | ||
| try { | ||
| writeDraftText(enhanced); | ||
| filled = promptEnhanceTextMatches(readDraftText(), enhanced); |
There was a problem hiding this comment.
已修复(commit 6d2b8bd)。新增回填闸 evaluatePromptEnhanceFill(packages/ui/src/v4/composer/promptEnhance/gates.ts):结果落地前先以发起时的草稿快照为基准比对当前草稿(归一化口径与还原闸一致,只做 CRLF 与首尾空白归一化),不一致就把整条结果丢弃——不回填、不立还原点、不覆盖用户在途写入的新内容——并 toast 说明原因(chat.toolbar.promptEnhance.draftChanged)。
编辑器本身仍不禁用(不打断用户继续写,running 只作用于工具条按钮),改过的草稿交给这道闸在结果回来时挡住。回归测试见 packages/ui/test/promptEnhance.test.ts 的「回填只在草稿仍等于发起时快照时放行」,含「在途补写」与「在途清空」两例。
| useEffect(() => { | ||
| // composer 用固定 key 跨 session 复用:上一个草稿的还原点与在途请求都属于旧 scope, | ||
| // 留着会让新会话看到「还原」按钮和一个不会结束的「增强中」。 | ||
| restorePointRef.current = null; | ||
| setCanRestore(false); |
There was a problem hiding this comment.
已修复(commit 6d2b8bd)。补了一条只含 cleanup 的 effect:组件卸载时 invalidateRun() 作废当前 run,并按发起时固化的 operationId 发出与「取消」同一条 best-effort 通知(cancelWorkspaceGenerateText),避免请求空跑到超时。
作废之后 runTracker.isCurrent() 不再返回 true,迟到的结果既不会回填草稿,也不会再弹出属于已离开会话的 toast。
| restorePointRef.current = null; | ||
| setCanRestore(false); | ||
| if (!decision.allowed) { |
There was a problem hiding this comment.
已修复(commit 6d2b8bd)。还原点改为读回确认写入落地之后才清:写入抛异常或读回不一致(writeDraftVerified(...) === false)都保留 restorePointRef 并提示 chat.toolbar.promptEnhance.restoreFailed(「还原未写入,草稿保持不变,可再试一次」),不再出现「原文没写回来、还原点也没了、却报成功」。
顺带说明:这条写入路径现在由回填与还原共用(writeDraftVerified),原先 restore 里内联的那份重复 try/catch 已收敛掉。还原闸(evaluatePromptEnhanceRestore)仍在写入前先判「草稿是否仍等于增强结果」,被拒时清点是刻意的——那说明当前草稿已不是增强结果,这个还原点不可能再用上。
| - 提升表达的清晰度和可理解性 | ||
| - 确保优化后的提示词能够产生更好的AI回应 | ||
|
|
||
| ## Constrains |
There was a problem hiding this comment.
已修复(commit 6d2b8bd)。packages/ui/src/v4/composer/promptEnhance/prompts.ts 的 basic 模板标题改为 Constraints,docs/prompt-enhance.md 同一处同步(spec 要求文档模板与代码逐字一致,仓库暂时没有自动化对齐测试,靠人工对齐)。
新登记的 3 个 core 测试文件(attachments、prompt-attachment-parity、 workspace-generate-text-accumulate)经 hydrator 间接 import create-workflow-graph-bounds.ts,后者从 @kcode/dynamic-workflow/projections 取值,而该子路径在 package.json 的 exports 里直接指向 dist/projections.js。 CI 是全新检出,根目录 typecheck 只做 --noEmit、不产出 dist,Focused tests 步骤原先只构建了 @kcode/contracts,于是这 3 个文件整个以 ERR_MODULE_NOT_FOUND 失败(本地因 dist 齐全而看不到,上游 main 的 CI 因为 清单里没有 core 测试也一直是绿的)。在同一个 step 补一条 pnpm --filter @kcode/dynamic-workflow build。 验证:模拟全新检出(移走除 contracts 外全部 20 个 dist)后按新顺序构建, Focused tests 76/76、release-signing-plan 4/4,无模块缺失;typecheck 退出 0、lint 0 error(63 条既有 warning)、architecture:check --changed 0 违规。 两份 spec 中「新测试文件必须登记进 ci.yml」的条款同步补上构建产物前提。
回应 PR 7836246#5 上 Copilot 的 4 条 review 意见: - HIGH(usePromptEnhance:373):编辑器在增强进行中不受 running 约束 (runningStartedAt 只作用于工具条按钮),原先只挡 scope 变化,用户在 等待期间补写的内容会被迟到的结果整条覆盖,而随后立下的还原点只含发起 时的原文,那些新输入再无找回路径。新增回填闸 evaluatePromptEnhanceFill:以发起时的草稿快照为基准比对(归一化口径 与其余比对一致),不一致即丢弃结果、不立还原点、不覆盖并提示。 - MEDIUM(usePromptEnhance:251):补一条只含 cleanup 的 effect,组件 卸载时作废 run 并按发起时固化的 operationId 发出与取消同一条 best-effort 通知,避免迟到结果回填已离开会话的草稿、Host 请求空跑到 超时。 - MEDIUM(usePromptEnhance:435):还原点改到「读回确认写入已落地」之后 才清,写异常与读回不一致都保留还原点并提示 restoreFailed,不再出现 「原文没写回来、还原点也没了、却报成功」。还原与回填因此共用 writeDraftVerified 这一条写入路径,restore 里内联的那份重复 try/catch 一并收敛。 - LOW(prompts.ts:54):basic 模板与 spec 同处把 Constrains 改为 Constraints(文档模板与代码要求逐字一致)。 i18n 新增 draftChanged / restoreFailed 两条,zh-CN 与 en-US 同键。 验证:tsx --test promptEnhance 23/23(含新增回填闸回归测试); pnpm typecheck 退出 0;pnpm lint 0 error(63 条既有 warning,与基线 一致);architecture:check --changed 0 违规;模拟全新检出(移走除 contracts 外全部 21 个 dist,按 ci.yml 顺序构建)Focused tests 77/77、 release-signing-plan 4/4。 docs/prompt-enhance.md 同步:用户故事 7 扩到「在途改动一律不被覆盖」、 流程图补回填闸与卸载取消、新增回填闸/卸载/还原清点顺序三段说明、日志 与测试决策清单更新、手动验证补两条场景(这两条仍需 pnpm dev:desktop 在真实模型调用下过一遍)。
887fe9d(让 TurnSteerDrained 自带截断事实)留下两处类型错误: - product-projection.ts 的 steerDrainedRowAttachments 声明返回 { attachments: ... } 必填,但「既无 intent.attachmentRefs 也无自带元信息」 那一支 return {},类型与实现自相矛盾(TS2741)。改为 attachments 可选: 两个非空分支都必有该字段,只有空对象分支不带,而调用方是 spread,字段 缺席即「无附件」。 - 同提交新增的测试 helper 手写了一份窄化的附件类型,漏掉 truncated / totalLines,于是两条「guide drain 自带截断事实」用例本身编译不过 (TS2353 ×2)。改为直接用契约里的 TurnAttachmentMeta:以后契约加字段会在 编译期暴露,而不是把用例悄悄变成不可编译。 影响不止测试:pnpm --filter @kcode/bootstrap build(tsc)失败会让 scripts/build-desktop-agent-cli.mjs 失败,pnpm dev:desktop 在 Electron 起来 之前就退出。CI 抓不到这类错误——根目录 typecheck 不覆盖 apps/kcode-cli/packages/*,lint 是 oxlint(不做类型检查),focused tests 走 tsx(类型被剥掉),所以带这个错误的提交可以「CI 全绿地合入」。 验证:bootstrap tsc --noEmit 退出 0;turn-attachments-resolved-projection 8/8;node scripts/build-desktop-agent-cli.mjs 退出 0;pnpm dev:desktop 正常 起 Electron 并渲染 chat 区。
只装了 Command Line Tools 的 macOS 机器上,toolchain 自带的 Swift 兼容库 (libswiftCompatibility*.a)只有 arm64 切片,-target x86_64-apple-macos11 的链接必然失败(ld: fat file missing arch 'x86_64';换 macos12 目标同样 失败,本机要 macos13 才链得上)。原脚本一条 slice 失败就整体放弃,结果是 「什么都没产出」:resources/macos-window-bounds/ 里只剩一个 -arm64 残片, 正式的 kcode-window-bounds 不存在,于是 ensure-local-runtime-assets 每次 dev 都重跑一遍这个必然失败的构建,权限浮窗也因为拿不到 bounds 数据源而 失去吸附。 改为逐片构建、成功几片算几片: - 两片都成功 → lipo 出 universal(原行为不变); - 只剩一片 → 直接复用它作为产物,并明确警告「发布包若需覆盖另一架构,请在 完整 Xcode(含双架构 Swift 兼容库)下重新构建」; - 全失败 → 保留原来的 warn 后 exit 0(吸附属观感增强,不该让构建挂掉)。 中间 slice 一律在 finally 里清掉,不再把残片留在 resources 里。 验证:本机(CLT-only,x86_64 slice 必然失败)跑 packages/desktop/scripts/build-macos-window-bounds.mjs,产出 arm64 单架构 kcode-window-bounds、无中间文件残留、退出码 0;pnpm dev:desktop 的 pre-dev 报 all local runtime assets are ready(此前每次启动都要重跑失败构建)。 未验证:双架构 lipo 成功分支(本机 toolchain 造不出 x86_64 slice)。
|
为了让本 PR 稳定停留在自己的提交上(它的 head 是 fork 的 main,会随 main 后续提交而膨胀),已改用专用分支 |
每个主题一条从上游 main 拉出的专用分支开 PR,不再用 fork 的 main 作 PR head —— 以 main 为 head 的 PR 会随之后续提交持续膨胀(7836246#5 就曾从 19 个 提交涨到 21 个),无法稳定停留在自己的提交上;main 只用于同步上游与集成。 同时记下:PR 的 head 不可改(只能改 base),换 head 只能关闭原 PR、用指向 原 head 提交的新分支重开等价 PR 并互链;从既有分支剥离主题时用 cherry-pick 到上游 main 之上重建分支,保证 PR 的 diff 只含该主题。



概述
本 PR 落地两项各自带自包含 spec 的功能,以及审查后补充的两处修复:
docs/prompt-enhance.md):composer(v4 输入区)工具条新增「增强 / 还原 / 设置」入口,按三档模式(基础 / 编程任务 / 创意)改写草稿并原地回填,绝不自动发送;支持取消、还原点与完整设置分区。docs/prompt-attachment.md):修 issue 能建个群吗? #3 —— 附件不再被伪装成历史 Read 工具结果,改为明话附件框法;截断状态如实说明,并在消息附件 chip 上标出「已截断」。提示词增强
packages/ui/src/v4/composer/promptEnhance/8 个纯模块(模板 / 拼装 / 闸门 / run 跟踪 / 取消句柄 / 请求参数 / 设置补齐 / 通道解析),全部不依赖 React,对 shared 仅 type-only 依赖,可直接tsx --test。generateWorkspaceText链路,querySource=prompt_enhance;取消走operationId(AbortSignal 过不了 RPC JSON 序列化);请求自带输出预算(取模型声明上限)与档位回落契约。Invalid JSON response失败;Git 提交消息同入口一并受益。$PATH//tmp等不再被误拦;allowInlineReferenceRewrite可显式放开放行。PromptEnhanceSection注册进basics分组,i18n 中英同步,旧chat.promptEnhance.*命名空间删除。附件表达与截断可见
truncated:false的 range view;展示元信息与recoverability同源,不再一处判截断、另一处报provider_ready。turn_attachments_resolved(additive、仅展示)把截断事实下发到 live 投影;冷恢复从part.metadata.preview派生同一形状;chip 标记与 tooltip 中英同步。TurnSteerDrained.drainedInputs[].attachments自带——guide 没有自己那一轮,主路径事件不会为它发射,按 turnId 补发还会错打到同轮的原输入行。审查后修复(本 PR 末尾两个提交)
887fe9d fix: 引导输入附件的截断标记改由 TurnSteerDrained 自带dafe9e9 chore: 增强模块改 type-only 导入并规范 scope 变化日志验证
pnpm typecheck通过;pnpm lint0 error(63 个 warning 均为存量文件既有告警);pnpm architecture:check --changed0 违规。.github/workflows/ci.yml的 Focused tests,本地tsx --test合计 52/52 通过。docs/prompt-enhance.md逐字比对一致(脚本与测试双确认)。待人工确认
pnpm dev:desktop)尚未执行:三档各跑一次增强、进行中取消、还原与拒还原、设置持久化、自动通道跟随模型切换;附件 chip 的 live 截断标记(含 busy 中引导发送)及刷新后一致性。说明
157079d(新增.kcodeignore与增强实现规范),随分支基线一并进入本 PR。