Skip to content

Releases: Asaiuta/dsh-blackhole

0.5.0 — first tagged release

Choose a tag to compare

@Asaiuta Asaiuta released this 25 Oct 15:35

dsh-blackhole v0.5.0 — first tagged release (source archive; consume via profile link: or the npm files layout).

Highlights of this cycle (full details below, carried verbatim from the CHANGELOG): preset-standard re-alignment against dsh-web-app rc.2 (the stale toolFilter rows broke new-session creation), explicit evidence-bearing recall queries with bounded excerpts, source-preserving index checkpoint carry, and the 0.5.0 index checkpoint format with settings routes.


  • The preset-standard copy no longer carries toolFilter. The bundle patch
    restates that preset's whole config.plugins list (patches replace config
    wholesale), so it also froze the toolFilter.deny: [schedule_*] rows that the
    0.2.1-alpha.2 baseline had on tool-subagent / tool-subagent-fork. Current
    @deepseek-ai/dsh-tool-subagent (0.2.1-rc.2) rejects persona and toolFilter
    in apply() with Object.hasOwn, which made the whole preset invalid
    (agent-preset/invalid) and broke new-session creation as soon as standard
    became the selected default. Re-aligned against dsh-web-app@0.2.1-rc.2
    presets/standard.patch.yml; restricting a child's tool surface is now an
    Agent Preset choice (agentPreset), not a filter row.

  • Recall queries are explicit and evidence-bearing. Only /pattern/flags
    enables regex; ordinary model names, paths and prose use keyword search.
    Bare regex callers must add delimiters (hook|inject → /hook|inject/).
    Ranked transcript results append up to three bounded verbatim hit excerpts
    when the head preview lacks query terms, with explicit full-context guidance.
    Excerpts reuse the cached redacted transcript, never raw Write/Edit bodies.
    Mixed Write/Edit schemas now redact unknown payload keys even alongside a
    standard body key; malformed object-valued paths are redacted too. Literal
    substring hits, including Chinese phrases inside a longer token, get excerpts.
    Exact expansion, lineage filtering and the 50 KB response cap are unchanged.

  • Local checkpoint carry is source-preserving. Index checkpoints retain rooted
    paths, result/call addresses, diagnostics, rules, revocations and omission notices
    across rounds. R: <root> declarations use explicit R:file references; shell
    labels stay literal. Pool sizing includes carried state and the engine enforces
    the overall checkpoint cap. Ledger carry preserves original P/F/C/K anchors and
    excludes the superseded checkpoint from fresh extraction.

  • Active rules survive whole. Index and ledger count/character limits apply
    to optional goals. Active constraints/preferences and revocation tombstones
    retain their kind and source seq; an insufficient mandatory budget fails
    explicitly. Ledger's global cap sheds optional records with a recall notice.

  • Index is available in settings and the rendered preview. Local format
    shortcuts also clear superseded remote replay state. Recall cache validation
    uses a seq map, unchanged logs reuse their index, and ranking reuses its lookup.
    Defaults remain blackhole, ledgerBudget 656 and kBudget 0.

  • A user message that fits the entry cap is quoted whole. Sentence picking
    kept 别在整手机了好吗? and dropped 直接电脑上操作 — the instruction was in
    sentence two, so U recorded a question nobody asked. The rule is now: if
    nothing would be lost, keep the sentence selection (so
    帮我把这个函数改快一点。先别改测试,也不要动公共接口。 still renders as two
    joined sentences); if the message fits WHOLE_MESSAGE_CHARS and the selection
    would drop a sentence with content, quote the whole message instead.
    5-session user-turn coverage 31/37 → 35/37 for +36 tokens. Revocations
    still take the long path: collectRequirements filters that kind out, so
    folding one into a whole-message goal would have dropped it.

  • user turns is measured separator-insensitively. The census marker is the
    first 40 characters of a user message and the U section joins quoted
    sentences with /, so a contiguous includes reported an instruction as
    lost when it was quoted verbatim one separator away — a yardstick that
    systematically rewarded formats copying whole messages over formats quoting
    their sentences. ledger moves 5/37 → 6/37; every other implementation is
    unaffected.

  • Addressability is counted from row lines, not from any #N. The unanchored
    /#(\d+)/ also matches the F #12 … diagnostic lines, so a result whose row
    was elided counted as addressable whenever it had a diagnostic: fixed pools
    reported 721/1,304 when only 677 rows were written, and the pool growth
    looked like it bought 583 addresses instead of 627. INDEX_ROW_REFS
    anchors to ^#12 ; pi-brief keeps the unanchored pattern because its
    addresses are mid-line ((#3, #5)).

  • An enumerated requirement now survives, not just the question above it.
    parseRequirementText kept the first sentence (the goal) plus sentences
    containing a rule word, so 这些都修复了吗?1. build_dsp_chain 从未把 chain 采样率 下发给各 stage。…48/96kHz 下 IR/FIR 卷积完全无声 recorded only
    这些都修复了吗? — five sentences of payload dropped by a question with no
    content. A numbered sentence is a requirement by construction (the user
    enumerated it, so it defines "done"); those are appended after the rule
    sentences, so when the 160-character cap bites a prohibition is what survives
    rather than item 3. Bullets are excluded on purpose: • Ran Get-ChildItem … is
    how a transcript renders a tool call, and a message carrying an injected
    context block pushed those in as if the user had listed them.

  • The aggregate prints n/a, not 0/0, for a format with no address space.
    dcp emits no #seq at all, while ledger has an address space it fills with
    calls (0 of 1,304 results reachable). Printed in one column as 0/0 and
    0/1,304, two different facts looked like one number, and dcp disappeared
    behind a plausible zero.

  • A format with no address space can no longer be a "winner". The
    per-session winner requires every diagnosis plus every result addressable;
    pointed === total is trivially true when both are 0, so a hypothetical format
    that dropped every result would have qualified. Now total > 0 as well.

  • A row says which call produced it: #seq tool@call arg. recall #92
    returns the output, and the command that produced it lives only on the
    assistant message — so a checkpoint without the call seq lets the model read
    every result back and still not know what was run. The readback sweep found
    225 of 1,456 addresses in that state, which is also why the row format
    changed rather than the readback alone.

  • The index pools are sized to the region (autoIndexOptions). The fixed
    2048/768/256 values were calibrated on a 152-result session and do not hold on
    a larger one: a 314-result TypeScript session elided 91 rows and kept
    21 of 54 failure lines — the format's one promise, broken. Pools are
    ceilings, not allocations, so raising them renders a byte-identical body
    wherever the region already fit; the cost is only paid where rows would
    otherwise be lost. 0 elided now on all six measured regions.
    requirementCount grows with the region too (8 → up to 32; a 30-turn fixture
    went 8/30 → 30/30).

  • Diagnostics are counted per (seq, line) pair, not per distinct line.
    failsOf de-duplicates within one result, but the renderer prints one F line
    per (result, line) — so the same text printed by two results costs twice. The
    budget estimator counted it once and starved one line on a real region.

  • renderExpandedMessage reserves the tool-call lines before filling prose.
    Blocks were rendered in order against one byte budget, so a long thinking block
    consumed it and the * bash <command> line after it never appeared: 225 of
    1,456 addresses resolved to a readback that no longer said what was run. The
    call lines are the operational half, so they get their bytes first and the
    prose takes the remainder.

  • Drilling into an address (#N) renders 2,000 characters of arguments, not
    the preview's 300.
    222 of 1,004 command lines came back truncated: the
    argument identified the call in preview but not in the readback that exists to
    identify it.

  • R <prefix> only accepts real paths. A command containing a slash was
    eligible to elect the root, which produced R ADB="D:/AI/… — a shell
    assignment as the "root" of the index, stripped off every row that started
    with it.

  • A row's argument comes from the raw command, and blank arguments fall
    through.
    shortCommand(compressBash(cmd, 120)) folded lines into a; b; c
    and cut at 120 characters, so a two-token slice could be an unterminated
    fragment like APK="/data/app/~~… that appears nowhere in what was run. A
    blank key argument (path: "" means the working directory) used to render an
    empty row and hide a later field — fffind sends {path: "", pattern: …} and
    lost its pattern. List-shaped arguments (ask_user_question →
    questions: [{header, …}]) now yield their label instead of a bare tool name.
    14 of 1,456 rows were argument-less; 6 remain, and they are all correct: a
    ls of the working directory, an empty bash {}, and four edit calls that
    arrived without a path (flagged !, with the failure line saying so).

  • Two new harnesses, because one session is not evidence.
    tests/eval-sessions.mjs re-runs the matrix on five independent sessions
    across four situations (TypeScript type-break, Rust build break, cargo test
    triage, multi-file refactor, pip install failure); tests/recall-verify.mjs
    puts every address back through the real recallSession — resolves, same
    message object, body present, F lines verbatim, @call resolves, @call
    closes over the toolCallId, c...

Read more