Skip to content

Commit 93f8e2e

Browse files
committed
fix: stripEnvelopeMetadata multiline state machine (adversarial review fixes)
1 parent 14c1029 commit 93f8e2e

2 files changed

Lines changed: 54 additions & 17 deletions

File tree

src/smart-extractor.ts

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -130,24 +130,17 @@ export function stripEnvelopeMetadata(text: string): string {
130130
// inline content on the same line (e.g. "[Subagent Task] Reply with brief ack.").
131131
// Also matches when the wrapper prefix is on its own line ("]\n" = no content after ]).
132132
const WRAPPER_LINE_RE = /^\[(?:Subagent Context|Subagent Task)\](?:\s|$|\n)?/i;
133-
const BOILERPLATE_RE = /^(?:Results auto-announce to your requester\.?|do not busy-poll for status\.?|Reply with a brief acknowledgment only\.?|Do not use any memory tools\.?)$/i;
133+
const BOILERPLATE_RE = /^(?:Results auto-announce to your requester\.?|do not busy-poll for status\.?|Reply with a brief acknowledgment only\.?|Do not use any memory tools\.?)$/im;
134134
const SUBAGENT_RUNNING_RE = /You are running as a subagent\b/i;
135135

136136
const originalLines = text.split("\n");
137137

138-
// Pre-scan: determine if there are leading wrappers AND actual user content.
139-
// Used to decide whether to strip boilerplate in the leading zone.
138+
// Pre-scan: determine if there are leading wrappers.
139+
// Needed to decide whether boilerplate in the leading zone should be stripped
140+
// (boilerplate without a wrapper prefix is preserved — it may be legitimate user text).
140141
const hasLeadingWrapper = originalLines.some((rawLine) =>
141142
WRAPPER_LINE_RE.test(rawLine.trim())
142143
);
143-
const hasActualUserContent = originalLines.some((rawLine) => {
144-
const trimmed = rawLine.trim();
145-
return (
146-
trimmed !== "" &&
147-
!WRAPPER_LINE_RE.test(trimmed) &&
148-
!BOILERPLATE_RE.test(trimmed)
149-
);
150-
});
151144

152145
// Single-pass state machine: find leading zone end and build result simultaneously.
153146
// Key: "You are running as a subagent..." on its own line AFTER a wrapper prefix
@@ -165,23 +158,22 @@ export function stripEnvelopeMetadata(text: string): string {
165158

166159
if (isWrapper) {
167160
prevWasWrapper = true;
168-
stillInLeadingZone = true;
169161
result.push(""); // strip wrapper
170162
continue;
171163
}
172164

173165
if (stillInLeadingZone) {
174166
if (isBoilerplate) {
175-
// Boilerplate in leading zone — strip if there are leading wrappers + user content
176-
prevWasWrapper = false;
177-
result.push(hasLeadingWrapper && hasActualUserContent ? "" : rawLine);
167+
// Boilerplate in leading zone — strip only when there was a wrapper prefix.
168+
// This preserves standalone boilerplate text that happens to match the
169+
// boilerplate pattern but has no wrapper context.
170+
result.push(hasLeadingWrapper ? "" : rawLine);
178171
continue;
179172
}
180173

181174
if (isSubagentContent) {
182175
// Multiline wrapper: "You are running as a subagent..." on its own line
183-
// after a wrapper prefix — strip it
184-
prevWasWrapper = false;
176+
// after a wrapper prefix — strip it; keep prevWasWrapper true
185177
result.push(""); // strip
186178
continue;
187179
}

test/strip-envelope-metadata.test.mjs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,4 +266,49 @@ describe("stripEnvelopeMetadata", () => {
266266
// regex requires both message_id AND sender_id
267267
assert.match(result, /message_id/);
268268
});
269+
270+
// -----------------------------------------------------------------------
271+
// Fix 1 regression tests: user content BEFORE boilerplate
272+
// -----------------------------------------------------------------------
273+
it("preserves boilerplate that appears BEFORE user content (user content first)", () => {
274+
const input = [
275+
"[Subagent Context]",
276+
"User content first.",
277+
"Results auto-announce to your requester.",
278+
].join("\n");
279+
280+
const result = stripEnvelopeMetadata(input);
281+
// Boilerplate appears AFTER user content, so it's outside the leading zone
282+
// and must be preserved
283+
assert.equal(result, "User content first.\nResults auto-announce to your requester.");
284+
});
285+
286+
// -----------------------------------------------------------------------
287+
// Fix 3 regression tests: consecutive subagent content lines
288+
// -----------------------------------------------------------------------
289+
it("strips all consecutive subagent content lines in the leading zone", () => {
290+
const input = [
291+
"[Subagent Context]",
292+
"You are running as a subagent (depth 1/1).",
293+
"You are running as a subagent (depth 2/2).",
294+
"Actual.",
295+
].join("\n");
296+
297+
const result = stripEnvelopeMetadata(input);
298+
assert.equal(result, "Actual.");
299+
});
300+
301+
// -----------------------------------------------------------------------
302+
// Edge case: only wrapper + boilerplate, no user content at all
303+
// -----------------------------------------------------------------------
304+
it("strips everything when there is only wrapper and boilerplate with no user content", () => {
305+
const input = [
306+
"[Subagent Context]",
307+
"You are running as a subagent (depth 1/1).",
308+
"Results auto-announce to your requester.",
309+
].join("\n");
310+
311+
const result = stripEnvelopeMetadata(input);
312+
assert.equal(result, "");
313+
});
269314
});

0 commit comments

Comments
 (0)