Minimal Electron shell over the DSH JSON-RPC runtime — a first-look at what a ChatGPT.app-style host on top of the DeepSeek Harness looks like, with the harness's normally-invisible internals (trace timeline, context surface, subagent tree, compaction, plugin registry, rubrics) brought forward as first-class UI surfaces so plugin authors and researchers can see what the agent is actually doing. Runs against three keyless-to-live profiles (stdio-echo works on master out of the box; daemon-echo / daemon-vibe-echo activate once the daemon-demo lands; stdio-deepseek and daemon-vibe hit the real DeepSeek API when you supply a key). HARNESS_DEV auto-resolves to the in-repo runtime when this shell ships under examples/desktop/, so a fresh clone launches without config; env DSH_DEV_ROOT overrides for custom layouts, and a sibling deepseek-harness-dev/ checkout is the original dev workflow. Cold-clone gate (P0 fixes for first-time-clone usability): - HARNESS_DEV: 3-candidate resolver (env → walk-up in-repo marker → sibling), unit-tested via mock fs so ordering is locked without needing either real layout on disk. - config yml leaves rewritten at assemble time so the sibling-clone paths (../../deepseek-harness-dev/examples/echo-agent/…) become the in-repo paths (../../echo-agent/…) in the released tree — source yml stays usable for local dev, released tree ships a working shape. - pnpm-workspace.yaml allowBuilds.electron = true (was placeholder). - missing-key card in stdio-deepseek offers a one-click switch to stdio-echo (the keyless profile that works on master) rather than daemon-echo (blocked on the not-yet-shipped daemon-demo). - assemble-oss-release.sh rewrites the source-side breadcrumb name 'dsh-desktop-demo' → 'dsh-desktop' for the released package.json. FOUC guard on the onboarding gate (41fc5df carried) keeps the first-launch splash from flashing before the runtime probe finishes. Test suite (1634 tests in source, 3990 in the runtime repo) covers resolver ordering, renderer classifiers, trace timeline shape, compaction diff rendering, rubric parity, and the missing-key onboarding paths.
7.8 KiB
Visualize checklist report — fix/default-profile-showcase
Branch: fix/default-profile-showcase
Latest verification run: default profile stdio-deepseek, real DeepSeek API, 2 turns / 1 session, 2026-07-18 18:42 UTC.
Script: scripts/showcase-12x12-verify.mjs (internal QA driver, kept out of the released tree — hardcoded absolute paths).
Raw evidence: /tmp/dsh-showcase/result.json + replay.log (audit trail).
What lands in this branch
Four commits, each with a test lock:
- Config (
config/deepseek-jsonrpc.yml) — pinthinking: enabledonllm-deepseekand add the fullfs-local+fs-policy+tool-fsstack so the model actually seesfs.edit. Test:test/deepseek-jsonrpc-showcase.test.js. - Renderer fallback (
src/renderer/renderer.js) — minimal, family-scoped: when atool/result'smetalacks acardfield but has adiffsarray AND the tool name is fs family (fs.edit/fs.write/fs.reador the short-form aliases), synthesize{ card: 'diff', title, diffs }and route through the existing diff-card renderer. Test:test/renderer-diff-card-fallback.test.js+test/fixtures/fs-edit-wire-shape.json(the real wire shape captured on this run — the shape lock protects against upstream shape drift AND against the fallback regressing). - Upstream ledger (
docs/upstream-ledger.md) — thepresentResultwiring gap written up withfile:linepins, so the follow-up upstream PR has an evidence starting point. - This report + screenshots — twelve verdicts, all with DOM-selector evidence and screenshot references.
The renderer change is deliberately narrow: it does NOT touch the presentResult call site, does NOT introduce a tool registry to the shell, and becomes dead code the moment the runtime seam emits a proper card: 'diff' view. The shape test still passes via the primary branch after that lands, so there is no coupling between land order.
12/12 verdict table
| # | Item | Verdict | Evidence |
|---|---|---|---|
| 1 | user bubble | PASS | .msg .role-label matches present in both turns of the stream. |
| 2 | assistant stream | PASS | .msg.assistant present with visible non-fork text — turn 1 emits the calculation body, turn 2 replies briefly. |
| 3 | reasoning fold | PASS | 6 .reasoning-block elements rendered across the two turns (thinking mode active — the config change pins thinking: enabled). |
| 4 | tool call row + args | PASS | 4 .tool-block rows across the two turns (bash + fs.read + fs.edit + supporting call); { } brace-button clicks reveal args. |
| 5 | diff card (family=fs) | PASS | 1 .card-diff element rendered after fs.edit; wire meta {diffs:[...]} (no card field on the wire) was routed through the fs-family fallback described in the "What lands" section. |
| 6 | terminal card (bash) | PASS | bash .tool-block.family-bash present; card body carries command text + captured stdout. |
| 7 | turn footer with real values | PASS | Footer text > 5 chars, no all-dash string. Shows real usage/duration numbers. |
| 8 | trace row + tri-view | PASS | .trace-card present, all three tri-chips (tree/timeline/graph) toggle their panels to visible. |
| 9 | JSON drawer payload | PASS | { } badge opens a drawer or reveals the inline <pre> containing the full args JSON. |
| 10 | Tracing page row | PASS | #tracing-page-tbody tr count = 1; the row matches this session's id and its cells are populated (not all-dash). |
| 11 | step boundary chips | PASS | .trace-step-chips / .trace-step-meta present in the stream. |
| 12 | context meter numeric | PASS | Meter title carries a real numeric token count against the assumed-window fallback. |
12/12 pass.
Screenshots:
- Chat view after both turns:
chat.png - Tracing page:
tracing.png
What the config change does
Two edits to config/deepseek-jsonrpc.yml:
- Pinned
thinking: enabledon thellm-deepseekentry. The provider default is alreadyenabledtoday, so this is a pin, not a semantic change. It guarantees that a future flip in the upstream default won't silently drop the reasoning-delta stream on the default profile (which would take the fold visualization with it). - Added the full model-facing filesystem stack:
fs-local(backend) +fs-policy(read-before-write contract) +tool-fs(model-facing fs.read/edit/write). The user's brief asked forfs-localalone (matchingdeepseek-vibe.yml), but the model needstool-fsto actually seefs.editin its tool list — without it the model literally replies "no fs tool available", which is what happened on the first verification pass. All three pieces are copy-mirrored fromexamples/coding-agent/cordis.yml, the canonical composition.
Test lock: test/deepseek-jsonrpc-showcase.test.js asserts both fields in the same static-text style as the existing deepseek-config-workspace-context.test.js.
What the renderer fallback does
Same tool wires the same meta shape today whether it succeeds through presentResult() or not — the runtime persists the tool's raw execute() return verbatim (see the upstream ledger). For fs.edit that shape is { diffs: [...] } with no card discriminant, and the primary dispatch at src/renderer/renderer.js:4744 was routing it to the raw-text fallback branch.
The added branch is scoped strictly to (!isError) + (fs-family tool name) + (view.diffs array present) + (no card field). It rebuilds a synthetic { card: 'diff', title, diffs } view and calls the same renderDiffCard the primary path uses, so the visual is identical to what a presentResult-emitting runtime would produce.
Profile tool matrix (default vs. vibe)
Both profiles now share the same headline-visualization surface. The other tools differ (vibe carries web + tool-cordis on top).
| Profile | thinking | bash | fs backend | fs policy | fs tools | web | tool-cordis |
|---|---|---|---|---|---|---|---|
| stdio-deepseek (default) | enabled (pinned) | yes | fs-local | fs-policy | tool-fs | — | — |
| stdio-vibe-deepseek | provider-default | yes | fs-local | — | — | yes | yes |
Upstream ledger reference
The presentResult wiring gap — root cause of the "diff card never renders without a shell-side workaround" symptom — is written up as ledger entry L-1 in docs/upstream-ledger.md, with file-line pins into deepseek-harness:
packages/fs/tool-fs/src/edit.ts:92-96—execute()returns raw{diffs}meta.packages/fs/tool-fs/src/edit.ts:111-116—presentResult()authors{card:'diff', ...}but is never invoked.packages/core/agent-loop/src/loop.ts:584-594— the runtime persistsexecute()meta verbatim on the wire.packages/core/tools/src/index.ts:787-790— the tool-registry side of the same seam.
The upstream fix is a small RFC (either agent-loop invokes presentResult at emit time, or the shell gains access to the tool registry to invoke it on receipt). Either way, the fallback added here becomes dead code but the shape test keeps passing via the primary card:'diff' branch, so the two changes are independent.
Isolation discipline followed
- CDP 9310 (not 9273/9299).
--user-data-dir=/tmp/dsh-showcase/userdata+DSH_DESKTOP_HOME=/tmp/dsh-showcase/dshhome.DSH_DEV_ROOT=$HOME/harness/deepseek-harness-dev..onboardedsentinel pre-seeded; modal-visibility recheck before touching#new-session.DEEPSEEK_API_KEYread from~/harness/deepseek-harness-dev/.env, never logged.DSH_QAexplicitly unset.- Child processes reclaimed on exit (SIGTERM then SIGKILL).
- No touch to
~/.dsh-desktop, no touch to the user's Electron install.
Cost
2 real API turns on the final green run (well under the ≤6 budget). Each turn was a full round-trip with tool calls; total wall time ≈ 15 s from prompt send to second turn/end.