Files
deepseek-harness/examples/desktop/docs/visualize-checklist-report.md
ZiyaZhang e8f5c0b51b feat(desktop): DSH Electron desktop shell — harness internals visualized
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.
2026-07-18 12:59:34 -07:00

92 lines
7.8 KiB
Markdown

# 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:
1. **Config (`config/deepseek-jsonrpc.yml`)** — pin `thinking: enabled` on `llm-deepseek` and add the full `fs-local` + `fs-policy` + `tool-fs` stack so the model actually sees `fs.edit`. Test: `test/deepseek-jsonrpc-showcase.test.js`.
2. **Renderer fallback (`src/renderer/renderer.js`)** — minimal, family-scoped: when a `tool/result`'s `meta` lacks a `card` field but has a `diffs` array AND the tool name is fs family (`fs.edit` / `fs.write` / `fs.read` or 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).
3. **Upstream ledger (`docs/upstream-ledger.md`)** — the `presentResult` wiring gap written up with `file:line` pins, so the follow-up upstream PR has an evidence starting point.
4. **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`](visualize-checklist-shots/chat.png)
- Tracing page: [`tracing.png`](visualize-checklist-shots/tracing.png)
## What the config change does
Two edits to `config/deepseek-jsonrpc.yml`:
1. **Pinned `thinking: enabled` on the `llm-deepseek` entry.** The provider default is already `enabled` today, 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).
2. **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 for `fs-local` alone (matching `deepseek-vibe.yml`), but the model needs `tool-fs` to actually see `fs.edit` in 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 from `examples/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`](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 persists `execute()` 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`.
- `.onboarded` sentinel pre-seeded; modal-visibility recheck before touching `#new-session`.
- `DEEPSEEK_API_KEY` read from `~/harness/deepseek-harness-dev/.env`, never logged.
- `DSH_QA` explicitly 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`.