-
Notifications
You must be signed in to change notification settings - Fork 960
feat(cursor): observe mid-stream envelope echoes and call-id corruption #2803
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
dd9c2dc
990a83f
adcde10
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -14,6 +14,14 @@ | |||||||||||
|
|
||||||||||||
| const ECHO_MARKERS = ["[Tool Result]", "[Tool Error]", "[tool_result]"] as const; | ||||||||||||
| const MAX_SNIFF_BYTES = 40; | ||||||||||||
| /** Mid-stream observer: max leading whitespace on a line before matching disarms. */ | ||||||||||||
| const MAX_MIDSTREAM_LINE_INDENT = 128; | ||||||||||||
| /** Mid-stream observer: post-marker window watched for call-id corruption. */ | ||||||||||||
| const MIDSTREAM_CORRUPTION_WINDOW = 512; | ||||||||||||
| /** Mid-stream observer: cumulative scan cap (UTF-16 code units, checked between feeds). */ | ||||||||||||
| export const MAX_MIDSTREAM_SCAN_LENGTH = 512 * 1024; | ||||||||||||
| /** Mid-stream observer: findings retained per turn. */ | ||||||||||||
| const MAX_MIDSTREAM_FINDINGS = 8; | ||||||||||||
| const MAX_ROUTING_COMMENTARY_BYTES = 512; | ||||||||||||
| /** Aggregate quarantine cap: past this, flush and disarm. */ | ||||||||||||
| const MAX_HOLD_BYTES = 8 * 1024; | ||||||||||||
|
|
@@ -43,6 +51,126 @@ export type EchoSnifferDecision = | |||||||||||
| | { kind: "flush" } | ||||||||||||
| | { kind: "echo"; marker: string }; | ||||||||||||
|
|
||||||||||||
| export interface MidstreamEchoFinding { | ||||||||||||
| marker: string; | ||||||||||||
| /** UTF-16 offset of the marker's line start within the turn's full text. */ | ||||||||||||
| offset: number; | ||||||||||||
| callIdCorrupt: boolean; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| /** | ||||||||||||
| * Diagnostic-only mid-stream envelope-echo observer (devlog 260828 F1/F2). | ||||||||||||
| * | ||||||||||||
| * The prefix sniffer only watches the first ~40 bytes of a turn, but live | ||||||||||||
| * probing caught grok-4.6 echoing "[Tool Result]" envelope blocks in the | ||||||||||||
| * MIDDLE of an agent message — after legitimate leading text — one of them | ||||||||||||
| * carrying a whitespace-spliced call-id ("fc_x mar-y" instead of "fc_x-y"). | ||||||||||||
| * Deltas at that point have already reached the client, so this observer | ||||||||||||
| * never throws and never withholds output: it records findings so the | ||||||||||||
| * adapter can emit a structured diagnostic at turn end. Only fixed marker | ||||||||||||
| * enums, numeric offsets, and corruption booleans are retained — never | ||||||||||||
| * content bytes. | ||||||||||||
| */ | ||||||||||||
| export class CursorMidstreamEchoObserver { | ||||||||||||
| private lineBuffer = ""; | ||||||||||||
| private lineStartOffset = 0; | ||||||||||||
| private totalLength = 0; | ||||||||||||
| private disarmed = false; | ||||||||||||
| private lineDisarmed = false; | ||||||||||||
| private corruptionWatch: { finding: MidstreamEchoFinding; remaining: number; window: string } | undefined; | ||||||||||||
| private readonly recorded: MidstreamEchoFinding[] = []; | ||||||||||||
|
|
||||||||||||
| feed(textDelta: string): void { | ||||||||||||
| if (this.disarmed && !this.corruptionWatch) return; | ||||||||||||
| let index = 0; | ||||||||||||
| while (index < textDelta.length) { | ||||||||||||
| const newline = textDelta.indexOf("\n", index); | ||||||||||||
| const segment = newline === -1 ? textDelta.slice(index) : textDelta.slice(index, newline); | ||||||||||||
| if (this.corruptionWatch) this.watchCorruption(segment + (newline === -1 ? "" : "\n")); | ||||||||||||
| if (!this.disarmed && !this.lineDisarmed && segment.length > 0) { | ||||||||||||
| this.lineBuffer += segment; | ||||||||||||
| if (this.lineBuffer.length > MAX_MIDSTREAM_LINE_INDENT + 32) { | ||||||||||||
| // Bound per-line work: nothing beyond the indent cap + longest marker can match. | ||||||||||||
| this.lineDisarmed = !this.lineMatchesPrefixSoFar(); | ||||||||||||
| this.lineBuffer = this.lineBuffer.slice(0, MAX_MIDSTREAM_LINE_INDENT + 32); | ||||||||||||
| } | ||||||||||||
| this.checkLine(); | ||||||||||||
| } | ||||||||||||
| if (newline === -1) break; | ||||||||||||
| this.lineBuffer = ""; | ||||||||||||
| this.lineDisarmed = false; | ||||||||||||
| this.lineStartOffset = this.totalLength + newline + 1; | ||||||||||||
| index = newline + 1; | ||||||||||||
| } | ||||||||||||
| this.totalLength += textDelta.length; | ||||||||||||
| if (this.totalLength > MAX_MIDSTREAM_SCAN_LENGTH) this.disarmed = true; | ||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win Disarm when the scan length reaches the cap. Line 106 uses Proposed fix- if (this.totalLength > MAX_MIDSTREAM_SCAN_LENGTH) this.disarmed = true;
+ if (this.totalLength >= MAX_MIDSTREAM_SCAN_LENGTH) this.disarmed = true;📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| findings(): readonly MidstreamEchoFinding[] { | ||||||||||||
| if (this.corruptionWatch) { | ||||||||||||
| this.settleCorruption(); | ||||||||||||
| } | ||||||||||||
| return this.recorded; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| private lineMatchesPrefixSoFar(): boolean { | ||||||||||||
| const probe = this.lineBuffer.replace(/^[ \t]*/, ""); | ||||||||||||
| return ECHO_MARKERS.some(marker => probe.startsWith(marker) || marker.startsWith(probe)); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| private checkLine(): void { | ||||||||||||
| const indentMatch = /^[ \t]*/.exec(this.lineBuffer); | ||||||||||||
| const indent = indentMatch ? indentMatch[0].length : 0; | ||||||||||||
| if (indent > MAX_MIDSTREAM_LINE_INDENT) { | ||||||||||||
| this.lineDisarmed = true; | ||||||||||||
| return; | ||||||||||||
| } | ||||||||||||
| const probe = this.lineBuffer.slice(indent); | ||||||||||||
| for (const marker of ECHO_MARKERS) { | ||||||||||||
| if (probe.startsWith(marker)) { | ||||||||||||
| // The prefix sniffer owns the very start of the turn; only offsets past | ||||||||||||
| // its window count as mid-stream. | ||||||||||||
| if (this.lineStartOffset === 0) { | ||||||||||||
| this.lineDisarmed = true; | ||||||||||||
| return; | ||||||||||||
| } | ||||||||||||
| const finding: MidstreamEchoFinding = { | ||||||||||||
| marker, | ||||||||||||
| offset: this.lineStartOffset, | ||||||||||||
| callIdCorrupt: false, | ||||||||||||
| }; | ||||||||||||
| this.corruptionWatch = { finding, remaining: MIDSTREAM_CORRUPTION_WINDOW, window: "" }; | ||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When an echoed envelope contains consecutive marker lines such as Useful? React with 👍 / 👎. |
||||||||||||
| this.lineDisarmed = true; | ||||||||||||
|
Comment on lines
+142
to
+143
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Include the matching segment suffix in the corruption window. Line 89 sends each segment to an existing watch before Line 142 creates a watch for its marker. Therefore, Proposed fix- this.corruptionWatch = { finding, remaining: MIDSTREAM_CORRUPTION_WINDOW, window: "" };
+ this.corruptionWatch = { finding, remaining: MIDSTREAM_CORRUPTION_WINDOW, window: "" };
+ this.watchCorruption(probe.slice(marker.length));
this.lineDisarmed = true;📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||
| return; | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
| if (!ECHO_MARKERS.some(marker => marker.startsWith(probe)) && probe.length > 0) { | ||||||||||||
| this.lineDisarmed = true; | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| private watchCorruption(text: string): void { | ||||||||||||
| const watch = this.corruptionWatch; | ||||||||||||
| if (!watch) return; | ||||||||||||
| const take = Math.min(watch.remaining, text.length); | ||||||||||||
| watch.window += text.slice(0, take); | ||||||||||||
| watch.remaining -= take; | ||||||||||||
| if (watch.remaining <= 0) this.settleCorruption(); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| private settleCorruption(): void { | ||||||||||||
| const watch = this.corruptionWatch; | ||||||||||||
| if (!watch) return; | ||||||||||||
| const window = watch.window; | ||||||||||||
| watch.finding.callIdCorrupt = | ||||||||||||
| /fc_[0-9a-f]+[ \t]+mar-/.test(window) | ||||||||||||
| || /call_id: \S+[ \t]+\S+_0\b/.test(window); | ||||||||||||
| if (this.recorded.length < MAX_MIDSTREAM_FINDINGS) this.recorded.push(watch.finding); | ||||||||||||
| // Window text is discarded here; only booleans/offsets survive. | ||||||||||||
| this.corruptionWatch = undefined; | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| /** | ||||||||||||
| * Incremental envelope-prefix sniffer. Leading whitespace is tolerated so a | ||||||||||||
| * marker copied after a newline is still caught. | ||||||||||||
|
|
||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: lidge-jun/opencodex
Length of output: 50378
🏁 Script executed:
Repository: lidge-jun/opencodex
Length of output: 50375
🏁 Script executed:
Repository: lidge-jun/opencodex
Length of output: 50376
🏁 Script executed:
Repository: lidge-jun/opencodex
Length of output: 7818
Document c4/c5 evidence before closing.
040_closure_round.md:5-6requires every class to have a result or disposition with NDJSON evidence.:24marksc4/c5asNOT RUNand cites onlyN1 x6andN3;021_probe_results_round1.md:14-16records those as N1/N3 results, not c4/c5 dispositions. Map each criterion to exact artifacts and dispositions, or run both criteria before closure.🤖 Prompt for AI Agents