Feat/3.0.0 beta1 - #2268
Merged
Merged
Conversation
…-md/lg/...) Replace arbitrary rounded-[Npx] with the Tailwind-aligned radius ladder across ~74 pages/components (6px→rounded-md, 8px→rounded-lg, etc.); extend design-token.cjs RADIUS (sm..4xl+full) and add the 4xl (32px) radius token in tailwind.config so the whole ladder has a class. Button medium/large radii moved onto the ladder too. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…and tints) Split --btn-gray-solid-bg from --btn-gray-text and add --btn-disabled-bg/-text (light) so roles can flip independently; add the full .dark btn ramp (disabled raised inert fill, neutral text flips light + fill ramp lightens, brand tints switch to alpha over the page). Button: disabled now uses the btn-disabled tokens, outlined fills bg-bg-page (theme-following, not white), and primary outlined/filled/text gain dark alpha tints; expose bg-page utility. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Group the 组件 sidebar into 基础/通用/反馈 categories, add a Design Token page + a 设计模式 group; alias bisheng-icons to the local install so spec mdx page bodies can import real icons; sidebar/TOC hover+active marked by text color only (drop the brand-tint background pill). Title cased to BISHENG. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
One table style across the whole site (native md tables + hand-styled spec mdx tables), matching the design-token page's TokenTable: full-width, rounded outer frame on --radius, light --fill-1 header with medium text, horizontal rules only in --border-base, no zebra, theme-switch in lockstep. Move 多端适配 from 设计规范 into the 设计模式 group. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… font stack Exclude 元-文档撰写规范 (author-only how-to) from routes+search alongside 00-总纲. Consolidate the mono stack into one --bs-font-mono var referenced by --rp-font-family-mono, the .rspress-doc code rule (now !important so the app's legacy code font can't out-specify it), and the spec pages' inline code — change the stack in one place. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Convert src/frontend to a pnpm workspace (catalog-pinned shared deps, only-allow pnpm, npm lockfiles removed, both Dockerfiles on corepack/pnpm) - New packages/ui (@bisheng/ui): source-shipped presentation-only library. First component Button (moved from client, which keeps a re-export shim so all call sites work unchanged), cn util, two-layer design tokens (tokens.css + tailwind-preset incl. dark ramp), design-token.cjs SSOT moved here (client re-exports it) - Component docs move into packages/ui/docs (git-tracked); rspress site stays hosted in client, root/outDir repointed, button demos import @bisheng/ui; doc_build + playground artifacts gitignored - pnpm-migration fixes: pin vite-plugin-node-polyfills to 0.23.0 (0.23.1 unenv rewrite breaks CJS named-export detection), rollup/vite overrides matching the old npm resolutions, fs/promises + node:fs/promises stubs - Harness rules: new packages/ui/AGENTS.md (library contract, token SSOT discipline, interaction rules, component definition-of-done); client AGENTS.md gains design-system hard rules; commands/docs updated to pnpm
…y violations Dify-style "freeze legacy, block new" quality gates for both frontend apps: - ESLint 9 flat configs for platform, client and @bisheng/ui (shared toolchain installed at workspace root). House rules: no-explicit-any, no raw axios imports (C7), no-console (warn/error allowed), react-hooks rules as errors, ts-expect-error with description only. - Legacy violations frozen per app in eslint-suppressions.json (may only shrink; run lint:prune after cleaning a file). - TS strict via typescript-strict-plugin: legacy failing files annotated with @ts-strict-ignore (platform 388, client 163); all other and all new files fully strict-checked; tsc-strict wired as typecheck script. - platform tsconfig target es5 -> ES2020. - New CI workflow frontend-quality.yml: PR-triggered, path-filtered, lint + typecheck for both apps. - Gate policy documented in root AGENTS.md; @bisheng/ui starts at zero suppressions (fixed 1 dead var, removed 6 stale disable directives).
- add unist-util-visit as an explicit dependency (imported by MessageMarkDown.tsx but only resolvable via legacy hoisting; broke tsc-strict in CI's frozen-lockfile install) - FilterByUser.tsx: import debounce from lodash-es (declared) instead of lodash (undeclared, unresolvable in a clean install — would break vite build)
Same class of issue as the platform fix: modules imported directly but never declared, resolvable locally only via legacy node_modules leftovers, broken under CI's frozen-lockfile install. - add explicit deps: unist-util-visit, unified, dedent, @tanstack/react-virtual, @rc-component/mini-decimal, @radix-ui/react-slot (via workspace catalog) - useMessages.ts: import useParams from react-router-dom (declared) instead of react-router (undeclared transitive)
## What 简要描述做了什么改动。 ## Why 为什么需要这个改动? ## How 实现方式、设计决策(如有)。 ## Test - [ ] 本地测试通过 - [ ] 114 测试服务器验证通过 ## Related - Issue/ticket:
- platform: esbuild drop console/debugger on build (aligns with client's terser drop_console) - client: remove legacy PropTypes runtime validation in SpeechToText (undeclared phantom dep, broke clean-install build)
…dger #5-#8) - client: recoil (archived by Meta), react-beautiful-dnd, react-virtualized - platform: react-query v3, xlsx, xlsx-populate, react-color, react-beautiful-dnd - existing violations frozen into eslint-suppressions.json (shrink-only) - AGENTS.md: client state-management rule rewritten (Recoil frozen), platform frozen-deps list added
… copy (ledger #29) - locales (en/zh-Hans/ja): de-brand 4 keys (attribution, code interpreter, admin warning, API key dialog) - mock startup config: BISHENG appTitle; drop LibreChat ToS modal, privacy/tos/help librechat.ai links, branded welcome - AuthLayout: logo alt fallback Deepseek -> BISHENG
- no-restricted-syntax flags Chinese in string/template/JSX literals in all three packages - existing violations frozen into eslint-suppressions.json (shrink-only) - @bisheng/ui: zero violations in component source; .cjs tooling exempt (token metadata is internal docs)
…ckage (ledger #30 pilot) - packages/locales: single source for api_errors domain (371 codes x zh-Hans/en/ja, merged from both apps, client wording wins on conflict) + build.mjs compiler (build / --check / --watch) - platform: new lazy api_errors i18next namespace; all call sites switch errors.<code> -> api_errors:<code>; errors block removed from bs.json - client: api_errors block moved out of translation.json into generated api_errors.gen.json merged at i18n init; call sites unchanged - regeneration wired into pre-scripts (enable-pre-post-scripts); CI verifies artifacts stay in sync; increment rules added to AGENTS.md
…erage (ledger #28/#11) - scripts/check-i18n.mjs: every locale file must expose identical key sets across zh-Hans/en/ja; shared packages/locales held to zero drift - reconciles backend errcode registry against api_errors copy: new backend codes without frontend copy fail CI (23 legacy gaps frozen) - legacy drift frozen in scripts/i18n-baseline.json, shrink-only (--update-baseline after healing); wired into frontend-quality.yml
…n (ledger #11) All three languages, calm actionable tone; codes were falling back to raw backend English Msg. Baseline shrunk to zero missing-copy codes.
…port) - full-key literal matching per app; dynamic-prefix keys excluded entirely (never auto-judged, per review decision); nested $t() refs inside locale values counted as live - classifies unreferenced keys into safe / needs-review; --delete mode removes safe keys of a scope+prefix from all languages - first scan: 529 safe-dead, 251 needs-review, 1410 dynamic untouched
…dger #28) - scanner-verified safe deletions only (full-key + leaf-segment + nested $t() reference checks); dynamic-prefix and needs-review keys untouched - includes cascade pass: 20 keys whose only reference was $t() nesting inside deleted keys - scanner fix: flat keys containing literal dots now delete correctly - both app production builds + typecheck + check-i18n green; baseline shrunk
New 基础-阴影与圆角规范.mdx: eight-step radius usage rules (modal fixed at 16px) + two shadow tiers (popup/modal, values taken from the already-shipped DropdownMenu/Select and Dialog/Confirm baselines), pending designer sign-off before tokenising. Radius/shadow sections move out of 01-设计规范.md into this doc; 多端适配原则 gains a pointer for full-screen/bottom-sheet container corners; charter progress board gains its row; docs-site sidebar entry. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The spec docs moved from the gitignored root docs-ui-refactor/ into the component-library package (git-tracked, evolves with the branch). Update the charter's structure/commit-scope notes, site index (pnpm dev:ui), components index (@bisheng/ui vs client imports), the normalize/detect scripts' headers and README paths, and the rspress config comments. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…conform docs 元-文档撰写规范 gains §0 (who each doc is for: designers/PMs, collaborating devs and their agents, the designer's own agent — hence a reader-facing part and an agent-facing hidden zone; technical detail, ledgers and decision history belong in the hidden zone). Conform the affected docs: 文案规范 drops the half-width-digits rule and renumbers, 滚动条规范 switches to Arabic section numbers and marks its leftover-exceptions/acceptance sections site-hide. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Auto table layout sized comparison columns by content, so the longer example took the wider column and the two peers read lopsided. Force table-layout:fixed for two-column tables whose body holds plain prose only — label+spec tables (which put code/strong in a long right column) still need content-based sizing and are excluded. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ewers (ledger #16) - new strict-TS zero-suppression package; xlsx/xlsx-populate consolidated here; based on the newer client variant (dead screen-size code + debug logs removed) - copy moves to packages/locales new `shared` domain (15 keys x 3 languages), addressed uniformly as shared:knowledge.excelPreview.* on both apps; fixes client's broken t() stub (labels rendered empty) and platform's missing excelParseFailed translation - both call sites switched; old duplicates deleted; suppressions pruned - main/types added to workspace packages (client tsc uses node10 resolution and ignores exports; @bisheng/ui was silently unresolved) - platform knowledge preview layout: bounded-height chain fixed on upload step 3 (flex fill instead of hardcoded 100vh offsets), padding aligned between preview and paragraph columns, fixed footer clearance
- new api_errors `fallback` key (3 languages); naked i18n keys can no longer
reach users when a code has no translation (gateway codes, pre-CI legacy)
- client (6 exits): SSE/WS/chat-input/route/request-layer fall back to generic
Chinese; t(key, vars) kept so {{xxx}} templates still interpolate
- platform (5 exits): fall back status_message -> generic copy (admin keeps
backend diagnostics); chatErrorMessage's literal "error" fallback removed
Only the primary landing path (login + main layout + chat home) stays in the entry chunk; 13 pages (knowledge, subscription, linsight, apps, share, standalone chat, previews, ...) now lazy-load on first navigation with a route-level Suspense fallback. Entry main.js: 1.6MB -> 959KB.
…oking link Two pieces of the same cover-up, on the rendering side. resolveDeliverableLink used to map ANY unmatched deliverable name onto the sole artifact when that artifact was named 报告.md — the backend's generic fallback name. So a link to 视频内容摘要.md opened a file with different contents under a different name, and the user had no way to know the claimed file was never written. Removed. The neighbouring case-insensitive retry stays: that resolves to a file which does exist, under a name whose casing the model got wrong, and is a fix for matchArtifactByRelPath comparing exactly. ArtifactLinkAnchor's unresolved branch rendered text-blue-600 — visually identical to a working link, silently inert on click. That is the worst of both: the user cannot tell a real deliverable from a phantom, and a link that does nothing reads as a broken app rather than as a model that overstated itself. Now muted with an explicit 「未生成」 badge and a tooltip, matching the treatment an unresolvable image ref already gets a few hundred lines down in this same file. Still deliberately not a plain <a>: the relative output/ path is not a route, and falling through spawns a new tab that cold-boots the SPA into a 404 — which tells the user nothing and blames the wrong component. console.warn sits in an effect, not in render: the component is memo'd but still re-renders with its parent, and a support session needs to distinguish "the model claimed a file it never wrote" from "our resolver failed to match a real one". Note the honest end state can look self-contradictory — a result panel holding 报告.md while the answer beside it shows 详细分析报告.md(未生成). That is not the platform contradicting itself; it is the platform accurately reporting that the model did. Please don't 'fix' it by restoring the mapping. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…rite Removes the task-mode fabrication introduced by 399e527 ("完成任务模式修改"). Hand-written rather than a git revert: that commit is fused with the audio/video upload feature, workbench_impl, chat_service and 13 frontend files, none of which is in scope here. When the model signed off with 「已保存为 详细分析报告.md」 having never called write_file, the platform created that file. That is not a fix, it is a cover-up: a model claiming a deliverable it never produced is a diagnosable model-behaviour defect, and materialising the claim destroys the evidence — afterwards nobody can tell it happened, how often, or in which sessions. With several claimed names it got worse, chopping one answer along ### headings into N documents nobody asked for. The relevance table that drove that split, _SECTION_HINTS, is the clearest sign this was never a product requirement: its keys are a single customer demo's vocabulary (somna / 索娜 / 宣传片 / 乔布斯 / mp4 / wav). For any other tenant it scored zero on every section, so multi-file splitting silently degraded to "give everyone the whole answer" outside that one demo. Three of its eight entries carried empty keyword tuples and could never score at all. Removed with it: should_synthesize_direct_answer_report and the two character thresholds, which had widened the F035 backstop to fire on any answer over 200 chars (or 80 with uploads present). build_fallback_report_file itself predates this (F035, 595c9f7) and stays — only its gate returns to `if not final_files and planned_tasks:`. Also gone: the claim extractor and its regexes, _strip_deliverables_block, and the section splitter/scorer. The fallback report again carries the answer verbatim, including any 「交付物:…」 list the model wrote. Quietly editing the model's false claim out of the artifact is the same masking in a smaller costume. KEPT ON PURPOSE — the two prompt lines in agent_factory.py (§3 and §风格) forbidding a save claim without write_file. Those attack the root cause instead of the symptom, and are the half of that commit worth having. The claim extractor returns in the next commit as a detector. Matches the 2.6 removal in #2265; the post-removal utils.py converges to the same content on both lines. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Replaces the removed fabrication with what the situation actually calls for: evidence. detect_phantom_deliverables compares the filenames an answer claims against the deliverables the run really produced; each of the three completion paths logs the mismatch and records it on output_result. Nothing is repaired. Two things the previous implementation got wrong, both fixed here because a detector fails differently from a fabricator: - It compared nothing. Fabrication only ran when the file list was empty, so "wrote a.md, claimed a.md AND b.md" — the common shape — was invisible. The detector diffs against the real list, so a partially-honest answer is caught. - It had no zone filter, which as a fabricator was survivable and as a detector is fatal. The extractor treats a bare filename or an explicit output/ path as a claim, and rejects anything else. Measured against the old regexes: an external citation ([规范](https://raw.../README.md)), a quoted-back upload ([briefing.md](uploads/briefing.md)) and a scratch note all registered as claims. uploads/ and scratch/ are excluded from final_files by select_deliverables *by construction*, so every turn that reads a user file would have warned, and the log would have been noise inside a week. A markdown link is judged as a pair — the text can look bare while the target names the zone. Extensions widen from .md to the delivery contract's full set, matching the client's DELIVERABLE_LINK_EXT so a link shown 未生成 in the UI and a phantom in the log can never disagree. Steps 3c/3d make export_docx/export_pdf the closing action, so 「已导出 报告.docx」 is precisely where a run that exhausts its turn budget stops — the .md-only version could not see it. Matching is case-insensitive: a case-only mismatch is a resolver problem, not proof the model lied, and a false accusation is worse than a miss here. output_result is a schema-less JSON column and the client reads only answer / final_files / error_message; "partial": True has shipped alongside since the salvage path. Adding a key is inert. Tests: 12 cases, most of them false-positive guards. Plus the first test build_fallback_report_file's body has ever had — all three call-site tests monkeypatch it, which is how it could be rewritten into a multi-file loop with the suite green. It now pins one file, the fixed name, the answer verbatim, and the ASCII object key that keeps Huawei OBS from 403-ing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit 1642366)
…oking link Two pieces of the same cover-up, on the rendering side. resolveDeliverableLink used to map ANY unmatched deliverable name onto the sole artifact when that artifact was named 报告.md — the backend's generic fallback name. So a link to 视频内容摘要.md opened a file with different contents under a different name, and the user had no way to know the claimed file was never written. Removed. The neighbouring case-insensitive retry stays: that resolves to a file which does exist, under a name whose casing the model got wrong, and is a fix for matchArtifactByRelPath comparing exactly. ArtifactLinkAnchor's unresolved branch rendered text-blue-600 — visually identical to a working link, silently inert on click. That is the worst of both: the user cannot tell a real deliverable from a phantom, and a link that does nothing reads as a broken app rather than as a model that overstated itself. Now muted with an explicit 「未生成」 badge and a tooltip, matching the treatment an unresolvable image ref already gets a few hundred lines down in this same file. Still deliberately not a plain <a>: the relative output/ path is not a route, and falling through spawns a new tab that cold-boots the SPA into a 404 — which tells the user nothing and blames the wrong component. console.warn sits in an effect, not in render: the component is memo'd but still re-renders with its parent, and a support session needs to distinguish "the model claimed a file it never wrote" from "our resolver failed to match a real one". Note the honest end state can look self-contradictory — a result panel holding 报告.md while the answer beside it shows 详细分析报告.md(未生成). That is not the platform contradicting itself; it is the platform accurately reporting that the model did. Please don't 'fix' it by restoring the mapping. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit 24d6ba7)
2.6 上同一改动的 3.0 版本,对应 #2265。**建议先看 #2265**,两边的最终 `utils.py` 与三个前端文件逐字节一致。 ## 撤的是什么 `399e527f0`「完成任务模式修改」(binfeng)里的一段:模型在回答里声称「已保存为 详细分析报告.md」但从没调用过 `write_file` 时,**系统按那个名字真的把文件造出来**;认领了多个名字就把回答按 `###` 切段分装进多个凭空出现的文档。 这不是修复,是掩盖 —— 模型谎报交付物是可诊断的模型行为缺陷,把文件造出来等于销毁证据。 该需求从未被提出:`399e527f0` 的 commit body 为空,`features/` 下无 spec,承载它的 PR #2237 正文是没填的模板。 **最能说明问题的是 `_SECTION_HINTS`** —— 驱动多文件拆分的打分表,键值是一次客户 demo 的词汇(`somna` / `索娜` / `宣传片` / `乔布斯` / `mp4` / `wav`)。对其他任何租户它每一段都是 0 分,所以多文件拆分在那次 demo 之外**从来没有真正生效过**(退化成「每个文件都塞完整回答」)。表里八条中还有三条的关键词是空元组,一分都加不了。 ## 三个 commit **1. `revert(linsight)`** —— 手写(`git revert 399e527` 不可行:它和音视频上传、`workbench_impl`、`chat_service`、13 个前端文件熔在一起)。删掉造假与拆分机制,`build_fallback_report_file` 还原成 F035 的单文件逐字版,触发条件退回 `if not final_files and planned_tasks:`。 **⚠️ 刻意保留**:`agent_factory.py` 里两行反幻觉提示词(禁止未 `write_file` 就声称已保存)—— 那是**治本**的一半。3.0 专有的 ASR 提示词行同样未动。 **2. `feat(linsight)`** —— 换成检测器(cherry-pick 自 2.6)。比对「回答声称的文件名」和「真实产出的 `final_files`」,三个完成路径各记一次:日志 `[linsight-phantom-deliverable]` + `output_result.phantom_deliverables`。**只检测,不修复。** 比原实现多做对两件事:能抓到「写了 a.md 却声称 a.md 和 b.md」(旧版只在文件列表为空时才跑);加了 zone/scheme 过滤 —— 实测旧正则会把外链引用、复述用户上传(`uploads/`)、中间产物(`scratch/`)全当成认领,而后两者被 `select_deliverables` 结构性排除在 `final_files` 之外,等于每个读用户文件的回合都误报。扩展名也从只认 `.md` 放宽到交付契约全集(`export_docx`/`export_pdf` 是收尾动作,「已导出 报告.docx」旧版完全抓不到)。 **3. `fix(client)`** —— cherry-pick 自 2.6。幻影链接不再伪装成正常链接:删掉「任意未匹配的名字 → 映射到唯一的 `报告.md`」,未解析的链接改成灰色 +「(未生成)」+ tooltip。 ## 验证 - 后端 `test/linsight`:**661 passed**。另 5 个 `test_share_session_version_list.py` 失败(`NoTenantContextError`)在**未改动的 `feat/3.0.0-beta1`** 上同样复现,是既有问题。 - 前端 jest **157 passed**;`check-imports` + `build` 通过;ruff 除既有 RUF001(中文全角标点)外无新增。 - **收敛核对**:`utils.py`、`artifactUtils.ts`、`Markdown.tsx`、`artifactUtils.test.ts`、新测试文件与 #2265 分支**逐字节一致**。 ## 行为变化 与 #2265 相同,摘要: 1. **能力回退(刻意)**:「没写 todo + 没产出文件 + 回答很长」的直答不再自动生成 `报告.md`。这是 `399e527f0` 之前的行为。 2. **界面会「自相矛盾」,这是刻意的**:结果区可能同时出现 `报告.md` 和灰色的「详细分析报告.md(未生成)」。这不是平台自相矛盾,是平台如实报告模型自相矛盾 —— 请不要通过把映射加回去来「修」它。 3. **一处历史数据可见变化**:唯一产物是通用 `报告.md`、答案里写别的名字的老会话,现在显示「未生成」而不是打开 `报告.md`。那条链接本来就是错的。 🤖 Generated with [Claude Code](https://claude.com/claude-code)
撤掉 #2263 里 `588b90284` 引入的一段行为,并换成它本该做的事。 ## 撤的是什么 模型在回答里声称「已保存为 详细分析报告.md」但从没调用过 `write_file` 时,**系统会按那个名字真的把文件造出来**;如果它一次认领了多个名字,还会把回答按 `###` 切段分装进多个凭空出现的文档。 这不是修复,是掩盖。模型谎报交付物是**可诊断的模型行为缺陷**,把文件造出来等于销毁证据 —— 事后没人知道它发生过、发生得多频繁、在哪些会话里。 该需求从未被提出:源提交 `399e527f0`(binfeng)的 commit body 为空,`features/` 下无 spec,承载它的 PR #2237 正文是没填的模板。而它那张给多文件打分的关键词表,在上游是从**一次客户 demo** 里抠出来的写死词(somna / 索娜 / 宣传片 / 乔布斯 / mp4),对其他任何租户恒为 0 分。 ## 三个 commit **1. `revert(linsight)`** —— `git revert 588b902`,机械可验证(已核实其后无任何提交动过相关文件)。同时把触发条件退回 F035 原有的 `if not final_files and planned_tasks:`。 **⚠️ 一处刻意的例外**:revert 会连 `agent_factory.py` 里两行反幻觉提示词(禁止未 `write_file` 就声称已保存)一起撤掉,那两行是**治本**的一半,已单独还原 —— 该文件与 base 相比零改动。 **2. `feat(linsight)`** —— 换成检测器。比对「回答声称的文件名」和「真实产出的 `final_files`」,三个完成路径各记一次:worker 日志一条 `[linsight-phantom-deliverable]` warning,外加 `output_result.phantom_deliverables`。**只检测,不修复。** 两处比原实现做得更对,因为检测器和造假器的失败方式不同: - **原来根本没有比对** —— 造假只在文件列表为空时才跑,所以「写了 a.md、却声称 a.md 和 b.md」这种最常见的形态完全看不见。现在能抓到。 - **原来没有 zone 过滤,这对造假器可以忍,对检测器是致命的。** 我拿旧正则实测过:外链引用(`[规范](https://raw.../README.md)`)、复述用户上传(`[briefing.md](uploads/briefing.md)`)、中间产物(`scratch/notes.md`)**全都会被当成认领**。而 `uploads/` 和 `scratch/` 被 `select_deliverables` 结构性排除在 `final_files` 之外 —— 也就是每一个读用户文件的回合都会误报,一周之内日志就废了。现在只有裸文件名或显式 `output/` 路径才算认领,markdown 链接**成对判断、由 target 决定 zone**(链接文字可能长得像裸文件名)。 扩展名从只认 `.md` 放宽到交付契约全集,与前端 `DELIVERABLE_LINK_EXT` 对齐 —— 提示词 3c/3d 让 `export_docx`/`export_pdf` 是收尾动作,「已导出 报告.docx」正是轮次耗尽时最可能的谎报,旧版完全抓不到。 **3. `fix(client)`** —— 幻影链接不再伪装成正常链接。删掉「任意未匹配的名字 → 映射到唯一的 `报告.md`」(它会打开一个名字和内容都不同的文件);未解析的链接从蓝色可点外观改成灰色 + 「(未生成)」小标 + tooltip,与本文件下方处理「图片引用解析不到」的做法一致。 ## 验证 - 后端 `test/linsight`:**659 passed**。另 5 个 `test_share_session_version_list.py` 失败(`NoTenantContextError`)在**未改动的 `hotfix/2.6.0-2`** 上同样复现,是既有问题。 - 前端 jest:**157 passed**;`check-imports` + `build` 通过;ruff 除既有的 RUF001(中文全角标点)外无新增。 - 新增 12 条测试,大半是误报守卫(外链 / `uploads/` / `scratch/` / 无保存动词的裸提及),因为设计的承重点在那里。 - **补了一个从来没有的测试**:`build_fallback_report_file` 的函数体至今零覆盖 —— 三个既有测试全都 monkeypatch 掉它,这正是它当初能被改成多文件循环而测试全绿的原因。现在钉住「恰好一个文件、固定文件名、内容逐字、ASCII object key(防华为 OBS 403)」。 ##⚠️ 发版动作 **`v2.6.0-fix2` 已经包含被撤的行为**(14:23 打的 tag,PR #2263 是 14:01 合的;上一个 `v2.6.0-fix` 不含)。这个 PR 合了之后需要补 `fix3` 或重打。 ## 需要知道的行为变化 **1. 能力回退(刻意)**:回退宽触发后,「没写 todo + 没产出文件 + 回答很长(或传了文件且回答 ≥80 字)」的直答会没有交付物,结果区显示「暂无产物文件」。**这正是 `v2.6.0-fix2` 之前每一个 2.6 版本的行为**,`hotfix/2.6.0` 至今如此 —— 是在恢复已上线数月的行为,不是制造回归。 **2. 界面会出现「自相矛盾」,这是刻意的**:模型认领了 `详细分析报告.md` 时,结果区会同时出现一个 `报告.md` 和一条灰色的「详细分析报告.md(未生成)」。这不是平台自相矛盾,是平台**如实报告模型自相矛盾**。请不要通过把映射加回去来「修」它。 **3. 一处历史数据上的可见变化**:唯一产物是通用 `报告.md`、而答案里写的是别的名字的老会话,现在显示「未生成」而不是打开 `报告.md`。那条链接本来就是错的,属于纠正。已造出来的文件本身不受影响(在 MinIO 和 DB 里,链接照常)。 ## 后续 3.0 线上同一段逻辑以 binfeng 原版形式存活(还带着那张客户 demo 关键词表),会另开 PR 清理。否则下次从 3.0 往下合会把它带回来。 🤖 Generated with [Claude Code](https://claude.com/claude-code)
反馈指针靠近右边缘后目录出来得慢。延迟其实叠了两段:80ms 开启防抖 加 150ms 淡入,约 230ms 才成型。 防抖本来是防「指针横扫路过误开」,但预览面板的右边缘是终点不是走廊 (停靠态右侧就是窗口边,全屏态铺满视口),根本没有横扫场景,这 80ms 纯属白等。改成命中即开;淡入 150→100ms,位移 6→4px。关闭仍留 200ms 懒延时,好让指针有时间从刻度轨走到卡片上。 顺带修一个跟着变快后会更容易撞上的问题:指针停在卡片上但已离开右边缘 判定区时,之前既不开也不关,先前排的关闭定时器照样会触发把卡片收走。 新增 holdOpen 分支,指针在卡片上就一直挂着,不论离刻度轨多远。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit d1f290b367a762f86f603325c13d78e3e6fb4438) (cherry picked from commit 96e3520)
…l on hover Design review on the five-file card: parked in a far-right gutter the glyph ended up roughly 1000px from a short name like "package.json", so the eye had to cross an empty row to work out which file the action belonged to. The gutter bought a stable truncation point, which only pays off for long names — the wrong trade for the common case. The action now follows the name, where the `↗` marker already sat, so a row's affordances stop being split across two ends of the card. Revealed on row hover, because ten identical glyphs stacked in a column read as noise — the repetition is the noise, not the glyph. Three exceptions keep that from degrading into a desktop-mouse-only affordance: - hides with OPACITY, never hidden/invisible, so the button keeps its box and revealing it can never re-truncate the name beside it - focus-visible: tabbing must not land on something invisible - data-[state=open]: the markdown format menu would otherwise fade its own trigger out from under itself when the pointer leaves the row - (hover: none): the gesture does not exist on touch, so the button stays put rather than becoming unreachable The single-deliverable sentence row keeps the always-visible variant: one glyph in prose is not a column, and it is the only download entry such a run has — the exact gap this feature was added to close. Drops the two-step resting grey the gutter needed; by the time a row glyph is visible the row is already hovered, so there is no earlier state to mark. The `!important` that pinned the old hover colour goes with it — every reveal rule now wins on specificity rather than on CSS source order. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit 0f2ed8e8df4703b7547d1c7735cfbaa519b188b6) (cherry picked from commit 58f9a2c)
反馈光标还没走到章节脊就展开了。原来的判定是「距面板右边缘 56px 的 竖带」,两头都太宽:横向比刻度轨自己(离边 8px、宽 26px)多出一截, 纵向更是铺满整个面板高度——刻度轨只在中间约 200px,在它上方去够工具栏、 或在下方靠近滚动条两端,都会误开。 改成直接读刻度轨的 getBoundingClientRect 判定,只留一点容差:横向左侧 6px,纵向上下各 20px(细长列横向好瞄,两端容易过冲)。右侧不设边界, 再往右只有面板边缘和滚动条,没什么要排除的。用真实盒子而不是常量, 刻度长度以后再改也不用同步维护这个数。 净效果:横向触发从离边 56px 收到 40px(刻度轨可见部分从 26px 处开始, 留 14px 提前量),纵向从整屏收到刻度轨±20px。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit f1c4aa9501cf2548d9988f393859fabc8c829435) (cherry picked from commit bce40c6)
The pin was `>=0.6.3` with no upper bound, so a fresh resolve could pull 0.7, where `write_todos` becomes opt-in. Task mode's progress display is fed entirely by that tool, and the turn-budget soft landing added here deliberately keeps it in `_POST_BUDGET_ALLOWED_TOOLS` for the same reason — blocking it makes a finished task render as 3/5. A silent jump to 0.7 would take both out. `_STEPS_PER_MODEL_TURN = 4` is the other reason to cap: it is a count of the middleware nodes deepagents compiles per model turn (model -> tool-loop-breaker -> TodoList -> tools), measured on the 0.6.x graph. A different node count makes the recursion floor wrong. Resolution is unaffected — 0.6.8 already satisfies the narrowed range, and the lock diff is the specifier line alone. This is not the 0.6.12 upgrade (c4a6221 on the 3.0 line); the floor stays at 0.6.3. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit 5041c1c)
PR #2263 合并时(08-07 14:01)我最后几次 push 还没到,这 4 个提交因此漏在了外面。它们是 #2263 里两个功能的后续打磨,已按当前 `hotfix/2.6.0-2` 重新 cherry-pick,零冲突。 ## 四项 | 提交 | 内容 | |---|---| | `d4132f31e` | md 预览「章节脊」目录浮层**即时展开**,去掉开启防抖 —— 鼠标移过去要等一下才出来,手感像卡了 | | `756dd753b` | 目录**触发区收进刻度轨自身**,不再提前开 —— 原来触发区比刻度轨宽,光标还没到就弹出来,挡正文 | | `2d89df219` | 产物文件行的**下载按钮挪到文件名旁**,hover 才显示 —— 原来常驻在行尾,一列按钮把视线从文件名上拽走 | | `1a6dc0d59` | `deepagents` pin 由 `>=0.6.3`(无上界)收成 `>=0.6.3,<0.7` | ## 关于那个版本上界 **不是升级** —— 下界没动,0.6.8 本来就满足新区间,`uv lock` 跑完 295 个包全部重解、锁文件只改了 specifier 一行,没有任何版本漂移。 加上界的原因是 0.7 把 `write_todos` 改成 opt-in:任务模式的进度显示完全靠它,#2263 带进来的轮次预算里 `_POST_BUDGET_ALLOWED_TOOLS` 也刻意放行它(拦掉会让做完的任务显示成 3/5)。另外轮次预算的 `_STEPS_PER_MODEL_TURN = 4` 数的是 0.6.x 图里每轮编译出的中间件节点数,节点数一变递归下限就算错。 ## 验证 - 前端 jest `src/components/Linsight`:**163 passed**(这 4 个提交自带 6 条新用例) - `check-imports` + `build` 通过 - 无后端代码改动(只有依赖 pin),故未重跑后端套件 ## 与已合并的 #2265 无冲突 已核实两边**文件零重叠** —— 本 PR 只碰 `MarkdownOutline` / `markdownOutlineUtils` / `ResultSection` / `SaveAsButton` / `WorkspaceDrawer` / `WorkspacePanel` / `pyproject.toml` / `uv.lock`,#2265 碰的是 `artifactUtils` / `Markdown.tsx` / 三个 locale。 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Both branches had already taken the same revert — the one that stops inventing the file a model merely claimed to write and records the false claim instead — so the linsight side of this merge is largely text that agrees with itself. What is left is narrow. Where each branch fixed the same thing its own way, the merge keeps the version that reads better rather than the side the marker put on top: the stranded-session repair set is declared as a field, not conjured lazily at the use site with getattr. Where 2.6 was written against an API that F048 replaced, the intent was kept and the implementation redone. `aget_writeable_app_ids` exists so an async endpoint can fill `add_extra_field`'s `writeable_ids` without a synchronous permission check underneath it — 2.6 resolved that through `ApplicationPermissionService.get_app_permission_map_async`, which no longer exists, so it now asks `_application_action_map` like every other permission question in that file. Only one line of it conflicted; the rest merged clean and would have referenced two symbols that are gone, with nothing to catch it before it ran. Its 2.6-era unit test went with the API it tested — F048 removed the rest of that file deliberately in edcbe81, and that deletion stands. Two tests needed adjusting for the same reason: one patched the old name of `filter_apps_by_action`, and one stubs the recommended-apps endpoint without knowing it gained a call. Verified: full compile, imports of every touched module, and test/{linsight,workflow,workstation} at 888 passing — the same 17 failures as before the merge, none new.
Backport of 604313a2d to 2.6, plus the backend half that branch was missing. A share recipient (neither owner nor admin) opening a task-mode link got "暂无查看权限" and was thrown to /c/new?error=11403 the moment they previewed or downloaded any produced file. Two independent causes, one on each side: client — POST /linsight/workbench/file_download is the one linsight call whose wrapper never sent the `share-token` header (its siblings in api/linsight.ts always have). With no header the backend falls straight through to the non-owner branch and raises UnAuthorizedError, returned as HTTP 200 with status_code 403, which the global interceptor turns into a whole-document location.href. So one unreachable file reads as "this share link has no permission". backend — file_download accepted only a linsight_session share (meta_data .versionId pinned to this version). A workbench_chat share carries resource_id = the session and no versionId, so even with the header it still 403'd. session-version-list and execute-task-detail already accepted both shapes; this brings the third endpoint behind a share page in line with them. The token is on the route (/share/:token/:vid?), but resolveArtifactUrl is a plain module — it cannot call useParams, and its call sites sit five component layers down (ExecutionFlow / WorkspacePanel / FilePreviewPanel / PreviewBody / SaveAsButton). Drilling a prop through all of them is exactly the shape that let file_download drift out of sync in the first place, so the token is derived once in the API layer instead, with an explicit override for callers outside the share route. - getShareTokenFromPath reads the token off the current location. - getLinsightFileDownloadApi takes it as a header. It also carries Content-Type explicitly: _post spreads `config` AFTER its defaults, so our `headers` key would otherwise drop it. - skip403Redirect stops a single unreachable file from ejecting the viewer; the caller's own catch already renders a scoped failure state. - resolveArtifactUrl defaults to the route token, which covers file preview, inline deliverable images and download in one place. - The /html viewer is a separate tab whose own location is not a share route, so openHtmlArtifactViewer forwards the token as a query param and WebView passes it back in explicitly. Deviations from the 3.0 commit, both because this branch predates a1ac5635b: the tenant-filter widening is left out (bypass_tenant_filter_if does not exist here, and the sibling share endpoints on this branch read unwidened too, so file_download stays consistent with them); and test_share_cross_tenant_read.py, which exercises that widening, is replaced by a focused test that pins only the file_download grant. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A share recipient (neither owner nor admin) opening a task-mode link got "暂无查看权限" and was thrown to /c/new?error=11403 the moment they previewed or downloaded any produced file. The share page itself was fine. Traced on a live env: share-link, chat/info, session-version-list and execute-task-detail all answered 200; the 403 came 21s later from POST /linsight/workbench/file_download, the one linsight call whose client wrapper never sent the `share-token` header (its siblings in api/linsight.ts always have). With no header the backend falls straight through to the non-owner branch and raises UnAuthorizedError — returned as HTTP 200 with status_code 403, which the global interceptor turns into a whole-document location.href to /c/new?error=11403. So one unreachable file reads as "this share link has no permission". The backend already accepted both share shapes and needed no change. The token is on the route (/share/:token/:vid?), but resolveArtifactUrl is a plain module — it cannot call useParams, and its call sites sit five component layers down (ExecutionFlow / WorkspacePanel / FilePreviewPanel / PreviewBody / SaveAsButton). Drilling a prop through all of them is exactly the shape that let file_download drift out of sync in the first place, so the token is derived once in the API layer instead, with an explicit override for callers that run outside the share route. - getShareTokenFromPath reads the token off the current location. - getLinsightFileDownloadApi takes it as a header. It also carries Content-Type explicitly: _post spreads `config` AFTER its defaults, so our `headers` key would otherwise drop it. - skip403Redirect stops a single unreachable file from ejecting the viewer; the caller's own catch already renders a scoped failure state. - resolveArtifactUrl defaults to the route token, which covers file preview, inline deliverable images and download in one place. - The /html viewer is a separate tab whose own location is not a share route, so openHtmlArtifactViewer forwards the token as a query param and WebView passes it back in explicitly. Verified against a live env with a real non-admin, non-owner account and the reported share link: without the header both the .md and .html deliverables answered 403, with it both answered 200 and the presigned links fetched their bytes. Unit tests cover the token parsing, the header/skip403Redirect plumbing and the opener→viewer round trip; a backend test pins the workbench_chat grant in file_download so the branch the client now depends on cannot quietly go away. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
srcDoc + sandbox="allow-scripts" 缺 allow-same-origin,文档 origin 为 null, 读 localStorage 抛 SecurityError。生成的 HTML(演示文稿这类)通常在主 script 块顶层读 storage 记住页码/主题/备注偏好,这个异常让整块脚本提前中止,之后 注册的 keydown/wheel/touch 一个都没绑上——页面渲染正常却翻不了页,而下载到 本地打开反而好用,因为 file:// 有真 origin。 改成注入内存版 storage 垫片让产物脚本跑完,而不是放开 allow-same-origin: srcdoc 文档会继承宿主 origin,给了它就等于把每份模型生成的产物变成 XSS 载体。 垫片插在 doctype 之后(插到前面会触发 quirks mode 毁排版),代价是偏好不再 跨刷新持久。同时补上 popups/modals/downloads/forms/fullscreen 这些不放松源 隔离的能力位,并在 onLoad 时聚焦 iframe——否则键盘事件永远留在父文档,用户 必须先点一下画面。 顺带修正 HtmlViewer 反了的沙箱组合:原先是 allow-same-origin 而没有 allow-scripts,脚本一行不跑,却把本站 origin 继承给了预览内容。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
srcDoc + sandbox="allow-scripts" 缺 allow-same-origin,文档 origin 为 null, 读 localStorage 抛 SecurityError。生成的 HTML(演示文稿这类)通常在主 script 块顶层读 storage 记住页码/主题/备注偏好,这个异常让整块脚本提前中止,之后 注册的 keydown/wheel/touch 一个都没绑上——页面渲染正常却翻不了页,而下载到 本地打开反而好用,因为 file:// 有真 origin。 改成注入内存版 storage 垫片让产物脚本跑完,而不是放开 allow-same-origin: srcdoc 文档会继承宿主 origin,给了它就等于把每份模型生成的产物变成 XSS 载体。 垫片插在 doctype 之后(插到前面会触发 quirks mode 毁排版),代价是偏好不再 跨刷新持久。同时补上 popups/modals/downloads/forms/fullscreen 这些不放松源 隔离的能力位,并在 onLoad 时聚焦 iframe——否则键盘事件永远留在父文档,用户 必须先点一下画面。 顺带修正 HtmlViewer 反了的沙箱组合:原先是 allow-same-origin 而没有 allow-scripts,脚本一行不跑,却把本站 origin 继承给了预览内容。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
外部技能(Claude/Codex)惯用首字母大写 name,导入时被 11051 一票否决且
前端只显示误导性的「压缩包须含 SKILL.md」。两处修复:
- 导入路径(上传/GitHub/整包更新)对非法 name 先走 slugify_pinyin 规范化,
改写包内 SKILL.md(原名转入 metadata.display-name,其余 frontmatter 键
原样保留),SkillDetail 新增 normalized_from 供前端提示;表单路径保持严格
- 前端 11051 不再吞后端 status_message,按「校验失败:{具体原因}」展示;
导入成功且发生规范化时弹「技能 ID 已自动规范化为 xxx」
2.6 适配:保留本线 PermissionService 形态(不引入 3.0 的 F048 owner
projection),test_skill_api fixture 维持 patch PermissionService。
(cherry picked from commit 4114d6a97)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
反馈指针靠近右边缘后目录出来得慢。延迟其实叠了两段:80ms 开启防抖 加 150ms 淡入,约 230ms 才成型。 防抖本来是防「指针横扫路过误开」,但预览面板的右边缘是终点不是走廊 (停靠态右侧就是窗口边,全屏态铺满视口),根本没有横扫场景,这 80ms 纯属白等。改成命中即开;淡入 150→100ms,位移 6→4px。关闭仍留 200ms 懒延时,好让指针有时间从刻度轨走到卡片上。 顺带修一个跟着变快后会更容易撞上的问题:指针停在卡片上但已离开右边缘 判定区时,之前既不开也不关,先前排的关闭定时器照样会触发把卡片收走。 新增 holdOpen 分支,指针在卡片上就一直挂着,不论离刻度轨多远。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…l on hover Design review on the five-file card: parked in a far-right gutter the glyph ended up roughly 1000px from a short name like "package.json", so the eye had to cross an empty row to work out which file the action belonged to. The gutter bought a stable truncation point, which only pays off for long names — the wrong trade for the common case. The action now follows the name, where the `↗` marker already sat, so a row's affordances stop being split across two ends of the card. Revealed on row hover, because ten identical glyphs stacked in a column read as noise — the repetition is the noise, not the glyph. Three exceptions keep that from degrading into a desktop-mouse-only affordance: - hides with OPACITY, never hidden/invisible, so the button keeps its box and revealing it can never re-truncate the name beside it - focus-visible: tabbing must not land on something invisible - data-[state=open]: the markdown format menu would otherwise fade its own trigger out from under itself when the pointer leaves the row - (hover: none): the gesture does not exist on touch, so the button stays put rather than becoming unreachable The single-deliverable sentence row keeps the always-visible variant: one glyph in prose is not a column, and it is the only download entry such a run has — the exact gap this feature was added to close. Drops the two-step resting grey the gutter needed; by the time a row glyph is visible the row is already hovered, so there is no earlier state to mark. The `!important` that pinned the old hover colour goes with it — every reveal rule now wins on specificity rather than on CSS source order. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
反馈光标还没走到章节脊就展开了。原来的判定是「距面板右边缘 56px 的 竖带」,两头都太宽:横向比刻度轨自己(离边 8px、宽 26px)多出一截, 纵向更是铺满整个面板高度——刻度轨只在中间约 200px,在它上方去够工具栏、 或在下方靠近滚动条两端,都会误开。 改成直接读刻度轨的 getBoundingClientRect 判定,只留一点容差:横向左侧 6px,纵向上下各 20px(细长列横向好瞄,两端容易过冲)。右侧不设边界, 再往右只有面板边缘和滚动条,没什么要排除的。用真实盒子而不是常量, 刻度长度以后再改也不用同步维护这个数。 净效果:横向触发从离边 56px 收到 40px(刻度轨可见部分从 26px 处开始, 留 14px 提前量),纵向从整屏收到刻度轨±20px。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
外部技能(Claude/Codex)惯用首字母大写 name,导入时被 11051 一票否决且
前端只显示误导性的「压缩包须含 SKILL.md」。两处修复:
- 导入路径(上传/GitHub/整包更新)对非法 name 先走 slugify_pinyin 规范化,
改写包内 SKILL.md(原名转入 metadata.display-name,其余 frontmatter 键
原样保留),SkillDetail 新增 normalized_from 供前端提示;表单路径保持严格
- 前端 11051 不再吞后端 status_message,按「校验失败:{具体原因}」展示;
导入成功且发生规范化时弹「技能 ID 已自动规范化为 xxx」
(本分支 test_skill_api 的 owner-projection fixture 已存在,保留原状)
(cherry picked from commit 4114d6a97)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Brings the 2.6 line onto 3.0: pnpm workspace + packages/ui + packages/locales, route-level code splitting, the i18n namespace split (api_errors.gen.json / shared.gen.json), and the lint/typecheck quality gates that 3.0 never had. Conflict calls worth recording: - Permission dialogs and PermissionListTab: took 3.0. F048 replaced the hand-rolled dialogs with the unified <PermissionDialog> (-240 lines each); 2.6's side was a 3-line cosmetic change on the pre-F048 version. - appChat/index.tsx: took 3.0 — 2.6's checkPermission is checkResourceAction on this line. - MessageFile / MessageBsChoose: took 3.0 — AppChatFileChip already branches on images, and 2.6's ChatFile no longer exists here. - routes: took 2.6's lazy() architecture and pulled 3.0's MediaPlaybackPage into it, since a lazy route without Suspense would have thrown. - knowledgeUploadCapabilities: 3.0's shape (media is a runtime flag now) with 2.6's webLink rollout. - vite.config: took 3.0 — 2.6's side had a hardcoded 192.168.106.105 left over from WeCom debugging. - translation.json ×3: three-way key merge. 2.6's ~100 "deletions" are the namespace split, not drops; no merged source still references them. The one key that would have been lost, api_errors.10027, was re-added to the packages/locales SSOT and regenerated. Quality gates, now that they apply to 3.0 code for the first time: real defects fixed (missing ServiceBusyNotice import, isParsingMedia read off a union that lacks it, an IntentNode with no step, a stale getItemPermissions argument, a dead ChatErrorCard import); style-level legacy violations frozen the way the project already does it. @types/testing-library__jest-dom@5 added because jest-dom 5.17 ships no types of its own. Backend: zero regression against the pre-merge baseline (157 failed both sides, 3708 -> 3753 passed). One test needed adapting: 2.6's _RecordingSession recorded a single statement, 3.0's records a list.
8f58400 landed this branch's content but committed with a single parent: the fast-forward pull of feat/3.0.0-beta1 right before it cleared MERGE_HEAD. Git therefore still saw hotfix/2.6.0-2 as unmerged and recomputed the merge base back to 07-30, turning every later merge into a wall of phantom conflicts. This records the ancestry only — -s ours keeps the tree byte-identical, since the content is already here (spot-checked across llm/workflow/auth).
The three commits pushed after the earlier merge: skill-import frontmatter name normalization, letting a share recipient open the task's deliverables, and HTML deliverables being completely inert inside the sandboxed iframe. With the ancestry repaired the merge base is d922c34, so this is a 4-conflict merge instead of the 23 phantom ones it would otherwise have been: - skill_service.py: both sides added a typing import; SkillMeta (NamedTuple) and the owner-projection port (Protocol) are both live here, so both stay. - deepagents pin (pyproject + uv.lock): kept 3.0's >=0.6.12, which is the stricter floor; both sides already cap below 0.7. - One comment reworded to match what this change actually does — open, not just download. The two test files this brings in (shareToken, resolveArtifactUrl) never faced a lint gate on their own branch — hotfix/2.6.0-2 has no suppressions file. Their `(global as any).__APP_ENV__` boilerplate is frozen rather than rewritten, so the files stay diffable against 2.6. Backend zero regression against the same baseline (157 failed, 3753 -> 3765 passed). lint + typecheck green.
Six commits pushed to 3.0 in parallel with this merge — the same skill-import, share-token and sandboxed-iframe fixes that also came in via hotfix/2.6.0-2, landed here as separate commits. The three-way merge recognised them: the files both lines added (shareToken.ts, sandboxedHtml.ts) are byte-identical, so nothing was introduced twice. One conflict, in test_skill_service.py, was pure formatting — the same frontmatter bytes written as a single line here and as an adjacent-literal block there. Took the wrapped form, matching the rest of 3.0.
Merging feat/2.6.0 I resolved client/vite.config.ts by taking 3.0 wholesale — right for the dev-proxy targets (2.6's side had a hardcoded 192.168.106.105 left from WeCom debugging), wrong for everything else on that side, which was infrastructure the pnpm layout depends on. Three things went missing: - `node:fs/promises` / `fs/promises` aliases to stubs/empty-module.ts. The stub file itself came across with the workspace commit, so the repo carried a stub nothing pointed at. vite-plugin-node-polyfills maps bare `fs` to its empty mock and then mangles the subpath into <mock>/empty.js/promises, which is what @dicebear/core's node-only toFile() branch resolves to. Dev server would not start. - build.commonjsOptions.transformMixedEsModules. Deps live in ../node_modules, outside this vite root, which defeats the plugin's node_modules exemption: its injected ESM imports land inside CJS deps and break rollup's named-export detection. - __APP_VERSION__ and the buildVersion() that feeds it. RouteErrorBoundary reads it for the crash report and falls back to 'unknown', so this failed quietly — the crash screen just stopped naming its build. Verified: dev server serves the dicebear path clean, and both apps build.
Auditing what the feat/2.6.0 merge dropped by taking one side wholesale: this file was the one real loss. 3.0's F048 rewrite cut it from 78 tests to 4 — correct, the rest pinned the retired permission-id runtime — but 2.6 had added two tests to the pre-F048 version that have nothing to do with permissions: the 10-layer folder cap on add_folder. The rule itself survived the merge (3.0 even extended it to folder moves), and batch upload keeps its own depth tests in test_knowledge_space_folder_upload. The single-folder path was the gap: add_folder carries its own check and had no test. Ported to the post-F048 shape — module-level, _require_action instead of _require_permission_id. Verified by mutation: relaxing the check to `> MAX_FOLDER_LEVEL + 1` turns the depth test red, so it pins the rule rather than just executing it.
…aily mode An image attached to a workflow-chat message rendered as a file card — a 40px thumbnail beside its filename — while the same image in daily mode rendered as a 100px picture square. Both already opened full screen on click; only the thumbnail differed, and the card wasted the one thing a picture is for. The switch lives inside AppChatFileChip, so all three message components pick it up. URL resolution and the preview dialog are untouched. Pictures and cards now get their own rows (AppChatFileList): a 100px square next to a ~56px card leaves a ragged line when they share one wrap container. Daily mode splits them the same way. isAppChatImageFile is exported so the layout groups by exactly what the chip will draw, rather than re-deriving it.
Follow-up to ab74c5c, which normalized 2.6 but never saw the files 3.0 added. Same rule as that pass: replace only where the arbitrary value already equals a ladder step, so nothing moves visually — 4px→sm, 6px→md, 12px→xl, 24px→3xl, including the side-scoped forms. 31 arbitrary radii stay: 20px (12), 10px (8), 2px (6), 5px (5) have no equal step, and rounding them to the nearest one is a design call, not a refactor.
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.
No description provided.