Files
Yichen Jiang 2b8a0a8cff feat(web): choose the agent preset on the new-session screen
The composer seat spent nearly all its life disabled: a session's
composition is fixed once a turn has run. Move the choice to the
new-session screen beside the workspace picker, where it still works,
and let the session header report what a running session runs.

The hero pick is staged rather than applied — that screen precedes the
session it belongs to. It lands when a session becomes current and is
still blank, which covers both the session a workspace connect creates
and the blank one it reuses; riding `sessions.create` would miss the
second. It is spent on first use, matching the workspace picker.

Fix the durability the header field claimed but never had: `agentPreset`
was declared on `SessionHeader` and dropped by the JSONL header line, the
SQLite sessions row, the derived query index, and the cold list
projection, so every resumed session came back composed from nothing.

Add the web e2e lane that would have caught it — the one lane that mounts
the shipped roster, which needed `cordis:group` in the scaffold's Loader
builtins, as `mountRootInclude` already registers.
2026-08-07 00:41:50 +08:00

115 lines
4.6 KiB
TypeScript

/**
* Display metadata is presentation, never capability: every way of getting it
* wrong degrades to "this preset has no display text" rather than to a
* preset that cannot be discovered or mounted. It also cannot carry identity
* — `id` is the directory and `trust` is the root, so neither is readable
* from the file a user can write.
*/
import { mkdtemp, mkdir, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { describe, expect, it } from 'vitest'
import { METADATA_FILE, readPresetMetadata, renderPresetMetadata } from '../src/metadata.ts'
/** A preset directory holding exactly the given metadata text. */
async function presetDir(content?: string): Promise<string> {
const dir = await mkdtemp(join(tmpdir(), 'dsh-preset-meta-'))
await mkdir(dir, { recursive: true })
if (content !== undefined) await writeFile(join(dir, METADATA_FILE), content)
return dir
}
describe('reading display metadata', () => {
it('reads a name and a description', async () => {
const dir = await presetDir('name: 标准模式\ndescription: 完整的编码 agent。\n')
expect(await readPresetMetadata(dir)).toEqual({ name: '标准模式', description: '完整的编码 agent。' })
})
it('treats an absent file as no metadata', async () => {
// The common case: every preset authored by duplicating another starts
// without one, and a picker simply falls back to the id.
expect(await readPresetMetadata(await presetDir())).toEqual({})
})
it('treats malformed YAML as no metadata', async () => {
const dir = await presetDir('name: [unclosed\n')
// Display text is not worth failing discovery over — the composition
// beside it still mounts.
expect(await readPresetMetadata(dir)).toEqual({})
})
it.each([
['a list', '- name: x\n'],
['a scalar', 'just a string\n'],
['an empty document', ''],
])('treats %s as no metadata', async (_label, content) => {
expect(await readPresetMetadata(await presetDir(content))).toEqual({})
})
it('ignores fields that are not text', async () => {
const dir = await presetDir('name: 42\ndescription:\n nested: true\n')
expect(await readPresetMetadata(dir)).toEqual({})
})
it('ignores blank text rather than showing an empty name', async () => {
const dir = await presetDir('name: " "\ndescription: ""\n')
expect(await readPresetMetadata(dir)).toEqual({})
})
it('trims surrounding whitespace', async () => {
const dir = await presetDir('name: " 极简模式 "\n')
expect(await readPresetMetadata(dir)).toEqual({ name: '极简模式' })
})
it('reads a declared order', async () => {
const dir = await presetDir('name: 标准模式\norder: 1\n')
expect(await readPresetMetadata(dir)).toEqual({ name: '标准模式', order: 1 })
})
it('ignores an order that is not a finite number', async () => {
expect(await readPresetMetadata(await presetDir('order: first\n'))).toEqual({})
expect(await readPresetMetadata(await presetDir('order: .inf\n'))).toEqual({})
})
it('cannot carry identity or trust', async () => {
const dir = await presetDir('name: mine\nid: standard\ntrust: system\n')
// A locally authored preset writing `trust: system` must not become a
// shipped one; identity comes from the directory and the root it sits in.
expect(await readPresetMetadata(dir)).toEqual({ name: 'mine' })
})
})
describe('rendering display metadata', () => {
it('round-trips through a read', async () => {
const rendered = renderPresetMetadata({ name: '创造模式', description: '可以改自己的组装。' })
const dir = await presetDir(rendered)
expect(await readPresetMetadata(dir)).toEqual({ name: '创造模式', description: '可以改自己的组装。' })
})
it('stores a declared order', () => {
expect(renderPresetMetadata({ name: '标准模式', order: 1 })).toBe('name: 标准模式\norder: 1\n')
})
it('omits an absent field rather than writing it blank', () => {
expect(renderPresetMetadata({ name: '极简模式' })).toBe('name: 极简模式\n')
// Description without a name is legal too: the picker falls back to the id.
expect(renderPresetMetadata({ description: '只做检索。' })).toBe('description: 只做检索。\n')
})
it('renders nothing when there is nothing to store', () => {
// Clearing both fields removes the file; an empty document would read as
// an intentional blank name.
expect(renderPresetMetadata({})).toBeUndefined()
expect(renderPresetMetadata({ name: ' ', description: '' })).toBeUndefined()
})
})