Skip to content

Commit ca5f69d

Browse files
committed
fix(recording): final codex review findings — viewer sync, chat pending terminal, startup fileSwitch filter
- Fix sync-types.mjs path (extension/src/services/ -> extension/src/extension/services/) so the script is no longer broken with ENOENT; regenerate recordingTypes.ts which now includes ConsentChangeEvent, StartupPhaseCompleteEvent, BuildResultEvent scoping fields, and InterventionEvent blocked/dismissReason/rawWanted fields; add consentChange + startupPhaseComplete to MARKER_COLORS and ALL_EVENT_TYPES in constants.ts - Fix orphaned pending IrisChatSendAttempt: fire status='failed' with send-rejected reason whenever sendMessage() returns result.sent===false, before handling the per-reason UX side-effects - Fix startup fileSwitch privacy leak: check shouldRecordUri on the active editor URI before emitting the event so out-of-exercise or blacklisted-scheme files are excluded
1 parent 1b9174f commit ca5f69d

5 files changed

Lines changed: 79 additions & 11 deletions

File tree

extension/src/extension/provider/chatWebviewProvider.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -633,6 +633,12 @@ export class ChatWebviewProvider extends BaseWebviewProvider implements vscode.W
633633
this._onDidAttemptIrisChatSend.fire({ content, status: 'sent' });
634634
this._onDidSendIrisChatMessage.fire(content);
635635
} else {
636+
// Fire terminal 'failed' so the pending event is never orphaned.
637+
this._onDidAttemptIrisChatSend.fire({
638+
content,
639+
status: 'failed',
640+
errorMessage: `send-rejected: ${result.reason ?? 'unknown'}`,
641+
});
636642
switch (result.reason) {
637643
case 'no-ai':
638644
this._postNoAiStatus(true);

extension/src/extension/services/telemetry/recording/sessionRecorder.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1375,14 +1375,14 @@ export class SessionRecorder implements vscode.Disposable, WebSocketMessageHandl
13751375
}
13761376

13771377
// 3. fileSwitch for the active editor (if any).
1378-
const activeUri = vscode.window.activeTextEditor?.document.uri.toString();
1379-
if (activeUri) {
1378+
const activeUri = vscode.window.activeTextEditor?.document.uri;
1379+
if (activeUri && shouldRecordUri(activeUri, this._exerciseRootUri)) {
13801380
this._recordInternal(
1381-
{ type: 'fileSwitch', timestamp: Date.now(), fromUri: undefined, toUri: activeUri },
1381+
{ type: 'fileSwitch', timestamp: Date.now(), fromUri: undefined, toUri: activeUri.toString() },
13821382
{ allowDuringStartup: true },
13831383
generation,
13841384
);
1385-
this._lastActiveEditorUri = activeUri;
1385+
this._lastActiveEditorUri = activeUri.toString();
13861386
}
13871387

13881388
// 4. terminalOpenClose('opened') for every already-open terminal.

recording-viewer/scripts/sync-types.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ import { dirname, join } from 'path';
1010
import { fileURLToPath } from 'url';
1111

1212
const __dirname = dirname(fileURLToPath(import.meta.url));
13-
const SOURCE = join(__dirname, '..', '..', 'extension', 'src', 'services', 'telemetry', 'recording', 'types.ts');
13+
const SOURCE = join(__dirname, '..', '..', 'extension', 'src', 'extension', 'services', 'telemetry', 'recording', 'types.ts');
1414
const DEST = join(__dirname, '..', 'src', 'generated', 'recordingTypes.ts');
1515

1616
const HEADER = `// DO NOT EDIT — auto-generated by sync-types.mjs
17-
// Source: extension/src/services/telemetry/recording/types.ts
17+
// Source: extension/src/extension/services/telemetry/recording/types.ts
1818
`;
1919

2020
let source = readFileSync(SOURCE, 'utf-8');

recording-viewer/src/constants.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ export const MARKER_COLORS: Record<EventType, string> = {
1212
fileSnapshot: '#4ade80',
1313
sessionStart: '#a5b4fc',
1414
sessionEnd: '#f87171',
15+
consentChange: '#fb7185',
16+
startupPhaseComplete: '#a5f3fc',
1517
irisChatMessage: '#f472b6',
1618
irisChatSendAttempt: '#fb923c',
1719
irisChatFeedback: '#a78bfa',
@@ -31,7 +33,7 @@ export const MARKER_COLORS: Record<EventType, string> = {
3133
};
3234

3335
export const ALL_EVENT_TYPES = [
34-
'sessionStart', 'sessionEnd',
36+
'sessionStart', 'sessionEnd', 'consentChange', 'startupPhaseComplete',
3537
'eqSnapshot', 'eqEngineState', 'intervention', 'buildResult',
3638
'textChange', 'save',
3739
'diagnostics',

recording-viewer/src/generated/recordingTypes.ts

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// DO NOT EDIT — auto-generated by sync-types.mjs
2-
// Source: extension/src/services/telemetry/recording/types.ts
2+
// Source: extension/src/extension/services/telemetry/recording/types.ts
33
// ── Serialization helpers ─────────────────────────────────────────────
44

55
export interface SerializedRange {
@@ -56,9 +56,16 @@ export interface BuildResultEvent {
5656
timestamp: number;
5757
successful: boolean | undefined;
5858
errorCount: number;
59+
/** Legacy: flat array of detailText strings for failed test feedbacks. Kept for backwards compat. */
5960
failedTests: string[];
6061
buildFailed: boolean;
6162
buildErrorFamilies?: string[];
63+
// Scoping fields (added in Block F)
64+
exerciseId?: number;
65+
participationId?: number;
66+
submissionId?: number;
67+
/** Structured failed-test details carrying both the test name and the failure message. */
68+
failedTestDetails?: { testName: string; detail: string }[];
6269
}
6370

6471
export interface WindowFocusEvent {
@@ -80,7 +87,7 @@ export interface SessionStartEvent {
8087
exerciseId: number;
8188
participantId: string | undefined;
8289
exerciseRoot?: string;
83-
/** Schema version for forward-compat parsing. Introduced in Block AB (version 2). */
90+
/** Schema version for forward-compat parsing. Block AB introduces version 2. */
8491
schemaVersion?: number;
8592
}
8693

@@ -90,16 +97,47 @@ export interface SessionEndEvent {
9097
exerciseId: number;
9198
}
9299

100+
/**
101+
* Emitted when user consent is downgraded (or upgraded) mid-session.
102+
* Minimal payload — carries no user data — acts as a marker only. The
103+
* downgraded path is followed by a `sessionEnd` and metadata finalisation.
104+
*/
105+
export interface ConsentChangeEvent {
106+
type: 'consentChange';
107+
timestamp: number;
108+
level: 'downgraded' | 'upgraded';
109+
}
110+
111+
/**
112+
* Marker event indicating that all synchronous startup work (snapshots,
113+
* initial diagnostics, initial-state events, startup contributors) has been
114+
* flushed to the event stream. Consumers can use this as a cut-point for
115+
* deterministic "seed state vs. runtime events" separation.
116+
*/
117+
export interface StartupPhaseCompleteEvent {
118+
type: 'startupPhaseComplete';
119+
timestamp: number;
120+
}
121+
93122
export interface IrisChatMessageEvent {
94123
type: 'irisChatMessage';
95124
timestamp: number;
96125
direction: 'sent' | 'received';
97126
content: string;
127+
// Added in Block H: optional metadata from server response / WebSocket payload
98128
messageId?: string;
99129
sessionId?: string;
100130
sentAt?: number;
101131
}
102132

133+
/**
134+
* Records a send attempt lifecycle: pending (before API call), sent (on success),
135+
* or failed (on error). Emitted in addition to irisChatMessage so that:
136+
* - Failed sends (which produce no irisChatMessage) are still visible in the recording.
137+
* - The pending→sent timing is available for latency analysis.
138+
*
139+
* Lifecycle: pending → sent OR pending → failed
140+
*/
103141
export interface IrisChatSendAttemptEvent {
104142
type: 'irisChatSendAttempt';
105143
timestamp: number;
@@ -108,6 +146,10 @@ export interface IrisChatSendAttemptEvent {
108146
errorMessage?: string;
109147
}
110148

149+
/**
150+
* Records a helpful/unhelpful rating submitted by the user for a received
151+
* Iris message. Wired up when the webview's feedback UI fires the event.
152+
*/
111153
export interface IrisChatFeedbackEvent {
112154
type: 'irisChatFeedback';
113155
timestamp: number;
@@ -143,12 +185,21 @@ export interface EqEngineStateEvent {
143185
export interface InterventionEvent {
144186
type: 'intervention';
145187
timestamp: number;
146-
action: 'shown' | 'accepted' | 'dismissed';
188+
action: 'shown' | 'accepted' | 'dismissed' | 'blocked';
147189
level: 'subtle' | 'notification' | 'proactive';
148190
shouldIntervene: boolean;
149191
eq: number;
150192
confidence: 'sufficient' | 'insufficient';
151193
triggerType?: 'execution-error' | 'multiline-paste' | 'idle' | 'selection-maintained';
194+
/** Populated when action='blocked'. Identifies why the intervention was blocked. */
195+
blockedReason?: 'cooldown' | 'warmup' | 'session-limit' | 'low-confidence';
196+
/** Populated when action='dismissed'. Identifies how the intervention was dismissed. */
197+
dismissReason?: 'user-action' | 'hidden' | 'replaced' | 'session-end';
198+
/**
199+
* Whether the EQ was above the severity threshold, regardless of confidence/guardrails.
200+
* Populated when action='blocked' to explain the signal that was suppressed.
201+
*/
202+
rawWanted?: boolean;
152203
}
153204

154205
export interface ViewNavigationEvent {
@@ -199,10 +250,17 @@ export interface TerminalOpenCloseEvent {
199250
terminalName: string;
200251
}
201252

253+
/**
254+
* Emitted once per URI after three consecutive snapshot-write failures.
255+
* Acts as a permanent "give up" marker so consumers know a snapshot is
256+
* missing and why. Written via `_writeLifecycleEvent` (bypass phase gate)
257+
* only while recording is active.
258+
*/
202259
export interface FileSnapshotErrorEvent {
203260
type: 'fileSnapshotError';
204261
timestamp: number;
205262
uri: string;
263+
/** Short human-readable reason, e.g. 'snapshot-write-failed-after-3-retries' or an fs error message. */
206264
reason: string;
207265
}
208266

@@ -251,6 +309,8 @@ export type RecordedEvent =
251309
| FileSnapshotEvent
252310
| SessionStartEvent
253311
| SessionEndEvent
312+
| ConsentChangeEvent
313+
| StartupPhaseCompleteEvent
254314
| IrisChatMessageEvent
255315
| IrisChatSendAttemptEvent
256316
| IrisChatFeedbackEvent
@@ -279,7 +339,7 @@ export interface SessionMetadata {
279339
startTime: number;
280340
endTime: number | undefined;
281341
eventCount: number;
282-
/** Schema version for forward-compat parsing. Introduced in Block D (version 2). */
342+
/** Schema version for forward-compat parsing. Block D introduces version 2. */
283343
schemaVersion?: number;
284344
/** Recorder version string, set by storageWriter at write time. */
285345
recorderVersion?: string;

0 commit comments

Comments
 (0)