mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-10-26 02:04:37 +08:00
fix(ai): harden contact session inspection
Use the all-history contact snapshot and validate raw session candidates inside the cancellable scan loop. Bound model-visible structural results to the configured token budget while preserving full tool details and continuation metadata.
This commit is contained in:
1 parent
f26c6f6eba
commit
10e0825578
4 files changed
+323
-6
No files matched your search
@@ -466,6 +466,103 @@ test('contact session inspection scans imported sessions and separates own messa
|
||||
}
|
||||
})
|
||||
|
||||
test('contact session inspection uses the all-history contact snapshot', async () => {
|
||||
const { env, contactsService: fixtureContactsService } = createFixture()
|
||||
const detailPresets: Array<string | undefined> = []
|
||||
try {
|
||||
const contactsService: Pick<ContactsService, 'getContactDetail' | 'getContactsPage'> = {
|
||||
...fixtureContactsService,
|
||||
getContactDetail: (key, options) => {
|
||||
detailPresets.push(options?.timeRangePreset)
|
||||
return options?.timeRangePreset === 'all' ? fixtureContactsService.getContactDetail(key, options) : detail(null)
|
||||
},
|
||||
}
|
||||
const service = createCrossChatAnalysisService({ adapter: env.adapter, contactsService })
|
||||
|
||||
const result = await service.inspectContactSessions({ contactKey: 'test:alice' })
|
||||
|
||||
assert.equal(result.contact?.contactKey, 'test:alice')
|
||||
assert.deepEqual(
|
||||
result.sessions.map((session) => session.sessionId),
|
||||
['group-work', 'private-alice']
|
||||
)
|
||||
assert.deepEqual(detailPresets, ['all'])
|
||||
} finally {
|
||||
env.cleanup()
|
||||
}
|
||||
})
|
||||
|
||||
test('contact session inspection validates raw candidates inside failure, time, and cancellation boundaries', async () => {
|
||||
const { env, contactsService } = createFixture()
|
||||
try {
|
||||
const candidateIds = [...env.adapter.listSessionCandidateIds!(), 'damaged']
|
||||
let eagerEnumerationCalled = false
|
||||
const failureAdapter: SessionRuntimeAdapter = {
|
||||
...env.adapter,
|
||||
listSessionIds: () => {
|
||||
eagerEnumerationCalled = true
|
||||
throw new Error('eager enumeration should not be used')
|
||||
},
|
||||
listSessionCandidateIds: () => candidateIds,
|
||||
openReadonly: (sessionId) => {
|
||||
if (sessionId === 'damaged') throw new Error('fixture failure')
|
||||
return env.adapter.openReadonly(sessionId)
|
||||
},
|
||||
}
|
||||
const failureResult = await createCrossChatAnalysisService({
|
||||
adapter: failureAdapter,
|
||||
contactsService,
|
||||
}).inspectContactSessions({ contactKey: 'test:alice' })
|
||||
|
||||
assert.equal(eagerEnumerationCalled, false)
|
||||
assert.deepEqual(
|
||||
failureResult.sessions.map((session) => session.sessionId),
|
||||
['group-work', 'private-alice']
|
||||
)
|
||||
assert.deepEqual(failureResult.coverage.failedSessionIds, ['damaged'])
|
||||
|
||||
let now = 0
|
||||
const openedSessions: string[] = []
|
||||
const timedAdapter: SessionRuntimeAdapter = {
|
||||
...env.adapter,
|
||||
openReadonly: (sessionId) => {
|
||||
openedSessions.push(sessionId)
|
||||
const db = env.adapter.openReadonly(sessionId)
|
||||
now += 9_000
|
||||
return db
|
||||
},
|
||||
}
|
||||
const timedResult = await createCrossChatAnalysisService({
|
||||
adapter: timedAdapter,
|
||||
contactsService,
|
||||
now: () => now,
|
||||
}).inspectContactSessions({ contactKey: 'test:alice', maxWallTimeMs: 8_000 })
|
||||
|
||||
assert.equal(openedSessions.length, 1)
|
||||
assert.equal(timedResult.coverage.scannedSessions, 1)
|
||||
assert.ok(timedResult.coverage.truncatedReasons.includes('time_budget'))
|
||||
assert.ok(timedResult.coverage.nextCursor)
|
||||
|
||||
const controller = new AbortController()
|
||||
const service = createCrossChatAnalysisService({ adapter: env.adapter, contactsService })
|
||||
await assert.rejects(
|
||||
() =>
|
||||
service.inspectContactSessions(
|
||||
{ contactKey: 'test:alice' },
|
||||
{
|
||||
signal: controller.signal,
|
||||
onProgress: (progress) => {
|
||||
if (progress.currentSessionId === 'group-work') controller.abort()
|
||||
},
|
||||
}
|
||||
),
|
||||
{ name: 'AbortError' }
|
||||
)
|
||||
} finally {
|
||||
env.cleanup()
|
||||
}
|
||||
})
|
||||
|
||||
test('contact session inspection honors time ranges, session-scoped identity, and continuation cursors', async () => {
|
||||
const { env, contactsService } = createFixture()
|
||||
try {
|
||||
|
||||
@@ -267,17 +267,23 @@ class DefaultCrossChatAnalysisService implements CrossChatAnalysisService {
|
||||
const includeRosterOnly = request.includeRosterOnly !== false
|
||||
const pageSize = clampInteger(request.pageSize, DEFAULT_INSPECTION_PAGE_SIZE, 1, MAX_INSPECTION_PAGE_SIZE)
|
||||
const maxWallTimeMs = clampInteger(request.maxWallTimeMs, DEFAULT_MAX_WALL_TIME_MS, 1, MAX_MAX_WALL_TIME_MS)
|
||||
const detail = this.deps.contactsService.getContactDetail(contactKey, { acceptStale: true })
|
||||
const detail = this.deps.contactsService.getContactDetail(contactKey, {
|
||||
acceptStale: true,
|
||||
timeRangePreset: 'all',
|
||||
})
|
||||
const contact = detail.contact
|
||||
if (!contact) {
|
||||
return emptyContactSessionsResult(detail.cache.status, range)
|
||||
}
|
||||
|
||||
const startedAt = this.now()
|
||||
const candidateSessionIds = contact.sessionScoped
|
||||
? contact.sessionId
|
||||
? [contact.sessionId]
|
||||
: []
|
||||
: [...new Set(this.deps.adapter.listSessionIds())].sort((left, right) => left.localeCompare(right))
|
||||
: [...new Set(this.deps.adapter.listSessionCandidateIds?.() ?? this.deps.adapter.listSessionIds())].sort(
|
||||
(left, right) => left.localeCompare(right)
|
||||
)
|
||||
const cursorFingerprint = createInspectionFingerprint({
|
||||
candidateSessionIds,
|
||||
contactKey,
|
||||
@@ -286,7 +292,6 @@ class DefaultCrossChatAnalysisService implements CrossChatAnalysisService {
|
||||
includeRosterOnly,
|
||||
})
|
||||
const cursor = parseInspectionCursor(request.cursor, cursorFingerprint, candidateSessionIds)
|
||||
const startedAt = this.now()
|
||||
const sessions: CrossChatContactSessionsResult['sessions'] = []
|
||||
const failedSessionIds: string[] = []
|
||||
const truncatedReasons = new Set<CrossChatContactSessionsResult['coverage']['truncatedReasons'][number]>()
|
||||
|
||||
Reference in new issue
Block a user