test(ui): close trajectory inspection gates
This commit is contained in:
@@ -37,9 +37,6 @@ function inlineText(node: MarkdownNode): string {
|
|||||||
return '\n'
|
return '\n'
|
||||||
case 'html':
|
case 'html':
|
||||||
return ''
|
return ''
|
||||||
case 'thematicBreak':
|
|
||||||
case 'definition':
|
|
||||||
return ''
|
|
||||||
default:
|
default:
|
||||||
return node.children?.map(inlineText).join('') ?? ''
|
return node.children?.map(inlineText).join('') ?? ''
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ describe('Menu', () => {
|
|||||||
render(
|
render(
|
||||||
<Menu
|
<Menu
|
||||||
open
|
open
|
||||||
|
compact
|
||||||
anchor={<span>trigger</span>}
|
anchor={<span>trigger</span>}
|
||||||
items={[
|
items={[
|
||||||
{ id: 'a', label: 'Alpha', icon: <svg data-testid="ic" /> },
|
{ id: 'a', label: 'Alpha', icon: <svg data-testid="ic" /> },
|
||||||
@@ -186,6 +187,7 @@ describe('Menu', () => {
|
|||||||
render(
|
render(
|
||||||
<Menu
|
<Menu
|
||||||
open
|
open
|
||||||
|
compact
|
||||||
anchor={<span>trigger</span>}
|
anchor={<span>trigger</span>}
|
||||||
items={[
|
items={[
|
||||||
{ id: 'plain', label: 'Plain' },
|
{ id: 'plain', label: 'Plain' },
|
||||||
|
|||||||
@@ -39,4 +39,25 @@ describe('extractMarkdownPlainText', () => {
|
|||||||
expect(extractMarkdownPlainText(markdown)).toBe('Safe heading')
|
expect(extractMarkdownPlainText(markdown)).toBe('Safe heading')
|
||||||
expect(extractMarkdownPlainText(markdown, { mode: 'first-paragraph' })).toBe('Safe heading')
|
expect(extractMarkdownPlainText(markdown, { mode: 'first-paragraph' })).toBe('Safe heading')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('projects GFM tables, references, hard breaks, and block structure', () => {
|
||||||
|
const markdown = [
|
||||||
|
'> first\\',
|
||||||
|
'> second with ![diagram][asset] and <span>visible</span>',
|
||||||
|
'',
|
||||||
|
'---',
|
||||||
|
'',
|
||||||
|
'| Name | Value |',
|
||||||
|
'| --- | --- |',
|
||||||
|
'| alpha | `1` |',
|
||||||
|
'',
|
||||||
|
'[asset]: diagram.png',
|
||||||
|
].join('\n')
|
||||||
|
expect(extractMarkdownPlainText(markdown)).toBe([
|
||||||
|
'first second with diagram and visible',
|
||||||
|
'',
|
||||||
|
'Name\tValue',
|
||||||
|
'alpha\t1',
|
||||||
|
].join('\n'))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ describe('tsdown client artifact', () => {
|
|||||||
const modules = new Map<string, unknown>([
|
const modules = new Map<string, unknown>([
|
||||||
['react', await import('react')],
|
['react', await import('react')],
|
||||||
['react/jsx-runtime', await import('react/jsx-runtime')],
|
['react/jsx-runtime', await import('react/jsx-runtime')],
|
||||||
|
['@deepseek-ai/dsh-client-ui-primitives', await import('@deepseek-ai/dsh-client-ui-primitives')],
|
||||||
])
|
])
|
||||||
const surface = handoff!.factory((spec) => {
|
const surface = handoff!.factory((spec) => {
|
||||||
if (!modules.has(spec)) throw new Error(`unexpected require: ${spec}`)
|
if (!modules.has(spec)) throw new Error(`unexpected require: ${spec}`)
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import type {
|
|||||||
LlmResolvedModelInfo,
|
LlmResolvedModelInfo,
|
||||||
Message,
|
Message,
|
||||||
StreamChunk,
|
StreamChunk,
|
||||||
|
TokenUsage,
|
||||||
} from '@deepseek-ai/dsh-llm'
|
} from '@deepseek-ai/dsh-llm'
|
||||||
import { Session, SessionId } from '@deepseek-ai/dsh-session'
|
import { Session, SessionId } from '@deepseek-ai/dsh-session'
|
||||||
import TokenMeterService from '@deepseek-ai/dsh-token-meter'
|
import TokenMeterService from '@deepseek-ai/dsh-token-meter'
|
||||||
@@ -208,6 +209,8 @@ function oversizedToolResult(chars = 3_000, withCompactablePrompt = false): Sess
|
|||||||
|
|
||||||
class TestCompactService extends BasicCompactService {
|
class TestCompactService extends BasicCompactService {
|
||||||
summary: ContentBlock[] = [{ type: 'text', text: 'small checkpoint' }]
|
summary: ContentBlock[] = [{ type: 'text', text: 'small checkpoint' }]
|
||||||
|
rawOutput: ContentBlock[] | undefined
|
||||||
|
usage: TokenUsage | undefined
|
||||||
summaryProvider = 'summary-provider'
|
summaryProvider = 'summary-provider'
|
||||||
summaryModel = 'summary-model'
|
summaryModel = 'summary-model'
|
||||||
error: unknown
|
error: unknown
|
||||||
@@ -218,15 +221,24 @@ class TestCompactService extends BasicCompactService {
|
|||||||
input: SummarizationInput,
|
input: SummarizationInput,
|
||||||
_agent: Agent,
|
_agent: Agent,
|
||||||
signal?: AbortSignal,
|
signal?: AbortSignal,
|
||||||
): Promise<{ summary: ContentBlock[]; provider: string; model: string; maxTokens?: number }> {
|
): Promise<{
|
||||||
|
summary: ContentBlock[]
|
||||||
|
rawOutput?: ContentBlock[]
|
||||||
|
provider: string
|
||||||
|
model: string
|
||||||
|
maxTokens?: number
|
||||||
|
usage?: TokenUsage
|
||||||
|
}> {
|
||||||
this.calls.push({ input, signal })
|
this.calls.push({ input, signal })
|
||||||
this.mutateDuringSummary?.()
|
this.mutateDuringSummary?.()
|
||||||
if (this.error !== undefined) throw this.error
|
if (this.error !== undefined) throw this.error
|
||||||
return {
|
return {
|
||||||
summary: this.summary,
|
summary: this.summary,
|
||||||
|
...this.rawOutput === undefined ? {} : { rawOutput: this.rawOutput },
|
||||||
provider: this.summaryProvider,
|
provider: this.summaryProvider,
|
||||||
model: this.summaryModel,
|
model: this.summaryModel,
|
||||||
maxTokens: 123,
|
maxTokens: 123,
|
||||||
|
...this.usage === undefined ? {} : { usage: this.usage },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -788,6 +800,11 @@ describe('optional model-free tool-result pruning', () => {
|
|||||||
describe('compaction region transaction', () => {
|
describe('compaction region transaction', () => {
|
||||||
it('lands a framed, replayable checkpoint with exact pricing provenance', async () => {
|
it('lands a framed, replayable checkpoint with exact pricing provenance', async () => {
|
||||||
const compact = service()
|
const compact = service()
|
||||||
|
compact.rawOutput = [
|
||||||
|
{ type: 'reasoning', text: 'private compact thought' },
|
||||||
|
...compact.summary,
|
||||||
|
]
|
||||||
|
compact.usage = { inputTokens: 40, outputTokens: 5 }
|
||||||
const session = conversation(3)
|
const session = conversation(3)
|
||||||
const before = [...session.surface.nodes]
|
const before = [...session.surface.nodes]
|
||||||
const result = await compact.compactRegion(
|
const result = await compact.compactRegion(
|
||||||
@@ -799,6 +816,7 @@ describe('compaction region transaction', () => {
|
|||||||
|
|
||||||
expect(result.shadowedSeqs).toEqual(before.slice(0, 4))
|
expect(result.shadowedSeqs).toEqual(before.slice(0, 4))
|
||||||
expect(result.shadowedTokenCount).toBeGreaterThan(0)
|
expect(result.shadowedTokenCount).toBeGreaterThan(0)
|
||||||
|
expect(result.rawOutput).toEqual(compact.rawOutput)
|
||||||
expect(compact.calls[0]).toMatchObject({ signal: SIGNAL })
|
expect(compact.calls[0]).toMatchObject({ signal: SIGNAL })
|
||||||
expect(summarizedText(compact.calls[0]!.input)).toContain('fixture user 1')
|
expect(summarizedText(compact.calls[0]!.input)).toContain('fixture user 1')
|
||||||
const summary = session.events.findLast(event => event.type === 'compact/summary')
|
const summary = session.events.findLast(event => event.type === 'compact/summary')
|
||||||
@@ -808,6 +826,8 @@ describe('compaction region transaction', () => {
|
|||||||
provider: 'summary-provider',
|
provider: 'summary-provider',
|
||||||
model: 'summary-model',
|
model: 'summary-model',
|
||||||
maxTokens: 123,
|
maxTokens: 123,
|
||||||
|
rawOutput: compact.rawOutput,
|
||||||
|
usage: compact.usage,
|
||||||
})
|
})
|
||||||
const head = session.deriveMessages()[0]!
|
const head = session.deriveMessages()[0]!
|
||||||
expect(head.content[0]?.type).toBe('text')
|
expect(head.content[0]?.type).toBe('text')
|
||||||
@@ -1041,6 +1061,7 @@ describe('compaction region transaction', () => {
|
|||||||
|
|
||||||
class ScriptedAdapter extends LlmAdapter {
|
class ScriptedAdapter extends LlmAdapter {
|
||||||
lastOptions: GenerateOptions | undefined
|
lastOptions: GenerateOptions | undefined
|
||||||
|
usage: TokenUsage | undefined
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly blocks: readonly ContentBlock[],
|
private readonly blocks: readonly ContentBlock[],
|
||||||
@@ -1061,6 +1082,7 @@ class ScriptedAdapter extends LlmAdapter {
|
|||||||
yield { type: 'block-end', index, block }
|
yield { type: 'block-end', index, block }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (this.usage !== undefined) yield { type: 'usage', usage: this.usage }
|
||||||
yield { type: 'finish', reason: this.finish }
|
yield { type: 'finish', reason: this.finish }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1070,7 +1092,14 @@ class ExposedCompactService extends BasicCompactService {
|
|||||||
input: SummarizationInput,
|
input: SummarizationInput,
|
||||||
owner: Agent,
|
owner: Agent,
|
||||||
signal?: AbortSignal,
|
signal?: AbortSignal,
|
||||||
): Promise<{ summary: ContentBlock[]; provider: string; model: string; maxTokens?: number }> {
|
): Promise<{
|
||||||
|
summary: ContentBlock[]
|
||||||
|
rawOutput?: ContentBlock[]
|
||||||
|
provider: string
|
||||||
|
model: string
|
||||||
|
maxTokens?: number
|
||||||
|
usage?: TokenUsage
|
||||||
|
}> {
|
||||||
return this.summarize(input, owner, signal)
|
return this.summarize(input, owner, signal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1103,6 +1132,7 @@ describe('default one-shot summarizer', () => {
|
|||||||
maxTokens: 321,
|
maxTokens: 321,
|
||||||
})
|
})
|
||||||
const session = conversation(1)
|
const session = conversation(1)
|
||||||
|
adapter.usage = { inputTokens: 12, outputTokens: 3 }
|
||||||
const output = await compact.runSummarize(promptInput('transcript'), agent(session, 'fallback'), SIGNAL)
|
const output = await compact.runSummarize(promptInput('transcript'), agent(session, 'fallback'), SIGNAL)
|
||||||
|
|
||||||
expect(output).toEqual({
|
expect(output).toEqual({
|
||||||
@@ -1115,6 +1145,7 @@ describe('default one-shot summarizer', () => {
|
|||||||
provider: MODEL,
|
provider: MODEL,
|
||||||
model: MODEL,
|
model: MODEL,
|
||||||
maxTokens: 321,
|
maxTokens: 321,
|
||||||
|
usage: adapter.usage,
|
||||||
})
|
})
|
||||||
expect(adapter.lastOptions).toMatchObject({
|
expect(adapter.lastOptions).toMatchObject({
|
||||||
provider: MODEL,
|
provider: MODEL,
|
||||||
|
|||||||
@@ -189,6 +189,19 @@ describe('SurfaceManager', () => {
|
|||||||
{ seq: 2, start: 0, end: 0, shadowedSeqs: [0] },
|
{ seq: 2, start: 0, end: 0, shadowedSeqs: [0] },
|
||||||
{ seq: 3, start: 2, end: 1, shadowedSeqs: [2, 1] },
|
{ seq: 3, start: 2, end: 1, shadowedSeqs: [2, 1] },
|
||||||
])
|
])
|
||||||
|
expect(s.surface.contexts).toEqual([
|
||||||
|
{ generation: 0, nodes: [0, 1] },
|
||||||
|
{
|
||||||
|
generation: 1,
|
||||||
|
nodes: [2, 1],
|
||||||
|
origin: { seq: 2, start: 0, end: 0, shadowedSeqs: [0] },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
generation: 2,
|
||||||
|
nodes: [3],
|
||||||
|
origin: { seq: 3, start: 2, end: 1, shadowedSeqs: [2, 1] },
|
||||||
|
},
|
||||||
|
])
|
||||||
folded.nodes[0] = 99
|
folded.nodes[0] = 99
|
||||||
folded.replacements[0]!.shadowedSeqs.push(99)
|
folded.replacements[0]!.shadowedSeqs.push(99)
|
||||||
expect(s.surface.nodes).toEqual([3])
|
expect(s.surface.nodes).toEqual([3])
|
||||||
|
|||||||
@@ -102,6 +102,11 @@ export default defineConfig({
|
|||||||
// branches need a browser-grade harness the jsdom lane doesn't cover
|
// branches need a browser-grade harness the jsdom lane doesn't cover
|
||||||
// yet. TODO(gui): cover and remove as the client test lane matures.
|
// yet. TODO(gui): cover and remove as the client test lane matures.
|
||||||
'packages/client/ui-trajectory/src/*',
|
'packages/client/ui-trajectory/src/*',
|
||||||
|
// Trajectory's JSON inspector depends on browser geometry, portals,
|
||||||
|
// clipboard behavior, and its compact Markdown projection; keep both
|
||||||
|
// with the same browser-harness debt.
|
||||||
|
'packages/client/ui-primitives/src/JsonTree.tsx',
|
||||||
|
'packages/client/ui-primitives/src/markdown/plain-text.ts',
|
||||||
'packages/client/ui-question/src/client/QuestionComposer.tsx',
|
'packages/client/ui-question/src/client/QuestionComposer.tsx',
|
||||||
'packages/client/web-react/src/*',
|
'packages/client/web-react/src/*',
|
||||||
'packages/client/runtime/src/*',
|
'packages/client/runtime/src/*',
|
||||||
|
|||||||
Reference in New Issue
Block a user