Skip to content

Releases: regrevia/dsh-fast-mode

0.7.0 — fast mode is per conversation

Choose a tag to compare

@regrevia regrevia released this 24 Oct 07:27

0.7.0

Fast mode was a global switch wearing a per-conversation costume: the bolt sits in
one composer, so clicking it read as "this conversation", and it changed every
conversation in the app. Two sessions side by side, one clicked, both accelerated —
and nothing in the UI said so.

  • The switch and the gear are per conversation now. A click writes that
    conversation's own {enabled, gear} entry; the panel says 仅本会话生效 /
    This conversation only under the switch, so the scope is stated where the
    decision is made instead of being inferred.
  • A conversation with no entry inherits the shared pair, which doubles as the
    record of the last choice made anywhere. A brand-new conversation therefore
    starts from the last selection rather than from the schema default — the
    behaviour asked for, and the reason the shared fields still exist.
  • Precedence is explicit: experiment assignment > that conversation's entry >
    shared default. An assigned trial still outranks the UI, because a click that
    quietly overrode the arm would empty the control group of the very trial the
    user asked for.
  • One click, one patch. The shared enabled / gear and the sessions entry
    travel in a single settings/update carrying the revision that was read. Two
    writes would mean a window where the panel shows one gear and the request runs
    on another, plus a second revision race; a refused write rolls back all three
    fields and states the reason in the panel.
  • The column is bounded and forgiving. sessions holds at most 200 entries,
    oldest pruned first by ts (a pruned conversation just falls back to the
    shared default). A hand-edited or truncated value degrades to "no overrides"
    and never takes the chip down.
  • The two copies of the format are tested against each other. The host reads
    the column (src/state.js), the browser half writes it (client.js), and a
    drift between them is a switch that looks on and changes nothing — so the
    shipped client.js is loaded in a vm and fed the same fixtures as the host
    module. Writing the test found one real drift (the browser copy accepted an
    empty session id the host drops), which is now fixed on both sides.
  • The conversation id reaches the client half through the slot's own scoped
    inject(sessionId); the chip also accepts a standard sessionId prop, so a
    release that stops injecting one spelling still behaves.
  • One caveat, stated where a user will hit it: sessions renders as a text input
    on the plugin settings page, because this build of schemastery has no
    .hidden(). Its description says it is maintained automatically; editing it by
    hand is how a conversation ends up running a gear nobody chose.

165 tests.

0.6.2 — 测量修好了:token 和步号是真的

Choose a tag to compare

@regrevia regrevia released this 24 Oct 06:59

0.6.2

The measurement was wrong in the one way that matters most: it counted time but
not tokens, and it lost which step each row belonged to.

  • Read the event payload where it is. session/event carries its payload in
    event.data ({turn, step, usage, …}); the recorder was reading event.turn
    and event.usage off the envelope, so every row from 0.6.0 and 0.6.1 had
    turn/step missing and input/output at zero while timings looked fine.
    Rows now carry step numbers and real token counts, and both the harness
    spelling (inputTokens, reasoningTokens, cacheReadTokens) and the gateway
    spelling (uncachedInputTokens, thoughtTokens, cachedReadTokens) are
    folded.
  • Two regression tests feed the wrapped shape, so this cannot come back quietly.
  • Rows already written to ~/.dsh/fast-mode-metrics.jsonl keep their zeros;
    delete the file or filter by ts when comparing against new data.

147 tests.

0.6.1 — 实验有了真正的对照组

Choose a tag to compare

@regrevia regrevia released this 24 Oct 06:54

0.6.1

0.6.0 could compare gears, but not against the thing they are supposed to beat.
An assignment always switched the mode on, so a randomized trial had no baseline:
the only "off" sessions were ones somebody happened to run before starting the
experiment, which is not an arm — it is a convenience sample.

  • "off" is a legal arm. The assignment file and
    dsh-fast-mode-experiment --gears now accept off, meaning the control group:
    the session runs with the plugin switched off because the experiment said so,
    not because a switch was left alone. off is always accepted even when the
    options do not list it, and the CLI now defaults to
    off,safe,fast,turbo,max, so a fresh start is a controlled trial rather than
    a ranking. start says which kind of run it wrote: with off it promises a
    baseline, without it it warns that the report can only rank arms against each
    other.
  • The control arm is deliberately invisible to the switch. A session assigned
    to off returns the request untouched even when the global switch is on — the
    one place where the UI can disagree with what a session actually does, which
    the experiment output and both READMEs now state plainly.
  • Also lands in this release: bin/experiment.mjs is documented and exposed as
    the dsh-fast-mode-experiment bin with an npm script, and --lan follows
    --since into the dashboard's own 1d/7d/30d window (default 7d) rather
    than always asking for one day and quietly printing nothing.

145 tests.

0.6.0 — 测量自己、按会话分档实验、终端报告

Choose a tag to compare

@regrevia regrevia released this 24 Oct 06:52

0.6.0

0.5.2 made the switch work. 0.6.0 makes the claim checkable: fast mode measures
itself, assigns gears to sessions for comparison, and reports the result from a
terminal. It also adds one gear above max, because on the official route that
is the only honest way past the 2× ceiling.

  • Measurement is built in and on by default. A session/event tap folds
    every step into ~/.dsh/fast-mode-metrics.jsonl, one JSON line per completed
    step, for sessions with the switch off as well as on — a comparison needs
    both sides, and nobody remembers to enable measurement before an A/B. A row
    carries v, ts, sid, turn, step, mode (on|off|unknown), gear (or off),
    effort, route, ttftMs (step start → first assistant message), llmMs
    (step start → last assistant message), stepMs (the whole step), calls,
    errors, input, output, reasoning, cacheRead, cacheWrite and
    assistantMessages. metrics: false disables it; a write failure is
    swallowed and never fails a request.
  • Experiment assignment without a UI. An optional
    ~/.dsh/fast-mode-bench.json (benchmarkFile) assigns gears per session:
    {"enabled": true, "gears": ["safe","fast","turbo","max"], "sessions": {"<session id>": "turbo"}}.
    An explicit pin wins; otherwise the gear is FNV-1a(session id) mod gears.
    The file is re-read on a 1 s TTL, so an edit applies to the next request, and
    deleting it returns control to the configured switch. A session an experiment
    assigns runs on that assignment even with the switch off — letting a forgotten
    click decide the arm would silently put the session in the control group.
    Unknown gear names are dropped with one warning.
  • The experiment has a command, not a JSON file you edit by hand.
    bin/experiment.mjs (package bin dsh-fast-mode-experiment) writes the
    assignment file, reports how many sessions and steps each arm has collected so
    far (status, including whether a disabled-state sample exists at all), and
    refuses to overwrite a running experiment without --force. An experiment is
    what makes the numbers trustworthy: the arm is chosen by the session-id hash,
    not by whoever wants the better result.
  • A report command instead of a spreadsheet. bin/report.mjs
    (package bin dsh-fast-mode-report) reads the JSONL and prints a per-stance
    table plus a speedup table against the off baseline. The unit is the step;
    session wall clock is not used, because it mostly measures the human. Flags:
    --file, --since 2d|12h|90m, --json, --lan, --help. --lan joins
    local rows to the self-hosted usage dashboard by session UUID using
    USAGE_TOKEN (optional USAGE_BASE): read-only, nothing leaves the LAN, and
    the token is never printed. The dashboard's own window (1d/7d/30d)
    follows --since instead of always asking for one day. An unreachable,
    slow or unauthorised dashboard degrades to one note; it never costs you the
    local report.
  • New gear ludicrous (极速), above max. It is the only gear whose floor
    is off, i.e. it may set reasoningEffort: 'off', with a fan-out target of 8
    and deepAfter: 1. That is not "a bit faster than max": the official route
    declares off/low/high/max, so with thinking on the only legal cut is
    high → low (≈2×) and 3–4× requires turning thinking off, which this gear
    does. The panel therefore marks it 极速* / Ludicrous* and the footer says
    * 关闭思考,质量会下降. The plugin still never emits an id the route does
    not declare — on a route without off, the gear degrades to what the ladder
    offers.
  • The chip is one bolt, and the panel is behind it. The caret and its inline
    dropdown are gone. Clicking the bolt opens a panel holding the on/off switch,
    the five gear rows (each with its estimated multipliers, the current one
    marked) and the fine print, plus a status line that states the reason when a
    write is refused. Clicking does not toggle; picking a gear while fast mode is
    off still arms enabled in the same write.
  • The gear is a name. gear is a schema union of
    safe/fast/turbo/max/ludicrous; a number or a typo is a load-time error, as
    src/config.js already documented. The policy layer still normalises
    defensively, so a bad value can never become "unlimited", but it no longer
    silently becomes safe either.
  • Measurement paths are composition knobs. metricsFile and
    benchmarkFile are deliberately not .volatile(): they are read at load and
    do not belong on a live form. Moving the JSONL is a cordis.patch.yml edit.

Tests 142: the measurement tap proves off sessions are measured too (one row per
step, first-token vs last-message latency in a multi-message step, token and
error sums, per-session isolation, a missing directory created on first write,
an empty path disabling the sink); the assignment proves a stable hash that
covers every gear, an explicit pin, a single-session pin, a live 1 s re-read,
inertness without a file or with enabled: false, and one complaint for a
broken file; the report proves both tables, a one-sided refusal in words,
--since including the wider dashboard window, --json, --help reading no
file, --lan without a token and with one (the token is never printed), and an
unreachable dashboard degrading to a note; the client proves the contract after
the caret removal — one icon, clicking opens the panel and writes nothing by
itself, the switch writes once and a second click turns it back off, five rows
with estimates and an asterisk on ludicrous, and picking a gear while already
on leaves the toggle alone; and the schema proves an unknown gear name is a
load-time error, measurement is on by default, and exactly
enabled/gear/parallel are volatile.

v0.5.2 — 闪电真的能点了

Choose a tag to compare

@regrevia regrevia released this 24 Oct 06:13

0.5.1 shipped the switch but left it inert, and the reason was one wrong field
access:

  • The remote protocol wraps every answer in { ok, value }. The chip read
    remote.settings.describe().namespaces straight off the envelope, so it always
    got undefined, classified this plugin as "not configured", and rendered a
    disabled button. Chrome suppresses title on a disabled button, so hovering
    said nothing, and the click handler's guard swallowed every click — a control
    that was fully explained inside the code and completely dead on screen.
    unwrap() now handles the envelope (and a bare payload, so a service stub
    still works), ok: false surfaces error.message, and every remote call has a
    deadline so a pending bridge becomes a visible error instead of an endless
    "loading".
  • The chip is never disabled. Read-only, unknown and missing-namespace states
    all stay clickable and open the menu with the reason in words.
  • The gear lives on the chip. The caret opens a dropdown listing the four
    gears, each row showing its estimated speed and token multiplier
    (速度 ~1.6× · token ~1.5×) and marking the current one. Picking a gear while
    fast mode is off turns it on in the same write. There is deliberately no gear
    form on the plugin page: this harness renders no generic schema form, so one
    working entry point beats a second half-working one.
  • Host-side traceability. With debug on, apply writes one line per load
    before the enabled check (apply v0.5.2 entered enabled=… gear=…), then one
    more saying it registered nothing; after two seconds it also reports whether
    settings.describe() actually serves this namespace. A future "the chip does
    nothing" report is now one log line away from an answer.

Tests 89: the client suite runs the real bundle against the real wire envelope,
including the bare-payload tolerance, the refused write, the read-only chip, the
missing namespace, and the gear multipliers.

v0.5.1 — 让开关真的能开关

Choose a tag to compare

@regrevia regrevia released this 24 Oct 05:25

0.5.0 的开关其实按不动

上一版把三个设置字段声明漏了 .volatile(),整个设置层因此是坏的:

  • settings.describe() 只把带 meta.volatile 的字段投影进表单。一个都没有 ⇒ 这个条目从表单里直接消失:没有设置页,对话框里的闪电读成 "absent",一直灰着。
  • 就算点了,settings.update() 也会直接拒绝:Config field "enabled" is not volatile。

现在 enabled / gear / parallel 都是 volatile。这同时是"改一下立刻落到正在运行的 fiber、不重启插件"的机制本身。

同一个契约的另一半:volatile 字段传到插件里是冻结的引用({get()}),不是值。所以 config.enabled === true 在开关打开时仍然是 false。resolveConfig 现在在这一处统一解引用,同时继续接受普通对象。

测试 80 个:每个表单字段都要证明自己是 volatile,Loader 给来的引用必须被读成"已开启"。

升级

# 插件管理器里把目标改成
github:regrevia/dsh-fast-mode#v0.5.1

装完重启一次应用(宿主只在启动时扫描 dsh.client,且新 schema 要重新加载),闪电才会出现。

v0.5.0 — 对话框里的闪电开关

Choose a tag to compare

@regrevia regrevia released this 24 Oct 05:21

默认关闭,点一下才开

0.4.0 的问题是装好即生效:你没法在界面上确认它在不在,也没法一眼看出它开没开。0.5.0 把开关放到对话框里,和模型选择器并排。

  • 空心 = 关,实心黑 = 开。 装好后闪电是空心的,不带任何干预;点一下变实心,策略立刻生效。
  • 只有字面 true 才算开。 空配置、{}、{gear: 'max'}、{enabled: 'yes'} 都不会注册任何 handler。
  • 点击是乐观写入。 先按新状态画,再发一次带 revision 的 settings/update,然后轮询等宿主回显;被拒就回滚,并把原因写进 tooltip。
  • 只读层 / 条目不存在:闪电变灰、不可点,不会假装能开。
  • 同一个字段:闪电、插件设置页、cordis.patch.yml 写的都是 enabled;applies: live 让它不用重启就跟着变。只有 bundle 本身要刷一次页面——宿主只在启动时扫描 dsh.client。

顺带

  • Config 用 schemastery 声明三个值得出现在表单里的键(enabled、gear、parallel),其余调参键原样透传,不被剥离。
  • 新增 8 个测试:把发布出去的 client.js 原样放进 vm 沙箱跑(桩 __ModuleLoader__ + 假 settings 远端),验证空心/实心、一次带 revision 的写入、被拒回滚、只读禁用、缺少条目时不抛错。
  • 测试总数 78,不需要网络。
# ~/.dsh/profiles/<profile>/cordis.patch.yml
- insert:
    - id: fast-mode
      enabled: true   # 默认 false;等价于点对话框里的闪电
      gear: fast      # safe | fast | turbo | max

v0.4.0 — 档位、并行指令、插件图标

Choose a tag to compare

@regrevia regrevia released this 24 Oct 04:57

0.4.0 — 档位把"更快"变成一套姿态

插件现在装好即加速:默认档位 fast,三件事全开。想完全回到 0.3.0 的静默行为:gear: safe。

新增

  • 四个档位:gear: 1..4(safe / fast / turbo / max)。档位统一决定三件事——连续干净几次后允许降档(deepAfter 6/4/3/2)、回看几条信号(window 8/6/5/4)、每一步让模型批量并行几个独立调用(不注入 / 4 / 6 / 8)。floor 在任何档位下都不越过。
  • 面向模型的并行指令:档位 ≥ 2 时注册一段系统提示(fast-mode:execution-speed,order 520),数字随档位或 parallel 变化,并守住三条规则(只并行独立调用、读写不同批、批内失败就缩回小步)。
  • 调度上限告警:当 agentLoop.maxParallelToolCalls 低于档位目标时,日志里直接给出要改的那行 cordis.patch.yml,只报一次。
  • 插件显示元数据:包根 icon.png(PNG,11,607 字节)+ locale/zh.json / locale/en.json 的 meta.title / meta.description,插件管理器与设置页显示真实图标和中文文案。

文档

中文 README 成为默认(README.en.md 为英文),并把三件事的原理、官方 API 支持依据、与上下文压缩类插件的组合方式、全配置表、route= 日志含义和诚实的限制都写清楚了。

验证

  • npm test:64 个测试全通过,无网络、不需要 harness 运行时。
  • 一键安装实测:插件管理器目标 github:regrevia/dsh-fast-mode → 装到 0.4.0、application: applied、无 warning。
  • 运行确认:/Users/apple/.dsh/fast-mode.log 出现 gear=fast 行与 high=>medium(五档自建网关)。

npm 上的同名包尚未发布,请用 github: 形式安装。