Files
deepseek-harness/examples/desktop/test/capabilities.test.js
T
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

131 lines
5.1 KiB
JavaScript

// Pure-module tests for src/renderer/capabilities.js (Ticket G, task #125).
//
// The normalizer's contract is what every gated UI surface keys off, so
// the "wire didn't say" default is locked here — a regression that grays
// a legacy v1 daemon by silently flipping this default would be
// user-visible in a demo (all buttons in a v1 runtime would go dark).
'use strict'
const test = require('node:test')
const assert = require('node:assert/strict')
const {
normalizeCapabilities,
capabilityDisabledTitle,
CAPABILITY_KEYS,
CAPABILITIES_ALL_SUPPORTED,
DISABLED_TOOLTIPS,
} = require('../src/renderer/capabilities.js')
test('normalizeCapabilities: null / undefined input → all six default to true (v1 server posture)', () => {
for (const bad of [null, undefined]) {
const out = normalizeCapabilities(bad)
for (const key of CAPABILITY_KEYS) {
assert.equal(out[key], true, `expected caps.${key} === true for ${bad} input, got ${out[key]}`)
}
}
})
test('normalizeCapabilities: non-object input → all six default to true (defensive shape)', () => {
for (const bad of ['yes', 42, true, false]) {
const out = normalizeCapabilities(bad)
for (const key of CAPABILITY_KEYS) {
assert.equal(out[key], true, `expected caps.${key} === true for ${bad} input, got ${out[key]}`)
}
}
})
test('normalizeCapabilities: empty object → all six default to true (wire-silent ≠ unsupported)', () => {
const out = normalizeCapabilities({})
for (const key of CAPABILITY_KEYS) {
assert.equal(out[key], true, `caps.${key} should default to true when the envelope lacks the key`)
}
})
test('normalizeCapabilities: explicit false is the only way to gray a bit', () => {
const out = normalizeCapabilities({
cancel: false,
fork: false,
plugins: false,
})
assert.equal(out.cancel, false)
assert.equal(out.fork, false)
assert.equal(out.plugins, false)
// Untouched bits stay `true` (default posture is preserved).
assert.equal(out.sessionQuery, true)
assert.equal(out.setConfig, true)
assert.equal(out.compact, true)
})
test('normalizeCapabilities: null and undefined for a specific key are NOT gray (only explicit false)', () => {
// A daemon that ships `capabilities: { cancel: null }` is a bug on that
// side, but it must not accidentally gray the Cancel button. Only
// `false` grays.
const out = normalizeCapabilities({ cancel: null, fork: undefined, plugins: 0 })
assert.equal(out.cancel, true, 'null must NOT gray cancel — only explicit false does')
assert.equal(out.fork, true, 'undefined must NOT gray fork — only explicit false does')
assert.equal(out.plugins, true, '0 must NOT gray plugins — only explicit false does')
})
test('normalizeCapabilities: unknown keys on the envelope are ignored (don\'t leak into the UI)', () => {
const out = normalizeCapabilities({ cancel: false, mysterious: true, weird: 'value' })
assert.equal(out.cancel, false)
assert.equal(out.mysterious, undefined, 'unknown keys must not surface on the normalized shape')
assert.equal(out.weird, undefined)
// All six known bits are present.
for (const key of CAPABILITY_KEYS) {
assert.ok(key in out, `known capability ${key} must be on the normalized shape`)
}
})
test('normalizeCapabilities: real integration/echo daemon shape is fully supported', () => {
// The current integration daemon (packages/ui/jsonrpc/src/server.ts:729)
// ships all six as `true`. Under the current server, the shell should
// gray nothing.
const wireShape = {
sessionLifecycle: true,
cancel: true,
sessionQuery: true,
setConfig: true,
fork: true,
plugins: true,
compact: true,
}
const out = normalizeCapabilities(wireShape)
for (const key of CAPABILITY_KEYS) {
assert.equal(out[key], true, `full-support wire shape must not gray ${key}`)
}
})
test('capabilityDisabledTitle: returns canonical string per capability', () => {
for (const key of CAPABILITY_KEYS) {
const t = capabilityDisabledTitle(key)
assert.ok(typeof t === 'string' && t.length > 0,
`capability ${key} must have a canonical disabled tooltip`)
assert.equal(t, DISABLED_TOOLTIPS[key])
}
})
test('capabilityDisabledTitle: unknown key returns empty string (do-no-harm fallback)', () => {
assert.equal(capabilityDisabledTitle('nonsense'), '')
assert.equal(capabilityDisabledTitle(''), '')
assert.equal(capabilityDisabledTitle(undefined), '')
})
test('CAPABILITIES_ALL_SUPPORTED matches every CAPABILITY_KEY and is all-true', () => {
const keys = Object.keys(CAPABILITIES_ALL_SUPPORTED).sort()
assert.deepEqual(keys, [...CAPABILITY_KEYS].sort(),
'CAPABILITIES_ALL_SUPPORTED must cover exactly the CAPABILITY_KEYS set')
for (const key of CAPABILITY_KEYS) {
assert.equal(CAPABILITIES_ALL_SUPPORTED[key], true)
}
})
test('normalize output has no aliasing between two calls (safe to mutate reads)', () => {
const a = normalizeCapabilities({ cancel: false })
const b = normalizeCapabilities({ fork: false })
a.compact = false
// b must not see a's mutation.
assert.equal(b.compact, true, 'each normalize call must yield an independent object')
})