feat: add economy/maximum presets, tool-lab and subagent-cursor extensions
Some checks failed
CI / windows node 24 / wine blocking (push) Has been skipped
CI / node 22.19 (push) Has been skipped
CI / node 26 (push) Has been skipped
CI / python 3.10 / keyless SDK (push) Has been skipped
CI / python runtime / release-shaped Linux x64 (push) Has been skipped
CI / wine apt cache (push) Successful in 7s
CI / serial / linux (push) Has been skipped
Deploy documentation / build (push) Failing after 1m25s
Deploy documentation / deploy (push) Has been skipped
Landlock Run / Matrix (push) Successful in 5s
Release (vendor) / Pack npm tarballs (push) Failing after 2m47s
Release (dsh) / Pack npm tarballs (push) Failing after 1m56s
Sandbox / sandbox e2e (landlock, ubuntu-24.04) (push) Failing after 1m57s
Sandbox / sandbox e2e (bwrap, ubuntu-latest) (push) Failing after 1m19s
Release (vendor) / Publish to npm (push) Has been skipped
Release (dsh) / Publish to npm (push) Has been skipped
CI / serial / windows (self-hosted standby) (push) Has been cancelled
CI / larger-runner-benchmark (16, linux, dsh-ubuntu-24-04-16core, typecheck) (push) Has been cancelled
Landlock Run / darwin (no platform package — degradation proof) (push) Has been cancelled
Landlock Run / ${{ matrix.platform }} (push) Has been cancelled
CI / node 24 / static (push) Has been cancelled
CI / node 24 / coverage (push) Has been cancelled
CI / node 24 / snapshots and artifacts (push) Has been cancelled
CI / windows node 24 / native complete (push) Has been cancelled
CI / serial / linux (self-hosted standby) (push) Has been cancelled
CI / serial / macos (push) Has been cancelled
CI / larger-runner-benchmark (16, windows, dsh-windows-2025-16core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (32, linux, dsh-ubuntu-24-04-32core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (32, windows, dsh-windows-2025-32core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (4, linux, dsh-ubuntu-24-04-4core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (4, windows, dsh-windows-2025-4core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (64, linux, dsh-ubuntu-24-04-64core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (64, windows, dsh-windows-2025-64core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (8, windows, dsh-windows-2025-8core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (96, linux, dsh-ubuntu-24-04-96core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (96, windows, dsh-windows-2025-96core, production-site) (push) Has been cancelled
CI / consolidated-runner-benchmark (16, linux, dsh-ubuntu-24-04-16core, 16) (push) Has been cancelled
CI / consolidated-runner-benchmark (16, windows, dsh-windows-2025-16core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (32, linux, dsh-ubuntu-24-04-32core, 32) (push) Has been cancelled
CI / consolidated-runner-benchmark (32, windows, dsh-windows-2025-32core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (4, linux, dsh-ubuntu-24-04-4core, 4) (push) Has been cancelled
CI / consolidated-runner-benchmark (4, windows, dsh-windows-2025-4core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (64, linux, dsh-ubuntu-24-04-64core, 32) (push) Has been cancelled
CI / consolidated-runner-benchmark (64, windows, dsh-windows-2025-64core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (8, linux, dsh-ubuntu-24-04-8core, 8) (push) Has been cancelled
CI / consolidated-runner-benchmark (8, windows, dsh-windows-2025-8core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (96, linux, dsh-ubuntu-24-04-96core, 32) (push) Has been cancelled
CI / consolidated-runner-benchmark (96, windows, dsh-windows-2025-96core, 2) (push) Has been cancelled
CI / all checks passed (push) Has been cancelled
Sandbox / sandbox e2e (seatbelt, macos-latest) (push) Has been cancelled
CI / larger-runner-benchmark (8, linux, dsh-ubuntu-24-04-8core, typecheck) (push) Has been cancelled
Sandbox / sandbox e2e (landlock, ubuntu-24.04-arm) (push) Has been cancelled
E2E (real DeepSeek API) / e2e (push) Failing after 1m24s
Some checks failed
CI / windows node 24 / wine blocking (push) Has been skipped
CI / node 22.19 (push) Has been skipped
CI / node 26 (push) Has been skipped
CI / python 3.10 / keyless SDK (push) Has been skipped
CI / python runtime / release-shaped Linux x64 (push) Has been skipped
CI / wine apt cache (push) Successful in 7s
CI / serial / linux (push) Has been skipped
Deploy documentation / build (push) Failing after 1m25s
Deploy documentation / deploy (push) Has been skipped
Landlock Run / Matrix (push) Successful in 5s
Release (vendor) / Pack npm tarballs (push) Failing after 2m47s
Release (dsh) / Pack npm tarballs (push) Failing after 1m56s
Sandbox / sandbox e2e (landlock, ubuntu-24.04) (push) Failing after 1m57s
Sandbox / sandbox e2e (bwrap, ubuntu-latest) (push) Failing after 1m19s
Release (vendor) / Publish to npm (push) Has been skipped
Release (dsh) / Publish to npm (push) Has been skipped
CI / serial / windows (self-hosted standby) (push) Has been cancelled
CI / larger-runner-benchmark (16, linux, dsh-ubuntu-24-04-16core, typecheck) (push) Has been cancelled
Landlock Run / darwin (no platform package — degradation proof) (push) Has been cancelled
Landlock Run / ${{ matrix.platform }} (push) Has been cancelled
CI / node 24 / static (push) Has been cancelled
CI / node 24 / coverage (push) Has been cancelled
CI / node 24 / snapshots and artifacts (push) Has been cancelled
CI / windows node 24 / native complete (push) Has been cancelled
CI / serial / linux (self-hosted standby) (push) Has been cancelled
CI / serial / macos (push) Has been cancelled
CI / larger-runner-benchmark (16, windows, dsh-windows-2025-16core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (32, linux, dsh-ubuntu-24-04-32core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (32, windows, dsh-windows-2025-32core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (4, linux, dsh-ubuntu-24-04-4core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (4, windows, dsh-windows-2025-4core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (64, linux, dsh-ubuntu-24-04-64core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (64, windows, dsh-windows-2025-64core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (8, windows, dsh-windows-2025-8core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (96, linux, dsh-ubuntu-24-04-96core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (96, windows, dsh-windows-2025-96core, production-site) (push) Has been cancelled
CI / consolidated-runner-benchmark (16, linux, dsh-ubuntu-24-04-16core, 16) (push) Has been cancelled
CI / consolidated-runner-benchmark (16, windows, dsh-windows-2025-16core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (32, linux, dsh-ubuntu-24-04-32core, 32) (push) Has been cancelled
CI / consolidated-runner-benchmark (32, windows, dsh-windows-2025-32core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (4, linux, dsh-ubuntu-24-04-4core, 4) (push) Has been cancelled
CI / consolidated-runner-benchmark (4, windows, dsh-windows-2025-4core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (64, linux, dsh-ubuntu-24-04-64core, 32) (push) Has been cancelled
CI / consolidated-runner-benchmark (64, windows, dsh-windows-2025-64core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (8, linux, dsh-ubuntu-24-04-8core, 8) (push) Has been cancelled
CI / consolidated-runner-benchmark (8, windows, dsh-windows-2025-8core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (96, linux, dsh-ubuntu-24-04-96core, 32) (push) Has been cancelled
CI / consolidated-runner-benchmark (96, windows, dsh-windows-2025-96core, 2) (push) Has been cancelled
CI / all checks passed (push) Has been cancelled
Sandbox / sandbox e2e (seatbelt, macos-latest) (push) Has been cancelled
CI / larger-runner-benchmark (8, linux, dsh-ubuntu-24-04-8core, typecheck) (push) Has been cancelled
Sandbox / sandbox e2e (landlock, ubuntu-24.04-arm) (push) Has been cancelled
E2E (real DeepSeek API) / e2e (push) Failing after 1m24s
- new economy and maximum agent presets with three-role pipeline skill - new packages/extensions/tool-lab (home-lab ComfyUI/Docling/Whishper tools) - new packages/subagent/subagent-cursor provider - openrouter balance UI with on-demand refresh - session projection context-seed boundary fold - regenerate docs catalogs; keep local searxng benchmark scripts
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
// @vitest-environment jsdom
|
||||
// BalanceBadge presentation: polls an injected snapshot callback and renders
|
||||
// 余额 in both widths, hiding unknown figures behind the empty placeholder.
|
||||
// 余额 in both widths, hiding unknown figures behind the empty placeholder,
|
||||
// and re-reads through the injected refresh callback when the user clicks it.
|
||||
|
||||
import { act, cleanup, render, screen } from '@testing-library/react'
|
||||
import { act, cleanup, fireEvent, render, screen } from '@testing-library/react'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { makeTranslate } from '@deepseek-ai/dsh-client-test-runtime'
|
||||
import { zh as commonZh } from '@deepseek-ai/dsh-client-locale/src/locales/zh.ts'
|
||||
@@ -34,6 +35,7 @@ function makeProps(over: Partial<BalanceBadgeProps> = {}) {
|
||||
return {
|
||||
wide: true,
|
||||
snapshot: vi.fn(async () => makeBalance()),
|
||||
refresh: vi.fn(async () => makeBalance()),
|
||||
t,
|
||||
...over,
|
||||
} as unknown as Parameters<typeof BalanceBadge>[0]
|
||||
@@ -67,4 +69,37 @@ describe('BalanceBadge', () => {
|
||||
render(<BalanceBadge {...makeProps({ snapshot: vi.fn(async () => makeBalance({ balanceUsd: null })) })} />)
|
||||
expect(await screen.findByLabelText('余额 ——')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('re-reads through refresh on click and renders the fetched figure', async () => {
|
||||
const refresh = vi.fn(async () => makeBalance({ balanceUsd: 42.25 }))
|
||||
render(<BalanceBadge {...makeProps({ refresh })} />)
|
||||
const badge = await screen.findByLabelText('余额 $10.50')
|
||||
await act(async () => { fireEvent.click(badge) })
|
||||
expect(refresh).toHaveBeenCalledOnce()
|
||||
expect(await screen.findByLabelText('余额 $42.25')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('ignores a click while a refresh is still outstanding', async () => {
|
||||
let release: (value: OpenRouterBalance) => void = () => {}
|
||||
const refresh = vi.fn(() => new Promise<OpenRouterBalance>((resolve) => { release = resolve }))
|
||||
render(<BalanceBadge {...makeProps({ refresh })} />)
|
||||
const badge = await screen.findByLabelText('余额 $10.50')
|
||||
await act(async () => { fireEvent.click(badge) })
|
||||
expect(badge.getAttribute('aria-busy')).toBe('true')
|
||||
await act(async () => { fireEvent.click(badge) })
|
||||
expect(refresh).toHaveBeenCalledOnce()
|
||||
await act(async () => { release(makeBalance({ balanceUsd: 7 })) })
|
||||
expect(badge.getAttribute('aria-busy')).toBe('false')
|
||||
await act(async () => { fireEvent.click(badge) })
|
||||
expect(refresh).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it('keeps the last-known figure when a refresh fails', async () => {
|
||||
const refresh = vi.fn(async () => { throw new Error('boom') })
|
||||
render(<BalanceBadge {...makeProps({ refresh })} />)
|
||||
const badge = await screen.findByLabelText('余额 $10.50')
|
||||
await act(async () => { fireEvent.click(badge) })
|
||||
expect(await screen.findByLabelText('余额 $10.50')).toBeTruthy()
|
||||
expect(badge.getAttribute('aria-busy')).toBe('false')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -37,8 +37,10 @@ async function bench() {
|
||||
new RemoteService(ctx)
|
||||
const snapshot = vi.fn<() => Promise<RemoteResult<OpenRouterBalance>>>()
|
||||
.mockResolvedValue({ ok: true, value: balance() })
|
||||
ctx.provide('remote.openRouterUsage', { snapshot })
|
||||
return { ctx, slots: ctx.get('slots') as SlotRegistry, locale, snapshot }
|
||||
const refresh = vi.fn<() => Promise<RemoteResult<OpenRouterBalance>>>()
|
||||
.mockResolvedValue({ ok: true, value: balance({ balanceUsd: 9 }) })
|
||||
ctx.provide('remote.openRouterUsage', { snapshot, refresh })
|
||||
return { ctx, slots: ctx.get('slots') as SlotRegistry, locale, snapshot, refresh }
|
||||
}
|
||||
|
||||
function declare(slots: SlotRegistry): () => void {
|
||||
@@ -84,11 +86,14 @@ describe('ui-openrouter-usage browser plugin', () => {
|
||||
expect(badge.options).toMatchObject({ id: 'openrouter-balance', order: 10 })
|
||||
expect(badge.locale).toBe(NS)
|
||||
|
||||
// Registration reads nothing live; the snapshot rolls at call time.
|
||||
// Registration reads nothing live; both reads roll at call time.
|
||||
expect(b.snapshot).not.toHaveBeenCalled()
|
||||
expect(b.refresh).not.toHaveBeenCalled()
|
||||
const injected = (badge.inject as unknown as () => BalanceBadgeActions)()
|
||||
await expect(injected.snapshot()).resolves.toEqual(balance())
|
||||
expect(b.snapshot).toHaveBeenCalledOnce()
|
||||
await expect(injected.refresh()).resolves.toEqual(balance({ balanceUsd: 9 }))
|
||||
expect(b.refresh).toHaveBeenCalledOnce()
|
||||
await b.ctx.fiber.dispose()
|
||||
})
|
||||
|
||||
@@ -102,6 +107,16 @@ describe('ui-openrouter-usage browser plugin', () => {
|
||||
await b.ctx.fiber.dispose()
|
||||
})
|
||||
|
||||
it('forwards a Remote failure out of the injected refresh verbatim', async () => {
|
||||
const b = await bench()
|
||||
declare(b.slots)
|
||||
await b.ctx.plugin({ inject: [...inject], apply }).await()
|
||||
b.refresh.mockResolvedValueOnce({ ok: false, error: { code: 'REMOTE_ERROR', message: 'nope', details: {} } })
|
||||
const injected = (b.slots.entries('sidebar.footer.action')[0]!.inject as unknown as () => BalanceBadgeActions)()
|
||||
await expect(injected.refresh()).rejects.toThrow('openRouterUsage.refresh failed: REMOTE_ERROR: nope')
|
||||
await b.ctx.fiber.dispose()
|
||||
})
|
||||
|
||||
it('follows locale and recovers across late declaration and declarer reload', async () => {
|
||||
const b = await bench()
|
||||
const fiber = b.ctx.plugin({ inject: [...inject], apply })
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// @vitest-environment jsdom
|
||||
// CostDock presentation: renders the running session cost from the
|
||||
// openRouterCost projection, and nothing at all until a step was priced.
|
||||
// openRouterCost projection plus the subagent subtree read off the session
|
||||
// list, and nothing at all until a step was priced.
|
||||
|
||||
import { act, cleanup, render, screen } from '@testing-library/react'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
@@ -13,9 +14,37 @@ afterEach(cleanup)
|
||||
|
||||
const t = makeTranslate(zh, commonZh)
|
||||
|
||||
function makeProps(over: Partial<Parameters<typeof CostDock>[0]> = {}) {
|
||||
const PARENT = 'session-parent'
|
||||
|
||||
/** One session-list row as much of it as the dock reads. */
|
||||
interface Row {
|
||||
id: string
|
||||
parentId?: string
|
||||
origin?: 'subagent'
|
||||
cost?: { totalUsd: number; pricedSteps: number; unknownModelSteps: number }
|
||||
}
|
||||
|
||||
/** A `useSessions` stub serving `byId` built from the given rows. */
|
||||
function sessionsHook(rows: readonly Row[]) {
|
||||
const byId: Record<string, unknown> = {}
|
||||
for (const row of rows) {
|
||||
byId[row.id] = {
|
||||
id: row.id,
|
||||
...row.parentId === undefined ? {} : { parentId: row.parentId },
|
||||
...row.origin === undefined ? {} : { origin: row.origin },
|
||||
...row.cost === undefined
|
||||
? {}
|
||||
: { projectionValues: { openRouterCost: { ...row.cost, currency: 'USD' } } },
|
||||
}
|
||||
}
|
||||
return (selector: (state: { byId: Record<string, unknown> }) => unknown) => selector({ byId })
|
||||
}
|
||||
|
||||
function makeProps(over: Partial<Parameters<typeof CostDock>[0]> = {}, rows: readonly Row[] = []) {
|
||||
return {
|
||||
sessionId: PARENT,
|
||||
useProjection: vi.fn(() => undefined),
|
||||
useSessions: sessionsHook(rows),
|
||||
t,
|
||||
...over,
|
||||
} as unknown as Parameters<typeof CostDock>[0]
|
||||
@@ -57,4 +86,53 @@ describe('CostDock', () => {
|
||||
act(() => {})
|
||||
expect(useProjection).toHaveBeenCalledWith('openRouterCost')
|
||||
})
|
||||
|
||||
it('adds the subagent subtree to the figure and names its share', () => {
|
||||
render(<CostDock {...makeProps(
|
||||
{ useProjection: () => ({ totalUsd: 1, pricedSteps: 1, unknownModelSteps: 0, currency: 'USD' }) },
|
||||
[{ id: 'child', parentId: PARENT, origin: 'subagent', cost: { totalUsd: 0.5, pricedSteps: 1, unknownModelSteps: 1 } }],
|
||||
)} />)
|
||||
const row = screen.getByText('会话费用 $1.50(含子代理 $0.50)')
|
||||
expect(row.getAttribute('title')).toBe('包含 1 个未知定价步骤')
|
||||
})
|
||||
|
||||
it('follows a nested subagent chain and ignores non-subagent children', () => {
|
||||
render(<CostDock {...makeProps(
|
||||
{ useProjection: () => ({ totalUsd: 1, pricedSteps: 1, unknownModelSteps: 0, currency: 'USD' }) },
|
||||
[
|
||||
{ id: 'child', parentId: PARENT, origin: 'subagent', cost: { totalUsd: 0.25, pricedSteps: 1, unknownModelSteps: 0 } },
|
||||
{ id: 'grandchild', parentId: 'child', origin: 'subagent', cost: { totalUsd: 0.125, pricedSteps: 1, unknownModelSteps: 0 } },
|
||||
// An ordinary fork of the same parent is not delegated work.
|
||||
{ id: 'sibling', parentId: PARENT, cost: { totalUsd: 9, pricedSteps: 1, unknownModelSteps: 0 } },
|
||||
],
|
||||
)} />)
|
||||
expect(screen.getByText('会话费用 $1.38(含子代理 $0.38)')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('shows the subagent subtree alone when the session itself priced nothing', () => {
|
||||
render(<CostDock {...makeProps(
|
||||
{ useProjection: () => undefined },
|
||||
[{ id: 'child', parentId: PARENT, origin: 'subagent', cost: { totalUsd: 0.75, pricedSteps: 2, unknownModelSteps: 0 } }],
|
||||
)} />)
|
||||
expect(screen.getByText('会话费用 $0.75(含子代理 $0.75)')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('skips a descendant the client holds no projection value for', () => {
|
||||
render(<CostDock {...makeProps(
|
||||
{ useProjection: () => ({ totalUsd: 2, pricedSteps: 1, unknownModelSteps: 0, currency: 'USD' }) },
|
||||
[{ id: 'cold-child', parentId: PARENT, origin: 'subagent' }],
|
||||
)} />)
|
||||
expect(screen.getByText('会话费用 $2.00')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('terminates on a parentId cycle', () => {
|
||||
render(<CostDock {...makeProps(
|
||||
{ useProjection: () => ({ totalUsd: 1, pricedSteps: 1, unknownModelSteps: 0, currency: 'USD' }) },
|
||||
[
|
||||
{ id: 'a', parentId: PARENT, origin: 'subagent', cost: { totalUsd: 0.5, pricedSteps: 1, unknownModelSteps: 0 } },
|
||||
{ id: PARENT, parentId: 'a', origin: 'subagent', cost: { totalUsd: 99, pricedSteps: 1, unknownModelSteps: 0 } },
|
||||
],
|
||||
)} />)
|
||||
expect(screen.getByText('会话费用 $1.50(含子代理 $0.50)')).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user