Merge remote-tracking branch 'origin/master' into feat/read-image-context
This commit is contained in:
File diff suppressed because one or more lines are too long
107
examples/headless-agent/tests/session-format-guard.snapshot.ts
Normal file
107
examples/headless-agent/tests/session-format-guard.snapshot.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* Assembled-app regression for the session-format refusal surface: resuming a
|
||||
* log written by a "newer" harness (format version ahead, or an unknown
|
||||
* required event type) fails loud through the real Loader composition, and the
|
||||
* error the product user sees names the direction and the raw log path.
|
||||
* @module session-format-guard-snapshot
|
||||
*/
|
||||
|
||||
import { join, dirname } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { Context } from '@deepseek-ai/cordis'
|
||||
import { LOADER_SMOKE_TEST_TIMEOUT_MS, runLoaderSmoke } from '@deepseek-ai/dsh-loader-smoke'
|
||||
import SessionStore, {
|
||||
SESSION_FORMAT_VERSION,
|
||||
SessionId,
|
||||
type SessionEvent,
|
||||
type SessionHeader,
|
||||
} from '@deepseek-ai/dsh-session'
|
||||
import SessionPersistenceJsonl from '@deepseek-ai/dsh-session-persistence-jsonl'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
const fixtureDir = join(dirname(fileURLToPath(import.meta.url)), 'workspace-context-resume-snapshots/offline-edit')
|
||||
const replayFixture = join(fixtureDir, 'replay.jsonl')
|
||||
const configPath = fileURLToPath(new URL('../workspace-context-resume.cordis.snapshot.yml', import.meta.url))
|
||||
const binScript = fileURLToPath(new URL('./fixtures/headless-driver.ts', import.meta.url))
|
||||
const tsconfigPath = fileURLToPath(new URL('../../../tsconfig.json', import.meta.url))
|
||||
// The resumed-agent fixture in the shared config resumes exactly this id.
|
||||
const sessionId = SessionId('workspace-context-resume')
|
||||
|
||||
/** Persist one session with the given header version and events, returning its log path. */
|
||||
async function seedSession(root: string, cwd: string, version: number, events: SessionEvent[]): Promise<string> {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SessionStore)
|
||||
await ctx.plugin(SessionPersistenceJsonl, { root, compression: 'none' })
|
||||
const meta: SessionHeader = { version, id: sessionId, createdAt: 1, cwd }
|
||||
try {
|
||||
await ctx.sessionPersistence.create(meta)
|
||||
await ctx.sessionPersistence.append(sessionId, events)
|
||||
const location = ctx.sessionPersistence.locate(meta)
|
||||
if (location === undefined) throw new Error('JSONL backend did not locate the seeded session')
|
||||
return location.path
|
||||
} finally {
|
||||
await ctx.fiber.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
function closedTurn(): SessionEvent[] {
|
||||
return [
|
||||
{ type: 'turn/start', seq: 0, time: 1, data: { turn: 1 } },
|
||||
{ type: 'turn/end', seq: 1, time: 2, data: { turn: 1, reason: { kind: 'completed' } } },
|
||||
]
|
||||
}
|
||||
|
||||
describe('session format guard through the assembled app', () => {
|
||||
it('refuses to resume a newer-format log, naming the upgrade direction and the raw log path', async () => {
|
||||
let sessionPath = ''
|
||||
const result = await runLoaderSmoke({
|
||||
label: 'newer-format resume refusal',
|
||||
tempDirPrefix: 'dsh-format-guard-version-',
|
||||
binScript,
|
||||
libBinScript: binScript,
|
||||
configPath,
|
||||
binArgs: [configPath, 'Try to resume.'],
|
||||
tsconfigPath,
|
||||
env: { DSH_SNAPSHOT_FILE: replayFixture },
|
||||
expectedExitCode: 1,
|
||||
prepare: async (runCwd) => {
|
||||
sessionPath = await seedSession(join(runCwd, '.sessions'), runCwd, SESSION_FORMAT_VERSION + 99, closedTurn())
|
||||
},
|
||||
})
|
||||
expect(result.stderr).toContain(
|
||||
`session "${sessionId}" uses log format v${SESSION_FORMAT_VERSION + 99}, but this harness reads only v${SESSION_FORMAT_VERSION}: the log was written by a newer harness — upgrade the harness to open it`,
|
||||
)
|
||||
// macOS reports the temp dir via the /private symlink parent; assert the
|
||||
// stable path suffix instead of the realpath-dependent prefix.
|
||||
expect(result.stderr).toContain('(raw log: ')
|
||||
expect(result.stderr).toContain(sessionPath.slice(sessionPath.indexOf('/.sessions/')))
|
||||
}, LOADER_SMOKE_TEST_TIMEOUT_MS)
|
||||
|
||||
it('refuses to resume a log with an unknown required event type', async () => {
|
||||
let sessionPath = ''
|
||||
const result = await runLoaderSmoke({
|
||||
label: 'unknown-event resume refusal',
|
||||
tempDirPrefix: 'dsh-format-guard-event-',
|
||||
binScript,
|
||||
libBinScript: binScript,
|
||||
configPath,
|
||||
binArgs: [configPath, 'Try to resume.'],
|
||||
tsconfigPath,
|
||||
env: { DSH_SNAPSHOT_FILE: replayFixture },
|
||||
expectedExitCode: 1,
|
||||
prepare: async (runCwd) => {
|
||||
sessionPath = await seedSession(join(runCwd, '.sessions'), runCwd, SESSION_FORMAT_VERSION, [
|
||||
...closedTurn(),
|
||||
{ type: 'future/event', seq: 2, time: 3, data: { payload: 1 } } as unknown as SessionEvent,
|
||||
])
|
||||
},
|
||||
})
|
||||
expect(result.stderr).toContain(
|
||||
`session "${sessionId}" contains event type "future/event" (seq 2) unknown to this harness and not marked ignorable; refusing to interpret the log — it was likely written by a newer harness`,
|
||||
)
|
||||
// macOS reports the temp dir via the /private symlink parent; assert the
|
||||
// stable path suffix instead of the realpath-dependent prefix.
|
||||
expect(result.stderr).toContain('(raw log: ')
|
||||
expect(result.stderr).toContain(sessionPath.slice(sessionPath.indexOf('/.sessions/')))
|
||||
}, LOADER_SMOKE_TEST_TIMEOUT_MS)
|
||||
})
|
||||
Reference in New Issue
Block a user