Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
5894329
test(mmc-trajectory): ship the guards that were only on one machine
avatasia Oct 11, 2026
3547053
test(mmc-trajectory): make the session-store guards machine-independent
avatasia Oct 11, 2026
af3375e
test(mmc-trajectory): guard the rest, with no service and no machine …
avatasia Oct 11, 2026
6e57455
Remove the commit-message scratch file that git add swept in
avatasia Oct 11, 2026
a4b6717
Give 异常 its own axis, and make its counts mean what they say
avatasia Oct 11, 2026
64ba78b
Make 重置 a reset, and put the row above back when a rule is ticked
avatasia Oct 11, 2026
905c5c9
异常轴加「多选」:默认单选,勾选后可叠加
avatasia Oct 11, 2026
7a92cef
异常轴复审:修正三处控件状态与合计口径,并沉淀验证方法论
avatasia Oct 11, 2026
cc42f8b
把两条筛选轴钉成一块,并把下方每层的偏移改成实测
avatasia Oct 11, 2026
5a283da
补一份分层设计:功能类型 → 层 → 归属
avatasia Oct 11, 2026
3be59b6
Stage 1 冻结 spec:先写清四问与不变量,再动结构
avatasia Oct 11, 2026
816c2d2
Stage 1 spec 修订:跨家族查漏的 15 条结论,改了 6 条断言、加了 3 条后果
avatasia Oct 11, 2026
ab717e3
Make the inspector follow the table, and own the re-measure at one exit
avatasia Oct 11, 2026
7f2eff8
Stop rebuilding the whole lineage on every poll
avatasia Oct 11, 2026
5b3f29b
Build the ledger in batches, and stop the inspector widening the window
Oct 11, 2026
1ec2469
Record the eight undone items, and one measured option that was rolle…
Oct 12, 2026
ed7e872
docs(mmc-trajectory): 归因滚动跳转为既有缺陷 D14,解除 content-visibility 路的阻塞
avatasia Oct 12, 2026
2f6a6ba
fix(mmc-trajectory): 滚动意图跨建行转交,点行不再把读者留在第 1 轮(D14)
avatasia Oct 12, 2026
e2f7560
perf(mmc-trajectory): 逐轮组推导 contain-intrinsic-size,「显示全部」建行 3,437 → 8…
avatasia Oct 12, 2026
c4992bc
test(mmc-trajectory): 声明 R6 的两处跨层配对,Stage 3 完成(未做清单 #5)
avatasia Oct 12, 2026
694cd8a
refactor(mmc-trajectory): 异常轴收成一个字段 state.anomaly {mode, rules, last}…
avatasia Oct 12, 2026
5d9c124
refactor(mmc-trajectory): 可见性收成一个入口 recordVisible(record, turn, selec…
avatasia Oct 12, 2026
ec7c45a
refactor(mmc-trajectory): 删掉 D2 那三句互相矛盾的设计声明,并补上它自己的变异控制(Stage 2-c 注释半)
avatasia Oct 12, 2026
ee85f7f
feat(mmc-trajectory): 增量协议 ?since=,会话增长时只传尾部(Stage 4)
avatasia Oct 12, 2026
52d592c
docs(mmc-trajectory): LAYERS.md 三处已过时——D5 重新计数、第 6 节状态、D15 标题
avatasia Oct 12, 2026
8c6265c
test(mmc-trajectory): 补上搜索框清空的覆盖——那条变异一直在测 state 而不是控件
avatasia Oct 12, 2026
730100d
fix(mmc-trajectory): 三处探针残留的空注释块,补结构守卫 + 2-b 浏览器实测
avatasia Oct 12, 2026
c5c1119
fix(mmc-trajectory): skipping class 跨重建留存,账本塌成 0px,小幅上滚直接到顶
avatasia Oct 12, 2026
192356d
docs(mmc-trajectory): 记 D17 —— 19 行空白系统记录,已查实成因与修法边界
avatasia Oct 12, 2026
7801484
feat(mmc-trajectory): 结算记录成为可筛查的异常规则(D17 修复)
avatasia Oct 12, 2026
d0f9cdb
feat(mmc-trajectory): 上下文代钉在类型行末(D18)——它没有「没有了」,只是住在会滚走的工具栏里
avatasia Oct 12, 2026
3f65f1b
feat(mmc-trajectory): 上下文代随连续下滚让位(D19)——位置对了,但不该在读长账本时白占一行
avatasia Oct 12, 2026
f35cf60
fix(mmc-trajectory): 概览条与窗口条之间的漏行——根因是守卫一直在断言错误的那个元素(D20)
avatasia Oct 12, 2026
e5e847f
revert(mmc-trajectory): 撤回 D19 下滚让位——picker 一滚动就消失
avatasia Oct 12, 2026
f11a967
docs(mmc-trajectory): 收口「滚动时全部/图表行变矮」——成因是 D19 自己,不是抓不到
avatasia Oct 12, 2026
29d6bb3
docs(mmc-trajectory): 关闭未做清单 1c——位置跳变已解释,不再是「很可能」
avatasia Oct 12, 2026
d7f7e23
fix(mmc-trajectory): 窗口条补上缺失的那个口径——加数,不改口径
avatasia Oct 12, 2026
c1609c0
fix(mmc-trajectory): 窄视口整页横向滚动,并撤回一个会骗人的修法(D21)
avatasia Oct 12, 2026
9794952
fix(mmc-trajectory): 空态「清除筛选」漏掉概览条刷选,顺带让基础轴默认值只剩一个写者
avatasia Oct 12, 2026
6f55019
窄屏检视面板三态:文档流 / 半高吸底 / 完全展开吸底
avatasia Oct 12, 2026
6f166cf
D23:吸底断点比单列断点更窄,功能在真实窗口里等于不存在
avatasia Oct 12, 2026
833b55a
D24:检视改成 1/4 高、可拖拽调高、无选中时不可见
avatasia Oct 12, 2026
12f26e9
Revert a mutation that leaked into 833b55a
avatasia Oct 12, 2026
b14595e
D25:「完全展开」不再把账本吃干净
avatasia Oct 12, 2026
c3c07f5
D26:「窗口的1/4」和「检视的高度」取大还是取小 —— 取最小,且机制变成守卫
avatasia Oct 12, 2026
e9920b8
D27 + D28:抓手从来没渲染过(resize 确实等于没做),中间高度 1/4 → 2/5
avatasia Oct 12, 2026
ddbc1ab
fix: 回应 PR #16 的 review —— 4 条必改全部确认存在并修掉,4 条建议采纳 3 条
avatasia Oct 12, 2026
ab1650c
D30 — 三处「能失败的检查」其实不能失败
avatasia Oct 12, 2026
f80d5f7
D31 — 外部评审抓到 checkFixture 的三个洞,以及它抓到的我一个错误结论
avatasia Oct 12, 2026
49df6df
fix: 上一条提交把一条变异卷进去了
avatasia Oct 12, 2026
2db88b3
fix(tests): 变异控制被中断时不再把 shipped 文件留在变异态
avatasia Oct 12, 2026
a77b1da
D32 — 选中一条记录,检视面板占满整屏
avatasia Oct 13, 2026
e6381d5
D33 — 首次出现既不是 2/5,也没有折叠按钮
avatasia Oct 13, 2026
f8356de
D34 — 「折叠到最小」这一态从来没做过
avatasia Oct 13, 2026
00afad5
D35 — 面板内容一滚,改面板高度的那个按钮就滚没了
avatasia Oct 13, 2026
b589bdb
docs(mmc-trajectory): 设计文档移出插件目录,并去掉其中的作者真名
avatasia Oct 13, 2026
0fc0572
D36 实现 — flow 落回页面底部,判据改为「全部/异常/轨迹带钉在视口顶」
avatasia Oct 13, 2026
d602267
D36 守卫与变异 — 86 -> 90 条断言,69 -> 75 条变异全捕
avatasia Oct 13, 2026
9563283
D38 实现 — 一个三态循环按钮拆成两个单职责按钮
avatasia Oct 13, 2026
6b8a758
D38 守卫与变异 — 90 -> 107 条断言, 75 -> 96 条变异全捕
avatasia Oct 13, 2026
4967cb2
D39 实现 — 最小化按钮挪到最右; 折叠态的出口改成「回到你按下去那一刻的高度」
avatasia Oct 13, 2026
38eec34
D39 守卫与变异 — 107 -> 118 条断言, 96 -> 105 条变异全捕
avatasia Oct 13, 2026
377be6a
D40: pin the inspector under the stacked strips on wide screens
avatasia Oct 13, 2026
f71ec98
Guards for D40, and the one that should have caught its comment
avatasia Oct 13, 2026
1013a7f
D41: keep the inspector's header put when its content scrolls
avatasia Oct 13, 2026
edb3565
Guards for D41, written about scope rather than about declarations
avatasia Oct 13, 2026
0692f09
D42: one sum behind both inspector ceilings
avatasia Oct 13, 2026
2468907
Guards for D42: assert the relation, not just the two sides
avatasia Oct 13, 2026
7f2be6e
Pin the inspector's three identifying rows as one unit
avatasia Oct 13, 2026
7c9dc0f
Assert the pinned unit as a whole, and what stopped existing
avatasia Oct 13, 2026
759377a
D44: judge the selection against the filter result, and add the ledge…
avatasia Oct 13, 2026
32cf36e
Guards for D44, and the two rules they had been asserting backwards
avatasia Oct 13, 2026
dfa3732
The session list's cache could not be hit even once
avatasia Oct 13, 2026
1bd056c
The two READMEs said 1.0.0 while the manifest said 1.1.0
avatasia Oct 13, 2026
48461a7
A tool call in a delta kept a blank duration until a refresh
avatasia Oct 13, 2026
b131f78
Pin the delta round-trip, and close a hole found while doing it
avatasia Oct 13, 2026
c7682ee
Keep the inspector's resize grip where a grip can be reached
avatasia Oct 13, 2026
e5c200f
Ask whether the grip can be reached, not whether it has properties
avatasia Oct 13, 2026
cf8360b
Stop offering a 64 KB prefix as a session''s message count
avatasia Oct 13, 2026
b86b879
Ask whether a reported count is a count, not whether one exists
avatasia Oct 13, 2026
a784455
Give the brushed range one writer, so R5 can be checked at all
avatasia Oct 13, 2026
314a3c0
Check that there is one writer, rather than how many there are
avatasia Oct 13, 2026
eef89e4
Point the ledger jump down until the reader has pressed it
avatasia Oct 13, 2026
5461c80
Assert the two states differ by a flag, and that the flag can only be…
avatasia Oct 13, 2026
30f284d
Let the shipped comment name the list the selection is actually judge…
avatasia Oct 14, 2026
b1251cf
Keep the shipped-file list to files that are in the package
avatasia Oct 14, 2026
952af70
Take a NUL byte out of server.mjs, and repoint its dangling references
avatasia Oct 14, 2026
6407f01
Make the guard that says "nothing points at a missing file" able to fail
avatasia Oct 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions plugins/avatasia/mmc-trajectory/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# The working tree of this plugin must be byte-identical to what is committed.
#
# Without this file, core.autocrlf=true rewrites every LF blob to CRLF on checkout — and git then
# reports the result as *clean*, because it normalises line endings when it compares. The rewrite is
# invisible to `git status` while it is happening, and it cost this plugin specifically:
#
# 1. Two working copies of the same file compared unequal, so a byte-for-byte check between the
# ship source and the local package reported a difference that was not one.
# 2. `git checkout` of any path silently rewrote it. That is how `tests/fixtures/` landed CRLF on
# disk while its blobs are LF, and why verifying the fixture meant knowing which copy was which.
# 3. Every mutation harness that anchors on a line ending has to guess which ending the checkout
# produced. `mutate-review16.py` did guess, and that was a bug waiting to fire the moment the
# ending moved. The anchors there no longer contain newlines for the same reason.
#
# `text=auto` leaves git free to keep deciding for itself which files are binary; `eol=lf` is the
# part that pins the result. Every blob here is already LF, so adding this changes no committed
# content — it only stops the working tree from disagreeing with it.
#
# This file lives in the plugin directory, not the repository root. At the root it would rewrite
# line endings for every other plugin in the repo, and this is a single-plugin change.
* text=auto eol=lf

# The one file in this plugin that is not text. `text=auto` would sniff it correctly on its own, but
# naming it keeps the intent visible next to the rule that would otherwise be the only one.
*.png binary
14 changes: 14 additions & 0 deletions plugins/avatasia/mmc-trajectory/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Python bytecode caches from running the mutation harnesses.
#
# tests/ has 25+ .py files and they run on every `node tests/run.mjs`, so CPython drops a
# __pycache__/ into the package directory every time. It is not tracked and must never be: it is
# machine-specific, it changes shape between Python versions, and the plugin directory is the
# package that ships. The repository root .gitignore does not cover it.
__pycache__/
*.pyc

# tests/run.mjs hashes the shipped files before the mutation controls rewrite them, so a run killed
# mid-mutation is caught on the next one instead of being inferred from whatever the source looks
# like afterwards. The snapshot is machine-specific bookkeeping and this directory is the package
# that ships to readers, so it must never be tracked.
tests/.shipped-snapshot.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"schemaVersion": 1,
"name": "mmc-trajectory",
"displayName": "会话轨迹",
"version": "1.0.0",
"version": "1.1.0",
"description": "按轮次浏览当前会话的模型轨迹:消息流、思考过程、工具调用与结果、token 与耗时统计。",
"author": "avatasia",
"icon": "icon.png",
Expand Down
21 changes: 19 additions & 2 deletions plugins/avatasia/mmc-trajectory/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,20 @@ English | [简体中文](README.zh-CN.md)

Browse the model trajectory of a MiniMax Code conversation: messages, reasoning, tool calls and their results, Token usage, and per-turn timing.

Author: [avatasia](https://github.com/avatasia) · Version: `1.0.0`
Author: [avatasia](https://github.com/avatasia) · Version: `1.1.0`

### Before you change this page

New features on this page grew one at a time, and most of the defects that survived review came from the same place: a control added without asking which layer it belongs to. So answer four questions in writing before writing any code.

1. **Layer** — which functional layer does this belong to, and which layer's state does it write?
2. **Relation** — beside, mutually exclusive with, or subordinate to which existing controls of that same layer?
3. **Layout** — which container holds it? Does it need a new sticky region, panel, or layer?
4. **Contract** — does it change a number another part of the page already promises? If yes, list every place that reads that number before you change it.

The fourth is where the coordination bugs come from: one feature changed a number and every other surface kept speaking the old one. Mutual exclusion belongs in one place — one writer per state field — never as a convention spread across functions.

`tests/` carries the regression guards for all of this; `npm test` runs them.

## Install and use

Expand All @@ -18,11 +31,15 @@ The author directory is only used to group contributions in the community reposi

The page follows the most recent conversation by default and re-reads it every 2.5 seconds, so it stays live while you work; it pauses while the page is hidden. Each read is validated with an HTTP `ETag`: when the session file has not grown, the service answers `304 Not Modified` with no body and the page keeps what it already rendered. An idle tab therefore costs a few bytes per poll instead of the whole trajectory. Use the session picker to switch to any other conversation. No API key and no dependency installation are required — the package is dependency-free and needs no build step.

When the session *has* grown, the poll carries `?since=<cursor>` alongside its validator and the service answers with the tail instead of the whole session: a payload of the same shape plus a `delta` marker, with `turns` holding only the turns that gained records. On a session of 21,000 records that is roughly 20 KB instead of 43 MB. The cursor is the highest `record.index` the page already holds — the same key `raw?index=` answers on — and it travels only with the validator it was taken against, so a session switch or a manual refresh sends the whole payload instead. `stats` and `session` travel whole in every tail: `stats` is an accumulator over all rows, so a tail cannot be added to it. A page that is ahead of the service (which is what a context-generation switch looks like from here) is answered with the whole payload, since a tail from there is empty and an empty tail is indistinguishable from "nothing to add". This saves transfer and the page's parsing, not the service's work — it still reads, parses and serializes the whole session to build the cache entry and to answer `raw`.

## What it shows

The ledger groups records by turn, following the `turn_id` recorded in the session file. Each record is classified as user, answer, narration, thinking, tool call, tool result, system, or compaction, and each turn header carries that turn's own Token counts and elapsed time. Selecting a record opens a detail panel with summary, raw JSON, tool arguments, tool result, and reasoning, plus a button that hands the record to the Agent's chat input.

**The type chips are the only filter over kinds.** One thing is being filtered — which record kinds are visible — and it is one array in the client, written from exactly three controls: the eight kind buttons, 全部, and 清除筛选. An earlier version also had a row of layer buttons (turn / thinking / call) sitting above the list as presets over those same chips. They were removed, because any preset a reader outgrows becomes a button that contradicts the chips it was supposed to summarise: untick 问答 while leaving 用户 ticked, and the turn button goes dark and claims the conversation is hidden when in fact it was narrowed. One control that cannot drift is worth more than three convenient ones that can. Two further controls cut across kinds instead of over them: a time range, which bounds the ledger to a wall-clock window, and an anomaly filter, whose rules pick out records that are wrong — a failed tool call, a compaction, a turn that never got an answer, an answer that came back empty — rather than records of a particular kind. Neither rewrites the kind array, so every kind chip still answers exactly one question.
**The type chips are the only filter over kinds.** One thing is being filtered — which record kinds are visible — and it is one array in the client, written from exactly four controls: the eight kind buttons, 全部, 清除筛选, and the anomaly filter. An earlier version also had a row of layer buttons (turn / thinking / call) sitting above the list as presets over those same chips. They were removed, because any preset a reader outgrows becomes a button that contradicts the chips it was supposed to summarise: untick 问答 while leaving 用户 ticked, and the turn button goes dark and claims the conversation is hidden when in fact it was narrowed. One control that cannot drift is worth more than three convenient ones that can. Two further controls cut across kinds instead of over them: a time range, which bounds the ledger to a wall-clock window, and an anomaly filter, whose rules pick out records that are wrong — a failed tool call, a compaction, a turn that never got an answer, an answer that came back empty — rather than records of a particular kind.

**The anomaly filter is the one cut-across control that also rewrites the kind array**, and deliberately so: ticking an anomaly rule calls the same `resetBaseFilters()` the 清除筛选 button calls, which returns kinds to all, clears the search box, drops any brush on the overview strip, and clears the time range. Without that, a reader could tick 工具失败 with 问答 unticked, see nothing, and have no way back except hunting for which of five controls to undo. The cost is stated here because it is a real cost: the chips stop being a filter you keep while you narrow by error — so 「只回答一个问题」 holds for the chips only outside anomaly mode, which is also when they are the only thing still in play. A time range and a search term still never rewrite the kind array.

Naming kinds directly costs one extra click and reaches everything a preset did — tick 用户 *and* 答复 for the conversation, untick both for its absence, and any scattered subset in between, such as only the user's questions or only system records.

Expand Down
19 changes: 17 additions & 2 deletions plugins/avatasia/mmc-trajectory/README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,20 @@

按轮次浏览一段 MiniMax Code 对话的模型轨迹:消息流、思考过程、工具调用与结果、token 消耗与每轮耗时。

作者:[avatasia](https://github.com/avatasia) · 版本:`1.0.0`
作者:[avatasia](https://github.com/avatasia) · 版本:`1.1.0`

### 动手改这个页面前

这个页面的功能是一次加一个长出来的,而活到 review 之后的缺陷大多来自同一个地方:加控件时没先问它属于哪一层。所以实现之前,先用文字答完四个问题。

1. **归层** — 它属于哪一层,改的是哪一层的状态?
2. **关联** — 和同层已有控件是并列、互斥,还是从属?
3. **布局** — 放进哪个已有容器?需不需要新的 sticky 区域、面板或层?
4. **口径** — 它是否改变了页面上其他部分已经承诺的数字?若是,先列出**所有**读这个数字的地方,再动手。

联动异常出在第四问:一个新功能改了一个数字,而别处仍在说旧的那个。互斥要写在**一处** —— 一个状态字段一个写者 —— 而不是变成散落在各函数间的约定。

`tests/` 里是这些规则的回归守卫,`npm test` 跑它们。

## 安装与使用

Expand All @@ -22,7 +35,9 @@

台账按 `turn_id` 把记录分组,轮次划分完全依据会话文件中记录的 `turn_id`。每条记录被归类为用户、答复、过程、思考、工具调用、工具结果、系统或压缩;每个轮次表头带该轮自己的 token 计数与耗时。选中任意记录会打开详情面板,含摘要、原始 JSON、工具入参、工具结果与思考内容,并提供一个把该记录内容送回 Agent 对话输入框的按钮。

**类型选项是唯一针对「类型」的筛选。** 被过滤的始终只有一件事——哪些记录类型可见——客户端里它是同一个数组,只由三个控件写入:八个类型按钮、「全部」和「清除筛选」。早期版本在列表上方还排了一行图层按钮(轮次 / 思考 / 调用),作为同一批类型选项的快捷预设,已经删除。任何读者能走偏的预设,最终都会变成一个与它本该概括的选项自相矛盾的按钮:只取消「过程」而保留「用户」,轮次按钮就熄灭并声称整个对话已隐藏,而实际上你只是收窄了它。一个不会跑偏的控件,比三个方便的控件更值钱。 另外两个控件并不在类型之上做筛选,而是横跨类型:时间区间把台账限制在一个墙上时钟窗口内;异常筛选挑出的是「出了问题的记录」—— 工具失败、上下文压缩、整轮没有答复、答复是空的 —— 而不是某一类记录。两者都不改写类型数组,所以每个类型选项始终只回答一个问题。
**类型选项是唯一针对「类型」的筛选。** 被过滤的始终只有一件事——哪些记录类型可见——客户端里它是同一个数组,只由四个控件写入:八个类型按钮、「全部」、「清除筛选」,以及异常筛选。早期版本在列表上方还排了一行图层按钮(轮次 / 思考 / 调用),作为同一批类型选项的快捷预设,已经删除。任何读者能走偏的预设,最终都会变成一个与它本该概括的选项自相矛盾的按钮:只取消「过程」而保留「用户」,轮次按钮就熄灭并声称整个对话已隐藏,而实际上你只是收窄了它。一个不会跑偏的控件,比三个方便的控件更值钱。 另外两个控件并不在类型之上做筛选,而是横跨类型:时间区间把台账限制在一个墙上时钟窗口内;异常筛选挑出的是「出了问题的记录」—— 工具失败、上下文压缩、整轮没有答复、答复是空的 —— 而不是某一类记录。

**异常筛选是唯一一个会改写类型数组的横跨控件,而且是故意的**:勾选任一异常规则时,它调用的是「清除筛选」按钮调的那个 `resetBaseFilters()`,把类型恢复为全选、清空搜索框、丢掉总览条上的刷选、并清掉时间区间。否则读者会勾上「工具失败」而「问答」还取消着,看到一片空白,却不知道该回去改哪一个——一共五个控件可以撤销。代价必须写出来,因为它是真代价:异常模式下类型选项就不再是「一边保留一边按错误收窄」的筛选器 —— 所以「每个类型选项只回答一个问题」这句话只在异常模式之外成立,而异常模式之外也正是类型选项唯一还在起作用的时候。时间区间和搜索词仍然不改写类型数组。

直接点名类型只多花一次点击,却能到达预设原本覆盖的全部范围:勾选「用户」**和**「答复」就是对话,两个都取消就是没有对话,中间任意散落的子集(只看用户提问、只看系统记录)同样可以。

Expand Down
Loading
Loading