Merge branch 'codex/simp-session-log-representation' into codex/simp-snapshot-fixture-inventory
# Conflicts: # packages/support/acp-snapshot/README.md # packages/support/acp-snapshot/src/suite.ts # packages/support/acp-snapshot/tests/suite.spec.ts
This commit is contained in:
+1
-1
@@ -1,2 +1,2 @@
|
||||
{"type":"session","id":"ccdc749f-56f3-4267-9750-598b5c60b7b2","createdAt":600,"cwd":"/var/folders/2g/b32ct0qn1d728l_v6tdkjytr0000gn/T/acp-snap-cwd-nOQ4Gy"}
|
||||
{"type":"request/header","seq":0,"time":4,"data":{"header":{"config":{"model":"fake"},"system":"{{system}}","tools":[{"name":"t1","description":"D1","parameters":{"type":"object"}}]},"reason":"initial"}}
|
||||
{"type":"request/header","seq":0,"time":4,"data":{"header":{"config":{"model":"fake"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"initial": [
|
||||
{
|
||||
"name": "t1",
|
||||
"description": "D1",
|
||||
"parameters": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
],
|
||||
"changes": []
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
{"type":"session","id":"12121212-3434-4545-8686-787878787878","createdAt":7,"cwd":"/rec/pin-cwd"}
|
||||
{"type":"request/header","seq":0,"time":7,"data":{"header":{"config":{"model":"fake"},"system":"{{system}}","tools":[{"name":"t1","description":"D1","parameters":{"type":"object"}}]},"reason":"initial"}}
|
||||
{"type":"request/header","seq":1,"time":7,"data":{"header":{"config":{"model":"fake"},"system":"{{system}}","tools":[{"name":"t1","description":"D1","parameters":{"type":"object"}}]},"reason":"change"}}
|
||||
{"type":"request/header","seq":0,"time":7,"data":{"header":{"config":{"model":"fake"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}
|
||||
{"type":"request/header","seq":1,"time":7,"data":{"header":{"config":{"model":"fake"},"system":"{{system}}","tools":"{{tools}}"},"reason":"change"}}
|
||||
{"type":"turn/start","seq":2,"time":7,"data":{"turn":1}}
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"initial": [
|
||||
{
|
||||
"name": "t1",
|
||||
"description": "D1",
|
||||
"parameters": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
],
|
||||
"changes": [
|
||||
[
|
||||
{
|
||||
"name": "t1",
|
||||
"description": "D1",
|
||||
"parameters": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
normalizeStdout,
|
||||
scrubRequestHeaders,
|
||||
scrubSystemPrompts,
|
||||
scrubToolSchemas,
|
||||
} from '../src/normalize.ts'
|
||||
|
||||
/**
|
||||
@@ -238,3 +239,45 @@ describe('scrubSystemPrompts', () => {
|
||||
expect(scrubSystemPrompts(out)).toBe(out)
|
||||
})
|
||||
})
|
||||
|
||||
describe('scrubToolSchemas', () => {
|
||||
it('scrubs only tool-schema payloads while keeping prompts and prefixes verbatim', () => {
|
||||
const header = JSON.stringify({
|
||||
type: 'request/header', seq: 1, time: 2,
|
||||
data: {
|
||||
header: {
|
||||
system: 'full prompt',
|
||||
tools: [{ name: 'read', description: 'full schema', parameters: { type: 'object' } }],
|
||||
messagePrefix: [{ role: 'user', content: [{ type: 'text', text: 'full prefix' }] }],
|
||||
},
|
||||
reason: 'initial',
|
||||
},
|
||||
})
|
||||
const changed = JSON.stringify({
|
||||
type: 'request/header', seq: 2, time: 3,
|
||||
data: {
|
||||
header: {
|
||||
system: 'new prompt',
|
||||
tools: [{ name: 'grep', description: 'new schema' }],
|
||||
messagePrefix: [{ role: 'user', content: [{ type: 'text', text: 'changed prefix' }] }],
|
||||
},
|
||||
reason: 'change',
|
||||
},
|
||||
})
|
||||
const systemOnly = JSON.stringify({
|
||||
type: 'request/header', seq: 3, time: 4,
|
||||
data: { header: { system: 'prompt only' }, reason: 'resume' },
|
||||
})
|
||||
|
||||
const out = scrubToolSchemas(`${header}\n${changed}\n${systemOnly}\n`)
|
||||
expect(out.match(/"tools":"{{tools}}"/g)).toHaveLength(2)
|
||||
expect(out).not.toContain('full schema')
|
||||
expect(out).not.toContain('new schema')
|
||||
expect(out).toContain('full prompt')
|
||||
expect(out).toContain('new prompt')
|
||||
expect(out).toContain('full prefix')
|
||||
expect(out).toContain('changed prefix')
|
||||
expect(out.split('\n')[2]).toBe(systemOnly)
|
||||
expect(scrubToolSchemas(out)).toBe(out)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -9,11 +9,16 @@ import {
|
||||
fixtureContext,
|
||||
formatSystemPromptSnapshot,
|
||||
headerChangeCount,
|
||||
formatToolSchemasSnapshot,
|
||||
normalizedHeaders,
|
||||
normalizedSystemPrompts,
|
||||
normalizedToolSchemas,
|
||||
parseToolSchemasSnapshot,
|
||||
refreshFixtureReplacements,
|
||||
sessionFixtureNames,
|
||||
restorePinnedToolSchemas,
|
||||
stabilizeRefreshLog,
|
||||
unknownToolCallIds,
|
||||
} from '../src/suite.ts'
|
||||
|
||||
/**
|
||||
@@ -75,6 +80,7 @@ afterAll(async () => {
|
||||
function staleRefreshFixtures(dir: string): void {
|
||||
writeFileSync(join(dir, 'plain-turn', 'stdout.golden.jsonl'), 'stale stdout\n')
|
||||
writeFileSync(join(dir, 'pin-turn', 'system-prompt.golden.md'), 'STALE PROMPT\n')
|
||||
writeFileSync(join(dir, 'pin-turn', 'tool-schemas.golden.json'), '{"initial":[{"name":"stale"}],"changes":[]}\n')
|
||||
|
||||
const plainBehaviorFile = join(dir, 'plain-turn', 'behavior.json')
|
||||
const plainBehavior = JSON.parse(readFileSync(plainBehaviorFile, 'utf8')) as Record<string, unknown>
|
||||
@@ -132,6 +138,9 @@ describe('defineAcpSnapshotSuite: refresh write-back', () => {
|
||||
'NEW PROMPT LINE',
|
||||
'',
|
||||
].join('\n'))
|
||||
const schemas = readFileSync(join(refreshDir, 'pin-turn', 'tool-schemas.golden.json'), 'utf8')
|
||||
expect(schemas).toContain('"description": "D1"')
|
||||
expect(schemas).not.toContain('"name":"stale"')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -277,6 +286,23 @@ describe('normalizedSystemPrompts', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('normalizedToolSchemas', () => {
|
||||
it('extracts normalized schema arrays and omits absent or non-array fields', () => {
|
||||
const log = [
|
||||
'{"type":"session","id":"a","createdAt":5,"cwd":"/w"}',
|
||||
'{"type":"request/header","seq":0,"time":9,"data":{"header":{"tools":[{"name":"read","description":"work in /w"}]}}}',
|
||||
'{"type":"request/header","seq":1,"time":9,"data":{"header":{}}}',
|
||||
'{"type":"request/header","seq":2,"time":9,"data":{"header":{"tools":null}}}',
|
||||
'{"type":"request/header","seq":3,"time":9,"data":{"header":null}}',
|
||||
'{"type":"request/header","seq":4,"time":9,"data":{"header":"invalid"}}',
|
||||
'',
|
||||
].join('\n')
|
||||
expect(normalizedToolSchemas(log, { sessionIds: [], cwd: '/w' })).toEqual([
|
||||
[{ name: 'read', description: 'work in {{cwd}}' }],
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('formatSystemPromptSnapshot', () => {
|
||||
it('adds a missing terminal newline without changing an existing one', () => {
|
||||
expect(formatSystemPromptSnapshot('prompt')).toBe('prompt\n')
|
||||
@@ -304,6 +330,61 @@ describe('headerChangeCount', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('tool-schema snapshots', () => {
|
||||
const snapshot = {
|
||||
initial: [{ name: 'read', description: 'Read a file.' }],
|
||||
changes: [[{ name: 'grep', description: 'Search files.' }]],
|
||||
}
|
||||
|
||||
it('formats and parses canonical structured JSON', () => {
|
||||
const formatted = formatToolSchemasSnapshot(snapshot.initial, snapshot.changes)
|
||||
expect(formatted).toBe(`${JSON.stringify(snapshot, null, 2)}\n`)
|
||||
expect(parseToolSchemasSnapshot(formatted)).toEqual(snapshot)
|
||||
})
|
||||
|
||||
it('rejects invalid top-level and field shapes', () => {
|
||||
expect(() => parseToolSchemasSnapshot('null')).toThrow(/must be an object/)
|
||||
expect(() => parseToolSchemasSnapshot('"invalid"')).toThrow(/must be an object/)
|
||||
expect(() => parseToolSchemasSnapshot('[]')).toThrow(/must be an object/)
|
||||
expect(() => parseToolSchemasSnapshot('{"initial":{},"changes":[]}')).toThrow(/array-valued/)
|
||||
expect(() => parseToolSchemasSnapshot('{"initial":[],"changes":{}}')).toThrow(/array-valued/)
|
||||
expect(() => parseToolSchemasSnapshot('{"initial":[],"changes":[{}]}')).toThrow(/array-valued/)
|
||||
})
|
||||
|
||||
it('restores initial schemas into the pinned header token', () => {
|
||||
expect(restorePinnedToolSchemas({ system: '{{system}}', tools: '{{tools}}' }, snapshot.initial))
|
||||
.toEqual({ system: '{{system}}', tools: snapshot.initial })
|
||||
})
|
||||
|
||||
it('rejects invalid headers and a missing tool token', () => {
|
||||
expect(() => restorePinnedToolSchemas(null, snapshot.initial)).toThrow(/must be an object/)
|
||||
expect(() => restorePinnedToolSchemas('invalid', snapshot.initial)).toThrow(/must be an object/)
|
||||
expect(() => restorePinnedToolSchemas([], snapshot.initial)).toThrow(/must be an object/)
|
||||
expect(() => restorePinnedToolSchemas({ tools: [] }, snapshot.initial)).toThrow(/must equal/)
|
||||
})
|
||||
})
|
||||
|
||||
describe('unknownToolCallIds', () => {
|
||||
it('returns structured UNKNOWN_TOOL call ids and ignores other results', () => {
|
||||
const log = [
|
||||
'{"type":"tool/result","data":{"callId":"missing","error":{"code":"UNKNOWN_TOOL"}}}',
|
||||
'{"type":"tool/result","data":{"callId":"failed","error":{"code":"EXECUTION_FAILED"}}}',
|
||||
'{"type":"tool/result","data":null}',
|
||||
'{"type":"tool/result","data":"invalid"}',
|
||||
'{"type":"tool/result","data":{"error":null}}',
|
||||
'{"type":"tool/result","data":{"error":"invalid"}}',
|
||||
'{"type":"assistant/message","data":{"error":{"code":"UNKNOWN_TOOL"}}}',
|
||||
'{"type":"tool/result","data":{"error":{"code":"UNKNOWN_TOOL"}}}',
|
||||
'',
|
||||
].join('\n')
|
||||
expect(unknownToolCallIds(log)).toEqual(['missing', '<missing callId>'])
|
||||
})
|
||||
|
||||
it('returns no failures for ordinary tool results', () => {
|
||||
expect(unknownToolCallIds('{"type":"tool/result","data":{"callId":"ok"}}\n')).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('refreshFixtureReplacements', () => {
|
||||
it('maps fresh ids and cwd values to the existing fixture values, skipping non-replacements', () => {
|
||||
const log = (content: string): HarvestedLog => ({ id: 'diagnostic', createdAt: 1, content })
|
||||
|
||||
Reference in New Issue
Block a user