dsh-context-compression-improved 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.ja.md +51 -0
- package/CHANGELOG.ko.md +51 -0
- package/CHANGELOG.md +55 -0
- package/CHANGELOG.zh.md +45 -0
- package/package.json +1 -1
- package/packages/selector/lib/advisor-state.js +4 -231
- package/packages/selector/lib/client.d.ts +0 -24
- package/packages/selector/lib/client.js +6 -501
- package/packages/selector/lib/index.d.ts +4 -10
- package/packages/selector/lib/index.js +65 -235
- package/packages/selector/lib/pruner.d.ts +13 -248
- package/packages/selector/lib/pruner.js +148 -552
- package/packages/selector/src/client/EstimatorControls.tsx +277 -378
- package/packages/selector/src/client/index.ts +0 -17
- package/packages/selector/src/client/locales.ts +196 -234
- package/packages/selector/src/client/preset-options.ts +3 -2
- package/packages/selector/src/client/settings-section.tsx +8 -17
- package/packages/selector/src/index.ts +463 -710
- package/packages/selector/src/preset-overlay.ts +60 -1
- package/packages/selector/src/profiles.ts +4 -27
- package/packages/selector/src/pruner/state.ts +50 -73
- package/packages/selector/src/pruner.ts +2402 -2730
- package/packages/selector/src/runtime/audit.ts +27 -21
- package/packages/selector/src/runtime/config.ts +6 -32
- package/packages/selector/src/runtime/tokenpilot/advisor-state.ts +16 -0
- package/packages/selector/src/runtime/tokenpilot/benefit.ts +200 -0
- package/packages/selector/src/runtime/types.ts +0 -17
- package/packages/selector/tests/built/client-artifact.spec.ts +9 -5
- package/packages/selector/tests/preset-options-write.client.spec.ts +7 -23
- package/packages/selector/tests/runtime/advice-never-withholds.host.spec.ts +232 -0
- package/packages/selector/tests/runtime/audit.spec.ts +35 -21
- package/packages/selector/tests/runtime/deprecated-preset-options.spec.ts +96 -0
- package/packages/selector/tests/runtime/tokenpilot/benefit.spec.ts +217 -0
- package/packages/selector/tests/runtime/tokenpilot/profile-baseline.spec.ts +4 -5
- package/packages/selector/tests/settings-seat.client.spec.ts +16 -10
- package/packages/selector/tests/standing-generation.host.spec.ts +54 -5
- package/scripts/packed-components-smoke.mjs +30 -8
- package/scripts/packed-install-e2e.mjs +69 -15
- package/packages/selector/src/client/ReviewOverlay.tsx +0 -320
- package/packages/selector/src/client/review-scope.ts +0 -16
- package/packages/selector/src/runtime/tokenpilot/proposal.ts +0 -267
- package/packages/selector/src/runtime/tokenpilot/review-queue.ts +0 -231
- package/packages/selector/src/runtime/tokenpilot/review-registry.ts +0 -117
- package/packages/selector/src/runtime/tokenpilot/review-storage.ts +0 -122
- package/packages/selector/tests/review-overlay.client.spec.tsx +0 -118
- package/packages/selector/tests/review-routes-registry.host.spec.ts +0 -142
- package/packages/selector/tests/review-routes.host.spec.ts +0 -290
- package/packages/selector/tests/runtime/tokenpilot/proposal.spec.ts +0 -393
- package/packages/selector/tests/runtime/tokenpilot/pruner-review.spec.ts +0 -382
- package/packages/selector/tests/runtime/tokenpilot/review-queue.spec.ts +0 -168
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TokenPilot-inspired R4: the storageDomain adapter for the review queue.
|
|
3
|
-
*
|
|
4
|
-
* The `storageDomain` seam is resolved OPTIONALLY at runtime (`ctx.get`), never
|
|
5
|
-
* declared as a hard plugin inject: a host without storage backends must load
|
|
6
|
-
* the plugin anyway and serve the review queue from its in-memory fallback.
|
|
7
|
-
* Any open failure degrades the same way — the caller receives `undefined` and
|
|
8
|
-
* logs one warning.
|
|
9
|
-
*
|
|
10
|
-
* The domain spec is a plain structural object with a hand-rolled `safeParse`
|
|
11
|
-
* validator, so the plugin carries no runtime dependency on
|
|
12
|
-
* `@deepseek-ai/dsh-storage-domain` (or on a compatible zod instance); hosts
|
|
13
|
-
* that reject the structural spec simply fall into the same degrade path.
|
|
14
|
-
*/
|
|
15
|
-
import type { ReviewQueueStore, ReviewSessionRecord } from './review-queue.ts'
|
|
16
|
-
|
|
17
|
-
/** Domain name — `UNIT_NAME_RE` (`/^[a-z][a-z0-9_]*$/`) allows no hyphens. */
|
|
18
|
-
export const REVIEW_STORAGE_DOMAIN = 'context_compression_review'
|
|
19
|
-
|
|
20
|
-
/** The one declared table: one record per session id. */
|
|
21
|
-
export const REVIEW_STORAGE_TABLE = 'sessions'
|
|
22
|
-
|
|
23
|
-
/** Minimal structural face of one opened domain table (sync reads, durable writes). */
|
|
24
|
-
interface ReviewStorageTableLike {
|
|
25
|
-
get(key: string): unknown
|
|
26
|
-
put(key: string, value: unknown): Promise<void>
|
|
27
|
-
keys(): IterableIterator<string>
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/** Minimal structural face of the `storageDomain` service. */
|
|
31
|
-
interface StorageDomainServiceLike {
|
|
32
|
-
open(spec: unknown): Promise<{ table(name: string): ReviewStorageTableLike }>
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/** Structural validator: accepts exactly the shape this module persists. */
|
|
36
|
-
function reviewSessionRecordValidator(): { safeParse(value: unknown): { success: boolean, data?: ReviewSessionRecord } } {
|
|
37
|
-
return {
|
|
38
|
-
safeParse(value: unknown): { success: boolean, data?: ReviewSessionRecord } {
|
|
39
|
-
if (typeof value !== 'object' || value === null) return { success: false }
|
|
40
|
-
const record = value as { version?: unknown, proposals?: unknown }
|
|
41
|
-
if (record.version !== 1 || !Array.isArray(record.proposals)) return { success: false }
|
|
42
|
-
for (const proposal of record.proposals) {
|
|
43
|
-
if (typeof proposal !== 'object' || proposal === null) return { success: false }
|
|
44
|
-
const entry = proposal as {
|
|
45
|
-
id?: unknown, sessionId?: unknown, kind?: unknown, status?: unknown,
|
|
46
|
-
items?: unknown, benefit?: unknown, enqueuedTurn?: unknown, lastTurnIndex?: unknown,
|
|
47
|
-
}
|
|
48
|
-
if (typeof entry.id !== 'string' || typeof entry.sessionId !== 'string') return { success: false }
|
|
49
|
-
if (entry.kind !== 'estimator' && entry.kind !== 'dedup' && entry.kind !== 'read-state') {
|
|
50
|
-
return { success: false }
|
|
51
|
-
}
|
|
52
|
-
if (entry.status !== 'pending' && entry.status !== 'approved') return { success: false }
|
|
53
|
-
if (!Number.isSafeInteger(entry.enqueuedTurn) || !Number.isSafeInteger(entry.lastTurnIndex)) {
|
|
54
|
-
return { success: false }
|
|
55
|
-
}
|
|
56
|
-
if (!Array.isArray(entry.items) || typeof entry.benefit !== 'object' || entry.benefit === null) {
|
|
57
|
-
return { success: false }
|
|
58
|
-
}
|
|
59
|
-
for (const item of entry.items) {
|
|
60
|
-
if (typeof item !== 'object' || item === null) return { success: false }
|
|
61
|
-
const one = item as { seq?: unknown, digest?: unknown }
|
|
62
|
-
if (!Number.isSafeInteger(one.seq) || typeof one.digest !== 'string') return { success: false }
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
return { success: true, data: value as ReviewSessionRecord }
|
|
66
|
-
},
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function reviewStorageSpec(): unknown {
|
|
71
|
-
return {
|
|
72
|
-
name: REVIEW_STORAGE_DOMAIN,
|
|
73
|
-
version: 1,
|
|
74
|
-
layout: 'per-record',
|
|
75
|
-
tables: {
|
|
76
|
-
[REVIEW_STORAGE_TABLE]: { valueSchema: reviewSessionRecordValidator() },
|
|
77
|
-
},
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/** Adapter presenting the sync KV face the queue expects over the domain table. */
|
|
82
|
-
class StorageDomainReviewStore implements ReviewQueueStore {
|
|
83
|
-
constructor(private readonly table: ReviewStorageTableLike) {}
|
|
84
|
-
|
|
85
|
-
load(sessionId: string): ReviewSessionRecord | undefined {
|
|
86
|
-
const value = this.table.get(sessionId)
|
|
87
|
-
return typeof value === 'object' && value !== null ? value as ReviewSessionRecord : undefined
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
save(sessionId: string, record: ReviewSessionRecord): void {
|
|
91
|
-
// Durability is fire-and-forget: the domain's write chain lands the record
|
|
92
|
-
// while the queue proceeds; failures are logged by the host backend and
|
|
93
|
-
// the in-memory state still serves reads.
|
|
94
|
-
void this.table.put(sessionId, record).catch(() => undefined)
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
ids(): readonly string[] {
|
|
98
|
-
return [...this.table.keys()]
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Attempt to open the review storage domain through the optional
|
|
104
|
-
* `storageDomain` seam.
|
|
105
|
-
* @param getService - resolved once with the seam name; `undefined` means the
|
|
106
|
-
* host lacks the service.
|
|
107
|
-
* @returns the durable store, or `undefined` when the seam is absent or fails
|
|
108
|
-
* (the caller falls back to the in-memory store and logs one warning).
|
|
109
|
-
*/
|
|
110
|
-
export async function openReviewStorage(
|
|
111
|
-
getService: (name: string) => unknown,
|
|
112
|
-
): Promise<ReviewQueueStore | undefined> {
|
|
113
|
-
let service: unknown
|
|
114
|
-
try {
|
|
115
|
-
service = getService('storageDomain')
|
|
116
|
-
} catch {
|
|
117
|
-
return undefined
|
|
118
|
-
}
|
|
119
|
-
if (service === undefined || service === null) return undefined
|
|
120
|
-
const domain = await (service as StorageDomainServiceLike).open(reviewStorageSpec())
|
|
121
|
-
return new StorageDomainReviewStore(domain.table(REVIEW_STORAGE_TABLE))
|
|
122
|
-
}
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
// @vitest-environment jsdom
|
|
2
|
-
|
|
3
|
-
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'
|
|
4
|
-
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
5
|
-
import { ReviewOverlay } from '../src/client/ReviewOverlay.tsx'
|
|
6
|
-
import type { SettingsScopeLike } from '../src/client/review-scope.ts'
|
|
7
|
-
|
|
8
|
-
afterEach(() => {
|
|
9
|
-
cleanup()
|
|
10
|
-
vi.unstubAllGlobals()
|
|
11
|
-
})
|
|
12
|
-
|
|
13
|
-
const QUEUE_ROUTE = '/api/dsh-context-compression-improved/review-queue'
|
|
14
|
-
const DECIDE_ROUTE = '/api/dsh-context-compression-improved/review-decide'
|
|
15
|
-
|
|
16
|
-
const PROPOSAL = {
|
|
17
|
-
sessionId: 's1',
|
|
18
|
-
id: 'abc123def456',
|
|
19
|
-
kind: 'read-state',
|
|
20
|
-
items: [{ seq: 6, tokensBefore: 2401, tokensAfter: 134 }],
|
|
21
|
-
benefit: { recoveredTokens: 400, paybackTurns: 2.25, expectedSaving: 312 },
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function scopeStub(reviewMode: boolean): SettingsScopeLike {
|
|
25
|
-
return {
|
|
26
|
-
getSnapshot: () => ({
|
|
27
|
-
status: 'ready',
|
|
28
|
-
value: { presetOptions: { reviewMode } },
|
|
29
|
-
}),
|
|
30
|
-
subscribe: () => () => {},
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
type FetchCall = { input: string | URL | Request, init?: RequestInit | undefined }
|
|
35
|
-
|
|
36
|
-
function stubFetch(responses: Array<{ match: (input: string) => boolean, body: unknown, status?: number }>): {
|
|
37
|
-
calls: FetchCall[]
|
|
38
|
-
} {
|
|
39
|
-
const calls: FetchCall[] = []
|
|
40
|
-
vi.stubGlobal('fetch', vi.fn(async (input: string | URL | Request, init?: RequestInit) => {
|
|
41
|
-
calls.push({ input, init })
|
|
42
|
-
const url = String(input)
|
|
43
|
-
const match = responses.find(entry => entry.match(url))
|
|
44
|
-
return {
|
|
45
|
-
ok: (match?.status ?? 200) < 400,
|
|
46
|
-
status: match?.status ?? 200,
|
|
47
|
-
json: async () => match?.body,
|
|
48
|
-
} as Response
|
|
49
|
-
}))
|
|
50
|
-
return { calls }
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const queueBody = {
|
|
54
|
-
ok: true,
|
|
55
|
-
total: 1,
|
|
56
|
-
pending: [PROPOSAL],
|
|
57
|
-
summary: { autoApplied: 2, reviewApplied: 1, expired: 3, voided: 0 },
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
describe('review overlay (client)', () => {
|
|
61
|
-
it('renders the pending list and the four-state summary row', async () => {
|
|
62
|
-
stubFetch([{ match: url => url.includes(QUEUE_ROUTE), body: queueBody }])
|
|
63
|
-
render(<ReviewOverlay scope={scopeStub(true)} t={key => key} />)
|
|
64
|
-
|
|
65
|
-
await waitFor(() => { expect(screen.getByText('review.badge 1')).toBeDefined() })
|
|
66
|
-
fireEvent.click(screen.getByText('review.badge 1'))
|
|
67
|
-
expect(screen.getByText('review.title')).toBeDefined()
|
|
68
|
-
expect(screen.getByText(/review.summary.autoApplied: 2/)).toBeDefined()
|
|
69
|
-
expect(screen.getByText(/review.summary.expired: 3/)).toBeDefined()
|
|
70
|
-
// Estimated saving is labelled as an estimate.
|
|
71
|
-
expect(screen.getByText(/review.row.estimated/)).toBeDefined()
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
it('posts the three decisions through the decide route and refreshes', async () => {
|
|
75
|
-
const { calls } = stubFetch([
|
|
76
|
-
{ match: url => url.includes(QUEUE_ROUTE), body: queueBody },
|
|
77
|
-
{ match: url => url.includes(DECIDE_ROUTE), body: { ok: true } },
|
|
78
|
-
])
|
|
79
|
-
render(<ReviewOverlay scope={scopeStub(true)} t={key => key} />)
|
|
80
|
-
await waitFor(() => { expect(screen.getByText('review.badge 1')).toBeDefined() })
|
|
81
|
-
fireEvent.click(screen.getByText('review.badge 1'))
|
|
82
|
-
|
|
83
|
-
fireEvent.click(screen.getByText('review.action.approve'))
|
|
84
|
-
await waitFor(() => {
|
|
85
|
-
expect(calls.some(call => String(call.input) === DECIDE_ROUTE && call.init?.method === 'POST')).toBe(true)
|
|
86
|
-
})
|
|
87
|
-
const posted = JSON.parse(String(calls.find(call => call.init?.method === 'POST')?.init?.body))
|
|
88
|
-
expect(posted).toEqual({ sessionId: 's1', proposalId: 'abc123def456', decision: 'approved' })
|
|
89
|
-
|
|
90
|
-
fireEvent.click(screen.getByText('review.action.reject'))
|
|
91
|
-
await waitFor(() => {
|
|
92
|
-
expect(calls.some(call => String(call.input) === DECIDE_ROUTE
|
|
93
|
-
&& JSON.parse(String(call.init?.body)).decision === 'rejected')).toBe(true)
|
|
94
|
-
})
|
|
95
|
-
fireEvent.click(screen.getByText('review.action.ignore'))
|
|
96
|
-
await waitFor(() => {
|
|
97
|
-
expect(calls.some(call => String(call.input) === DECIDE_ROUTE
|
|
98
|
-
&& JSON.parse(String(call.init?.body)).decision === 'ignored')).toBe(true)
|
|
99
|
-
})
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
it('renders nothing while nothing is pending', async () => {
|
|
103
|
-
stubFetch([{ match: url => url.includes(QUEUE_ROUTE), body: { ok: true, total: 0, pending: [] } }])
|
|
104
|
-
const { container } = render(<ReviewOverlay scope={scopeStub(true)} t={key => key} />)
|
|
105
|
-
await waitFor(() => {
|
|
106
|
-
expect((container.querySelector('.dsh-cc-review-badge'))).toBeNull()
|
|
107
|
-
})
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
it('renders nothing while review mode is off', async () => {
|
|
111
|
-
const fetchMock = vi.fn()
|
|
112
|
-
vi.stubGlobal('fetch', fetchMock)
|
|
113
|
-
const { container } = render(<ReviewOverlay scope={scopeStub(false)} t={key => key} />)
|
|
114
|
-
await new Promise(resolve => setTimeout(resolve, 30))
|
|
115
|
-
expect(container.querySelector('.dsh-cc-review-badge')).toBeNull()
|
|
116
|
-
expect(fetchMock).not.toHaveBeenCalled()
|
|
117
|
-
})
|
|
118
|
-
})
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Production-shaped guard for the review routes' pruner resolution.
|
|
3
|
-
*
|
|
4
|
-
* The older suite provided `toolResultPruner` at the TOP LEVEL, which is a shape
|
|
5
|
-
* production never has: `canonicalCompressionRows()` mounts every pruner inside
|
|
6
|
-
* an agent preset's isolated group, so a top-level `ctx.get('toolResultPruner')`
|
|
7
|
-
* resolves nothing and the queue route answered 503 "review pipeline
|
|
8
|
-
* unavailable" for every request while the pipeline itself ran normally.
|
|
9
|
-
*
|
|
10
|
-
* These cases mount NO top-level service and publish through the review registry
|
|
11
|
-
* from a CHILD fiber instead — the path the fix relies on.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { Context } from '@deepseek-ai/cordis'
|
|
15
|
-
import { afterEach, describe, expect, it } from 'vitest'
|
|
16
|
-
import { apply } from '../src/index.ts'
|
|
17
|
-
import { registerReviewPruner, type ReviewPrunerFace } from '../src/runtime/tokenpilot/review-registry.ts'
|
|
18
|
-
|
|
19
|
-
const QUEUE_ROUTE = '/api/dsh-context-compression-improved/review-queue'
|
|
20
|
-
|
|
21
|
-
interface RegisteredRoute {
|
|
22
|
-
kind: string
|
|
23
|
-
path: string
|
|
24
|
-
handler: (req: unknown, res: unknown) => unknown
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
let ctx: Context | undefined
|
|
28
|
-
|
|
29
|
-
afterEach(async () => {
|
|
30
|
-
await ctx?.fiber.dispose()
|
|
31
|
-
ctx = undefined
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
const settle = (): Promise<void> => new Promise(resolve => setTimeout(resolve, 20))
|
|
35
|
-
|
|
36
|
-
/** A pruner face carrying one pending proposal for session `s1`. */
|
|
37
|
-
const SAMPLE = {
|
|
38
|
-
id: 'abc123def456',
|
|
39
|
-
kind: 'read-state',
|
|
40
|
-
items: [{ seq: 6, kind: 'read-state', component: 'history', tokensBefore: 2401, tokensAfter: 134 }],
|
|
41
|
-
benefit: { recoveredTokens: 400, penaltyTokens: 900, paybackTurns: 2.25 },
|
|
42
|
-
enqueuedTurn: 3,
|
|
43
|
-
lastTurnIndex: 3,
|
|
44
|
-
} as const
|
|
45
|
-
|
|
46
|
-
function samplePruner(): ReviewPrunerFace {
|
|
47
|
-
return {
|
|
48
|
-
listReviewProposals: (session: unknown) => ((session as { id?: string }).id === 's1' ? [SAMPLE] : []),
|
|
49
|
-
listAllReviewProposals: () => [{ sessionId: 's1', proposals: [SAMPLE] }],
|
|
50
|
-
reviewSummary: () => ({ autoApplied: 2, reviewApplied: 1, expired: 3, voided: 0 }),
|
|
51
|
-
decideReviewProposal: (_session: unknown, proposalId: string) => (proposalId === SAMPLE.id
|
|
52
|
-
? { ok: true }
|
|
53
|
-
: { ok: false, reason: 'unknown-proposal' }),
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/** Mount the fake web server the routes register against. */
|
|
58
|
-
async function mountWebServer(runtime: Context, routes: RegisteredRoute[]): Promise<void> {
|
|
59
|
-
await runtime.plugin({
|
|
60
|
-
name: 'fake-webserver',
|
|
61
|
-
apply(webCtx) {
|
|
62
|
-
webCtx.provide('webServer', {
|
|
63
|
-
tables: { exact: new Map<string, RegisteredRoute>() },
|
|
64
|
-
register(this: { tables: { exact: Map<string, RegisteredRoute> } }, route: RegisteredRoute) {
|
|
65
|
-
const table = this.tables.exact
|
|
66
|
-
if (table.has(route.path)) throw new Error(`webserver: duplicate ${route.path}`)
|
|
67
|
-
table.set(route.path, route)
|
|
68
|
-
routes.push(route)
|
|
69
|
-
return () => {}
|
|
70
|
-
},
|
|
71
|
-
})
|
|
72
|
-
},
|
|
73
|
-
})
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/** Boot the host plugin with both routes opted in, as the Bundle row does. */
|
|
77
|
-
async function bootWithRoutes(): Promise<RegisteredRoute[]> {
|
|
78
|
-
const routes: RegisteredRoute[] = []
|
|
79
|
-
ctx = new Context()
|
|
80
|
-
await mountWebServer(ctx, routes)
|
|
81
|
-
apply(ctx, { estimatorCatalogRoute: true, reviewQueueRoute: true })
|
|
82
|
-
await settle()
|
|
83
|
-
return routes
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
async function readQueue(routes: RegisteredRoute[]): Promise<{ status: number, body: string }> {
|
|
87
|
-
const route = routes.find(candidate => candidate.path === QUEUE_ROUTE)
|
|
88
|
-
expect(route, `route ${QUEUE_ROUTE} must be registered`).toBeDefined()
|
|
89
|
-
// `exactOptionalPropertyTypes` forbids assigning an explicit `undefined` to an
|
|
90
|
-
// optional property, so the widened type must spell it out.
|
|
91
|
-
const captured: { status?: number, body?: string | undefined } = {}
|
|
92
|
-
const res = {
|
|
93
|
-
writeHead(code: number) { captured.status = code },
|
|
94
|
-
end(body?: string) { captured.body = body },
|
|
95
|
-
}
|
|
96
|
-
await route!.handler({ url: QUEUE_ROUTE }, res)
|
|
97
|
-
return { status: captured.status ?? 0, body: captured.body ?? '' }
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
describe('review routes resolve a preset-scoped pruner', () => {
|
|
101
|
-
it('answers 503 while no pruner has been published', async () => {
|
|
102
|
-
const routes = await bootWithRoutes()
|
|
103
|
-
const { status, body } = await readQueue(routes)
|
|
104
|
-
expect(status).toBe(503)
|
|
105
|
-
expect(body).toContain('review pipeline unavailable')
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
it('reaches a pruner published from a CHILD fiber with no top-level service', async () => {
|
|
109
|
-
const routes = await bootWithRoutes()
|
|
110
|
-
// Production shape: the pruner is mounted in a preset's isolated group.
|
|
111
|
-
await ctx!.plugin({
|
|
112
|
-
name: 'preset-scoped-pruner',
|
|
113
|
-
apply(childCtx) {
|
|
114
|
-
childCtx.effect(() => registerReviewPruner(samplePruner()), 'test-preset-pruner')
|
|
115
|
-
},
|
|
116
|
-
})
|
|
117
|
-
await settle()
|
|
118
|
-
|
|
119
|
-
const { status, body } = await readQueue(routes)
|
|
120
|
-
expect(status).toBe(200)
|
|
121
|
-
const parsed = JSON.parse(body) as { ok: boolean, total: number, pending: readonly { id: string }[] }
|
|
122
|
-
expect(parsed.ok).toBe(true)
|
|
123
|
-
expect(parsed.total).toBe(1)
|
|
124
|
-
expect(parsed.pending[0]?.id).toBe(SAMPLE.id)
|
|
125
|
-
})
|
|
126
|
-
|
|
127
|
-
it('drops back to 503 once the publishing fiber disposes', async () => {
|
|
128
|
-
const routes = await bootWithRoutes()
|
|
129
|
-
const fork = await ctx!.plugin({
|
|
130
|
-
name: 'preset-scoped-pruner',
|
|
131
|
-
apply(childCtx) {
|
|
132
|
-
childCtx.effect(() => registerReviewPruner(samplePruner()), 'test-preset-pruner')
|
|
133
|
-
},
|
|
134
|
-
})
|
|
135
|
-
await settle()
|
|
136
|
-
expect((await readQueue(routes)).status).toBe(200)
|
|
137
|
-
|
|
138
|
-
await fork.dispose()
|
|
139
|
-
await settle()
|
|
140
|
-
expect((await readQueue(routes)).status).toBe(503)
|
|
141
|
-
})
|
|
142
|
-
})
|
|
@@ -1,290 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Host-side guard for the review pipeline's HTTP transport (R4).
|
|
3
|
-
*
|
|
4
|
-
* Pins the client↔runtime contract: the two routes (queue read, decide write)
|
|
5
|
-
* register under both prefixes, the happy path answers a sanitized payload
|
|
6
|
-
* (ids/seqs/counts, never digests or content), and every error path answers a
|
|
7
|
-
* precise status — 400 for malformed input, 404 for unknown session/proposal,
|
|
8
|
-
* 503 when the review pipeline is not serving the session.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { Context } from '@deepseek-ai/cordis'
|
|
12
|
-
import { afterEach, describe, expect, it } from 'vitest'
|
|
13
|
-
import { apply } from '../src/index.ts'
|
|
14
|
-
|
|
15
|
-
const QUEUE_ROUTE = '/api/dsh-context-compression-improved/review-queue'
|
|
16
|
-
const DECIDE_ROUTE = '/api/dsh-context-compression-improved/review-decide'
|
|
17
|
-
const LEGACY_QUEUE_ROUTE = '/endpoint/dsh-context-compression-improved/review-queue'
|
|
18
|
-
const LEGACY_DECIDE_ROUTE = '/endpoint/dsh-context-compression-improved/review-decide'
|
|
19
|
-
|
|
20
|
-
interface RegisteredRoute {
|
|
21
|
-
kind: string
|
|
22
|
-
path: string
|
|
23
|
-
handler: (req: unknown, res: unknown) => unknown
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
interface FakeResponse {
|
|
27
|
-
status?: number
|
|
28
|
-
body?: string
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
let ctx: Context | undefined
|
|
32
|
-
|
|
33
|
-
afterEach(async () => {
|
|
34
|
-
await ctx?.fiber.dispose()
|
|
35
|
-
ctx = undefined
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
const settle = (): Promise<void> => new Promise(resolve => setTimeout(resolve, 20))
|
|
39
|
-
|
|
40
|
-
async function mountWebServer(runtime: Context, routes: RegisteredRoute[]): Promise<void> {
|
|
41
|
-
await runtime.plugin({
|
|
42
|
-
name: 'fake-webserver',
|
|
43
|
-
apply(webCtx) {
|
|
44
|
-
webCtx.provide('webServer', {
|
|
45
|
-
tables: { exact: new Map<string, RegisteredRoute>() },
|
|
46
|
-
register(this: { tables: { exact: Map<string, RegisteredRoute> } }, route: RegisteredRoute) {
|
|
47
|
-
const table = this.tables.exact
|
|
48
|
-
if (table.has(route.path)) {
|
|
49
|
-
throw new Error(`webserver: duplicate ${route.kind} route "${route.path}"`)
|
|
50
|
-
}
|
|
51
|
-
table.set(route.path, route)
|
|
52
|
-
routes.push(route)
|
|
53
|
-
return () => {}
|
|
54
|
-
},
|
|
55
|
-
})
|
|
56
|
-
},
|
|
57
|
-
})
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
interface PrunerBehavior {
|
|
61
|
-
readonly reviewOn: boolean
|
|
62
|
-
decideOutcome?: { ok: true } | { ok: false, reason: string } | undefined
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function mountReviewServices(runtime: Context, behavior: PrunerBehavior): void {
|
|
66
|
-
void runtime.plugin({
|
|
67
|
-
name: 'fake-review-services',
|
|
68
|
-
apply(serviceCtx) {
|
|
69
|
-
serviceCtx.provide('agents', {
|
|
70
|
-
get: (id: unknown) => (id === 's1' ? { session: { id: 's1' } } : undefined),
|
|
71
|
-
})
|
|
72
|
-
const sampleProposal = {
|
|
73
|
-
id: 'abc123def456',
|
|
74
|
-
kind: 'read-state',
|
|
75
|
-
status: 'pending',
|
|
76
|
-
items: [{
|
|
77
|
-
seq: 6, kind: 'read-state', component: 'history', tokensBefore: 2401, tokensAfter: 134,
|
|
78
|
-
digest: 'dd'.repeat(32),
|
|
79
|
-
}],
|
|
80
|
-
benefit: { recoveredTokens: 400, penaltyTokens: 900, paybackTurns: 2.25 },
|
|
81
|
-
enqueuedTurn: 3,
|
|
82
|
-
lastTurnIndex: 3,
|
|
83
|
-
}
|
|
84
|
-
serviceCtx.provide('toolResultPruner', {
|
|
85
|
-
listReviewProposals: (session: unknown) => {
|
|
86
|
-
const id = (session as { id?: string }).id
|
|
87
|
-
return id === 's1' && behavior.reviewOn ? [sampleProposal] : []
|
|
88
|
-
},
|
|
89
|
-
listAllReviewProposals: () => (behavior.reviewOn
|
|
90
|
-
? [{ sessionId: 's1', proposals: [sampleProposal] }]
|
|
91
|
-
: []),
|
|
92
|
-
reviewSummary: () => ({ autoApplied: 2, reviewApplied: 1, expired: 3, voided: 0 }),
|
|
93
|
-
decideReviewProposal: (_session: unknown, proposalId: string) => {
|
|
94
|
-
if (behavior.reviewOn === false) return undefined
|
|
95
|
-
if (proposalId !== 'abc123def456') return { ok: false, reason: 'unknown-proposal' }
|
|
96
|
-
return behavior.decideOutcome ?? { ok: true }
|
|
97
|
-
},
|
|
98
|
-
})
|
|
99
|
-
},
|
|
100
|
-
})
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
async function invoke(route: RegisteredRoute, req?: unknown): Promise<FakeResponse> {
|
|
104
|
-
const captured: FakeResponse = {}
|
|
105
|
-
const res = {
|
|
106
|
-
writeHead(code: number) { captured.status = code },
|
|
107
|
-
end(body?: string) { if (body !== undefined) captured.body = body },
|
|
108
|
-
}
|
|
109
|
-
await route.handler(req ?? { method: 'GET' }, res)
|
|
110
|
-
await settle()
|
|
111
|
-
return captured
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
describe('review queue route registration', () => {
|
|
115
|
-
it('registers both route prefixes only when the row opts in', async () => {
|
|
116
|
-
const routes: RegisteredRoute[] = []
|
|
117
|
-
const runtime = new Context()
|
|
118
|
-
ctx = runtime
|
|
119
|
-
await mountWebServer(runtime, routes)
|
|
120
|
-
|
|
121
|
-
apply(runtime, { reviewQueueRoute: true })
|
|
122
|
-
await settle()
|
|
123
|
-
|
|
124
|
-
expect(routes.map(route => route.path)).toEqual([
|
|
125
|
-
LEGACY_QUEUE_ROUTE, QUEUE_ROUTE, LEGACY_DECIDE_ROUTE, DECIDE_ROUTE,
|
|
126
|
-
])
|
|
127
|
-
|
|
128
|
-
const routesAfterOptOut: RegisteredRoute[] = []
|
|
129
|
-
const plain = new Context()
|
|
130
|
-
ctx = plain
|
|
131
|
-
await mountWebServer(plain, routesAfterOptOut)
|
|
132
|
-
apply(plain, { presetOverlay: false })
|
|
133
|
-
await settle()
|
|
134
|
-
expect(routesAfterOptOut).toHaveLength(0)
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
it('answers the queue read with a sanitized payload', async () => {
|
|
138
|
-
const routes: RegisteredRoute[] = []
|
|
139
|
-
const runtime = new Context()
|
|
140
|
-
ctx = runtime
|
|
141
|
-
await mountWebServer(runtime, routes)
|
|
142
|
-
mountReviewServices(runtime, { reviewOn: true })
|
|
143
|
-
apply(runtime, { reviewQueueRoute: true })
|
|
144
|
-
await settle()
|
|
145
|
-
|
|
146
|
-
const route = routes.find(candidate => candidate.path === QUEUE_ROUTE)
|
|
147
|
-
const response = await invoke(route as RegisteredRoute, { url: `${QUEUE_ROUTE}?sessionId=s1` })
|
|
148
|
-
|
|
149
|
-
expect(response.status).toBe(200)
|
|
150
|
-
const body = JSON.parse(String(response.body)) as {
|
|
151
|
-
ok: boolean
|
|
152
|
-
summary?: Record<string, number>
|
|
153
|
-
pending: readonly { id: string, items: readonly Record<string, unknown>[] }[]
|
|
154
|
-
}
|
|
155
|
-
expect(body.ok).toBe(true)
|
|
156
|
-
expect(body.pending).toHaveLength(1)
|
|
157
|
-
expect(body.pending[0]!.id).toBe('abc123def456')
|
|
158
|
-
expect(body.summary).toEqual({ autoApplied: 2, reviewApplied: 1, expired: 3, voided: 0 })
|
|
159
|
-
// Digests never leave the runtime: the client only needs ids and counts.
|
|
160
|
-
expect(String(response.body)).not.toContain('digest')
|
|
161
|
-
expect(String(response.body)).not.toContain('"content"')
|
|
162
|
-
})
|
|
163
|
-
|
|
164
|
-
it('aggregates every live session when the read carries no sessionId', async () => {
|
|
165
|
-
const routes: RegisteredRoute[] = []
|
|
166
|
-
const runtime = new Context()
|
|
167
|
-
ctx = runtime
|
|
168
|
-
await mountWebServer(runtime, routes)
|
|
169
|
-
mountReviewServices(runtime, { reviewOn: true })
|
|
170
|
-
apply(runtime, { reviewQueueRoute: true })
|
|
171
|
-
await settle()
|
|
172
|
-
|
|
173
|
-
const route = routes.find(candidate => candidate.path === QUEUE_ROUTE) as RegisteredRoute
|
|
174
|
-
const response = await invoke(route, { url: QUEUE_ROUTE })
|
|
175
|
-
|
|
176
|
-
expect(response.status).toBe(200)
|
|
177
|
-
const body = JSON.parse(String(response.body)) as {
|
|
178
|
-
ok: boolean
|
|
179
|
-
total: number
|
|
180
|
-
pending: readonly { sessionId: string, id: string }[]
|
|
181
|
-
}
|
|
182
|
-
expect(body).toMatchObject({ ok: true, total: 1 })
|
|
183
|
-
expect(body.pending[0]).toMatchObject({ sessionId: 's1', id: 'abc123def456' })
|
|
184
|
-
})
|
|
185
|
-
|
|
186
|
-
it('rejects a queue read for an unknown session', async () => {
|
|
187
|
-
const routes: RegisteredRoute[] = []
|
|
188
|
-
const runtime = new Context()
|
|
189
|
-
ctx = runtime
|
|
190
|
-
await mountWebServer(runtime, routes)
|
|
191
|
-
mountReviewServices(runtime, { reviewOn: true })
|
|
192
|
-
apply(runtime, { reviewQueueRoute: true })
|
|
193
|
-
await settle()
|
|
194
|
-
const route = routes.find(candidate => candidate.path === QUEUE_ROUTE) as RegisteredRoute
|
|
195
|
-
|
|
196
|
-
const unknown = await invoke(route, { url: `${QUEUE_ROUTE}?sessionId=nope` })
|
|
197
|
-
expect(unknown.status).toBe(404)
|
|
198
|
-
})
|
|
199
|
-
|
|
200
|
-
it('answers 503 when the pruner is not serving review proposals', async () => {
|
|
201
|
-
const routes: RegisteredRoute[] = []
|
|
202
|
-
const runtime = new Context()
|
|
203
|
-
ctx = runtime
|
|
204
|
-
await mountWebServer(runtime, routes)
|
|
205
|
-
apply(runtime, { reviewQueueRoute: true })
|
|
206
|
-
await settle()
|
|
207
|
-
|
|
208
|
-
const route = routes.find(candidate => candidate.path === QUEUE_ROUTE) as RegisteredRoute
|
|
209
|
-
const response = await invoke(route, { url: `${QUEUE_ROUTE}?sessionId=s1` })
|
|
210
|
-
expect(response.status).toBe(503)
|
|
211
|
-
})
|
|
212
|
-
|
|
213
|
-
it('rejects malformed decide bodies with 400', async () => {
|
|
214
|
-
const routes: RegisteredRoute[] = []
|
|
215
|
-
const runtime = new Context()
|
|
216
|
-
ctx = runtime
|
|
217
|
-
await mountWebServer(runtime, routes)
|
|
218
|
-
mountReviewServices(runtime, { reviewOn: true })
|
|
219
|
-
apply(runtime, { reviewQueueRoute: true })
|
|
220
|
-
await settle()
|
|
221
|
-
const route = routes.find(candidate => candidate.path === DECIDE_ROUTE) as RegisteredRoute
|
|
222
|
-
|
|
223
|
-
const notJson = await invoke(route, { on: (event: string, listener: (chunk?: Buffer) => void) => {
|
|
224
|
-
if (event === 'end') listener()
|
|
225
|
-
} })
|
|
226
|
-
expect(notJson.status).toBe(400)
|
|
227
|
-
|
|
228
|
-
const missingDecision = await invoke(route, { on: (event: string, listener: (chunk?: Buffer) => void) => {
|
|
229
|
-
if (event === 'data') listener(Buffer.from(JSON.stringify({ sessionId: 's1', proposalId: 'abc123def456' })))
|
|
230
|
-
if (event === 'end') listener()
|
|
231
|
-
} })
|
|
232
|
-
expect(missingDecision.status).toBe(400)
|
|
233
|
-
|
|
234
|
-
const badDecision = await invoke(route, { on: (event: string, listener: (chunk?: Buffer) => void) => {
|
|
235
|
-
if (event === 'data') listener(Buffer.from(JSON.stringify({ sessionId: 's1', proposalId: 'abc123def456', decision: 'maybe' })))
|
|
236
|
-
if (event === 'end') listener()
|
|
237
|
-
} })
|
|
238
|
-
expect(badDecision.status).toBe(400)
|
|
239
|
-
})
|
|
240
|
-
|
|
241
|
-
it('maps decide outcomes to 200, 404, and 503', async () => {
|
|
242
|
-
const routes: RegisteredRoute[] = []
|
|
243
|
-
const runtime = new Context()
|
|
244
|
-
ctx = runtime
|
|
245
|
-
await mountWebServer(runtime, routes)
|
|
246
|
-
mountReviewServices(runtime, { reviewOn: true })
|
|
247
|
-
apply(runtime, { reviewQueueRoute: true })
|
|
248
|
-
await settle()
|
|
249
|
-
const route = routes.find(candidate => candidate.path === DECIDE_ROUTE) as RegisteredRoute
|
|
250
|
-
|
|
251
|
-
const post = (body: unknown): unknown => ({
|
|
252
|
-
on: (event: string, listener: (chunk?: Buffer) => void) => {
|
|
253
|
-
if (event === 'data') listener(Buffer.from(JSON.stringify(body)))
|
|
254
|
-
if (event === 'end') listener()
|
|
255
|
-
},
|
|
256
|
-
})
|
|
257
|
-
|
|
258
|
-
const ok = await invoke(route, post({ sessionId: 's1', proposalId: 'abc123def456', decision: 'approved' }))
|
|
259
|
-
expect(ok.status).toBe(200)
|
|
260
|
-
expect(JSON.parse(String(ok.body))).toMatchObject({ ok: true, decision: 'approved' })
|
|
261
|
-
|
|
262
|
-
const unknownProposal = await invoke(route, post({ sessionId: 's1', proposalId: 'nope', decision: 'approved' }))
|
|
263
|
-
expect(unknownProposal.status).toBe(404)
|
|
264
|
-
|
|
265
|
-
const unknownSession = await invoke(route, post({ sessionId: 'nope', proposalId: 'abc123def456', decision: 'approved' }))
|
|
266
|
-
expect(unknownSession.status).toBe(404)
|
|
267
|
-
})
|
|
268
|
-
|
|
269
|
-
it('answers 503 when review mode is off for the session', async () => {
|
|
270
|
-
const routes: RegisteredRoute[] = []
|
|
271
|
-
const runtime = new Context()
|
|
272
|
-
ctx = runtime
|
|
273
|
-
await mountWebServer(runtime, routes)
|
|
274
|
-
mountReviewServices(runtime, { reviewOn: false })
|
|
275
|
-
apply(runtime, { reviewQueueRoute: true })
|
|
276
|
-
await settle()
|
|
277
|
-
|
|
278
|
-
const queueRoute = routes.find(candidate => candidate.path === QUEUE_ROUTE) as RegisteredRoute
|
|
279
|
-
expect((await invoke(queueRoute, { url: `${QUEUE_ROUTE}?sessionId=s1` })).status).toBe(200)
|
|
280
|
-
|
|
281
|
-
const decideRoute = routes.find(candidate => candidate.path === DECIDE_ROUTE) as RegisteredRoute
|
|
282
|
-
const response = await invoke(decideRoute, {
|
|
283
|
-
on: (event: string, listener: (chunk?: Buffer) => void) => {
|
|
284
|
-
if (event === 'data') listener(Buffer.from(JSON.stringify({ sessionId: 's1', proposalId: 'abc123def456', decision: 'approved' })))
|
|
285
|
-
if (event === 'end') listener()
|
|
286
|
-
},
|
|
287
|
-
})
|
|
288
|
-
expect(response.status).toBe(503)
|
|
289
|
-
})
|
|
290
|
-
})
|