The e2e run reported success while live-turn had actually thrown. Two separate
problems, both mine:
- The suite inherited JCODE_ACTIVE_PROVIDER, so a stale login for whichever
provider the caller happened to have selected turned every turn into a fatal
auth error, even though the same machine answers fine through failover. It now
falls back to the configured default.
- The Windows port left an unconditional `mut` on the listener, which is an
unused_mut warning on Unix. Declared per platform instead.
I also reported that run as green, which it was not: piping the script through
`tail` discarded its exit code. Checking the exit code, not the tail, is the
lesson.
Publishing was blocked on two things only the user can do, but finding that out
meant rediscovering which npm names are free and what a scoped name requires.
RELEASING.md records that (@jcode and jcode-sdk are free, unscoped jcode is
taken by an unrelated project) alongside the publish and post-publish
verification steps.
sdk_publish_preflight.sh runs the gates and then reports exactly what remains,
so the answer is a command's output rather than a research task. It currently
reports one blocker: not logged in.
Writing the options table exposed that two of them did not. A reused
`jcodeHome` threw EEXIST relinking credentials that were already there, and
once past that, connected to the previous run's leftover socket with nothing
behind it: startup waits for the socket to appear, and a stale file appears
instantly. Neither is visible in a suite that only ever launches fresh.
Also corrected a documentation error rather than papering over it:
listSessions() reports what the daemon announced to this connection, not what
is on disk, so a persistent home is read back by id with peekSession.
live-options.mjs now checks each documented option against its promise.
Every other check runs inside the repo against a locally built binary, which
is the one setup a consumer does not have. This installs the packed tarball
into a throwaway project and launches jcode from PATH, then asserts the
instance home stays gone and no daemon is left behind: the two failures that
looked fine from inside the repo and would have shipped.
The two new live suites were not wired into the e2e script, so nothing in CI
exercised launch() or checked that an instance stays out of the user's
sessions. They run against a private instance rather than the shared bridge,
which is the point: isolation is a property of a separate instance.
The README also still told every reader to start a bridge first, which is not
true for launch().
The catch-all `{ ev: string }` member widened the discriminant, so no
`switch (event.ev)` case narrowed and every field came back `unknown` for
consumers. Split it into UnknownApiEvent/AnyApiEvent, drop the casts it forced
inside run(), and pin it with a consumer-side type test in CI.
Single-shot smokes prove one request works. The failure users hit is turn 2+,
where the provider must replay prior context, tool calls and opaque
thought_signature blobs. A model can pass a one-shot smoke and still 400 on
turn 2.
live-turn.mjs proves the happy path only. This drives attach, cancel, soft
interrupt, clear, rewind and detach, each of which crosses a different
translation path that a mock server cannot validate.
Adds sdk/typescript (@jcode/sdk): a typed client for the versioned harness
API, mirroring crates/jcode-harness-api. NDJSON framing over the API socket,
version handshake, reply correlation, per-kind and async-iterator event
streams, and a run() helper that collects one full turn.
Drift is guarded from both sides so a hand-written mirror cannot rot: a Rust
test fails when a variant is added without a TS counterpart, and a Node test
fails when the tag sets diverge.
Two real bugs surfaced from end-to-end runs against a live bridge:
- The bridge forwarded `ping` to the daemon even before attach. The daemon
classifies ping as a one-shot lightweight control request, answers it, and
closes the connection, so a liveness probe destroyed the caller's session.
Unattached pings are now answered by the bridge.
- send_message has no request-level reply (the harness acknowledges with a
message_accepted event), so awaiting one always timed out. The SDK now
awaits the acknowledgement event instead.
scripts/test_sdk_e2e.sh drives a real turn through a real bridge, which is
what caught both; the mock-based unit tests could not.
onboarding_graph.rs declares the onboarding flow as data, including the three
states the flow always had but never modelled: EnvBlocked (a login that could
never be saved), LoginFailed (a classified failure), and CredRejected (a
permanently rejected credential). check_invariants enforces the properties that
keep users unstuck: no dead ends, every failure node has a retry or hand-off
edge, an escape hatch from every node the user can sit on, every node reachable,
no self-loops (the shape of the two-day OpenAI retry loop), and a bounded
keystroke budget to a settled state. Wired into check_guardrails.sh.
telemetry-core::onboarding_trace records a traversal rather than a log. Node,
edge, reason, and outcome are all &'static str from closed enums, so there is no
free-text field through which user data could reach the payload; a test walks
the serialized JSON and rejects any string outside the vocabulary. Timings are
bucketed to 100ms and capped, traces are capped at 64 steps, and preview_json
renders the exact bytes that would be sent.
auth::browser_suppressed now consults the env probe, so a machine that
positively cannot use a browser skips to a device/paste flow instead of waiting
out a callback timeout. Unknown stays optimistic and a container with a
confirmed browser (SSH with X forwarding) keeps the loopback flow.
"Discovery" named the agent's internal act rather than the outcome the
catalog delivers. "Integration" names the thing produced, matching the
tool description's existing "vetted integrations" language.
Model-facing surface only (stage 1):
- tool name discover_tools -> integration_tools
- actions browse/select -> search/setup; suggest unchanged
- listing/selection renderers reworded to integration vocabulary
`select` in particular read as bookkeeping the model could skip, while
`setup` promises something it wants. select_rate is 0.0 across every
saved baseline, so this is the change most likely to move it.
Backwards compatibility, since baselines and resumed sessions carry the
old vocabulary: `browse`/`select` still parse as aliases, `discover_tools`
resolves to `integration_tools` via the tool-name alias table, and both
benchmark scripts match either wording when scanning transcripts.
Internal Rust identifiers, docs, and telemetry columns keep the discovery
name for now and are renamed in a later stage.
Tests: jcode-app-core discover (24) and tool::tests (26) pass;
test_benchmark_discovery.py (14) and test_benchmark_discovery_rate.py (15)
pass. jcode-tui has unrelated pre-existing build errors from in-flight
work in skill.rs/input_dispatch.rs, untouched here.
Keeps lib.rs near its budget and gives the platform-independent Windows
checks an obvious home, with a module doc explaining why they exist: the
cfg(not(unix)) tests beside them never run in this repo's Linux CI, which is
the blind spot that hid #715.
One baseline line moves (1216 -> 1219) for the module declaration itself.
Hand-written cases are how the block-comment bug survived the first fix: they
only cover constructs I had already thought of. This uses niri itself as the
oracle instead.
The generator emits random KDL built from constructs that stress a brace
scanner (block comments with unbalanced braces, nested and multiline block
comments, /- slashdash nodes, line comments ending in a brace, quoted and raw
strings, nested compositor binds), keeps only configs real niri accepts, and
the test asserts each still validates after jcode splices its block in.
550-case corpus committed for a hermetic ~3s test. Beyond it, ~2400 configs
across five independent seeds were run out-of-band with no failures.
Verified the corpus earns its keep: removing block-comment handling fails with
'niri rejected a spliced config that it accepted before'.
Keeps editor.rs near the code-size budget, following this test directory's
stated 'split by concern so no file grows unbounded' rule.
Rebaselines the two entries the fixes genuinely grow: editor.rs (+9, the
selection guards) and comm_session.rs (+2, the new isolate_memory argument).
No other baselines touched.
apply_login_provider_profile_env unconditionally called
force_apply_openai_compatible_profile_env, which clears
JCODE_NAMED_PROVIDER_PROFILE. `auth-test --provider-profile <name>`
therefore probed the built-in generic openai-compatible slot and reported
not_configured for profiles that work fine for inference, sending users to
debug config that was never broken.
Named profiles now short-circuit, matching the precedence the explicit
`--provider <compatible>` path already applies. Diagnosed by @e-kotov.
The clang export sat before the mode dispatch, so it applied to `system`
mode too. On a machine without clang the script logged 'using system linker
settings' and then told cargo to invoke a linker that is not installed,
failing every build script with exit 101. Only mold and lld need clang as
the driver; system mode now leaves the choice to cargo's default cc.
A trial that never reached the model measures nothing, so retry it with linear
backoff rather than letting a transient rate limit shrink the sample. The
comparison runner now takes a trial count and honors PROVIDER, since free-tier
providers throttle and budgeted ones run out mid-run.
Runs one case subset against two jcode binaries with a single model, so the
only variable is the discover_tools description, and prints a side-by-side
metric table.
Measures the policy we want to hold: discover_tools is called whenever the
agent reaches for an external product, and vendor commitment goes through
action=select rather than around Discovery.
Unlike scripts/benchmark_discovery.py this suite is catalog-independent. It
scores browse recall over 20 capability-gap tasks (one per category plus two
open-category tasks) against 10 purely local controls that must stay clean, so
recall cannot be bought by over-triggering. It also reports bypass rate: the
agent installing a vendor SDK, driving a vendor CLI, hitting a vendor endpoint,
or connecting an MCP server with no Discovery call at all.
Trials that never reached the model (auth, quota, billing, cost ceiling, rate
limit, connectivity) are marked invalid and excluded from every rate, and a run
with nothing scored cannot pass. Each trial gets a pristine workspace so one
case cannot prime the next. Bypass detection reads tool input only, anchored to
command position, with offline fixtures pinning both directions.
The reporter says a second /rewind N in a session ignores its argument and
lands on the first rewind's target. #432 covered the first rewind's
numbering; nothing covered the repeat-after-undo sequence, which is the one
that loses transcript if targets are computed once and reused.
The test passes, which is itself the useful result: the session layer
recomputes targets correctly after an undo, so the reported bug is not here.
That narrows it to the client/server index path, where the remote client
numbers its own display_messages() while the server re-derives targets from
the stored transcript.
states.rs 1241 -> 1243 and transcript.rs 3177 -> 3213 from the heading-size
and nested-quote work. Recomputed from the committed tree at origin/master.
This is the fourth rebaseline for transcript.rs today, which is the argument
in #692 for splitting it rather than repeatedly moving the line.
Recomputed from the committed tree so they reflect only what is pushed.
Covers the newly-tracked desktop2 files (scene.rs, states.rs, todo.rs),
transcript.rs growth, and two new swallowed-error sites.
This is the mechanical unblock; #692 still tracks whether transcript.rs
should be split rather than repeatedly rebaselined.
#691 proposed returning profile_id from OpenRouterProvider::name(). That
would regress the contract documented on the Provider trait and settled in
6c855cc97 (#329): billing, routing, and provider-class matching key off
name(), so it stays constant per provider class, while the profile identity
is surfaced through display_name().
An existing test pinned the stable name() for a profile *runtime* instance,
but nothing covered a *named* profile, which is the case #691 raised. Assert
both halves there so the split cannot be undone by accident.
70ce4b236 landed between the previous rebaseline and its CI run, growing
transcript.rs further (2799 -> 2874) and adding one unwrap_or_default in
markdown_render_support.rs. Both baselines recomputed from the committed
tree at origin/master, so they reflect only what is pushed.
All three ratchets now pass on that tree. #692 still tracks whether the
markdown-structure rendering should be split out instead of growing.
Quality Guardrails has been red on master since 7ff0e4157 grew
transcript.rs from 2364 to 2799 LOC for the markdown-structure rendering
work. Baseline recomputed from the committed tree at origin/master, so it
reflects only what is pushed, not anyone's in-flight edits.
This unblocks CI; #692 stays open for the real question of whether that
rendering should be split into its own module.
Follow-through on the triage fixes: each was covered at the unit level, but
each bug manifested one layer up, so pin the guarantee where it broke.
- #687: assert the serialized OpenAI tool payload from build_tools contains
no 'uniqueItems', using the reporter's actual MCP schema, and that the
parameter plus its supported constraints survive.
- #689: load the reporter's exact [display] block from a real config.toml
through the global config cache and check both the applied values and the
/config summary the user reads. The bug was whole-file parsing, which a
field-level test cannot see.
- #684: stub wl-copy/xclip/xsel on a temporary PATH to pin the fallback
ordering, including the regression guard that adding the X11 helpers does
not steal the working Wayland path, and that an all-failing chain reports
failure so arboard/OSC 52 still run instead of a false 'Copied' toast.
Also corrects a stale comment that still described arboard as the X11 path.
The picker discovers transcripts from Claude Code, Codex, Pi, OpenCode, and
Cursor so they can be resumed or imported. That is deliberate, but it was
not optional, and it clutters the list for users who only want jcode's own
sessions.
Add `[display] external_sessions` (default true, so behavior is unchanged)
with a JCODE_EXTERNAL_SESSIONS override. Opting out skips the external scans
entirely rather than filtering afterwards, and the setting is part of both
the in-memory and on-disk picker cache keys so toggling takes effect instead
of serving a stale list. Older cache files still parse and are treated as
externals-included.
A value serde did not recognize (e.g. diagram_mode = "inline") aborted
parsing of the whole file, so Config::load silently fell back to defaults
and every unrelated [display] setting - centered, idle_animation,
show_thinking, reasoning_display - looked like it was ignored.
Parse the string-valued display enums leniently: an unknown value degrades
that one field to its default (or None) and leaves the rest of the config
intact. Also accept "inline"/"off" as spellings of diagram_mode = none,
which is exactly the inline-only rendering users mean.
DisplayConfig moves to its own module (lib.rs was at its size ratchet).
- ensure_test_jcode_home_if_unset now takes the shared test-env lock for
the unset->set transition so ~570 create_test_app callers cannot race
tests that scope their own JCODE_HOME (root cause of intermittent
ambient/header/model-picker failures).
- create_test_app clears process-global render state under
render_state_test_lock so it cannot reset a concurrent render test
mid-assertion.
- copy_to_clipboard never touches the OS clipboard under cfg(test);
the wl-copy fork could hang the test binary indefinitely.
- Add scripts/find_unlocked_env_tests.py to report tests mutating
JCODE_HOME without the lock.
- restore pinned_todos_* field initializers dropped by the #662 branch
(master's todos_view/todo_card code still reads them)
- point the scroll/viewport test locks back at RenderStateTestGuard's
MutexGuard signature from the #662 branch
- refresh ratchet baselines for the combined tree
221159294 declared `mod frame_meter;` and `mod scroll_profile;` in
jcode-desktop2/src/main.rs without committing either file. That breaks rustfmt,
not just the build:
cargo fmt --all -- --check
Error writing files: failed to resolve mod `frame_meter`:
crates/jcode-desktop2/src/frame_meter.rs does not exist
rustfmt is the first step of both the Format job and Quality Guardrails, so this
single omission makes every later gate unreachable, and it reports as a
formatting problem rather than a missing file. That is the same masking dynamic
#662 was filed about.
scripts/check_module_files.py checks that every `mod x;` in a tracked .rs file
resolves, handling `#[path = "..."]` overrides, inline `mod x { ... }` blocks,
and the non-mod.rs parent directory form. It runs in ~0.6s with no compiler, and
is wired in ahead of rustfmt in both CI and check_guardrails.sh so the real cause
is named first.
Scope, stated honestly: this catches the mod-without-file variant only. The
earlier instances of the same habit (c9ccb4f01, 96a4a91ed) landed a reference
without its definition, which only the compiler can catch, and I verified this
script reports clean on both. The variant it does catch is the one worth a
dedicated gate, because it is the one that disables rustfmt and therefore hides
everything else.
Verified: fails on current master naming both missing files, and reports clean
on the v0.62.1 release commit (d9ca2a232) and on c9ccb4f01 / 96a4a91ed~1, so it
is not merely matching everything.
refs #662
Master's perf and tui-style work moved ui_header.rs, dispatch.rs,
ui_frame_metrics.rs and several others past their entries. None of this growth
is from this branch; refreshed so the gates reflect current master.