WWW.YOUINFO.SITE
标签聚合 IMPORTANT

/tag/IMPORTANT

linux.do · 2026-04-26 17:21:29+08:00 · tech

Haku – 26 Apr 26 排版的艺术:一条很长很长的路 | Haku 排版是门技术活,这不用多说。 [!IMPORTANT] 观前提示 我在我的博客里设计了一些排版,在 L 站展示不出来。 本文中为了展示大量的、或好或坏的排版,插入了很多图片。尽管现在的浏览器会聪明地使用懒加载,但我仍然推荐你在 Wi-Fi 环境下阅读,毕竟移动数据是越来越贵了。 还有,如果一边照着本文一边翻 Mozilla 的 CSS 文档,阅读体验会好一些。这儿有 中文版 。 [!NOTE] 号外! 开了个新坑,就叫做 漫漫长路 ,这里写杂记和思考。 小小的 leading 我的博客经历了若干次重构,从一开始用 Hexo 的 icarus 、 amazing 主题,再到后来看了不少 Hexo 的坏话 [1] ,渐渐地坚定了迁移的决心。然后换到了 Astro,就是那个黑白配色,以排版为主要特色的博客主题(不然它也不会胸有成竹地管自己叫做 Retypeset )。最后,也就是你看到的这个,是用 Hugo [2] 一点一点打出来的,设计 CSS 用上了我关于 排版 的一生所学。 重构,排版 。设计博客样式时,这两兄弟大概是最离不开的字眼。头一个就不说了,太磨人。我要说的是第二个:明明简单却总是被人搞得如同地狱一样的 排版(Typography) 。 肯定有维新派叫嚷着: [!TIP] Protip! 2026 年了,还写什么博客?这种老掉牙的东西就应该丢进历史的垃圾堆!我都到微信公众号去写了! 但你写微信公众号推文总要在乎排版吧?但我这里不是要教你怎么给推文排版,因为优秀的排版都是相通的,并不是像公众号里边营销号吹的那样,只有带 qq 两个字的网页才能用。 [!Note] 排版真难……对吗? 糟糕的排版 废话少说,直接看一个。 一个人点进来这个网站,结果第一印象是:我到底要看什么? 这种排版就是失败的。那还有一个问题: 失败在哪儿? 排版的目的,就是要让读者马上意识到 我是来干什么的 ,是读文档也好,是看点作品集也罢,哪怕只是随意地读一读碎碎念,视线落下来的第一秒,读者的心里就要想:“喔,重点在这里”。 上面的排版,失败之处就在于字太小、分隔不清、图片和代码块喧宾夺主,正文像个被排挤的临时工。 [3] 这是 LINUX DO 社区的早期 UI,我管这种 UI 叫做“高额头和宽下巴”。 我的网站只放了萌备,因此效果不算恶劣。如果这种排版被幸运地在你的博客上展示,它会毁了你的博客:点进一个内容驱动型网站,必须要忍受这么破碎的信息,脾气好的读者可能跑路了事,脾气差点的直接找上评论区开骂了。 优秀排版和你 优秀排版的纲要显而易见: 层级必须清晰。 留白必须充足。 上面两个排版,之所以糟糕,就是因为看着太叫人窒息了。 虽然这份我自己总结出的纲要只有短短十二字,但是要实现它的确不容易。排版如果只是单纯的技术问题或者美学问题就好了,可惜它不是。 内容为王·王也要穿衣服 我们这里并不是在说 安徒生的童话 。曾有一位智者这样说过—— [!QUOTE] 智者的箴言 好的排版会让你的文章变得更好,哪怕文章的内容只是一坨大便。 其实文章的内容不是一坨大便也行。世界级名著《提问的智慧》 [4] ,它的作者埃里克·斯蒂芬·雷蒙把这旷世神作发到 个人网站上 时也没有写任何样式。 [5] 当然会有别扭精秉承着“这可是名著! ”的精神生啃。但我们是普通人,看这玩意会觉得很头大,不是吗?你要是用电脑看的话,十有八九会蹦出一个疑惑 :“我刚刚是不是看到这一行了?”更叫人挠头的是,原文是英文。 但是,在 GitHub 上的版本因为有完善的样式表,读起来简单轻松许多。对比看看?这和中英文没有太大关系。 读得开心多了吧? 不过也别想着马上兴高采烈地用 github-markdown-css 来给自己的网站上样式——你得照着自己网站的个性来。还有最重要的:你得喜欢你用的样式。写了一篇好玩的游记,读者还要在一个长得神似 GitHub 的网站看,这也太可怜了。 当然,如果你嫌麻烦,Tailwind CSS 提供的 @tailwindcss/typography ,以及 Sivan 开发的 赫蹏 可能更适合你,这玩意足够大众,技术文档到生活日志全部通吃。 当年汉字排版还在搞活字印刷术、西文排版还在纠结 fi 、 ti 怎么连字的时候,排版就是拼拼图。但咱们今天有那么多的 CSS 属性,反而都不会排版了。没有内容的排版是空壳。你选再漂亮的字体、再精致的留白,如果读者读完三分钟不知道你在说什么,那就是失败。这也是为什么很多人信奉“内容为王”——只要我写得好,其他不重要。但是,王也需要穿衣服。裸奔的国王走到街上,无论他多有智慧,大伙儿第一反应都是“这个人没穿衣服”。 要吐槽还能写一堆,按下不表。 字体·丢掉你的 LXGW 看到这标题千万别皱眉。 LXGW WenKai,也就是霞鹜文楷 ,用来排版长文跟在厕纸上印刷泰戈尔诗集没什么区别:可以看,但是看得很别扭。所以,放过它吧。 这个字体又开源,又文艺青年,还有手写的提按,哪里有问题? 问题就在这,它太吵了。LXGW 的笔画粗细太明显,确实很有楷书的顿挫和毛笔的飞白,单看几个字很漂亮,但是放到长文里就成了夹生饭。它既不是黑体字,也不是楷体字,一眼扫过去磕磕绊绊的。 字体就是空气,没人想在呼吸的时候被迫分辨空气的味道。所以,字体老老实实地选用默认的无衬线体(苹方、Inter、Noto Sans),要是想模拟英语课本你就用 Times New Roman。 下面的一些小短文使用了 <div> 标签结合内联样式展示不同的字体,从上往下依次是 Times New Roman、宋体(SimSun)和楷体(KaiTi)。为了避免太乱它们已经被折叠起来。 展开 (点击了解更多详细信息) 好的字体就像空气一样。现在几乎所有的中文图书都使用黑体和宋体进行排版,就是这个道理。不过即使它们优秀,不同的分支间亦有差异,就例如,思源宋体的横画非常的浅,白底黑字的情况下汉字的横几乎消失。 你要是不会选字体,就老老实实地用微软雅黑或者苹方。 .markdown { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "PingFang SC", "Microsoft YaHei", sans-serif; } [!TIP] 宋体和楷体并不适合网页排版,它们的细节在网页上会被大量的锯齿替代,丢失掉原来的美感。如果为了美观我更推荐思源黑体 CJK,它专门对中文标点做了优化,例如引号和逗号、句号之间的间距。 颜色·别只会用黑白胶卷 爱画画的人可能听过这样一句理论:“给草稿勾线时,千万不要用纯黑色,而是用深蓝色或者红棕色。”我看的入门教程都没给出这样做的理由。但也很好理解:黑色在图画里是黑洞,它会吸引走人的注意力,而且不给反馈。 网页也是一样,单调的黑白,就好比 bearblog 那种黑白风格,极简确实极简,但是看了直叫人一阵空虚。好的配色应该灵动一点,别什么反馈都没有。 对了,我在配色时喜欢先选取主色调,然后排 CSS 变量,以后无论要用什么颜色直接从变量里挑选,就不另外硬编码 Hex 颜色了。 [6] :root { color-scheme: light dark; --bg: #f3eee8; --text: #1f2937; --heading: #1a1a1a; --muted: #6b7280; --meta: #6b6b6b; --subtle: #9ca3af; --accent: #517ad1; --accent-soft: #4c7bdf; --surface: #f8f4ef; --line: #d2c7bc; --inline-code-bg: #ece4dc; --code-bg: #172033; --selection: #c4826f47; --tag-pill-bg: #eee6de; --tag-pill-border: #d2c7bc; --tag-pill-text: #2b211d; --tag-pill-hover-bg: #e7ddd4; --tag-pill-hover-border: #c6b9ac; --tag-pill-hover-text: #241b17; --tag-pill-active-bg: #e1d6cc; --tag-pill-active-border: #bbac9e; --tag-pill-active-text: #1f1713; --focus-ring: #9db5e8; --scrollbar-track: #ece4dc; --scrollbar-thumb: #c8b9ab; --scrollbar-thumb-hover: #bbaa9a; --scrollbar-thumb-active: #ad9b8b; --font-title: "Noto Serif SC", "Source Han Serif SC", "Songti SC", "STSong", "Georgia", "Times New Roman", serif; --font-body-zh: "Noto Sans SC", "Source Han Sans SC", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif; --font-body-en: "SN Pro", "SF Pro Text", "SF Pro Display", -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif; --font-mono: "Fira Code", "JetBrains Mono", "Cascadia Mono", "SFMono-Regular", "Consolas", "Liberation Mono", monospace; } 还有一个暗色版的,节约篇幅就不贴了。 一旦选定颜色,就不要乱贴什么大红大绿的东西,特别是标题、链接这些地方。如果可以,把评论区的颜色也用 CSS 变量替换掉。应该说,现在百分之八十的 WordPress 主题都没有这样的认识,它们只是把博主 可能 感兴趣的全部粘上去,丝毫不考虑读者。但你要知道,开博客是要给读者看的,给自己看的还是别放到公网了。 配色就见仁见智了,通常也就是红橙蓝紫几位,绿色是大忌。有一些比较复古的(比如阮一峰)就用氧化后的纸的颜色做背景,也没有暗色模式,同样赏心悦目。 或者,比这哥们做得好就行。 [7] [!WARNING] 配色人人都明白,无非就是让人看着舒服、一目了然,但是实际用上就知道有多吃审美眼光了。所以配色绝对不能太多,贪多嚼不烂,一个主色调就足够。网上说的撞色系、莫吉托,都是骗人的。 插个题外话,有个配色跟设计做的跟互联网厕所一样的博客,人气反而很旺。本来想作为反例放上来的,但是……我害怕后遗症,如果你也看过应该知道我在说什么。 间距·不要在夹缝中求生 Web 2.0 时代,谁也没想到会有 Vue、React 这些玩意,当时的网页就是一筒无限长的卷纸,一边扯一边看,像是把一本书的书页全部撕下,然后首尾相连粘起来。这样做的技术债就是,后来为了处理垂直居中,要花很大的力气。 咱们当然不用研究垂直居中,但跟它关系很大:我们要搞的是间距。没有任何一本书会忽略间距的设定,我在做我的网站时也是这样。 让我们回到 leading 部分。你不用动滚轮,下边有图片。 很明显吧?这里的行距是被脚注的数字顶开的,这就让字时起时落,很怪异。 但是,这样的问题还少吗?咱大可以先看点很叫人窒息的间距。 这里一共(应该)有三个间距: 截图的边界和折叠块之间的间距。 折叠块边界和 blockquote admonition 之间的间距。 blockquote 之间的间距。 段落之间的间距。 第二项和第三项都没有,看起来窒息那就是理所当然的了。 很多人喜欢在 CSS 里面加一个 text-align: justify; 。不要这样做。中文是方块字,右端不对齐不会很明显,但是,换到英文里就变味了。 里面用红线标注出的部分,叫做“ 文档的河流 ”。如果河流太多,人的注意力就会很快地涣散。所以不要自作聪明地加上两端对齐,直接用最原始的左对齐就好了。Microsoft Office Word 还有 LaTeX 都是这么干的,遵守它总不会被人笑话。 大致的图形搞定了,我们还漏了什么吗?当然有,那就是行间距和字重。 千万别吝惜行间距,该用就用。我读小学的时候,就有人告诉我每一段开头要空两格,但是网页里不能这样做。博客的段落是依靠行间距划分的,每一行顶格写都可以,不会导致两段的界限不清。 语文教材不同,里面文本的行间距就没变过,因此必须用分隔符来划分段落。HTML 不同啊,它可有 <p> 来告诉浏览器什么是段落。 如果你实在介意,CSS 里边有两个属性: text-indent (把它的值设成 2em 就是两个汉字的首行缩进)还有 text-autospace 。你的空格要是没坏就手动在汉字和英文之间插个 space 吧,很简单的,最多按两下。 配角·滚出我的文档流 你肯定看过这种页面:侧边栏、跳转按钮、光标特效还有花里胡哨的页面背景。 还有!Live2D!Live2D!Live2D!我到底是来看文章的还是看小人乱动的? 还有太长的图片、代码块。拜托,假如不是非看不可就 滚出我的文档流 好吗? 如果你正在我的网站上阅读,页面中应该只有左侧的目录,其余部分全部被我赶走了。包括存在意义简直莫名其妙的侧栏 résumé,这些东西塞在 /about 页面就好,而且大多数人也不怎么感兴趣就是了。 啊!不对!这里怎么出现了一张莫名其妙的图片! mediawiki 用了一种非常聪明的方法来处理这种 不是主要内容,但是也不能删掉 的非文档流内容,叫做破版(layout break) [8] ,比如,我想告诉你这个破版的来源,但它跟我要写的排版关系没那么密切,就可以把它放在插图里。 它的问题也很明显,行为太不可预测,分分钟图片跳到下一段去了。不过比起被硬生生打断,还是好得太多。代码块……因为代码块常常是正文的一部分,所以我对于折叠代码或者移开它没有什么想法。毕竟是 读 代码, 看 图片,主次要分明。 还没完,总是有人搞出这种界面。 乱得没边了。 在后面 排版这东西,难看的大家都知道,但是做出来好的很难。因为优秀的排版就像是水,不会给人的阅读造成任何阻力。像个偏执狂一样,把行高从 1.625rem 调到 1.7rem ,然后又调回去,如此反复。读者记得吗?当然不知道。但他们知道你的排版很好,好到读完时像是喝了一杯冰冰凉凉的可乐,没有任何阻力。 排版不是设计竞赛。所有的东西怎么打磨都是为了看见,打磨排版却是为了叫它消失。 (这一集很有教育意义) 每次看到沟槽的排版,我都有种拼命找出作者的邮箱然后给他发恐吓邮件的冲动。 但那没有用:不会排版还是不会排版。嘴臭留在自己的网站上就行。 时代在变,技术在变。排版很难,从头到尾都一样。但是,别在 line-height: 1.625 和 margin-bottom: 1.25em 的死循环里纠结,有时候简单粗暴的 margin: 0.7rem 0.7rem 反而更有效。 祝你在接下来的日子里排得开心 。 哦对,记得写博客,不写博客再好的排版都是废纸一张。 See Also 【Haku】我对各种字体的看法(Go 语言版) ←我自己写的,在 IDE 里讨论了一个合适字体的重要性。 【纸鹿摸鱼处】谈谈不受欢迎的博客技术特征 ←句句见血,将讨人厌的博客网站特征全部点出来了,尽管不针对排版,还是值得一读。 【知乎】为什么海外杂志英文段落右端不对齐? ←本文复制了其中的图片。 本文所展示的网页截图版权属于原作者,此处仅作为增强效果的工具。 Hexo 早就不活跃了。它使用的已经是十年前的老古董 ejs 模板,从今天回看它也是太过于原始了。而且,更重要的是,它既笨重,又慢。 ↩︎ Hugo 很好,真的。写 Hugo 的模板像 Go 语言一样优雅。哪怕是对于一个写习惯了 Python、不熟悉前端框架的人来说,Hugo 也足够友好。 ↩︎ 好极了,这只是最突出的问题。这个排版的问题多了去了。 ↩︎ 即 How To Ask Questions The Smart Way 。两个著名的缩写:STFW(Search the fucking web)以及 RTFM(Read the fucking manual)就是出自本文。 ↩︎ 好极了,我不能在我的网站发原文的截图, 因为它不是依赖 CC 协议发布的。 ↩︎ Codex 这家伙老喜欢用 color-mix() 这种函数了……看着就惹人厌。 ↩︎ 可喜可贺,我在 Reddit 上边找的演示,这哥们已经听人劝,改到了比较清新的风格。 ↩︎ Layout break 在大多数语境下是恶性 bug,它会把在容器内排好的文本挤得到处都是。但我希望看到的是次要的内容被挤走,所以反而在这里用上了。 ↩︎ 1 个帖子 - 1 位参与者 阅读完整话题

linux.do · 2026-04-25 15:44:38+08:00 · tech

[!IMPORTANT] 原文来自我的 个人博客 ,可以在这里阅读,也可以去博客 由于mermaid流程图是AI生成,本文的mermaid都是截图 不支持移动和缩放 上下文管理 [!NOTE] 本文借助CodeX辅助阅读源码,文章内容纯手工,mermaid图片为AI生成 水平有限,仅供自己学习参考,不保证深度 关于cc的源码,网上有太多分析了,但是很多一眼AI生成,很多感觉是追热点新闻。 只要一看到是AI的端倪,我就怀有不安,内心深处总觉得不太能相信,所以我自己亲自动手看看。 虽然我也借助了AI,但是我至少让他给出每个部分的源码在哪,自己理解 总结。 而且这部分内容我会复用,作为prompt给我自己的 Coding Agent项目 重新设计 feature/context 分支。 光是这一个部分(上下文管理),我就大概花了一周时间弄了个大概,以下是我的学习笔记 产出 Context Management在Agent系统中极其重要,下面我将参考Claude Code泄露的源码,针对上下文管理的模块,整理学习。 一轮对话包含的上下文 对于这样的Coding Agent,简单来说 当用户输入消息之后,传递给LLM的消息就包含这样的结构: + System Prompt + Tools + Messages ...... 如果会话持续特别久,工具产生的消息和用户/AI产生的消息就会充满这个结构,直到超出Context Window 而且,就算没有超出Context Window,也可能因为Transformer的特性,导致模型注意力分散,被一些噪音污染 此外,考虑到后续可能会生成摘要,所以有效的上下文窗口会预留一部分( src/services/compact/autoCompact.ts 定义的常量 MAX_OUTPUT_TOKENS_FOR_SUMMARY = 20_000 ),保证压缩正常运转 Canonical Transcript 在详细分析压缩策略之前,先补充说明一下 Canonical Transcript 不知道怎么翻译这个名词,权威抄本?总之这个应该是会话恢复的唯一真信息来源。 当执行会话恢复的时候,会读取这个脚本(一般是JSONL格式存储),通过里面的字段,恢复之前的会话。 JSONL文件存储了包括用户/AI/Tool等消息,这种文件根据项目名和会话id存储在 ~/.claude/projects/{project_name}/{session_id}.jsonl 每一轮对话或者执行工具,都会记录到这个cannonical transcript中 与JSONL同名的 {session_id} 这个文件夹下,还可能有 subagents 和 tool-results 文件夹 本文只针对这个transcript的内容说明一下,特别是对于工具结果,这个脚本有两个字段相关: 压缩策略 Claude Code采用了好几种上下文压缩策略,说实话,这部分内容,我搜了不少版本看了一下,感觉太乱了。 好多都说四种、五种压缩策略,然后每一个“四层压缩策略”里面的四个内容,每个文章还不一样! 我不敢说我这个有多么正确,我尽量参考源码给出的内容,输出自己理解的上下文管理。 参考 src/query.ts 里面的 async function* queryLoop 函数 里面基本上每个 queryCheckpoint('xxx_start') 和 queryCheckpoint('xxx_end') 包围的,就是一个模块。 我认为总的来说,应该包含这几个 模块 : Tool Result Budget: 处理工具结果的时候就考虑context的预算,如果结果很大,不可能一下子给LLM History Snip : 这个部分用 feature('HISTORY_SNIP') 判定执行的,实际实现未知,本文不讨论 Microcompact: 分两种,time-based和cached,前者代码层面处理过时的工具结果;后者应该是Anthropic那边特殊的处理,也用了 feature() 包围 Context Collapse : 这个部分也是`feature(‘CONTEXT_COLLAPSE’)包围的,实际实现未知,本文不讨论 Auto-Compact: 这个Auto-Compact模块,检测上下文压力,具体执行压缩又分为 Session Memory Compact 和 Full Compact Tool Result Budget 大概就是限制工具结果的逻辑 对于工具执行产生的结果,如果工具产生的结果太大,那就得持久化它。(比如执行find或者grep产生的结果) 持久化的路径一般是 ~/.claude/projects/{project_name}/{session_id}/tool-results/{tool_use_id}.txt Claude Code工具产生的结果的处理非常精妙,不同工具产生结果的处理策略也完全不同。 一般而言,每个工具内置声明了 maxResultSizeChars ,然后结果经过 mapToolResultToToolResultBlockParam() 处理之后,再给LLM或者前端渲染。 如果工具结果太大,前端就不会渲染具体结果。 src/Tools.ts 对此有这样的定义: mapToolResultToToolResultBlockParam( content: Output, toolUseID: string, ): ToolResultBlockParam /** * Optional. When omitted, the tool result renders nothing (same as returning * null). Omit for tools whose results are surfaced elsewhere (e.g., TodoWrite * updates the todo panel, not the transcript). */ 具体来说,每个类型的工具对于结果阈值(决定是否需要持久化)如下表: 类别 工具 单个结果外置阈值 Shell 工具 Bash, PowerShell 30,000 字符 搜索工具 Grep 20,000 字符 认证工具 McpAuth 10,000 字符 Read 工具 FileReadTool 特殊处理 其他普通工具 xxx 50,000 字符 也就是说,如果单个工具产生的结果超过了这个阈值,就会将结果存储到 ~/.claude/projects/{project_name}/{session_id}/tool-results/{tool_use_id}.txt ,最大保留64MB,超过会截断。 如果没单个工具超过阈值,但是同一轮多个并行执行的工具结果合并到一起,超过某个阈值( src/constants/toolLimits.ts 定义了 MAX_TOOL_RESULTS_PER_MESSAGE_CHARS 是200K字符),就会持久化最大、最新的工具结果,JSONL额外写入标记 content-replacement metatdata 如果单个工具没超过阈值,并且一轮工具结果合并也没超过,就保留原文到JSONL里 工具结果聚合的逻辑大概是不断聚合 tool_result 然后看是否超过200K字符,如果超过,就不断从最新的、最大结果开始外置到文件夹 tool-results ,然后JSONL的内容替换成 persisted-output preview 。其实也很复杂,本文不多赘述吧。 我觉得这里面最特殊的就是Read,因为如果读了一个超大文件,用同样的方法,因为超过阈值就把结果外置到 {tool_use_id}.txt ,那这个 txt 文件还是一个超大文件,根本没啥用。 所以Read工具首先对文本文件设置了默认限制 maxSizeBytes 为256KB, maxTokens 设定为25K,并且根据函数参数,分段读取大文件。(参考 src/tools/FileReadTool/limits.ts ) Read工具有几个参数: file_path , offset , limit 和 pages ( pages 用在读取pdf文件) 其中, offset 和 limit 分别用来控制,从哪一行开始读、读多少行。如果发现前面三个参数都一样,并且文件修改时间没变,那就不需要读,直接返回 file_unchanged 。 参考: src/tools/FileReadTool/FileReadTool.ts 每一次读执行成功(可能是大文件分段读)之后,结果写入canonical transcript JSONL文件 也就是: { ... "message": { "role": "user", "content": [ { "type": "tool_result", "tool_use_id": "toolu_xxx", "content": "101\t这是第101行\n102\t ..." } ] }, ... "toolUseResult": { "type": "text", "file": { "filePath": "FileReadTool.ts", "content": "...这部分原文,用于前端渲染/会话恢复...", "numLines": 80, "startLine": 101, "totalLines": 1000 } }, ... } 这两份信息都可以在JSONL找到,第一个 message.content 就是实际给大模型的,这个内容是带行号的,会进入上下文。 注意,第一个字段的 message.content 里面的工具结果是可能会被加工的,比如带行号( cat -n 风格)、安全提醒、空输出提醒、图片的结果、被压缩之后的结果。 (比如bash工具产生的超大输出可能会被外置,然后给LLM看的内容只有 <persisted-output> 这种标记) 第二个 toolUseResult 是工具调用时的、结构化的原始结果,用来前端渲染/会话恢复,所以渲染UI的时候,基本是原始版本,不会把参杂加工版本的内容渲染。 (比如被工具消息压缩之后,关掉了CLI,然后再resume会话,UI统计渲染这个工具读了多少行,就得参考原始结果,而不是压缩之后的行数) 这里是我用GPT-5.4分析读工具执行流程之后,生成的 mermaid 流程图,描述了 Read 工具的流程: (点击了解更多详细信息) ipynb 、 image 和 pdf 的读取就不赘述了。 当然,如果文件真的很大,就算分段读,最后发给大模型仍然逼近/超过上下文窗口怎么办? 所以得启动压缩策略。 Microcompact 在 src/services/compact/microCompact.ts 的第253行开始可以看到,微压缩分为两种路径,第一种是time-based microcompact,第二种是cached microcompact。 Time-based Microcompact 第一种是基于时间的微压缩,其实就是因为服务器那边缓存了prompt,但是prompt也是有TTL的,时间过去很久的话,这个缓存会失效。 我查了一下 deepseek 和 qwen 的API文档,都有大概说明缓存的TTL。DeepSeek文档说时间一般为几个小时到几天?Qwen说显式缓存5分钟,隐式缓存不确定。 Claude Code这部分在 src/services/compact/microCompact.ts 有设定阈值,默认是60分钟( gapThresholdMinutes = 60 )。根据当前时间和最后一条 assistant message 的时间戳,来决定是否出触发。 假如时间过的很久,那么上一次调用的缓存大概率失效了,既然缓存失效了,下一次请求无论如何肯定要把前面的context给LLM重新计算。 既然这个开销无论如何都会产生,那就不如在发送请求之前,把旧工具的结果清除了,减少重写内容和开销。 个人理解大概是这样: A. 缓存还在 [system + tools + history] + {新对话}: 只需要计算{新对话} B. 缓存失效 {system + tools + history + 新对话}: 全都要重新计算 这部分compact设定了白名单,只有 Read , Shell , Grep , Glob , WebSearch , WebFetch , Edit 和 Write 工具才能微压缩。 这部分工具,要么是可以通过canonical transcript中结构化的 toolUseResult 字段的参数来重新获取/复现结果;要么是信息量比较低/时效性比较强的结果。 而且,还有个参数 keepRecent = 5 设定了保留最近的5个工具的结果,只把比较旧的结果清理,也就是结果换成 [Old tool result content cleared] 。 所以如果启用了time-based microcompact,并且满足触发条件。在本轮API请求之前,即将发送给LLM的上下文里面,会把旧工具结果替换掉(不影响canonical transcript)。参考 src/query.ts Cached Microcompact 第二种是已经缓存的内容的压缩,这个好像是API服务端那边做的事情。 大概是,cache在API服务器还存在的时候,借助API的cache editing能力删除缓存里的旧工具结果。 这个完全是Claude Code独占,下面po一个grok expert模式的调研: 总的来说,Microcompact的mermaid流程图如下: (点击了解更多详细信息) Auto Compact 参考: src/services/compact/autoCompact.ts ,这个文件写的真不错,挺清晰的,其实也不用分析什么了。 和前文说的一样,这里会计算有效的上下文窗口: getEffectiveContextWindowSize(model) 预留了 20_000 的token,用于后续摘要/压缩 有效窗口再扣除 AUTOCOMPACT_BUFFER_TOKENS = 13_000 ,得到的就是Auto Compact的触发阈值 gautocompactThreshold 然后开始判断是否需要compact,查看函数 async function shouldAutoCompact : 他先做了几个判断,首先如果传入的参数 querySource 是 session_memory / compact / marble_origami /* tengu_cobalt_raccoon *就返回 false [!TIP] 这里后面两个名字挺有意思的,日语和英语混杂 最后一个cobalt和raccoon联合google一下居然能扯上生化危机,浣熊市是吧 目前意义不明,感兴趣可以看看源码 ps: marble_origami 看注释说是上下文agent,泄露的源码里面就这里出现了,还有一处是 src/utils/analyzeContext.ts 的注释 再进一步估算当前消息的token数量,通过 calculateTokenWarningState 状态判断函数计算几个层级的阈值 看一下这个函数的定义就知道了: export function calculateTokenWarningState( tokenUsage: number, model: string, ): { percentLeft: number isAboveWarningThreshold: boolean isAboveErrorThreshold: boolean isAboveAutoCompactThreshold: boolean isAtBlockingLimit: boolean } 简单理解就是当前的 tokenCount 要是超过了auto compact的阈值,那么这个 shouldAutoCompact 函数肯定返回 true 然后这个函数最终在 autoCompactIfNeeded 这个函数被调用,这个函数总体大概流程是这样: 熔断保护 如果连续auto compact失败三次,就不压缩了 (三次是因为 MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES 常量设定,可能是因为有些消息/输入本身就是直接超出上下文窗口了) 判断触发条件 就是调用 shouldAutoCompact 函数,检查上下文有没有达到阈值 构造RecompactionInfo 这部分应该是主要记录当前上下文是否是连续压缩的/上一次压缩过了多少轮对话/上一次压缩的turnID/模型auto compact阈值 尝试Session Memory Compact 不过这里注释写的是 EXPERIMENT: Try session memory compaction first ,不知道实际使用的时候有没有开启 如果Session Memroy Compact失败,调用Full Compact 调用的是 compactConversation 函数,这里就是真正总结历史,压缩成摘要了。 收尾 看这个代码挺直观的 try { const compactionResult = await compactConversation( messages, toolUseContext, cacheSafeParams, true, // Suppress user questions for autocompact undefined, // No custom instructions for autocompact true, // isAutoCompact recompactionInfo, ) // Reset lastSummarizedMessageId since legacy compaction replaces all messages // and the old message UUID will no longer exist in the new messages array setLastSummarizedMessageId(undefined) // 收尾1 runPostCompactCleanup(querySource) // 收尾2 return { wasCompacted: true, compactionResult, // Reset failure count on success consecutiveFailures: 0, } } catch (error) ... 如果full compact也失败,就是上面catch到的error处理,连续压缩失败次数加一 Session Memory Compact 那么具体来说,Session Memory Compact是怎么压缩的? Session Memory Extraction Session Memory Compact 首先得知道 Session Memory 这个名词的含义,其实就是由 forked agent 创建的 结构化的markdown文件 这里涉及multi-agent的系统,但是我还不太清楚。 目前知道的是,主对话每次LLM完成回复,都会经过Post-Sampling Hook,关于是否提取session memory,有一系列判断条件。 比如,必须是主线程、开启了这个功能以及 shouldExtractMemory(messages) 函数返回真值。 这个函数判断的条件是根据当前上下文 + 上一次提取session memory增长的token数 + 工具调用次数 联合判断的: session memory初始化的触发条件是当前上下文超过10K tokens 初始化之后,必须达到 currentTokenCount - tokensAtLastExtraction >= 5K 要么是上一轮对话中LLM没有调用工具;要么是上一轮工具调用超过三次,就会触发提取session memory 直接看源码 src/services/SessionMemory/sessionMemory.ts : // Trigger extraction when: // 1. Both thresholds are met (tokens AND tool calls), OR // 2. No tool calls in last turn AND token threshold is met // (to ensure we extract at natural conversation breaks) // // IMPORTANT: The token threshold (minimumTokensBetweenUpdate) is ALWAYS required. // Even if the tool call threshold is met, extraction won't happen until the // token threshold is also satisfied. This prevents excessive extractions. const shouldExtract = (hasMetTokenThreshold && hasMetToolCallThreshold) || (hasMetTokenThreshold && !hasToolCallsInLastTurn) // 这里hasMetTokenThreshold是session memory已经初始化后,意思是增长超过5K tokens 触发之后,就会启动一个 forked agent 这个 forked agent 在后台异步进行总结之前会话,然后生成结构化的 summary.md 这个agent只能用文件工具,只能编辑 summary.md ,所以比较安全,然后文件在 ~/.claude/projects/{project_name}/{session_id}/session-memory/summary.md 这个文件的结构如下: export const DEFAULT_SESSION_MEMORY_TEMPLATE = ` # Session Title _A short and distinctive 5-10 word descriptive title for the session. Super info dense, no filler_ # Current State _What is actively being worked on right now? Pending tasks not yet completed. Immediate next steps._ # Task specification _What did the user ask to build? Any design decisions or other explanatory context_ # Files and Functions _What are the important files? In short, what do they contain and why are they relevant?_ # Workflow _What bash commands are usually run and in what order? How to interpret their output if not obvious?_ # Errors & Corrections _Errors encountered and how they were fixed. What did the user correct? What approaches failed and should not be tried again?_ # Codebase and System Documentation _What are the important system components? How do they work/fit together?_ # Learnings _What has worked well? What has not? What to avoid? Do not duplicate items from other sections_ # Key results _If the user asked a specific output such as an answer to a question, a table, or other document, repeat the exact result here_ # Worklog _Step by step, what was attempted, done? Very terse summary for each step_ ` 传给这个 forked agent 的prompt是 src/srvices/SessionMemory/prompt.ts 的 getDefaultUpdatePrompt 返回的字符串。 最后这个部分完成之后,记录这次提取的tokens数、更新 lastSummarizedMessageId (方便知道这次memory覆盖到哪条消息) 参考 src/services/compact/sessionMemoryCompact.ts 比如,必须是主线程、开启了这个功能以及 shouldExtractMemory(messages) 函数返回真值。 前面条件都符合之后,就开始进入 async function trySessionMemoryCompaction 函数 首先检查功能是否启用,等待正在进行session memory提取的工作(可能会有后台运行的 forked agent 还没完成 summary.md )。 接下来,首先获取更新的 lastSummarizedMessageId 以及这个session memory的内容 summary.md 通过这个消息的uuid查找在消息内的索引 lastSummarizedIndex ;当然有一个特殊情况就是 lastSummarizedMessageId 不存在,这是因为当前会话是resume来的,这个uuid只在内存存在,新开的会话resume之后,还没有这个uuid。这种情况Index设置为最后一条消息的位置。 然后要通过这个 lastSummarizedIndex 计算 startIndex (也就是真正开始保留消息的索引),从 startIndex 往后的消息都保留原样。 正常逻辑来说, lastSummarizedIndex 表示的是 summary.md 能覆盖到的消息的索引,所以 startIndex 应该就是从 lastSummarizedIndex + 1 开始。 但是有很多特殊情况,需要通过 calculateMessagesToKeepIndex 来计算真正的 startIndex (这个 startIndex 是有可能比summraized index + 1小的) 比如: index N - 1 -> xxxxx index N -> compact_boundary index N + 1 -> 用户问问题 index N + 2 -> LLM回答 index N + 3 -> tool_use index N + 4 -> tool_result <- lastSummarizedIndex index N + 5 -> 用户追问 <- 理论上的startIndex index N + 6 -> LLM只回答 index N + 7 -> 用户问问题2 index N + 8 -> LLM只回答2 ...... 从 [startIndex, ..., end] 后面的内容是保留区域, startIndex 默认是在summarized index后面,但是会动态调整。 比如,保留区域的token数不能太小,否则就会往前扩展(startIndex减小)。默认是保留区域希望是10K tokens并且至少包含5个text block的消息(// TODO: 解释text block) 往前扩展的时候,往前一个下标,就计算一次token,如果保留区域的token数太大,达到大约40K tokens就停止; 并且,往前扩展的时候,不能超过 compact_boundary 这个标记; 并且,往前扩展的时候,不能把tool_use和tool_result这一对拆分开,这两个必须对应。 还有一种情况是LLM回答的时候分片了,多个message共享一个id,这个也不能拆开。 (参考: src/services/compact/sessionMemoryCompact.ts 的 calculateMessagesToKeepIndex 和 adjustIndexToPreserveAPIInvariants ) 最后完成session memory compact之后,通过 buildPostCompactMessages 组装得到: [compact_boundary] [session memory summary] [messages to keep] [attachments] [hook results] 如果Session Memory Compact不能用,或者压缩之后,仍然超过Auto-Compact的阈值,就执行最后的压缩Full Compact Full Compact 除了前面的步骤(Auto-Compact触发),还可以用户通过slash命令( /compact [user_messages] )主动触发。 [!NOTE] 不过通过 /compact 命令,没有输入任何指示的时候,仍然会先尝试Session Memory Compact;如果带有指示,就会直接进入Full Compact 参考 src/commands/compact/compact.ts 下面参考 src/services/compact/compact.ts 分析 主要是 async function compactConversation 这个函数 除了一开始的前置检查+token估算,就是执行 PreCompact Hooks ,这个函数第一个参数内就包含了判断是 auto | manual const hookResult = await executePreCompactHooks( { trigger: isAutoCompact ? 'auto' : 'manual', customInstructions: customInstructions ?? null, }, ... ) // 这个地方,如果是/compact触发的,参数customInstrcutions就不会是null // 如果是自动触发,这个地方就是null 后续针对这个 hookResult ,会合并 instructions 这部分其实就是额外补充一下compact的prompt 然后开始构造compact prompt compact prompt大概如下: [NO_TOOLS_PREAMBLE]: 简单来说就是禁止调用工具以及用特定的 <analysis> block和<summary> block [BASE_COMPACT_PROMPT]: 要求结构化的压缩信息 [Additional Instructions] [NO_TOOLS_TRAILER]: 再次强调不要调用工具以及用特定的 <analysis> block和<summary> block 然后这个 compactPrompt 封装用户消息,变量名 summaryRequest ,作为参数传给 streamCompactSummary() 函数(真正的核心部分) summaryResponse = await streamCompactSummary({ messages: messagesToSummarize, // compactConversation的参数,在auto-compact那里传进来的 summaryRequest, // compact prompt包装的用户消息 appState, // context, // ToolUseContext preCompactTokenCount, // token统计 cacheSafeParams: retryCacheSafeParams, }) // 这个函数内部实现,也是异步调用`runForkedAgent()` summary = getAssistantMessageText(summaryResponse) // 提取文本 这个 summary 就是最终的压缩结果,这个大概是: <analysis> ... </analysis> <summary> # 结构化摘要 每个section固定 1. Primary Request and Intent: 2. xxx # 完整模版查阅`src/services/compact/prompt.ts`的BASE_COMPACT_PROMPT </summary> 后续还有针对 summary 更健壮的处理,有几种情况: 带有PTL标记: (Prompt Too Long)会把旧消息裁剪( truncateHeadForPTLRetry ),再重试 不带PTL标记: 退出死循环,检查是否为空,空的话直接抛出错误;检查是否带有 api error 前缀,同样抛出错误 然后就是考虑把旧的状态存储一下、缓存清除;然后把新的summary替换原来的旧历史 首先是清理压缩之前的 context.readFileState ,清理之前先保存;这部分还跟工具缓存有点关系,太复杂了,建议看源码。 其次,再对压缩进行一些后处理 因为压缩之后清理了一些状态,这里要补充一些关键的状态,比如: 前面保存的 readFileState 压缩后需要重新注入的运行时状态 plan和plan模式的一些指示 调用涉及的skill相关的 deferred/agent/MCP相关的 这些状态都通过 postCompactFileAttachments.push() 补充。 然后才执行SessionStart hooks: 收集这些hooks返回的消息,最后追加到最终的上下文 然后再创建 compact boundary 标记,用来作文新的上下文的起点 封装summary成用户消息;记录压缩前后的大小、统计压缩后到token、判断是否下一轮再次压缩; 最终Full Compact完成后,得到的消息大概如下: [compact boundary] [summary messages] [attachments] [hook results] 最后这组值,加上一些token、usage和 userDisplayMessage 返回给auto-compact 再交给 query.ts 通过 buildPostCompactMessages() 组装压缩后的消息,把当前的messages替换成上述这份新的上下文 杂项 apiMicrocompact.ts 这个文件名感觉有点误导,但是实际上做的事情只有构造请求体好像 Reactive Compact 这个应该算是错误处理的一部分,泄露的源码好像也没有出现实现的细节。 参考 src/query.ts 的1119行,还有 src/commands/compact/compact.ts 多个文件提及 reactiveCompact.ts 但是泄露的源码似乎没有这个文件 Partial Compact 部分压缩?这部分也是一样 在 src/services/compact/compact.ts 能找到 partialCompactConversation 函数(看了一下和full compact一样都是调用 streamCompactSummary() 函数,后台agent生产结构化摘要) 流程和Full Compact很相似,先决定压缩/保留区域,构造prompt,生成summary,生成attachments,执行sessionStart hooks和创建compact boundary再包装成message 最终可能是这样的: [compact boundary] [partial compact summary] [messagesToKeep]: 有点像session memory compact保留区域 [attachments] [hookResults] 但是这个函数,从泄露的代码来看,只有 src/screens/REPL.tsx 调用了它,更像是前端交互层的功能。 简单google一下,发现官方repo有不少issue关于这个,比如 issue#26488 从源码来看,在claude code的CLI页面,双击esc会弹出一个rewind,选中消息之后,有一个选项是 Summarize from here 也就是我们用户自己主动选择范围触发的partial compact 这个触发之后,UI会渲染一个 Summarized conversation ,如果你 ctrl + o 展开的话: ⏺ Summarized conversation ⎿ This session is being continued from a previous conversation that ran out of context. The summary below covers the earlier portion of the conversation.` Summary: 1. Primary Request and Intent: xxxx 2. Key Technical Concepts: xxxx 3. Files and Code Sections: xxxx 4. Errors and fixes: xxxx 5. Problem Solving: xxxx 6. All user messages: xxxx 7. Pending Tasks: xxxx 8. Current Work: xxxx 9. Optional Next Step: If you need specific details from before compaction (like exact code snippets, error messages, or content you generated), read the full transcript at: /Users/chen/.claude/projects/{project_name}/{session_id}.jsonl 能看见很明显的结构化的markdown格式,并且最后给出了canonical transcript的路径,这一点和Full Compact几乎一样( src/services/compact/prompt.ts 的 BASE_COMPACT_PROMPT 和 PARTIAL_COMPACT_PROMPT )。 感悟 真的复杂,而且这还只是冰山一角,似乎有好多内容是这份源码没有逆向到的。 之前还经常有人说agent不过是prompt engineering,但是我看了这个内容,太复杂了… 这不是简单的一句prompt工程就能构造出来的 这里面还有很多我不知道的,比如hooks系统、工具系统、MCP、Multi-Agents等等等等 而且,API服务器那边,还有KV Cache、服务端怎么处理过时的消息的… 直接晕了 2 个帖子 - 2 位参与者 阅读完整话题

linux.do · 2026-04-25 10:54:14+08:00 · tech

ClawCloud Run 5月11日就要关闭了 ClawCloud Run Question IMPORTANT: ClawCloud Run Service Discontinuation & Refund Notice - ClawCloud... Dear Customers, After careful consideration, we have made the difficult decision to discontinue our product and related services. We sincerely appreciate your trust and support throughout our journey together. This notice provides the full ... 用这个平台搭的cpa用了2个月了,很稳定速度也很快 佬们,快支支招,换哪个平台呢 2 个帖子 - 2 位参与者 阅读完整话题

linux.do · 2026-04-24 08:33:20+08:00 · tech

RT,具体见下链接: ClawCloud Run Question IMPORTANT: ClawCloud Run Service Discontinuation & Refund Notice - ClawCloud... Dear Customers, After careful consideration, we have made the difficult decision to discontinue our product and related services. We sincerely appreciate your trust and support throughout our journey together. This notice provides the full ... 还有服务的佬友们记得备份!!! 不能爽爽白嫖5刀了呜呜呜( 7 个帖子 - 7 位参与者 阅读完整话题

www.v2ex.com · 2026-04-24 01:13:04+08:00 · tech

IMPORTANT: ClawCloud Run Service Discontinuation & Refund Notice https://question.run.claw.cloud/questions/10010000000003261 Discontinuation Schedule The following table outlines the timeline for each service module: Module Action Effective Date (UTC) Console New customer sign-ups and purchases suspended. April 23, 00:00 UTC Official Website Taken offline; no longer visible to customers. May 11, 00:00 UTC Console Existing free-tier customers: all services discontinued. May 11, 00:00 UTC Console Existing paid customers: eligible for a pro-rated refund based on remaining service period. Submit a refund request via the provided form. Refund deadline: May 20, 00:00 UTC Documentation Taken offline; no longer visible to customers. Site will not be renewed. May 11, 00:00 UTC Blog Taken offline; no longer visible to customers. Site will not be renewed. May 11, 00:00 UTC Q&A Forum Taken offline; no longer visible to customers. Site retained for 2 months before permanent removal. July 11, 00:00 UTC

www.v2ex.com · 2026-04-24 00:13:04+08:00 · tech

IMPORTANT: ClawCloud Run Service Discontinuation & Refund Notice https://question.run.claw.cloud/questions/10010000000003261 Discontinuation Schedule The following table outlines the timeline for each service module: Module Action Effective Date (UTC) Console New customer sign-ups and purchases suspended. April 23, 00:00 UTC Official Website Taken offline; no longer visible to customers. May 11, 00:00 UTC Console Existing free-tier customers: all services discontinued. May 11, 00:00 UTC Console Existing paid customers: eligible for a pro-rated refund based on remaining service period. Submit a refund request via the provided form. Refund deadline: May 20, 00:00 UTC Documentation Taken offline; no longer visible to customers. Site will not be renewed. May 11, 00:00 UTC Blog Taken offline; no longer visible to customers. Site will not be renewed. May 11, 00:00 UTC Q&A Forum Taken offline; no longer visible to customers. Site retained for 2 months before permanent removal. July 11, 00:00 UTC

www.v2ex.com · 2026-04-23 23:13:04+08:00 · tech

IMPORTANT: ClawCloud Run Service Discontinuation & Refund Notice https://question.run.claw.cloud/questions/10010000000003261 Discontinuation Schedule The following table outlines the timeline for each service module: Module Action Effective Date (UTC) Console New customer sign-ups and purchases suspended. April 23, 00:00 UTC Official Website Taken offline; no longer visible to customers. May 11, 00:00 UTC Console Existing free-tier customers: all services discontinued. May 11, 00:00 UTC Console Existing paid customers: eligible for a pro-rated refund based on remaining service period. Submit a refund request via the provided form. Refund deadline: May 20, 00:00 UTC Documentation Taken offline; no longer visible to customers. Site will not be renewed. May 11, 00:00 UTC Blog Taken offline; no longer visible to customers. Site will not be renewed. May 11, 00:00 UTC Q&A Forum Taken offline; no longer visible to customers. Site retained for 2 months before permanent removal. July 11, 00:00 UTC

www.v2ex.com · 2026-04-23 22:38:29+08:00 · tech

IMPORTANT: ClawCloud Run Service Discontinuation & Refund Notice https://question.run.claw.cloud/questions/10010000000003261 Discontinuation Schedule The following table outlines the timeline for each service module: Module Action Effective Date (UTC) Console New customer sign-ups and purchases suspended. April 23, 00:00 UTC Official Website Taken offline; no longer visible to customers. May 11, 00:00 UTC Console Existing free-tier customers: all services discontinued. May 11, 00:00 UTC Console Existing paid customers: eligible for a pro-rated refund based on remaining service period. Submit a refund request via the provided form. Refund deadline: May 20, 00:00 UTC Documentation Taken offline; no longer visible to customers. Site will not be renewed. May 11, 00:00 UTC Blog Taken offline; no longer visible to customers. Site will not be renewed. May 11, 00:00 UTC Q&A Forum Taken offline; no longer visible to customers. Site retained for 2 months before permanent removal. July 11, 00:00 UTC

v2ex.com · 2026-04-23 22:06:05+08:00 · tech

IMPORTANT: ClawCloud Run Service Discontinuation & Refund Notice https://question.run.claw.cloud/questions/10010000000003261 Discontinuation Schedule The following table outlines the timeline for each service module: Module Action Effective Date (UTC) Console New customer sign-ups and purchases suspended. April 23, 00:00 UTC Official Website Taken offline; no longer visible to customers. May 11, 00:00 UTC Console Existing free-tier customers: all services discontinued. May 11, 00:00 UTC Console Existing paid customers: eligible for a pro-rated refund based on remaining service period. Submit a refund request via the provided form. Refund deadline: May 20, 00:00 UTC Documentation Taken offline; no longer visible to customers. Site will not be renewed. May 11, 00:00 UTC Blog Taken offline; no longer visible to customers. Site will not be renewed. May 11, 00:00 UTC Q&A Forum Taken offline; no longer visible to customers. Site retained for 2 months before permanent removal. July 11, 00:00 UTC