-
Notifications
You must be signed in to change notification settings - Fork 14
feat(session): low-yield repeated probing detection #264
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
Merged
Merged
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
3b57eb3
feat(diagnostics): add v1 loop detection schema types
Astro-Han 8be4f48
feat(diagnostics): add truncateForRenderer with codepoint and percent…
Astro-Han 5e7b53e
feat(diagnostics): mark fallback target summaries with targetHashIsFa…
Astro-Han 6e65e7b
feat(diagnostics): move recover firing to observeToolError; persist l…
Astro-Han b438bd4
feat(diagnostics): add deriveParentLoopState and queryGateAction
Astro-Han 6b85628
feat(session): add stop-message loop renderer with degraded fallbacks
Astro-Han 03ff8dc
feat(diagnostics): dispatch consumeReminders text by sigKey prefix (t…
Astro-Han 5d2eb70
feat(processor): expose loop gate boundary helpers and stop synthesis
Astro-Han 0c60409
feat(prompt): pre-execution loop gate with symmetric block/stop via E…
Astro-Han d42d48c
feat(export): emit diagnostics.loop.last for block parts
Astro-Han 209e500
test(session): e2e loop detection reminder→block→resume→stop chain
Astro-Han 078fb08
fix(diagnostics): scope targetRepeatCount by tool to match v1 cross-t…
Astro-Han 31094ff
fix(diagnostics): review polish (v0 reminder fallback, target-first n…
Astro-Han a3a84f4
feat(session): i18n loop stop summary, default English with zh from u…
Astro-Han File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,126 @@ | ||
| import { SessionDiagnostics } from "./diagnostics" | ||
|
|
||
| export namespace LoopRenderer { | ||
| export type RenderInput = { | ||
| tool: string | ||
| state: SessionDiagnostics.SignatureState | ||
| locale?: string | ||
| } | ||
|
|
||
| export function render(input: RenderInput): string { | ||
| const { tool, state, locale } = input | ||
| const errorLine = firstErrorLine(state.lastError) | ||
| const scrubbedError = errorLine ? scrubErrorText(errorLine) : undefined | ||
| const truncatedError = scrubbedError ? SessionDiagnostics.truncateForRenderer(scrubbedError) : undefined | ||
| const isZh = (locale ?? "").toLowerCase().startsWith("zh") | ||
|
|
||
| if (tool === "webfetch") { | ||
| const rawURL = extractURL(state.lastInput) | ||
| const cleanedURL = rawURL ? stripQueryAndFragment(rawURL) : undefined | ||
|
Astro-Han marked this conversation as resolved.
|
||
| const truncatedURL = cleanedURL ? SessionDiagnostics.truncateForRenderer(cleanedURL) : undefined | ||
| if (state.kind === "input") { | ||
| if (isZh) { | ||
| return truncatedURL | ||
| ? `我重复调用了相同的请求 ${state.completedFailures} 次没成功,已停止。请求:webfetch ${truncatedURL}` | ||
| : `我重复调用了相同的请求 ${state.completedFailures} 次没成功,已停止。` | ||
| } | ||
| return truncatedURL | ||
| ? `I made the same request ${state.completedFailures} times without success and stopped. Request: webfetch ${truncatedURL}` | ||
| : `I made the same request ${state.completedFailures} times without success and stopped.` | ||
| } | ||
| if (isZh) { | ||
| if (truncatedURL && truncatedError) | ||
| return `我重复抓取同一个目标 ${state.completedFailures} 次都失败,已停止。目标:${truncatedURL} 错误:${truncatedError}` | ||
| if (truncatedURL) | ||
| return `我重复抓取同一个目标 ${state.completedFailures} 次都失败,已停止。目标:${truncatedURL}` | ||
| if (truncatedError) | ||
| return `我重复抓取同一个目标 ${state.completedFailures} 次都失败,已停止。错误:${truncatedError}` | ||
| return `我重复抓取同一个目标 ${state.completedFailures} 次都失败,已停止。` | ||
| } | ||
| if (truncatedURL && truncatedError) | ||
| return `I failed to fetch the same target ${state.completedFailures} times and stopped. Target: ${truncatedURL} Error: ${truncatedError}` | ||
| if (truncatedURL) | ||
| return `I failed to fetch the same target ${state.completedFailures} times and stopped. Target: ${truncatedURL}` | ||
| if (truncatedError) | ||
| return `I failed to fetch the same target ${state.completedFailures} times and stopped. Error: ${truncatedError}` | ||
| return `I failed to fetch the same target ${state.completedFailures} times and stopped.` | ||
| } | ||
|
|
||
| if (state.kind === "target") { | ||
| if (isZh) { | ||
| return truncatedError | ||
| ? `我重复在同一个目标上失败了 ${state.completedFailures} 次,已停止。工具:${tool} 错误:${truncatedError}` | ||
| : `我重复在同一个目标上失败了 ${state.completedFailures} 次,已停止。工具:${tool}` | ||
| } | ||
| return truncatedError | ||
| ? `I failed against the same target ${state.completedFailures} times and stopped. Tool: ${tool} Error: ${truncatedError}` | ||
| : `I failed against the same target ${state.completedFailures} times and stopped. Tool: ${tool}` | ||
| } | ||
|
|
||
| if (isZh) { | ||
| return truncatedError | ||
| ? `我重复调用了 ${tool} ${state.completedFailures} 次都失败,已停止。最近一次错误:${truncatedError}` | ||
| : `我重复调用了 ${tool} ${state.completedFailures} 次都失败,已停止。` | ||
| } | ||
| return truncatedError | ||
| ? `I called ${tool} ${state.completedFailures} times without success and stopped. Last error: ${truncatedError}` | ||
| : `I called ${tool} ${state.completedFailures} times without success and stopped.` | ||
| } | ||
|
|
||
| function extractURL(value: unknown): string | undefined { | ||
| if (typeof value === "string") return value | ||
| if (!value || typeof value !== "object") return undefined | ||
| const r = value as Record<string, unknown> | ||
| if (typeof r.url === "string") return r.url | ||
| if (typeof r.href === "string") return r.href | ||
| return undefined | ||
| } | ||
|
|
||
| function stripQueryAndFragment(url: string): string { | ||
| try { | ||
| const u = new URL(url) | ||
| return `${u.protocol}//${u.host}${u.pathname}` | ||
| } catch { | ||
| const q = url.indexOf("?") | ||
| const f = url.indexOf("#") | ||
| const cuts = [q, f].filter((i) => i >= 0) | ||
| if (!cuts.length) return url | ||
| return url.slice(0, Math.min(...cuts)) | ||
| } | ||
| } | ||
|
|
||
| // Strip query strings and fragments from any URLs embedded in free text (error messages, | ||
| // stack traces, etc.). Tokens often live in `?token=...` or `#access_token=...`. The `i` | ||
| // flag covers uppercase scheme variants like `HTTPS://`. | ||
| function scrubURLsInText(text: string): string { | ||
| return text.replace(/(https?:\/\/[^\s,;)]+)/gi, (match) => stripQueryAndFragment(match)) | ||
| } | ||
|
Astro-Han marked this conversation as resolved.
|
||
|
|
||
| // Multi-pass scrub for tool error text. Tool errors echo URLs with tokens, fully-qualified | ||
| // file paths, quoted user input, and Bearer/Basic auth headers. Mask each class so the | ||
| // synthetic stop summary does not become a leak vector. Path detection uses a negative | ||
| // lookbehind so `Error:at /tmp/x` and `failed at '/home/alice/key'` get scrubbed too — not | ||
| // only paths preceded by start-of-line or whitespace. Path char class allows spaces | ||
| // (real paths: `/Users/alice/My Documents/...`) and stops at structural delimiters | ||
| // (quote, comma, semicolon, colon, paren/bracket, newline) — over-scrub of trailing | ||
| // descriptive text is preferred to under-scrub leaving secrets visible. | ||
| function scrubErrorText(text: string): string { | ||
| return scrubURLsInText(text) | ||
| .replace(/(?:Bearer|Basic|token|api[_-]?key)[\s:=]+[^\s'"`,;)]+/gi, "<token>") | ||
| .replace(/['"`][^'"`]*['"`]/g, "<quoted>") | ||
| .replace(/\b[A-Za-z]:\\[^'"`,;:)\]\n]+/g, "<path>") | ||
| // Forward-slash Windows paths (`C:/Users/...`) — JS path normalization on Windows can | ||
| // produce these, and the general path regex below excludes `:` as preceding char to | ||
| // avoid stripping URL paths, so drive-letter forward-slash paths need their own pass. | ||
| .replace(/\b[A-Za-z]:\/[^'"`,;:)\]\n]+/g, "<path>") | ||
| // Relative paths starting with `./` or `../`. Without this pass the absolute-path | ||
| // regex below only catches the `/...` suffix and leaves the leading dots visible. | ||
| .replace(/(?<![\w.])\.\.?\/[^'"`,;:)\]\n]+/g, "<path>") | ||
| .replace(/(?<![\w/:])\/[^'"`,;:)\]\n]+/g, "<path>") | ||
|
Astro-Han marked this conversation as resolved.
|
||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| function firstErrorLine(error: unknown): string | undefined { | ||
| const line = SessionDiagnostics.firstLine(error) | ||
| return line === "" ? undefined : line | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.