dsh-context-compression-improved 0.4.0 → 0.5.0
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 +19 -0
- package/CHANGELOG.ko.md +19 -0
- package/CHANGELOG.md +21 -0
- package/CHANGELOG.zh.md +17 -0
- package/docs/installation.md +25 -1
- package/docs/installation.zh.md +24 -1
- package/package.json +1 -1
- package/packages/selector/lib/{review-registry.js → advisor-state.js} +105 -4
- package/packages/selector/lib/client.d.ts +7 -0
- package/packages/selector/lib/client.js +32 -2
- package/packages/selector/lib/index.d.ts +7 -0
- package/packages/selector/lib/index.js +101 -2
- package/packages/selector/lib/pruner.d.ts +82 -0
- package/packages/selector/lib/pruner.js +545 -11
- package/packages/selector/src/client/preset-options.ts +2 -0
- package/packages/selector/src/index.ts +108 -0
- package/packages/selector/src/profiles.ts +48 -0
- package/packages/selector/src/pruner/state.ts +3 -0
- package/packages/selector/src/pruner.ts +113 -0
- package/packages/selector/src/runtime/audit.ts +22 -0
- package/packages/selector/src/runtime/config.ts +58 -0
- package/packages/selector/src/runtime/tokenpilot/advisor-prompt.ts +188 -0
- package/packages/selector/src/runtime/tokenpilot/advisor-state.ts +133 -0
- package/packages/selector/src/runtime/tokenpilot/advisor.ts +419 -0
- package/packages/selector/src/runtime/tokenpilot/sidechannel.ts +24 -9
- package/packages/selector/src/runtime/types.ts +21 -0
- package/packages/selector/tests/advisor-report.host.spec.ts +223 -0
- package/packages/selector/tests/runtime/advisor-invariant.spec.ts +272 -0
- package/packages/selector/tests/runtime/advisor.spec.ts +226 -0
- package/packages/selector/tests/runtime/audit.spec.ts +44 -0
- package/packages/selector/tests/runtime/tokenpilot/profile-baseline.spec.ts +12 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-side guard for the advisory advisor's HTTP report route.
|
|
3
|
+
*
|
|
4
|
+
* Pins the read-only contract: the route registers under both prefixes only
|
|
5
|
+
* when the row opts in, a session whose advisor never ran reports nulls and
|
|
6
|
+
* empty arrays (not an error), a populated session reports its decay figure
|
|
7
|
+
* and score distribution content-free, and every error path answers a
|
|
8
|
+
* precise status — 400 for a missing sessionId, 404 for an unknown session,
|
|
9
|
+
* 503 when no agents service can resolve sessions at all.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { Context } from '@deepseek-ai/cordis'
|
|
13
|
+
import { afterEach, describe, expect, it } from 'vitest'
|
|
14
|
+
import { apply } from '../src/index.ts'
|
|
15
|
+
import { getAdvisorState, recordScore, recordRecertified } from '../src/runtime/tokenpilot/advisor-state.ts'
|
|
16
|
+
|
|
17
|
+
const REPORT_ROUTE = '/api/dsh-context-compression-improved/advisor-report'
|
|
18
|
+
const LEGACY_REPORT_ROUTE = '/endpoint/dsh-context-compression-improved/advisor-report'
|
|
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
|
+
/** Mount an agents service that knows session 's1' (and nothing else). */
|
|
61
|
+
function mountAgents(runtime: Context): void {
|
|
62
|
+
void runtime.plugin({
|
|
63
|
+
name: 'fake-agents',
|
|
64
|
+
apply(serviceCtx) {
|
|
65
|
+
serviceCtx.provide('agents', {
|
|
66
|
+
get: (id: unknown) => {
|
|
67
|
+
if (id !== 's1') return undefined
|
|
68
|
+
const session = { id: 's1' }
|
|
69
|
+
const state = getAdvisorState(session as never)
|
|
70
|
+
state.summary = {
|
|
71
|
+
overallTask: 'migrate the auth module',
|
|
72
|
+
activeSubtasks: ['port login flow'],
|
|
73
|
+
keywords: ['auth'],
|
|
74
|
+
todoVersion: 'aaaa1111',
|
|
75
|
+
turn: 4,
|
|
76
|
+
}
|
|
77
|
+
state.lastDecay = { decay: 0.42, weightedChars: 10_000, turn: 4 }
|
|
78
|
+
recordScore(state, 3, { score: 0.95, turn: 4 })
|
|
79
|
+
recordScore(state, 5, { score: 0.10, turn: 4 })
|
|
80
|
+
recordRecertified(state, 5, 4)
|
|
81
|
+
return { session }
|
|
82
|
+
},
|
|
83
|
+
})
|
|
84
|
+
},
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function invoke(route: RegisteredRoute, req?: unknown): Promise<FakeResponse> {
|
|
89
|
+
const captured: FakeResponse = {}
|
|
90
|
+
const res = {
|
|
91
|
+
writeHead(code: number) { captured.status = code },
|
|
92
|
+
end(body?: string) { if (body !== undefined) captured.body = body },
|
|
93
|
+
}
|
|
94
|
+
await route.handler(req ?? { method: 'GET' }, res)
|
|
95
|
+
await settle()
|
|
96
|
+
return captured
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
describe('advisor report route registration', () => {
|
|
100
|
+
it('registers both prefixes only when the row opts in', async () => {
|
|
101
|
+
const routes: RegisteredRoute[] = []
|
|
102
|
+
const runtime = new Context()
|
|
103
|
+
ctx = runtime
|
|
104
|
+
await mountWebServer(runtime, routes)
|
|
105
|
+
apply(runtime, { advisorReportRoute: true })
|
|
106
|
+
await settle()
|
|
107
|
+
const paths = routes.map(route => route.path)
|
|
108
|
+
expect(paths).toContain(REPORT_ROUTE)
|
|
109
|
+
expect(paths).toContain(LEGACY_REPORT_ROUTE)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('registers nothing by default (advisor off adds zero behavior)', async () => {
|
|
113
|
+
const routes: RegisteredRoute[] = []
|
|
114
|
+
const runtime = new Context()
|
|
115
|
+
ctx = runtime
|
|
116
|
+
await mountWebServer(runtime, routes)
|
|
117
|
+
apply(runtime, {})
|
|
118
|
+
await settle()
|
|
119
|
+
expect(routes.map(route => route.path)).not.toContain(REPORT_ROUTE)
|
|
120
|
+
})
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
describe('advisor report route responses', () => {
|
|
124
|
+
async function mountedRoute(): Promise<RegisteredRoute> {
|
|
125
|
+
const routes: RegisteredRoute[] = []
|
|
126
|
+
const runtime = new Context()
|
|
127
|
+
ctx = runtime
|
|
128
|
+
await mountWebServer(runtime, routes)
|
|
129
|
+
apply(runtime, { advisorReportRoute: true })
|
|
130
|
+
await settle()
|
|
131
|
+
const route = routes.find(entry => entry.path === REPORT_ROUTE)
|
|
132
|
+
expect(route).toBeDefined()
|
|
133
|
+
return route as RegisteredRoute
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
it('answers 503 without an agents service', async () => {
|
|
137
|
+
const route = await mountedRoute()
|
|
138
|
+
const response = await invoke(route, { url: `${REPORT_ROUTE}?sessionId=s1` })
|
|
139
|
+
expect(response.status).toBe(503)
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('answers 404 for an unknown session and 400 for a missing sessionId', async () => {
|
|
143
|
+
const routes: RegisteredRoute[] = []
|
|
144
|
+
const runtime = new Context()
|
|
145
|
+
ctx = runtime
|
|
146
|
+
await mountWebServer(runtime, routes)
|
|
147
|
+
mountAgents(runtime)
|
|
148
|
+
apply(runtime, { advisorReportRoute: true })
|
|
149
|
+
await settle()
|
|
150
|
+
const route = routes.find(entry => entry.path === REPORT_ROUTE) as RegisteredRoute
|
|
151
|
+
|
|
152
|
+
const unknown = await invoke(route, { url: `${REPORT_ROUTE}?sessionId=other` })
|
|
153
|
+
expect(unknown.status).toBe(404)
|
|
154
|
+
|
|
155
|
+
const missing = await invoke(route, { url: REPORT_ROUTE })
|
|
156
|
+
expect(missing.status).toBe(400)
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
it('serves the decay figure, summary, and scores content-free', async () => {
|
|
160
|
+
const routes: RegisteredRoute[] = []
|
|
161
|
+
const runtime = new Context()
|
|
162
|
+
ctx = runtime
|
|
163
|
+
await mountWebServer(runtime, routes)
|
|
164
|
+
mountAgents(runtime)
|
|
165
|
+
apply(runtime, { advisorReportRoute: true })
|
|
166
|
+
await settle()
|
|
167
|
+
const route = routes.find(entry => entry.path === REPORT_ROUTE) as RegisteredRoute
|
|
168
|
+
|
|
169
|
+
const response = await invoke(route, { url: `${REPORT_ROUTE}?sessionId=s1` })
|
|
170
|
+
expect(response.status).toBe(200)
|
|
171
|
+
const body = JSON.parse(response.body ?? '{}') as {
|
|
172
|
+
ok: boolean
|
|
173
|
+
sessionId: string
|
|
174
|
+
advisor: {
|
|
175
|
+
summary: { overallTask: string } | null
|
|
176
|
+
decay: number | null
|
|
177
|
+
weightedChars: number | null
|
|
178
|
+
scores: { seq: number, score: number }[]
|
|
179
|
+
lowRelevanceSeqs: number[]
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
expect(body.ok).toBe(true)
|
|
183
|
+
expect(body.sessionId).toBe('s1')
|
|
184
|
+
expect(body.advisor.decay).toBeCloseTo(0.42, 12)
|
|
185
|
+
expect(body.advisor.summary?.overallTask).toBe('migrate the auth module')
|
|
186
|
+
expect(body.advisor.scores).toHaveLength(2)
|
|
187
|
+
expect(body.advisor.lowRelevanceSeqs).toEqual([5])
|
|
188
|
+
// Content-free: no message text ever rides along.
|
|
189
|
+
expect(response.body).not.toContain('"text"')
|
|
190
|
+
expect(response.body).not.toContain('"content"')
|
|
191
|
+
expect(response.body).not.toContain('apiKey')
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
it('answers an empty report (nulls, not an error) for a session whose advisor never ran', async () => {
|
|
195
|
+
const routes: RegisteredRoute[] = []
|
|
196
|
+
const runtime = new Context()
|
|
197
|
+
ctx = runtime
|
|
198
|
+
await mountWebServer(runtime, routes)
|
|
199
|
+
void runtime.plugin({
|
|
200
|
+
name: 'fake-agents-empty',
|
|
201
|
+
apply(serviceCtx) {
|
|
202
|
+
serviceCtx.provide('agents', {
|
|
203
|
+
get: (id: unknown) => (id === 's1' ? { session: { id: 's1' } } : undefined),
|
|
204
|
+
})
|
|
205
|
+
},
|
|
206
|
+
})
|
|
207
|
+
apply(runtime, { advisorReportRoute: true })
|
|
208
|
+
await settle()
|
|
209
|
+
const route = routes.find(entry => entry.path === REPORT_ROUTE) as RegisteredRoute
|
|
210
|
+
|
|
211
|
+
const response = await invoke(route, { url: `${REPORT_ROUTE}?sessionId=s1` })
|
|
212
|
+
expect(response.status).toBe(200)
|
|
213
|
+
const body = JSON.parse(response.body ?? '{}') as {
|
|
214
|
+
ok: boolean
|
|
215
|
+
advisor: { summary: unknown, decay: unknown, scores: unknown[], lowRelevanceSeqs: unknown[] }
|
|
216
|
+
}
|
|
217
|
+
expect(body.ok).toBe(true)
|
|
218
|
+
expect(body.advisor.summary).toBeNull()
|
|
219
|
+
expect(body.advisor.decay).toBeNull()
|
|
220
|
+
expect(body.advisor.scores).toEqual([])
|
|
221
|
+
expect(body.advisor.lowRelevanceSeqs).toEqual([])
|
|
222
|
+
})
|
|
223
|
+
})
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Advisor integration and the advisory-only invariant.
|
|
3
|
+
*
|
|
4
|
+
* The invariant this spec exists to pin (K13): the advisor is statistics and
|
|
5
|
+
* suggestions only. Whatever its channel returns — extreme scores, a hostile
|
|
6
|
+
* summary, garbage, or nothing at all — `pruneSession` must land exactly the
|
|
7
|
+
* same reductions it lands with the advisor off and the state empty.
|
|
8
|
+
*/
|
|
9
|
+
import { describe, expect, it } from 'vitest'
|
|
10
|
+
import { Context } from '@deepseek-ai/cordis'
|
|
11
|
+
import {
|
|
12
|
+
ToolCallId as CallId,
|
|
13
|
+
createMessage,
|
|
14
|
+
createUserMessage,
|
|
15
|
+
createToolResultMessage,
|
|
16
|
+
} from '@deepseek-ai/dsh-llm'
|
|
17
|
+
import { canonicalHeader, Session, SessionId } from '@deepseek-ai/dsh-session'
|
|
18
|
+
import type { SessionEvent } from '@deepseek-ai/dsh-session'
|
|
19
|
+
import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection'
|
|
20
|
+
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
|
21
|
+
import TokenMeter from '@deepseek-ai/dsh-token-meter'
|
|
22
|
+
import ToolRuntime from '@deepseek-ai/dsh-tools'
|
|
23
|
+
import SessionStore from '@deepseek-ai/dsh-session'
|
|
24
|
+
import ToolResultPruner from '../../src/pruner.ts'
|
|
25
|
+
import {
|
|
26
|
+
collectTaskSemantics,
|
|
27
|
+
runSessionAdvisorPass,
|
|
28
|
+
type AdvisorCandidate,
|
|
29
|
+
} from '../../src/runtime/tokenpilot/advisor.ts'
|
|
30
|
+
import { getAdvisorState, recordScore } from '../../src/runtime/tokenpilot/advisor-state.ts'
|
|
31
|
+
import type { AdvisorOutcomeAuditRecord } from '../../src/runtime/audit.ts'
|
|
32
|
+
import type { AdvisorChannel } from '../../src/runtime/tokenpilot/advisor.ts'
|
|
33
|
+
|
|
34
|
+
// ── Orchestration against a scripted channel ───────────────────────────────
|
|
35
|
+
|
|
36
|
+
const CANDIDATES: AdvisorCandidate[] = [
|
|
37
|
+
{ seq: 3, characterPressure: 4_000, preview: 'auth module login flow' },
|
|
38
|
+
{ seq: 5, characterPressure: 6_000, preview: 'database migration notes' },
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
const TASK = {
|
|
42
|
+
source: 'todos' as const,
|
|
43
|
+
todoVersion: 'aaaa1111',
|
|
44
|
+
taskText: 'migrate the auth module',
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function advisorInput(turn: number, signal: AbortSignal): Parameters<typeof runSessionAdvisorPass>[3] {
|
|
48
|
+
return {
|
|
49
|
+
profile: 'tokenpilot-inspired',
|
|
50
|
+
turn,
|
|
51
|
+
candidates: CANDIDATES,
|
|
52
|
+
task: TASK,
|
|
53
|
+
advisor: { refreshTurns: 8, scoreThreshold: 0.35, sampleLimit: 16, minTokens: 250 },
|
|
54
|
+
tailText: 'working on the migration',
|
|
55
|
+
signal,
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function scriptedChannel(responses: (string | undefined)[]): { channel: AdvisorChannel, calls: { system: string, user: string }[] } {
|
|
60
|
+
const calls: { system: string, user: string }[] = []
|
|
61
|
+
return {
|
|
62
|
+
calls,
|
|
63
|
+
channel: {
|
|
64
|
+
identity: () => 'host:mock/model',
|
|
65
|
+
ask: async request => {
|
|
66
|
+
calls.push({ system: request.system, user: request.user })
|
|
67
|
+
return responses.shift()
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
describe('runSessionAdvisorPass orchestration (K11)', () => {
|
|
74
|
+
it('writes summary, scores, recertification marks, and emits three audits on success', async () => {
|
|
75
|
+
const session = Session.create(SessionId('advisor-ok'))
|
|
76
|
+
const { channel, calls } = scriptedChannel([
|
|
77
|
+
'{"overallTask":"migrate the auth module","activeSubtasks":["port login flow"],"keywords":["auth","login"]}',
|
|
78
|
+
'{"seq":3,"score":0.95,"reason":"current task"}\n{"seq":5,"score":0.10,"reason":"stale"}',
|
|
79
|
+
])
|
|
80
|
+
const audits: AdvisorOutcomeAuditRecord[] = []
|
|
81
|
+
const outcome = await runSessionAdvisorPass(session, channel, record => audits.push(record), advisorInput(4, new AbortController().signal))
|
|
82
|
+
|
|
83
|
+
expect(calls).toHaveLength(2)
|
|
84
|
+
expect(outcome).toBeDefined()
|
|
85
|
+
const state = getAdvisorState(session)
|
|
86
|
+
expect(state.summary?.overallTask).toBe('migrate the auth module')
|
|
87
|
+
expect(state.scores.get(3)?.score).toBe(0.95)
|
|
88
|
+
expect(state.scores.get(5)?.score).toBe(0.10)
|
|
89
|
+
// Below-threshold segment recertified as a suggestion only.
|
|
90
|
+
expect(state.recertified.get(5)).toBe(4)
|
|
91
|
+
expect(state.recertified.has(3)).toBe(false)
|
|
92
|
+
expect(state.watermarkSeq).toBe(5)
|
|
93
|
+
expect(audits.map(record => record.phase)).toEqual(['summary', 'scoring', 'decay'])
|
|
94
|
+
expect(audits.every(record => record.ok)).toBe(true)
|
|
95
|
+
expect(audits.find(record => record.phase === 'decay')?.decay).toBeDefined()
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('leaves state untouched and audits ok:false when the channel answers nothing', async () => {
|
|
99
|
+
const session = Session.create(SessionId('advisor-fail'))
|
|
100
|
+
const { channel, calls } = scriptedChannel([undefined, undefined])
|
|
101
|
+
const audits: AdvisorOutcomeAuditRecord[] = []
|
|
102
|
+
const outcome = await runSessionAdvisorPass(session, channel, record => audits.push(record), advisorInput(2, new AbortController().signal))
|
|
103
|
+
|
|
104
|
+
expect(outcome).toBeUndefined()
|
|
105
|
+
expect(calls).toHaveLength(1) // summary failed; scoring never ran
|
|
106
|
+
const state = getAdvisorState(session)
|
|
107
|
+
expect(state.summary).toBeUndefined()
|
|
108
|
+
expect(state.scores.size).toBe(0)
|
|
109
|
+
expect(audits).toHaveLength(1)
|
|
110
|
+
expect(audits[0]?.ok).toBe(false)
|
|
111
|
+
expect(audits[0]?.reason).toBe('channel-empty')
|
|
112
|
+
expect(state.inFlight).toBe(false)
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('skips entirely while a pass is in flight (re-entry guard)', async () => {
|
|
116
|
+
const session = Session.create(SessionId('advisor-reentry'))
|
|
117
|
+
const { channel, calls } = scriptedChannel([])
|
|
118
|
+
const state = getAdvisorState(session)
|
|
119
|
+
state.inFlight = true
|
|
120
|
+
const outcome = await runSessionAdvisorPass(session, channel, () => undefined, advisorInput(1, new AbortController().signal))
|
|
121
|
+
expect(outcome).toBeUndefined()
|
|
122
|
+
expect(calls).toHaveLength(0)
|
|
123
|
+
state.inFlight = false
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it('does not advance the watermark on failure, so candidates rescore later', async () => {
|
|
127
|
+
const session = Session.create(SessionId('advisor-watermark'))
|
|
128
|
+
const failing = scriptedChannel([undefined])
|
|
129
|
+
await runSessionAdvisorPass(session, failing.channel, () => undefined, advisorInput(1, new AbortController().signal))
|
|
130
|
+
expect(getAdvisorState(session).watermarkSeq).toBe(0)
|
|
131
|
+
|
|
132
|
+
const succeeding = scriptedChannel([
|
|
133
|
+
'{"overallTask":"t","activeSubtasks":[],"keywords":["auth"]}',
|
|
134
|
+
'{"seq":3,"score":0.9}',
|
|
135
|
+
])
|
|
136
|
+
const audits: AdvisorOutcomeAuditRecord[] = []
|
|
137
|
+
const outcome = await runSessionAdvisorPass(session, succeeding.channel, record => audits.push(record), advisorInput(2, new AbortController().signal))
|
|
138
|
+
expect(outcome).toBeDefined()
|
|
139
|
+
expect(getAdvisorState(session).watermarkSeq).toBe(3)
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('returns undefined without any channel call when the session has no task semantics', async () => {
|
|
143
|
+
const session = Session.create(SessionId('advisor-notask'))
|
|
144
|
+
const { channel, calls } = scriptedChannel(['{"overallTask":"x","keywords":["k"]}'])
|
|
145
|
+
const outcome = await runSessionAdvisorPass(session, channel, () => undefined, {
|
|
146
|
+
...advisorInput(1, new AbortController().signal),
|
|
147
|
+
task: undefined,
|
|
148
|
+
})
|
|
149
|
+
expect(outcome).toBeUndefined()
|
|
150
|
+
expect(calls).toHaveLength(0)
|
|
151
|
+
})
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
describe('collectTaskSemantics over real event shapes', () => {
|
|
155
|
+
it('reads a todo/write event appended alongside message events', () => {
|
|
156
|
+
const events = [
|
|
157
|
+
{
|
|
158
|
+
type: 'user/message',
|
|
159
|
+
seq: 1,
|
|
160
|
+
time: 0,
|
|
161
|
+
data: { content: [{ type: 'text', text: 'start the migration' }] },
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
type: 'todo/write',
|
|
165
|
+
seq: 2,
|
|
166
|
+
time: 0,
|
|
167
|
+
data: { todos: ['migrate the auth module', { content: 'write tests', status: 'pending' }] },
|
|
168
|
+
},
|
|
169
|
+
] as unknown as readonly SessionEvent[]
|
|
170
|
+
const task = collectTaskSemantics(events)
|
|
171
|
+
expect(task?.source).toBe('todos')
|
|
172
|
+
expect(task?.taskText).toContain('write tests')
|
|
173
|
+
})
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
// ── K13: the advisory-only invariant, against the real pruner ──────────────
|
|
177
|
+
|
|
178
|
+
describe('advisory-only invariant (K13): advisor outputs never change landings', () => {
|
|
179
|
+
async function prunedResult(session: Session): Promise<unknown> {
|
|
180
|
+
const ctx = new Context()
|
|
181
|
+
try {
|
|
182
|
+
await ctx.plugin(SessionStore).await()
|
|
183
|
+
await ctx.plugin(SystemPrompt).await()
|
|
184
|
+
await ctx.plugin(ToolRuntime).await()
|
|
185
|
+
await ctx.plugin(SessionProjectionRegistry).await()
|
|
186
|
+
await ctx.plugin(TokenMeter).await()
|
|
187
|
+
await ctx.plugin(ToolResultPruner, {
|
|
188
|
+
profile: 'native',
|
|
189
|
+
nativeTriggerTokens: 100,
|
|
190
|
+
nativeTargetTokens: 64,
|
|
191
|
+
headChars: 8,
|
|
192
|
+
tailChars: 8,
|
|
193
|
+
}).await()
|
|
194
|
+
return ctx.toolResultPruner.pruneSession(session, { stage: 'pressure' })
|
|
195
|
+
} finally {
|
|
196
|
+
await ctx.fiber.dispose()
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function buildSession(id: string): Session {
|
|
201
|
+
const session = Session.create(SessionId(id))
|
|
202
|
+
const callId = CallId('call-1')
|
|
203
|
+
session.append('turn/start', { turn: 1 })
|
|
204
|
+
session.append('request/header', {
|
|
205
|
+
reason: 'initial',
|
|
206
|
+
header: canonicalHeader({ config: { provider: 'deepseek', model: 'deepseek-v4-flash' } }),
|
|
207
|
+
})
|
|
208
|
+
session.append('user/message', createUserMessage({
|
|
209
|
+
content: [{ type: 'text', text: 'please inspect the failing module' }],
|
|
210
|
+
source: { kind: 'user' },
|
|
211
|
+
}), { surfaceOp: 'append' })
|
|
212
|
+
session.append('step/start', { turn: 1, step: 1 })
|
|
213
|
+
session.append('assistant/message', {
|
|
214
|
+
stream: [],
|
|
215
|
+
turn: 1,
|
|
216
|
+
step: 1,
|
|
217
|
+
message: createMessage({
|
|
218
|
+
role: 'assistant',
|
|
219
|
+
content: [{ type: 'tool-call', id: callId, name: 'bash', arguments: '{}' }],
|
|
220
|
+
source: { kind: 'model', provider: 'deepseek', model: 'deepseek-v4-flash' },
|
|
221
|
+
}),
|
|
222
|
+
}, { surfaceOp: 'append' })
|
|
223
|
+
session.append('tool/call', { turn: 1, step: 1, callId, name: 'bash', arguments: '{}' })
|
|
224
|
+
session.append('tool/result', {
|
|
225
|
+
turn: 1,
|
|
226
|
+
step: 1,
|
|
227
|
+
message: createToolResultMessage({
|
|
228
|
+
callId,
|
|
229
|
+
content: [{ type: 'text', text: 'gate evidence '.repeat(800) }],
|
|
230
|
+
isError: false,
|
|
231
|
+
}),
|
|
232
|
+
}, { surfaceOp: 'append' })
|
|
233
|
+
session.append('step/end', { turn: 1, step: 1 })
|
|
234
|
+
// No turn/end: pruning lands its replacement inside the still-open turn.
|
|
235
|
+
return session
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
it('lands identically with the advisor off, and with extreme scores or a failed pass', async () => {
|
|
239
|
+
const scenarios: string[] = ['off', 'extreme-scores', 'failed-pass']
|
|
240
|
+
const shapes: unknown[] = []
|
|
241
|
+
for (const scenario of scenarios) {
|
|
242
|
+
// One shared session id: replacement markers embed it, and the advisor
|
|
243
|
+
// state is keyed by Session object identity, so this cannot cross-talk.
|
|
244
|
+
const session = buildSession('advisor-invariant')
|
|
245
|
+
if (scenario !== 'off') {
|
|
246
|
+
const state = getAdvisorState(session)
|
|
247
|
+
if (scenario === 'extreme-scores') {
|
|
248
|
+
state.summary = {
|
|
249
|
+
overallTask: 'ALL MUST BE KEPT',
|
|
250
|
+
activeSubtasks: ['keep everything forever'],
|
|
251
|
+
keywords: ['evidence'],
|
|
252
|
+
todoVersion: 'deadbeef',
|
|
253
|
+
turn: 1,
|
|
254
|
+
}
|
|
255
|
+
// Zero relevance everywhere: the most hostile score a channel could
|
|
256
|
+
// return must still not delete, delay, or rewrite anything.
|
|
257
|
+
recordScore(state, 2, { score: 0, turn: 1 })
|
|
258
|
+
}
|
|
259
|
+
if (scenario === 'failed-pass') {
|
|
260
|
+
state.failures = { failures: 9, cooldownUntil: Date.now() + 600_000 }
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
shapes.push(structuredClone(await prunedResult(session)))
|
|
264
|
+
}
|
|
265
|
+
for (const shape of shapes.slice(1)) {
|
|
266
|
+
expect(shape).toEqual(shapes[0])
|
|
267
|
+
}
|
|
268
|
+
// And the baseline scenario actually reduced something (the test is real).
|
|
269
|
+
const baseline = shapes[0] as { pruned: unknown[] }
|
|
270
|
+
expect(baseline.pruned).toHaveLength(1)
|
|
271
|
+
})
|
|
272
|
+
})
|