fix(transcript): mirror turn liveness into meta.activity, live and cold

This commit is contained in:
qer 2026-08-20 13:36:25 +08:00
parent 57ee405347
commit 609d849596
3 changed files with 25 additions and 2 deletions

View file

@ -384,6 +384,7 @@ export class AgentTranscriptProjector {
this.openText = undefined;
this.openThinking = undefined;
ops.push({ op: 'turn.upsert', turn: this.currentTurn });
ops.push({ op: 'meta.merge', meta: { activity: 'turn' } });
return ops;
}
@ -421,6 +422,7 @@ export class AgentTranscriptProjector {
usage: this.takeTurnUsage(turnId),
};
ops.push({ op: 'turn.upsert', turn: this.currentTurn });
ops.push({ op: 'meta.merge', meta: { activity: 'idle' } });
this.currentStep = undefined;
this.pendingTaskNotifications = [];
if (event.reason === 'cancelled' && event.interruptReason === 'user_cancelled') {

View file

@ -524,7 +524,13 @@ export class TranscriptService {
messages,
sawTurnPrompt ? { taskOriginTurnTaskIds } : undefined,
);
return foldWireRecordFacts(records, base);
const lastTurn = base.items.findLast((item) => item.kind === 'turn');
const activity =
lastTurn?.kind === 'turn' && lastTurn.state === 'running' ? 'unknown' : 'idle';
return foldWireRecordFacts(records, {
...base,
meta: { ...base.meta, activity },
});
}
/** Dispose the live store + binding for a session (session closed / server shutdown). */

View file

@ -1984,11 +1984,26 @@ describe('AgentTranscriptProjector', () => {
expect(turnOps('t0', tx.getItems()).state).toBe('failed');
});
it('maps cron / task origins onto the turn header', () => {
it('mirrors turn liveness into meta.activity', () => {
const projector = new AgentTranscriptProjector('main');
const tx = new AgentTranscript('main');
const feed = (event: ProjectorBusEvent): void => void tx.apply(projector.map(event));
expect(tx.getMeta().activity).toBeUndefined();
feed(ev({ type: 'turn.started', turnId: 1, origin: { kind: 'user' } }));
expect(tx.getMeta().activity).toBe('turn');
feed(ev({ type: 'turn.ended', turnId: 1, reason: 'completed' }));
expect(tx.getMeta().activity).toBe('idle');
feed(ev({ type: 'turn.started', turnId: 2, origin: { kind: 'user' } }));
expect(tx.getMeta().activity).toBe('turn');
feed(ev({ type: 'turn.ended', turnId: 2, reason: 'failed' }));
expect(tx.getMeta().activity).toBe('idle');
});
it('maps cron / task origins onto the turn header', () => { const projector = new AgentTranscriptProjector('main');
const tx = new AgentTranscript('main');
const feed = (event: ProjectorBusEvent): void => void tx.apply(projector.map(event));
feed(
ev({
type: 'turn.started',